[firebase-br] Melhorar consulta

Sandro Souza escovadordebits em gmail.com
Sex Ago 16 14:16:48 -03 2013


Bom dia/tarde/noite Carlos.

Meu nobre amigo, posso estar redondamente errado, mas acredito que os 
operadores "CONTAINING" é que vão impedir qualquer melhora de 
performance, pois na prática, esses trechos nunca utilizarão qualquer 
índice.

Mesmo que as outras condições de relacionamento entre as tabelas 
envolvidas utilizem índices, sempre haverá um processamento de texto 
para cada registro/linha selecionado pelas condições.

Talvez precisasse reformular essa consulta, de forma a evitar o uso do 
operador CONTAINING.

É uma tarefa bem complicada.

Aqui vai apenas uma sugestão que pode não ser viável para o seu caso, 
mas que poderá dar idéias novas para chegar à solução ideal para o seu caso:

1 - Criar uma tabela de palavras compartilhadas, contendo apenas um 
código (numérico) e a palavra compartilhada (um varchar).

2 - Criar uma tabela filha/detalhe para cada uma das tabelas que possuem 
um campo/coluna de nome a ser pesquisado, apenas para relacionar as 
palavras que constam no(s) nome(s) contidos naquele registro/linha.

3 - Quando inserir um novo registro em uma dessas tabelas que contém um 
nome a ser pesquisado, dividir o nome em palavras e cadastrá-las na 
tabela de palavras compartilhadas, caso ainda não constem lá, incluindo 
também, na respectiva tabela filha/detalhe, a relação entre o registro 
atual e as respectivas palavras.

4 - Quando alterar um ou mais campos/colunas de nome, fazer os 
respectivos ajustes (nas tabelas filhas) para manter tudo sincronizado.

5 - Quando for efetuar a consulta, dividir o texto informado em 
palavras, e gerar as condições que pesquisem essas palavras na tabela de 
palavras compartilhadas e também relacionem com as respectivas tabelas 
filhas. Dessa forma, não necessitaria utilizar o operador CONTAINING, e 
utilizariam apenas os respectivos índices.

Exemplo:

1 - Criação da tabela de palavras compartilhadas.

CREATE TABLE PALAVRA(
   CODIGO INTEGER DEFAULT 0 NOT NULL,
   TEXTO VARCHAR(100) CHARACTER SET ISO8859_1 NOT NULL COLLATE PT_BR,
   REFERENCIAS INTEGER DEFAULT 1 NOT NULL,
   CONSTRAINT PK_PALAVRA PRIMARY KEY(CODIGO),
   CONSTRAINT UK_PALAVRA_TEXTO UNIQUE(PALAVRA));

COMMENT ON TABLE PALAVRA IS 'Palavras compartilhadas que formam os nomes';
COMMENT ON COLUMN PALAVRA.CODIGO IS 'Codigo da palavra compartilhada';
COMMENT ON COLUMN PALAVRA.TEXTO IS 'A palavra compartilhada propriamente 
dita';
COMMENT ON COLUMN PALAVRA.REFERENCIAS IS 'A quantidade de vezes que essa 
palavra esta sendo utilizada';

SET TERM ^ ;

CREATE TRIGGER TG_PALAVRA_BI FOR PALAVRA BEFORE INSERT AS
BEGIN
   -- Nao informou o codigo da nova palavra compartilhada?
   IF (NEW.CODIGO = 0) THEN
     -- Calcula o codigo da nova palavra compartilhada (use um 
gerador/sequence se desejar).
     SELECT
       COALESCE(MAX(CODIGO), 0) + 1
     FROM
       PALAVRAS
     INTO
       NEW.CODIGO;
END^

CREATE PROCEDURE INCLUA_PALAVRA(
   NOVA_PALAVRA VARCHAR(100) CHARACTER SET ISO8859_1 COLLATE PT_BR
)RETURNS(
   CODIGO INTEGER
)AS
BEGIN
   -- Verifica se ja consta a palavra informada.
   SELECT CODIGO FROM PALAVRA WHERE TEXTO = :NOVA_PALAVRA INTO :CODIGO;
   -- Ainda nao consta?
   IF (ROW_COUNT = 0) THEN
     -- Insere a nova palavra compartilhada e obtem o codigo dela.
     INSERT INTO PALAVRA(TEXTO)VALUES(:NOVA_PALAVRA)RETURNING CODIGO 
