12.4. Funcionalidades adicionais #

12.4.1. Manuseio de documentos
12.4.2. Manuseio de consultas
12.4.3. Gatilhos para atualizações automáticas
12.4.4. Coleta de estatísticas de documentos

Esta seção descreve funções e operadores adicionais, úteis em conexão com a procura de texto completo.

12.4.1. Manuseio de documentos #

Na Seção 12.3.1 foi mostrado como documentos textuais brutos podem ser convertidos em valores tsvector. O PostgreSQL também fornece funções e operadores que podem ser usados para manipular documentos que já estão no formato tsvector.

tsvector || tsvector

O operador de concatenação de tsvector retorna um vetor que combina os lexemas e as informações de posição dos dois vetores fornecidos como argumentos. As posições e os rótulos de peso são preservados durante a concatenação. As posições que aparecem no vetor à direita são deslocadas pela maior posição mencionada no vetor à esquerda, de modo que o resultado é quase equivalente ao resultado da execução de to_tsvector na concatenação dos dois documentos originais. (A equivalência não é exata, porque palavras de parada removidas do final do argumento à esquerda não afetam seu resultado, mas afetam as posições dos lexemas no argumento à direita, se comparado com a concatenação dos textos.)

Uma vantagem de usar a concatenação no formato vetorial, em vez de concatenar o texto antes de aplicar a função to_tsvector, é possibilitar o uso de configurações diferentes para analisar diferentes partes do documento. Além disso, como a função setweight marca todos os lexemas do vetor fornecido da mesma maneira, é necessário analisar o texto e chamar setweight antes de concatenar, se for desejado rotular partes diferentes do documento com pesos diferentes.

setweight(vector tsvector, weight "char") returns tsvector

A função setweight retorna uma cópia do vetor de entrada, onde cada posição está rotulada com o peso (weight) especificado, um entre A, B, C, ou D. (D é o padrão para os novos vetores, e como tal não é mostrado na saída.) Esses rótulos são preservados quando os vetores são concatenados, permitindo que palavras de diferentes partes do documento sejam pontuadas de forma diferente pelas funções de pontuação.

Note que os rótulos de peso são aplicados às posições, e não aos lexemas. Se as posições forem removidas do vetor de entrada, então a função setweight não fará nada.

length(vector tsvector) returns integer

Retorna o número de lexemas armazenados no vetor.

strip(vector tsvector) returns tsvector

Retorna um vetor que lista os mesmos lexemas que o vetor fornecido, mas sem nenhuma informação de posição ou de peso. O resultado é geralmente bem menor do que o vetor original fornecido, mas também menos útil. A pontuação por relevância não funciona tão bem nos vetores sem informação de posição ou de peso, quanto nos vetores com essas informações. Além disso, o operador <-> (FOLLOWED BY) do tsquery nunca vai corresponder a uma entrada sem informação de posição, porque não pode determinar a distância entre as ocorrências dos lexemas.

Uma lista completa de funções relacionadas a tsvector está disponível na Tabela 9.43.

Exemplo 12.1. Exemplo do tradutor

Uso das funções to_tsvector, setweight e strip

Neste exemplo é usada a função to_tsvector, para criar tsvector, a função setweight, para atribuir rótulos de peso aos tsvector criados pela função to_tsvector, e a função strip para remover os rótulos de peso e de posição, além do operador de concatenação de tsvector.

select
    setweight(to_tsvector('portuguese', 'Estrela supernova de Kepler'), 'A') ||
    setweight(to_tsvector('portuguese', 'Uma supernovidade da Estrela'), 'B');
                 ?column?
-------------------------------------------
 'estrel':1A,8B 'kepl':4A 'supernov':2A,6B
select strip(
    setweight(to_tsvector('portuguese', 'Estrela supernova de Kepler'), 'A') ||
    setweight(to_tsvector('portuguese', 'Uma supernovidade da Estrela'), 'B'));
           strip
----------------------------
 'estrel' 'kepl' 'supernov'

12.4.2. Manuseio de consultas #

Na Seção 12.3.2 foi mostrado como consultas textuais brutas podem ser convertidas em valores tsquery. O PostgreSQL também fornece funções e operadores que podem ser usados para manipular consultas que já estão no formato tsquery.

tsquery && tsquery

Retorna a combinação AND das duas consultas fornecidas.

tsquery || tsquery

Retorna a combinação OR das duas consultas fornecidas.

!! tsquery

Retorna a negação (NOT) da consulta fornecida.

