Esta seção descreve funções e operadores adicionais, úteis em conexão com a procura de texto completo.
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'
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
<.
Por exemplo:
N>
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
-----------
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'
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.
A função ts_stat é útil para verificar a
configuração e encontrar candidatos a palavras de parada.
ts_stat(sqlquerytext, [weightstext, ] OUTwordtext, OUTndocinteger, OUTnentryinteger) returnssetof 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;