INTO :CODIGO;
   ELSE
     -- Incrementa o contador de referencias dessa palavra.
     UPDATE PALAVRA SET REFERENCIAS = REFERENCIAS + 1 WHERE CODIGO = 
:CODIGO;
   -- Retorna o codigo da palavra informada.
   SUSPEND;
END^

COMMENT ON PROCEDURE INCLUA_PALAVRA IS 'Inclui uma nova palavra 
compartilhada'^
COMMENT ON PARAMETER INCLUA_PALAVRA.NOVA_PALAVRA IS 'A nova palavra a 
ser compartilhada'^
COMMENT ON PARAMETER INCLUA_PALAVRA.CODIGO IS 'O codigo da nova palavra 
compartilhada'^

CREATE PROCEDURE EXCLUA_PALAVRA(
   CODIGO INTEGER
)AS
DECLARE VARIABLE REFERENCIAS INTEGER;
BEGIN
   -- Obtem a quantidade de referencias da palavra informada.
   SELECT REFERENCIAS FROM PALAVRA WHERE CODIGO = :CODIGO INTO :REFERENCIAS;
   -- Encontrou alguma palavra com o codigo informado?
   IF (REFERENCIAS IS NOT NULL) THEN
     -- So consta essa unica referencia de uso?
     IF (REFERENCIAS = 1) THEN
       -- Exclui essa palavra, ja que ela nao esta sendo mais utilizada.
       DELETE FROM PALAVRA WHERE CODIGO = :CODIGO;
     ELSE
       -- Decrementa o contador de referencias dessa palavra.
       UPDATE PALAVRA SET REFERENCIAS = REFERENCIAS - 1 WHERE CODIGO = 
:CODIGO;
END^

COMMENT ON PROCEDURE EXCLUA_PALAVRA IS 'Exclui uma palavra compartilhada'^
COMMENT ON PARAMETER EXCLUA_PALAVRA.CODIGO IS 'O codigo da palavra 
compartilhada a ser excluida'^

SET TERM ; ^

Dessa forma, use as stored procedures INCLUA_PALAVRA e EXCLUA_PALAVRA 
para gerenciar a tabela de palavras compartilhadas.

Para a tabela PESSOAFISICA, poderíamos criar uma tabela detalhe como a 
seguinte:

CREATE TABLE PESSOAFISICAPALAVRA(
   IDENTIFICADOR INTEGER NOT NULL,
   CODIGO_PALAVRA INTEGER NOT NULL,
   CONSTRAINT PK_PESSOAFISICAPALAVRA PRIMARY 
KEY(IDENTIFICADOR,CODIGO_PALAVRA),
   CONSTRAINT FK_PESSOAFISICAPALAVRA_PF FOREIGN KEY(IDENTIFICADOR) 
REFERENCES PESSOAFISICA ON UPDATE CASCADE ON DELETE CASCADE,
   CONSTRAINT FK_PESSOAFISICAPALAVRA_PL FOREIGN KEY(CODIGO_PALAVRA) 
REFERENCES PALAVRAS ON UPDATE CASCADE ON DELETE CASCADE);

COMMENT ON TABLE PESSOAFISICAPALAVRA IS 'Palavras que formam os nomes 
das pessoas fisicas';
COMMENT ON COLUMN PESSOAFISICAPALAVRA.IDENTIFICADOR IS 'Identificador da 
pessoa fisica';
COMMENT ON COLUMN PESSOAFISICAPALAVRA.CODIGO_PALAVRA IS 'Codigo da 
palavra que forma o nome dessa pessoa fisica';

Vamos criar uma stored procedure para podermos facilmente gerenciar 
essas tabelas detalhes:

SET TERM ^ ;