tsquery <-> tsquery

Retorna uma consulta que procura correspondência para a primeira consulta fornecida, seguida imediatamente pela correspondência para a segunda consulta fornecida, usando o operador tsquery <-> (FOLLOWED BY). Por exemplo:

SELECT to_tsquery('english', 'fat') <-> to_tsquery('english', 'cat | rat');

          ?column?
-----------------------------
 'fat' <-> ( 'cat' | 'rat' )

-- Exemplo do tradutor abaixo

SELECT to_tsquery('portuguese', 'gordo') <-> to_tsquery('portuguese', 'gato | rato');

           ?column?
------------------------------
 'gord' <-> ( 'gat' | 'rat' )

tsquery_phrase(query1 tsquery, query2 tsquery [, distance integer ]) returns tsquery

Retorna uma consulta que procura correspondência para a primeira consulta fornecida, seguida pela correspondência para a segunda consulta fornecida, a uma distância de exatamente distance lexemas, usando o operador tsquery <N>. Por exemplo:

SELECT tsquery_phrase(to_tsquery('english', 'fat'),
                      to_tsquery('english', 'cat'), 10);

  tsquery_phrase
------------------
 'fat' <10> 'cat'

-- Exemplo do tradutor abaixo

SELECT tsquery_phrase(to_tsquery('portuguese', 'rato'),
                      to_tsquery('portuguese', 'gordo'), 10);

  tsquery_phrase
-------------------
 'rat' <10> 'gord'

numnode(query tsquery) returns integer

Retorna o número de nós (lexemas mais operadores) no tsquery. Essa função é útil para determinar se a consulta (query) faz sentido (retorna > 0), ou se contém apenas palavras de parada (retorna 0). Exemplos:

SELECT numnode(plainto_tsquery('english', 'the any'));

NOTA:  a consulta de procura de texto completo contém somente
       palavras ignoradas ou não contém lexemas, ignorada

 numnode
---------
       0

-- Exemplos do tradutor abaixo

SELECT numnode(plainto_tsquery('portuguese', 'um dos'));

NOTA:  a consulta de procura de texto completo contém somente
       palavras ignoradas ou não contém lexemas, ignorada
 numnode
---------
       0

SELECT numnode('bom & bar'::tsquery);

 numnode
---------
       3

querytree(query tsquery) returns text

Retorna a parte de uma tsquery que pode ser usada para procurar um índice. Esta função é útil para detectar consultas não indexáveis, como, por exemplo, aquelas que contêm apenas palavras de parada ou apenas termos negados. Por exemplo:

SELECT querytree(to_tsquery('english', 'defined'));

querytree
-----------
 'defin'

SELECT querytree(to_tsquery('english', '!defined'));

qquerytree
-----------
 T

-- Exemplos do tradutor abaixo

SELECT querytree(to_tsquery('portuguese', 'definido'));

qquerytree
-----------
 'defin'

SELECT querytree(to_tsquery('portuguese', '!definido'));

 querytree
-----------
 T

SELECT querytree(to_tsquery('portuguese', 'um & dos'));

NOTA:  a consulta de procura de texto completo contém somente
       palavras ignoradas ou não contém lexemas, ignorada
 querytree
-----------

12.4.2.1. Reescrita de consulta #

A família de funções ts_rewrite procura por ocorrências de uma subconsulta de destino em uma determinada tsquery, e substitui cada ocorrência por uma subconsulta substituta. Essencialmente, essa operação é uma versão de substituição de sub-cadeias de caracteres específica para tsquery. Uma combinação de destino e substituto pode ser pensada como uma regra de reescrita de consulta. Uma coleção dessas regras de reescrita pode ser um poderoso auxílio de procura. Por exemplo, a procura pode ser expandida usando sinônimos (por exemplo, new york, big apple, nyc, gotham), ou restringida para direcionar o usuário para algum tópico em voga. Há alguma sobreposição de funcionalidade entre este recurso e os thesaurus (dicionários de sinônimos) (veja a Seção 12.6.4). No entanto, através dessas funções é possível modificar um conjunto de regras de reescrita dinamicamente sem reindexação, enquanto a atualização de um thesaurus exige a reindexação.

ts_rewrite (query tsquery, target tsquery, substitute tsquery) returns tsquery

Essa forma de ts_rewrite apenas aplica uma única regra de reescrita: target é substituído por substitute, onde quer que apareça em query. Por exemplo:

