12.1. Introdução #

12.1.1. O que é um documento?
12.1.2. Correspondência básica de texto
12.1.3. Configurações

A procura de texto completo (ou apenas procura de texto), fornece a capacidade de identificar documentos em linguagem natural, que satisfaçam uma consulta e, opcionalmente, classificá-los por relevância para a consulta. O tipo mais comum de procura é encontrar todos os documentos que contenham determinados termos de consulta, e devolvê-los na ordem de sua semelhança com a consulta. As noções de consulta e de semelhança são muito flexíveis, e dependem da aplicação específica. A procura mais simples considera consulta como um conjunto de palavras, e semelhança como a frequência das palavras da consulta no documento.

Operadores de procura por texto existem nos bancos de dados há anos. O PostgreSQL possui os operadores ~, ~*, LIKE e ILIKE para tipos de dados textuais, mas estes operadores não possuem muitas propriedades essenciais exigidas pelos sistemas de informação modernos:

A indexação para procura de texto completo permite que os documentos sejam pré-processados, e um índice seja salvo para procura rápida posterior. O pré-processamento inclui:

Os dicionários permitem um controle fino sobre como os fragmentos (tokens) são normalizados. Com dicionários apropriados, é possível:

É fornecido o tipo de dados tsvector para armazenar documentos pré-processados, junto com o tipo de dados tsquery para representar consultas processadas (Seção 8.11). Existem muitas funções e operadores disponíveis para estes tipos de dados (Seção 9.13), sendo o mais importante deles o operador de correspondência @@, introduzido na Seção 12.1.2. As procuras de texto completo podem ser aceleradas usando índices (Seção 12.9).

12.1.1. O que é um documento? #

Um documento é a unidade de procura em um sistema de procura de texto completo; por exemplo, um artigo de revista ou mensagem de e-mail. O mecanismo de procura de texto completo deve conseguir analisar documentos e armazenar associações de lexemas (palavras-chave) com seu documento pai. Posteriormente, estas associações são usadas para procurar documentos que contenham palavras da consulta.

Para procuras dentro do PostgreSQL, um documento é normalmente um campo textual dentro da linha de uma tabela do banco de dados ou, possivelmente, uma combinação (concatenação) destes campos, talvez armazenados em várias tabelas ou obtidos dinamicamente. Em outras palavras, um documento pode ser construído a partir de diferentes fragmentos para indexação, e pode não estar armazenado em nenhum lugar como um todo. Por exemplo:

SELECT título || ' ' ||  autor || ' ' ||  resumo || ' ' || corpo AS documento
FROM mensagens
WHERE mid = 12;

SELECT m.título || ' ' || m.autor || ' ' || m.resumo || ' ' || d.corpo AS documento
FROM mensagens m, docs d
WHERE m.mid = d.did AND m.mid = 12;

Nota

Na verdade, nestas consultas exemplo, deveria ter sido usada a função coalesce para evitar que um único atributo NULL cause um resultado NULL para todo o documento.

Outra possibilidade é armazenar os documentos como arquivos de texto simples no sistema de arquivos. Nesse caso, o banco de dados pode ser usado para armazenar o índice de texto completo e executar procuras, e algum identificador único pode ser usado para recuperar o documento do sistema de arquivos. Entretanto, recuperar arquivos de fora do banco de dados requer permissões de superusuário ou suporte a funções especiais, portanto, isto é geralmente menos conveniente do que manter todos os dados dentro do PostgreSQL. Além disso, manter tudo dentro do banco de dados permite fácil acesso aos metadados do documento para auxiliar na indexação e exibição.

Para fins de procura de texto, cada documento deve ser reduzido ao formato tsvector pré-processado. A procura e a classificação são realizadas inteiramente na representação tsvector de um documento — o texto original só precisa ser recuperado quando o documento é selecionado para ser exibido ao usuário. Por isto, muitas vezes falamos do tsvector como sendo o documento, mas é claro que é apenas uma representação compacta do documento completo.

12.1.2. Correspondência básica de texto #

Nota

Nesta tradução, os exemplos foram executados em um servidor PostgreSQL com o parâmetro de configuração default_text_search_config definido como pg_catalog.portuguese. Portanto, as funções de procura de texto que aceitam o argumento opcional regconfig, usando assim a configuração especificada por default_text_search_config quando este argumento é omitido, não precisam que seja especificado regconfig como portuguese, porque este é o padrão, mas precisam que regconfig seja especificado como english quando o texto está em inglês, a não ser que default_text_search_config seja redefinido na sessão. Veja a Seção 12.1.3 abaixo. (N. T.)

