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:
Não há suporte linguístico, nem mesmo para o inglês.
Expressões regulares não são suficientes, por não poderem lidar
facilmente com palavras derivadas, como, por exemplo,
satisfaz e satisfeito.
Pode deixar passar documentos que contenham
satisfeito, embora provavelmente se queira
encontrá-los ao procurar por satisfaz.
É possível usar OR para procurar por várias
formas derivadas, mas isto é entediante e propenso a erros
(algumas palavras podem ter dezenas de derivações
[97]).
Não fornecem classificação (pontuação) dos resultados da procura, o que os torna ineficazes quando são encontrados milhares de documentos que correspondem ao texto procurado.
Tendem a ser lentos, por não haver suporte de índice, então devem processar o documento todo para cada procura.
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:
Dividir o documento em fragmentos
(tokens).
É útil identificar várias classes de fragmentos, como,
por exemplo, números, palavras, palavras complexas, endereços de
e-mail ..., para poderem ser processados de maneira diferente.
A princípio, as classes de fragmentos dependem da
aplicação específica, mas para a maioria das finalidades é adequado
usar um conjunto predefinido de classes.
O PostgreSQL usa o
analisador (parser) para
realizar esta etapa.
É fornecido um analisador padrão, podendo ser criados analisadores
personalizados para necessidades específicas.
Converter os fragmentos (tokens) em
lexemas
[98]
[99].
Um lexema é uma cadeia de caracteres, assim como o
fragmento, mas normalizado para
que diferentes derivações da mesma palavra se tornem iguais.
Por exemplo, a normalização quase sempre inclui converter letras
maiúsculas em minúsculas, e envolve geralmente a remoção de sufixos
(como s ou es em Inglês).
Isto permite que as procuras encontrem formas derivadas da mesma
palavra, sem inserir todas as derivações possíveis.
Além disso, normalmente nesta etapa são eliminadas as
palavras de parada
(stop words), palavras tão
comuns que são inúteis para a procura.
(Resumindo, os fragmentos são pedaços brutos do texto
do documento, enquanto os lexemas são palavras que se acredita
serem úteis para a indexação e procura.)
O PostgreSQL usa
dicionários para realizar esta etapa.
São fornecidos diversos dicionários padrão, podendo ser criados
dicionários personalizados para necessidades específicas.
Armazenar documentos pré-processados otimizados para procura. Por exemplo, cada documento pode ser representado por uma matriz ordenada de lexemas normalizados. Além dos lexemas, muitas vezes se deseja armazenar também informações de posição para usar na pontuação de proximidade, de modo que um documento que contenha uma região mais “densa” das palavras da consulta, receba uma pontuação mais alta do que outro contendo as palavras da consulta mais dispersas.
Os dicionários permitem um controle fino sobre como os fragmentos
(tokens) são normalizados.
Com dicionários apropriados, é possível:
Definir palavras de parada que não devem ser indexadas.
Mapear sinônimos para uma única palavra usando a aplicação Ispell
Mapear frases para uma única palavra usando um thesaurus (dicionário de sinônimos).
Mapear diferentes derivações de uma palavra para uma forma canônica usando a aplicação Ispell.
Mapear diferentes derivações de uma palavra para uma forma canônica usando as regras do lematizador Snowball.
É 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).
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;
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.
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 <,
onde N>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.)
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.