SELECT ts_rewrite('a & b'::tsquery, 'a'::tsquery, 'c'::tsquery);

 ts_rewrite
------------
 'b' & 'c'

ts_rewrite (query tsquery, select text) returns tsquery

Essa forma da função ts_rewrite aceita uma consulta (query) inicial, e um comando SQL select, fornecido como uma cadeia de caracteres. O comando select deve produzir duas colunas do tipo tsquery. Para cada linha de resultado do comando select, as ocorrências do valor da primeira coluna (destino) serão substituídas pelo valor da segunda coluna (substituto), dentro do valor corrente da query. Por exemplo:

CREATE TEMPORARY TABLE aliases (t tsquery PRIMARY KEY, s tsquery);
INSERT INTO aliases VALUES('a', 'c');
SELECT ts_rewrite('a & b'::tsquery, 'SELECT t, s FROM aliases');

 ts_rewrite
------------
 'b' & 'c'

Note que, quando várias regras de reescrita são aplicadas dessa maneira, a ordem da aplicação pode ser importante; então, na prática vai ser desejado que a consulta use ORDER BY para impor uma determinada ordem às regras de reescrita.

Vamos considerar um exemplo astronômico da vida real. Expandiremos a consulta supernovae usando regras de reescrita baseadas em tabelas:

CREATE TEMPORARY TABLE aliases (t tsquery primary key, s tsquery);
INSERT INTO aliases VALUES(
    to_tsquery('english', 'supernovae'),
    to_tsquery('english', 'supernovae|sn'));
SELECT ts_rewrite(
    to_tsquery('english', 'supernovae & crab'),
    'SELECT * FROM aliases');

           ts_rewrite
---------------------------------
 'crab' & ( 'supernova' | 'sn' )

-- Exemplo do tradutor abaixo

TRUNCATE TABLE aliases;
INSERT INTO aliases VALUES(
    to_tsquery('portuguese', 'supernova'),
    to_tsquery('portuguese', 'supernova|sn'));
SELECT ts_rewrite(
    to_tsquery('portuguese', 'supernova & Kepler'),
    'SELECT * FROM aliases');

           ts_rewrite
--------------------------------
 'kepl' & ( 'supernov' | 'sn' )

As regras de reescrita podem ser alteradas apenas atualizando a tabela:

UPDATE aliases
SET s = to_tsquery('english', 'supernovae|sn & !nebulae')
WHERE t = to_tsquery('english', 'supernovae');
SELECT * FROM aliases;

      t      |               s
-------------+--------------------------------
 'supernova' | 'supernova' | 'sn' & !'nebula'

SELECT ts_rewrite(
    to_tsquery('supernovae & crab'),
    'SELECT * FROM aliases');

                 ts_rewrite
---------------------------------------------
 'crab' & ( 'supernova' | 'sn' & !'nebula' )

-- Exemplos do tradutor abaixo

UPDATE aliases
SET s = to_tsquery('portuguese', 'supernova|sn & !nebulosa')
WHERE t = to_tsquery('portuguese', 'supernova');
SELECT * FROM aliases;

     t      |              s
------------+------------------------------
 'supernov' | 'supernov' | 'sn' & !'nebul'

SELECT ts_rewrite(
    to_tsquery('portuguese', 'supernova & Kepler'),
    'SELECT * FROM aliases');

                ts_rewrite
-------------------------------------------
 'kepl' & ( 'supernov' | 'sn' & !'nebul' )

A reescrita pode ser lenta quando há muitas regras de reescrita, porque são verificadas todas as regras para uma possível correspondência. Para filtrar as regras não candidatas óbvias, podem ser usados os operadores de contém para o tipo tsquery. No exemplo abaixo, são selecionadas apenas as regras que podem corresponder à consulta original:

SELECT ts_rewrite('a & b'::tsquery,
                  'SELECT t,s FROM aliases WHERE ''a & b''::tsquery @> t');

 ts_rewrite
------------
 'b' & 'c'

12.4.3. Gatilhos para atualizações automáticas #

Nota

O método descrito nessa seção se tornou obsoleto devido ao uso de colunas geradas armazenadas, conforme descrito na Seção 12.2.2.

Ao usar uma coluna separada para armazenar a representação tsvector dos documentos, é necessário criar um gatilho para atualizar a coluna tsvector quando as colunas de conteúdo do documento forem alteradas. Estão disponíveis duas funções de gatilho nativas para essa finalidade, mas você pode escrever suas próprias funções.