CREATE PROCEDURE RELACIONA_PALAVRAS(
   NOME VARCHAR(100) CHARACTER SET ISO8859_1 COLLATE PT_BR,
   TABELADETALHE VARCHAR(30),
   CAMPOSCHAVESINCLUSAO VARCHAR(100),
   VALORESCHAVESINCLUSAO VARCHAR(200),
   CONDICOESPESQUISA VARCHAR(200)
)AS
DECLARE VARIABLE CODIGO INTEGER;
DECLARE VARIABLE PALAVRA VARCHAR(100);
DECLARE VARIABLE POSICAO SMALLINT;
BEGIN
   -- Laco de exclusao dos relacionamentos atuais.
   FOR EXECUTE STATEMENT ('SELECT CODIGO FROM ' || :TABELADETALHE || ' 
WHERE ' || :CONDICOESPESQUISA) INTO :CODIGO DO
     -- Exclui a palavra atual.
     EXECUTE PROCEDURE EXCLUA_PALAVRA(:CODIGO);
   -- Exclui os relacionamentos atuais.
  EXECUTE STATEMENT ('DELETE FROM ' || :TABELADETALHE || ' WHERE ' || 
:CONDICOESPESQUISA);
   -- Remove os espacos em branco excedentes do nome informado.
   NOME = TRIM(NOME);
   -- Laco de processamento do nome.
   WHILE (NOME > '') DO
   BEGIN
     -- Obtem a posicao do espaco em branco dentro do nome.
     POSICAO = POSITION(' ' IN NOME);
     -- Nao consta?
     IF (CODIGO = 0) THEN
     BEGIN
       -- Trata-se da ultima palavra do nome.
       PALAVRA = TRIM(NOME);
       -- Sem mais palavras a processar.
       NOME = '';
     END -- IF
     ELSE
     BEGIN
       -- Obtem a proxima palavra do nome.
       PALAVRA = SUBSTRING(NOME FROM 1 FOR (POSICAO - 1));
       -- Remove essa palavra do nome.
       NOME = TRIM(SUBSTRING(NOME FROM POSICAO FOR 200));
     END -- ELSE
     -- Insere ou reutiliza a palavra compartilhada e obtem o seu 
respectivo codigo.
     SELECT CODIGO FROM INCLUA_PALAVRA(:PALAVRA) INTO :CODIGO;
     -- Insere o registro da nova palavra relacionada a essa pessoa fisica.
     EXECUTE STATEMENT ('INSERT INTO ' || :TABELADETALHE || '(' || 
:CAMPOSCHAVES || ',CODIGO)VALUES(' || :VALORESCHAVES || ', ' || :CODIGO 
|| ')');
   END -- WHILE
END^

COMMENT ON PROCEDURE RELACIONA_PALAVRAS IS 'Relaciona as palavras do 
nome com um registro';
COMMENT ON PARAMETER RELACIONA_PALAVRAS.NOME IS 'Nome a ser processado';
COMMENT ON PARAMETER RELACIONA_PALAVRAS.TABELADETALHE IS 'Nome da tabela 
detalhe onde ficara o relacionamento';
COMMENT ON PARAMETER RELACIONA_PALAVRAS.CAMPOSCHAVESINCLUSAO IS 'Nomes 
dos campos chaves, separados por virgula, para inclusao';
COMMENT ON PARAMETER RELACIONA_PALAVRAS.VALORESCHAVESINCLUSAO IS 
'Valores dos campos chaves, separados por virgula, para inclusao';
COMMENT ON PARAMETER RELACIONA_PALAVRAS.CONDICOESPESQUISA IS 'Valores 
dos campos chaves, separados por virgula, para consulta e exclusao';

SET TERM ; ^

Agora vamos criar um gatilho para a tabela PESSOAFISICA, para que esses 
relacionamentos entre as palavras e o nome dos registros sejam feitas 
automaticamente:

SET TERM ^ ;

CREATE PROCEDURE RELACIONA_PALAVRAS(
   NOME VARCHAR(100) CHARACTER SET ISO8859_1 COLLATE PT_BR,
   TABELADETALHE VARCHAR(30),
   CAMPOSCHAVESINCLUSAO VARCHAR(100),
   VALORESCHAVESINCLUSAO VARCHAR(200),
   CONDICOESPESQUISA VARCHAR(200)
)AS
DECLARE VARIABLE CODIGO INTEGER;
DECLARE VARIABLE PALAVRA VARCHAR(100);
DECLARE VARIABLE POSICAO SMALLINT;
BEGIN
   -- Laco de exclusao dos relacionamentos atuais.
   FOR EXECUTE STATEMENT ('SELECT CODIGO FROM ' || :TABELADETALHE || ' 
WHERE ' || :CONDICOESPESQUISA) INTO :CODIGO DO
     -- Exclui a palavra atual.
     EXECUTE PROCEDURE EXCLUA_PALAVRA(:CODIGO);
   -- Exclui os relacionamentos atuais.
  EXECUTE STATEMENT ('DELETE FROM ' || :TABELADETALHE || ' WHERE ' || 
:CONDICOESPESQUISA);
   -- Remove os espacos em branco excedentes do nome informado.
   NOME = TRIM(NOME);
   -- Laco de processamento do nome.
   WHILE (NOME > '') DO
   BEGIN
     -- Obtem a posicao do espaco em branco dentro do nome.
     POSICAO = POSITION(' ' IN NOME);
     -- Nao consta?
     IF (CODIGO = 0) THEN
     BEGIN
       -- Trata-se da ultima palavra do nome.
       PALAVRA = TRIM(NOME);
       -- Sem mais palavras a processar.
       NOME = '';
     END -- IF
     ELSE
     BEGIN
       -- Obtem a proxima palavra do nome.
       PALAVRA = SUBSTRING(NOME FROM 1 FOR (POSICAO - 1));
       -- Remove essa palavra do nome.
       NOME = TRIM(SUBSTRING(NOME FROM POSICAO FOR 200));
     END -- ELSE
     -- Insere ou reutiliza a palavra compartilhada e obtem o seu 
respectivo codigo.
     SELECT CODIGO FROM INCLUA_PALAVRA(:PALAVRA) INTO :CODIGO;
     -- Insere o registro da nova palavra relacionada a essa pessoa fisica.
     EXECUTE STATEMENT ('INSERT INTO ' || :TABELADETALHE || '(' || 
:CAMPOSCHAVES || ',CODIGO)VALUES(' || :VALORESCHAVES || ', ' || :CODIGO 
|| ')');
   END -- WHILE
END^

COMMENT ON PROCEDURE RELACIONA_PALAVRAS IS 'Relaciona as palavras do 
nome com um registro';
COMMENT ON PARAMETER RELACIONA_PALAVRAS.NOME IS 'Nome a ser processado';
COMMENT ON PARAMETER RELACIONA_PALAVRAS.TABELADETALHE IS 'Nome da tabela 
detalhe onde ficara o relacionamento';
COMMENT ON PARAMETER RELACIONA_PALAVRAS.CAMPOSCHAVESINCLUSAO IS 'Nomes 
dos campos chaves, separados por virgula, para inclusao';
COMMENT ON PARAMETER RELACIONA_PALAVRAS.VALORESCHAVESINCLUSAO IS 
'Valores dos campos chaves, separados por virgula, para inclusao';
COMMENT ON PARAMETER RELACIONA_PALAVRAS.CONDICOESPESQUISA IS 'Valores 
dos campos chaves, separados por virgula, para consulta e exclusao';

CREATE TRIGGER TG_PESSOAFISICA_AIU FOR PESSOAFISICA AFTER INSERT OR 
UPDATE POSITION 100 AS
BEGIN
   -- Processa o nome dessa nova pessoa fisica e cria a relacao de 
palavras com esse novo registro/linha.
   EXECUTE PROCEDURE RELACIONA_PALAVRAS(
     -- NOME
     NEW.NOME,
     -- TABELADETALHE
     'PESSOAFISICAPALAVRA',
     -- CAMPOSCHAVESINCLUSAO
     'IDENTIFICADOR',
     -- VALORESCHAVESINCLUSAO
     NEW.IDENTIFICADOR,
     -- CONDICOESPESQUISA
     'IDENTIFCADOR = ' || NEW.IDENTIFICADOR);
END^

SET TERM ; ^

Agora, quando você incluir uma nova pessoa física, ou alterar o seu 
nome, os relacionamentos serão criados e atualizados automaticamente.

Vamos supor que estamos cadastrando uma pessoa física com identificador 
100 e nome "Fulano da Silva".

Separando o nome "Fulano da Silva" em palavras, temos "Fulano", "da" e 
"Silva".

Seriam incluídas 3 palavras na tabela de palavras compartilhadas 
("Fulano", "da" e "Silva") e seriam incluídos 3 registros/linhas na 
tabela PESSOAFISICAPALAVRA.

Tudo ficaria dessa forma:

Tabela PALAVRA:

CODIGO = 1, TEXTO = 'Fulano', REFERENCIAS = 1
CODIGO = 2, TEXTO = 'da', REFERENCIAS = 1
CODIGO = 3, TEXTO = 'Silva', REFERENCIAS = 1

Tabela PESSOAFISICAPALAVRA:

IDENTIFICADOR = 100, CODIGO_PALAVRA = 1
IDENTIFICADOR = 100, CODIGO_PALAVRA = 2
IDENTIFICADOR = 100, CODIGO_PALAVRA = 3

Nesse cenário, podemos fazer uma consulta de todas as pessoas físicas 
que tem a palavra "Fulano" (caso insensitivo pela colação PT_BR) da 
seguinte forma:

SELECT
   PF.IDENTIFICADOR,
   PF.NOME
FROM
   PALAVRA P
   PESSOAFISICAPALAVRA PFP,
   PESSOAFISICA PF
WHERE
   (P.TEXTO = 'Fulano')AND
   (PFP.CODIGO_PALAVRA = P.CODIGO)AND
   (PF.IDENTIFICADOR = PFP.IDENTIFICADOR);

Usando o operador EXISTS, você poderia refazer as condições de sua 
pesquisa, substituindo o CONTAINING por subselects dentro de EXISTS.

Dessa forma, você poderia realmente usar índices, e não mas ser obrigado 
a processar nome a nome.

É só uma sugestão.

Espero ter ajudado mais que atrapalhado. :D

Em 15-08-2013 21:30, Carlos Phelippe escreveu:
> Olá pessoal,
> preciso de ajuda para otimizar a consulta abaixo:
>
> BEGIN
> FOR SELECT C.IDENTIFICADOR, C.DATA, C.HORA, C.SONORO, C.AVISADO, C.OPCAO, C.TEXTO, C.CONTATO, C.LOGIN, C.PUBLICO, C.QUEM, C.SINCRONIZAR, C.DATAGRAVACAO, C.TIPO, TC.COR
>
> FROM COMPROMISSO C LEFT JOIN PESS0A_COMPROMISSO PC ON PC.ID_COMPROMISSO = C.IDENTIFICADOR
> LEFT JOIN TIPOCOMPROMISSO TC ON TC.NOME = C.TIPO
> LEFT JOIN PESSOAFISICA PF ON PF.IDENTIFICADOR = PC.ID_PESSOA AND PC.TIPO_PESSOA = :TIPO_PESSOA_PF
> LEFT JOIN PESSOAJURIDICA PJ ON PJ.IDENTIFICADOR = PC.ID_PESSOA AND PC.TIPO_PESSOA = :TIPO_PESSOA_PJ
> LEFT JOIN CONTATO_PJ C_PJ ON C_PJ.IDENTIFICADOR = PC.ID_PESSOA AND PC.TIPO_PESSOA = :TIPO_PESSOA_C_PJ
>
> WHERE C.DATA BETWEEN :DATA_01 AND :DATA_02 AND COALESCE(C.TEXTO,'') CONTAINING :TEXTO_ AND COALESCE(PJ.NOME,'')||COALESCE(PF.NOME,'')||COALESCE(C_PJ.NOME,'') CONTAINING :NOME AND C.OPCAO IN (:OPCAO_01, :OPCAO_02, :OPCAO_03) INTO :IDENTIFICADOR, :DATA, :HORA, :SONORO, :AVISADO, :OPCAO, :TEXTO, :CONTATO, :LOGIN, :PUBLICO, :QUEM, :SINCRONIZAR, :DATAGRAVACAO, :TIPO, :COR DO SUSPEND;
>
> END
>
> Como poderia otimizar a consulta acima?
>
> [http://www.digilab.com.br/wp-content/themes/Digilab/images/logo_digilab.jpg]   Carlos Phelippe
> Analista de Sistemas
> +55 48 3234 4041
> digilab.com.br<http://www.digilab.com.br>
>
> ______________________________________________
> FireBase-BR (www.firebase.com.br) - Hospedado em www.locador.com.br
> Para saber como gerenciar/excluir seu cadastro na lista, use: http://www.firebase.com.br/fb/artigo.php?id=1107
> Para consultar mensagens antigas: http://firebase.com.br/pesquisa





Mais detalhes sobre a lista de discussão lista