A procura de texto completo no PostgreSQL é baseada no operador de correspondência @@, que retorna true se um tsvector (documento) corresponde a um tsquery (consulta). Não importa qual tipo de dados se escreve primeiro:

SET default_text_search_config = 'pg_catalog.english';
SET

SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector @@ 'cat & rat'::tsquery;

 ?column?
----------
 t

SELECT 'fat & cow'::tsquery @@ 'a fat cat sat on a mat and ate a fat rat'::tsvector;

 ?column?
----------
 f

Como o exemplo acima sugere, tsquery não é apenas texto bruto, assim como tsvector também não é. O tsquery contém termos de procura, que devem ser lexemas já normalizados, podendo combinar vários termos usando os operadores AND, OR, NOT e FOLLOWED BY. (Para obter detalhes da sintaxe, veja a Seção 8.11.2.) Existem as funções to_tsquery, plainto_tsquery e phraseto_tsquery, úteis na conversão do texto escrito pelo usuário para o tipo tsquery adequado, principalmente normalizando as palavras que aparecem no texto. Da mesma forma, a função to_tsvector é usada para analisar e normalizar uma cadeia de caracteres de documento. Então, na prática, uma correspondência de procura de texto seria mais parecida com:

SELECT to_tsvector('english', 'fat cats ate fat rats')
     @@ to_tsquery('english', 'fat & rat');

 ?column?
----------
 t

SELECT to_tsvector('portuguese', 'gatos gordos comem ratos gordos') -- Exemplo do tradutor
     @@ to_tsquery('portuguese', 'gordo & rato');

 ?column?
----------
 t

Note que esta correspondência não é bem-sucedida quando escrita como

SELECT 'gatos gordos comem ratos gordos'::tsvector
    @@ to_tsquery('gordo & rato');

 ?column?
----------
 f

uma vez que aqui não ocorrerá a normalização das palavras ratos e gordos. Os elementos do tsvector são lexemas, assumidos como já estando normalizados, então ratos não corresponde a rato, nem gordos corresponde a gordo.

O operador @@ também oferece suporte a entradas do tipo text, permitindo que a conversão explícita de uma cadeia de caracteres de texto para tsvector ou tsquery seja ignorada em casos simples. As formas disponíveis são:

tsvector @@ tsquery
tsquery  @@ tsvector
text @@ tsquery
text @@ text

As duas primeiras formas já foram vistas. A forma text @@ tsquery equivale a to_tsvector(x) @@ y. A forma text @@ text equivale a to_tsvector(x) @@ plainto_tsquery(y).

Dentro de tsquery, o operador & (AND ) especifica que os dois argumentos devem aparecer no documento para haver correspondência. De forma semelhante, o operador | (OR) especifica que pelo menos um de seus argumentos deve aparecer, enquanto o operador ! (NOT) especifica que seu argumento não deve aparecer para haver correspondência. Por exemplo, a consulta gordo & ! rato corresponde em documentos que contenham gordo, mas não contenham rato.

A procura de frases é possível com a ajuda do operador <-> (FOLLOWED BY) no tsquery, que corresponde apenas se os seus argumentos tiverem correspondências adjacentes, e na ordem especificada. Por exemplo:

SHOW default_text_search_config;

 default_text_search_config
----------------------------
 pg_catalog.portuguese

SELECT to_tsvector('erro fatal') @@ to_tsquery('erro <-> fatal');

 ?column?
----------
 t

SELECT to_tsvector('esse erro não é fatal') @@ to_tsquery('fatal <-> erro');

 ?column?
----------
 f

Existe uma versão mais geral do operador FOLLOWED BY com a forma <N>, onde N é um número inteiro representando a diferença entre as posições dos lexemas correspondentes. <1> é a mesma coisa que <->, enquanto <2> permite que exatamente um outro lexema apareça entre as correspondências, e assim por diante. A função phraseto_tsquery usa este operador para construir uma tsquery que pode corresponder a uma frase com várias palavras, quando algumas palavras são palavras de parada. Por exemplo:

SELECT phraseto_tsquery('gatos comem ratos');

     phraseto_tsquery
---------------------------
 'gat' <-> 'com' <-> 'rat'

SELECT phraseto_tsquery('Os gatos comem os ratos');

     phraseto_tsquery
---------------------------
 'gat' <-> 'com' <2> 'rat'

Um caso especial, às vezes útil, é que <0> pode ser usado para exigir que dois padrões correspondam à mesma palavra.