tsvector_update_trigger(tsvector_column_name,​ config_name, text_column_name [, ... ])
tsvector_update_trigger_column(tsvector_column_name,​ config_column_name, text_column_name [, ... ])

Estas funções de gatilho calculam automaticamente uma coluna tsvector a partir de uma ou mais colunas de texto, sob o controle dos parâmetros especificados no comando CREATE TRIGGER. Um exemplo de uso é:

CREATE TABLE messages (
    title       text,
    body        text,
    tsv         tsvector
);
CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
ON messages FOR EACH ROW EXECUTE FUNCTION
tsvector_update_trigger(tsv, 'pg_catalog.english', title, body);
INSERT INTO messages VALUES('title here', 'the body text is here');
SELECT * FROM messages;

   title    |         body          |            tsv
------------+-----------------------+----------------------------
 title here | the body text is here | 'bodi':4 'text':5 'titl':1

SELECT title, body FROM messages WHERE tsv @@ to_tsquery('english','title & body');

   title    |         body
------------+-----------------------
 title here | the body text is here

Uma vez criado este gatilho, qualquer mudança no campo title ou no campo body será automaticamente refletida em tsv, sem que a aplicação precise se preocupar com isto.

O primeiro argumento do gatilho deve ser o nome da coluna tsvector a ser atualizada. O segundo argumento especifica a configuração de procura de texto a ser usada para realizar a conversão. Para a função tsvector_update_trigger, o nome da configuração é fornecido simplesmente como o segundo argumento do gatilho. Deve ser qualificado pelo esquema conforme mostrado acima, para que o comportamento do gatilho não seja alterado devido a alterações no search_path. Para a função tsvector_update_trigger_column, o segundo argumento do gatilho é o nome de outra coluna da tabela, que deve ser do tipo regconfig. Isso permite ser feita uma seleção de configuração por linha. Os argumentos restantes são os nomes das colunas de texto (do tipo text, varchar, ou char). Esses serão incluídos no documento na ordem indicada. Os valores nulos são ignorados (mas as outras colunas ainda serão indexadas).

Uma limitação desses gatilhos nativos é o fato de tratarem todas as colunas de entrada da mesma forma. Para processar colunas de forma diferente — por exemplo, para pontuar o título de forma diferente do corpo — é necessário escrever um gatilho personalizado. A seguir está um exemplo usando PL/pgSQL como a linguagem do gatilho:

CREATE FUNCTION messages_trigger() RETURNS trigger AS $$
begin
  new.tsv :=
     setweight(to_tsvector('pg_catalog.english', coalesce(new.title,'')), 'A') ||
     setweight(to_tsvector('pg_catalog.english', coalesce(new.body,'')), 'D');
  return new;
end
$$ LANGUAGE plpgsql;

CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
    ON messages FOR EACH ROW EXECUTE FUNCTION messages_trigger();

Lembre-se ser importante especificar o nome da configuração explicitamente ao criar valores tsvector dentro de gatilhos, para que o conteúdo da coluna não seja afetado por alterações em default_text_search_config. A falha em fazer isto provavelmente levará a problemas, como resultados de procura alterados após salvar e recuperar o banco de dados.

12.4.4. Coleta de estatísticas de documentos #

A função ts_stat é útil para verificar a configuração e encontrar candidatos a palavras de parada.

ts_stat(sqlquery text, [ weights text, ]
        OUT word text, OUT ndoc integer,
        OUT nentry integer) returns setof record

O argumento sqlquery é um valor de texto contendo uma consulta SQL que deve retornar uma única coluna tsvector. A função ts_stat executa a consulta e retorna estatísticas sobre cada lexema distinto (palavra) contido nos dados tsvector. As colunas retornadas são:

  • word text — o valor do lexema

  • ndoc integer — o número de documentos (tsvector) onde a palavra foi encontrada

  • nentry integer — o número total de ocorrências da palavra

Se forem especificados pesos (weights), apenas as ocorrências com um desses pesos serão contadas.

Por exemplo, para encontrar as dez palavras mais frequentes em uma coleção de documentos:

SELECT * FROM ts_stat('SELECT vector FROM apod')
ORDER BY nentry DESC, ndoc DESC, word
LIMIT 10;

A mesma consulta, mas contando apenas ocorrências de palavras com peso A ou B:

SELECT * FROM ts_stat('SELECT vector FROM apod', 'ab')
ORDER BY nentry DESC, ndoc DESC, word
LIMIT 10;