Parênteses podem ser usados para controlar o aninhamento dos operadores do tsquery. Sem parênteses, | une menos firmemente, depois &, depois <-> e ! mais firmemente.

Vale a pena notar que os operadores AND/OR/NOT significam algo sutilmente diferente quando estão dentro dos argumentos do operador FOLLOWED BY do que quando não estão, porque dentro de FOLLOWED BY a posição exata da correspondência tem significado. Por exemplo, normalmente !x corresponde apenas a documentos que não contenham x em nenhum lugar, mas !x <-> y corresponde com y, se não estiver logo após o x; uma ocorrência de x em outro lugar no documento não impede a correspondência. Outro exemplo, é que x & y normalmente requer apenas que ambos x e y apareçam em algum lugar no documento, mas (x & y) <-> z requer que x e y correspondam no mesmo lugar, logo antes de um z. Portanto, esta consulta se comporta de forma diferente de x <-> z & y <-> z, que vai corresponder a um documento contendo duas sequências separadas x z e y z. (Esta consulta específica é inútil da forma como está escrita, porque x e y não podem corresponder no mesmo lugar; mas em situações mais complexas, como padrões de correspondência de prefixo, uma consulta dessa forma pode ser útil.)

12.1.3. Configurações #

Os exemplos acima são todos exemplos de procura de texto simples. Como mencionado anteriormente, a funcionalidade de procura de texto completo inclui a capacidade de fazer muito mais coisas: saltar a indexação de certas palavras (palavras de parada), processar sinônimos e usar análise sofisticada, como, por exemplo, análise baseada em mais do que apenas espaço em branco. Esta funcionalidade é controlada pelas configurações de procura de texto. O PostgreSQL vem com configurações predefinidas para muitos idiomas, e você pode criar facilmente suas próprias configurações. (O comando \dF do psql mostra todas as configurações disponíveis.)

Durante a instalação, uma configuração apropriada é selecionada, e default_text_search_config é definido no arquivo postgresql.conf segundo a localidade. Se estiver sendo usada a mesma configuração de procura de texto para toda a instância (cluster), pode ser usado o valor do arquivo postgresql.conf. Para usar configurações diferentes no agrupamento, mas usar a mesma configuração em todos os bancos de dados, deve ser usado o comando ALTER DATABASE ... SET. Caso contrário, pode ser definido default_text_search_config em cada sessão.

Toda função de procura de texto dependente de uma configuração, tem o argumento opcional regconfig para que a configuração a ser usada possa ser especificada explicitamente. O valor do parâmetro de configuração default_text_search_config é usado apenas quando este argumento é omitido.

Para facilitar a criação de configurações personalizadas de procura de texto, uma configuração é criada a partir de objetos de banco de dados mais simples. O recurso de procura de texto do PostgreSQL fornece quatro tipos de objetos de banco de dados relacionados à configuração:

  • Os analisadores de procura de texto completo dividem os documentos em tokens, e classificam cada token (por exemplo, como palavras ou números).

  • Os dicionários de procura de texto completo convertem os tokens para a forma normalizada, e rejeitam palavras de parada.

  • Os modelos de procura de texto completo (templates) fornecem as funções necessárias para os dicionários. (O dicionário apenas determina um modelo e o conjunto de parâmetros para este modelo.)

  • As configurações de procura de texto completo selecionam um analisador e um conjunto de dicionários, a serem usados para normalizar os tokens produzidos pelo analisador.

Os analisadores e modelos de procura de texto completo são construídos a partir de funções C de baixo nível; portanto, é necessária a capacidade de programação na linguagem C para desenvolver novos analisadores e modelos, e privilégios de superusuário para instalá-los no banco de dados. (Existem exemplos de analisadores e modelos complementares na área contrib/ da distribuição do PostgreSQL.) Como os dicionários e as configurações apenas parametrizam e conectam alguns analisadores e modelos subjacentes, nenhum privilégio especial é necessário para criar dicionário ou configuração. Exemplos de criação de dicionários e configurações personalizados são mostrados mais adiante nesse capítulo.



[97] Derivação é um processo de formação de novas palavras pelo acréscimo de afixos a um radical.(N. T.)

[98] lexema: Conjunto de palavras de mesma classe morfológica que diferem entre si por sufixos reflexivos. Fonte: Dicionário inFormal. (N. T.)

[99] lexema: Unidade linguística que combina a forma (gráfica ou fonética) e o significado, o qual não é divisível em unidades menores; unidade lexical; Do grego lẽxis, «vocábulo». Fonte: infopédia (N. T.)