12.3. Controle da procura de texto completo #

12.3.1. Análise de documentos
12.3.2. Análise de consultas
12.3.3. Pontuação dos resultados da procura
12.3.4. Realçando os resultados

Para implementar a procura de texto completo, deve haver função para criar o tsvector a partir do documento, e para criar o tsquery a partir da consulta do usuário. Além disso, também é necessário retornar os resultados em uma ordem útil, portanto é necessária uma função para comparar os documentos baseado na sua relevância para a consulta. Também é importante conseguir exibir os resultados de forma apropriada. O PostgreSQL fornece suporte para todas estas funções.

12.3.1. Análise de documentos #

O PostgreSQL disponibiliza a função to_tsvector para converter o documento para o tipo de dados tsvector.

to_tsvector([ config regconfig, ] document text) returns tsvector

A função to_tsvector converte o documento textual em tokens, reduz os tokens a lexemas, e retorna o tipo de dados tsvector, que lista os lexemas junto com suas posições no documento. O documento é processado segundo a configuração de procura de texto especificada, ou segundo a configuração padrão, se nenhuma configuração for especificada. A seguir está um exemplo simples:

SELECT to_tsvector('english', 'a fat  cat sat on a mat - it ate a fat rats');

                  to_tsvector
-----------------------------------------------------
 'ate':9 'cat':3 'fat':2,11 'mat':7 'rat':12 'sat':4

-- Exemplo do tradutor abaixo

SELECT to_tsvector('portuguese', 'um gato gordo sentou no tapete - comeu ratos gordos');

                      to_tsvector
-------------------------------------------------------
 'com':7 'gat':2 'gord':3,9 'rat':8 'sent':4 'tapet':6

No exemplo em inglês acima, vemos que o tsvector resultante não contém as palavras a, on ou it, a palavra rats tornou-se rat, e o sinal de pontuação - foi ignorado. (no exemplo em português também ocorreram situações semelhantes, 'um' e 'no' não aparecem no resultado, mas suas posições foram contadas, '-' não aparece no resultado nem sua posição foi contada, enquanto as demais palavras foram convertidas nos seus lexemas)

A função to_tsvector chama internamente o analisador que divide o texto do documento em tokens, e atribui um tipo a cada token. Para cada token é consultada uma lista de dicionários (veja a Seção 12.6), onde a lista pode variar dependendo do tipo do token. O primeiro dicionário que reconhecer o token gera um ou mais lexemas normalizados para representar o token. Por exemplo, em inglês rats se tornou rat, porque um dos dicionários reconheceu que a palavra rats é o plural de rat. Algumas palavras são reconhecidas como sendo palavras de parada (stop words) (veja a Seção 12.6.1), o que faz com que sejam ignoradas, porque ocorrem com muita frequência para serem úteis na procura. No exemplo em inglês estas palavras são a, on e it, e no exemplo em português 'um' e 'no'. Se nenhum dicionário da lista reconhecer o token, ele também será ignorado. Nesse exemplo isto aconteceu com o sinal de pontuação -, porque, de fato, não há dicionários atribuídos ao seu tipo de token (Símbolos de espaço), significando que os tokens de espaço nunca são indexados. As escolhas de analisador, dicionários, e quais tipos de tokens indexar, são determinadas pela configuração de procura de texto selecionada (veja a Seção 12.7). É possível haver muitas configurações diferentes no mesmo banco de dados, e estão disponíveis configurações predefinidas para vários idiomas. No exemplo em inglês foi usada a configuração padrão english para o idioma inglês, e 'portuguese' para o idioma português.

A função setweight pode ser usada para rotular as entradas de tsvector com um determinado rótulo de peso, (weight), onde o rótulo de peso é uma das letras A, B, C ou D. Normalmente esta função é usada para marcar entradas provenientes de partes diferentes do documento, como título versus corpo. Depois estas informações podem ser usadas para pontuar os resultados da procura.

Como to_tsvector(NULL) retorna NULL, é recomendado usar a função coalesce sempre que um campo puder ser nulo. Abaixo está o método recomendado para criar tsvector a partir de um documento estruturado:

UPDATE tt SET ti =
    setweight(to_tsvector(coalesce(title,'')), 'A')    || -- título
    setweight(to_tsvector(coalesce(keyword,'')), 'B')  || -- palavra-chave
    setweight(to_tsvector(coalesce(abstract,'')), 'C') || -- resumo
    setweight(to_tsvector(coalesce(body,'')), 'D');       -- corpo do documento

Aqui é usada a função setweight para acrescentar um rótulo de peso ao tsvector retornado, conforme a origem de cada lexema, e, em seguida, os valores com seus pesos são concatenados usando operador de concatenação || de tsvector. (A Seção 12.4.1 fornece detalhes sobre estas operações.)

12.3.2. Análise de consultas #

O PostgreSQL disponibiliza as funções to_tsquery, plainto_tsquery, phraseto_tsquery e websearch_to_tsquery para converter uma consulta para o tipo de dados tsquery. A função to_tsquery oferece acesso a mais recursos do que plainto_tsquery ou phraseto_tsquery, mas é mais exigente quanto a entrada. A função websearch_to_tsquery é uma versão simplificada da função to_tsquery com uma sintaxe alternativa, semelhante à usada pelos motores de busca na web.

to_tsquery([ config regconfig, ] querytext text) returns tsquery

A função to_tsquery cria um valor tsquery a partir do querytext, que deve consistir em tokens únicos separados pelos operadores tsquery & (AND), | (OR), ! (NOT) e <-> (FOLLOWED BY), possivelmente agrupados usando parênteses. Em outras palavras, a entrada para to_tsquery já deve seguir as regras gerais para entrada de tsquery, conforme descrito na Seção 8.11.2. A diferença, é que enquanto a entrada básica de tsquery recebe os tokens diretamente, a função to_tsquery normaliza cada token em um lexema usando a configuração especificada, ou a configuração padrão, e descarta todos os tokens que são palavras de parada segundo a configuração. Por exemplo:

SELECT to_tsquery('english', 'The & Fat & Rats');

  to_tsquery
---------------
 'fat' & 'rat'

-- Exemplo do tradutor abaixo

SELECT to_tsquery('portuguese', 'Os & Ratos & Gordos');

   to_tsquery
----------------
 'rat' & 'gord'

Como na entrada básica de tsquery, podem ser anexados rótulos de peso a cada lexema, para restringi-los de forma que correspondam apenas aos lexemas de tsvector com estes rótulos de peso. Por exemplo:

SELECT to_tsquery('english', 'Fat | Rats:AB');

    to_tsquery
------------------
 'fat' | 'rat':AB

-- Exemplo do tradutor abaixo

SELECT to_tsquery('portuguese', 'Gordos | Ratos:AB');

    to_tsquery
-------------------
 'gord' | 'rat':AB

Além disso, pode ser anexado um * a um lexema para especificar a correspondência de prefixo:

SELECT to_tsquery('english', 'supern:*A & star:A*B');

        to_tsquery
--------------------------
 'supern':*A & 'star':*AB

-- Exemplo do tradutor abaixo

SELECT to_tsquery('portuguese', 'estrela:A*B & supernova:*A');

          to_tsquery
------------------------------
 'estrel':*AB & 'supernov':*A

SELECT to_tsquery('portuguese', 'estrela:A & supernova:*AB')
    @@ setweight(to_tsvector('portuguese', 'Uma supernovidade da Estrela'), 'A');

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

Estes lexemas vão corresponder a qualquer palavra em um tsvector que comece com a cadeia de caracteres fornecida.

A função to_tsquery também aceita frases entre apóstrofos. É útil principalmente quando a configuração inclui um thesaurus (dicionário de sinônimos) que pode acionar tais frases. No exemplo abaixo, o thesaurus contém a regra supernovae stars : sn:

SELECT to_tsquery('''supernovae stars'' & !crab');

  to_tsquery
---------------
 'sn' & !'crab'

Sem os apóstrofos, a função to_tsquery gera um erro de sintaxe para tokens que não são separados pelo operador AND, OR, ou FOLLOWED BY.

plainto_tsquery([ config regconfig, ] querytext text) returns tsquery

A função plainto_tsquery transforma o texto não formatado querytext em um valor tsquery. O texto é analisado e normalizado como para a função to_tsvector, então o operador & (AND) do tsquery é inserido entre as palavras sobreviventes.

Exemplo:

SELECT plainto_tsquery('english', 'The Fat Rats');

 plainto_tsquery
-----------------
 'fat' & 'rat'

-- Exemplo do tradutor abaixo

SELECT plainto_tsquery('portuguese', 'Os Ratos Gordos');

 plainto_tsquery
-----------------
 'rat' & 'gord'

Note que a função plainto_tsquery não reconhece operadores tsquery, rótulos de peso ou rótulos de correspondência de prefixo em sua entrada:

SELECT plainto_tsquery('english', 'The Fat & Rats:C');

   plainto_tsquery
---------------------
 'fat' & 'rat' & 'c'

-- Exemplo do tradutor abaixo

SELECT plainto_tsquery('portuguese', 'Os Ratos:C Gordos');

   plainto_tsquery
----------------------
 'rat' & 'c' & 'gord'

Nos exemplos acima, os sinais gráficos da entrada (:) são desprezados.

phraseto_tsquery([ config regconfig, ] querytext text) returns tsquery

A função phraseto_tsquery se comporta de maneira muito parecida com a função plainto_tsquery, exceto por inserir o operador <-> (FOLLOWED BY) entre as palavras sobreviventes, em vez do operador & (AND). Além disso, as palavras de parada não são simplesmente descartadas, sendo contabilizadas pela inserção de operadores <N>, em vez de operadores <->. Esta função é útil na procura por sequências exatas de lexemas, porque os operadores FOLLOWED BY verificam a ordem dos lexemas, e não apenas a presença de todos os lexemas.

Exemplo:

SELECT phraseto_tsquery('english', 'The Fat Rats');

 phraseto_tsquery
------------------
 'fat' <-> 'rat'

Assim como a função plainto_tsquery, a função phraseto_tsquery não irá reconhecer operadores tsquery, rótulos de peso ou rótulos de correspondência de prefixo em sua entrada:

SELECT phraseto_tsquery('english', 'The Fat & Rats:C');

    phraseto_tsquery
-------------------------
 'fat' <-> 'rat' <-> 'c'

-- Exemplo do tradutor abaixo

SELECT phraseto_tsquery('portuguese', 'Os Ratos:C Gordos');

     phraseto_tsquery
--------------------------
 'rat' <-> 'c' <-> 'gord'

websearch_to_tsquery([ config regconfig, ] querytext text) returns tsquery

A função websearch_to_tsquery cria um valor tsquery a partir de querytext, usando uma sintaxe alternativa onde o texto simples não formatado é uma consulta válida. Diferentemente de plainto_tsquery e phraseto_tsquery, esta função reconhece certos operadores. Além disso, esta função nunca gera erros de sintaxe, possibilitando o uso de entrada bruta fornecida pelo usuário para procura. Há suporte para:

  • unquoted text: o texto não delimitado é convertido em termos separados pelo operador &, como se tivesse sido processado pela função plainto_tsquery.

  • "quoted text": o texto delimitado é convertido em termos separados pelo operador <->, como se tivesse sido processado pela função phraseto_tsquery.

  • OR: a palavra or é convertida no operador | (OR).

  • -: o hífen é convertido no operador ! (NOT).

As demais pontuações são ignoradas. Então, assim como plainto_tsquery e phraseto_tsquery, a função websearch_to_tsquery não reconhece operadores do tsquery, rótulos de peso, ou rótulos de correspondência de prefixo, em sua entrada.

Exemplos:

SELECT websearch_to_tsquery('english', 'The fat rats');

 websearch_to_tsquery
----------------------
 'fat' & 'rat'

SELECT websearch_to_tsquery('english', '"supernovae stars" -crab');

       websearch_to_tsquery
----------------------------------
 'supernova' <-> 'star' & !'crab'

SELECT websearch_to_tsquery('english', '"sad cat" or "fat rat"');

       websearch_to_tsquery
-----------------------------------
 'sad' <-> 'cat' | 'fat' <-> 'rat'

SELECT websearch_to_tsquery('english', 'signal -"segmentation fault"');

         websearch_to_tsquery
---------------------------------------
 'signal' & !( 'segment' <-> 'fault' )

SELECT websearch_to_tsquery('english', '""" )( dummy \\ query <->');

 websearch_to_tsquery
----------------------
 'dummi' <-> 'queri'

Exemplos em português do tradutor:

SELECT websearch_to_tsquery('portuguese', 'Os Ratos Gordos');

 websearch_to_tsquery
----------------------
 'rat' & 'gord'

SELECT websearch_to_tsquery('portuguese', '"estrelas supernovas" -Kepler');

       websearch_to_tsquery
-----------------------------------
 'estrel' <-> 'supernov' & !'kepl'

-- 'or' precisa ser mantido em inglês

SELECT websearch_to_tsquery('portuguese', '"gato triste" or "rato gordo"');

         websearch_to_tsquery
--------------------------------------
 'gat' <-> 'trist' | 'rat' <-> 'gord'

-- 'ou' é palavra de parada, não tem a função de 'or'

SELECT websearch_to_tsquery('portuguese', '"gato triste" ou "rato gordo"');

         websearch_to_tsquery
--------------------------------------
 'gat' <-> 'trist' & 'rat' <-> 'gord'

SELECT websearch_to_tsquery('portuguese', 'sinal -"falha de segmentação"');

        websearch_to_tsquery
-------------------------------------
 'sinal' & !( 'falh' <2> 'segment' )

SELECT websearch_to_tsquery('portuguese', '""" )( consulta \\ fictícia <->');

  websearch_to_tsquery
------------------------
 'consult' <-> 'fictíc'

12.3.3. Pontuação dos resultados da procura #

A pontuação (classificação) tenta medir a relevância dos documentos para uma determinada consulta, de modo que, quando ocorrem muitas correspondências, os documentos mais relevantes possam ser exibidos primeiro. O PostgreSQL fornece duas funções de pontuação predefinidas, que consideram informações léxicas, de proximidade e estruturais; ou seja, estas funções consideram com que frequência os termos da consulta aparecem no documento, quão próximos os termos estão um do outro no documento, e quão importante é a parte do documento onde eles ocorrem. Entretanto, o conceito de relevância é vago e muito específico da aplicação. Aplicações diferentes podem exigir informações adicionais para a pontuação, como, por exemplo, a hora da modificação do documento. As funções de classificação nativas são apenas exemplos. Você pode escrever suas próprias funções de pontuação, e/ou combinar seus resultados com fatores adicionais para atender suas necessidades específicas.

As duas funções de pontuação disponíveis atualmente são:

ts_rank([ weights float4[], ] vector tsvector, query tsquery [, normalization integer ]) returns float4

Pontua vetores com base na frequência de seus lexemas correspondentes.

ts_rank_cd([ weights float4[], ] vector tsvector, query tsquery [, normalization integer ]) returns float4

Esta função calcula a pontuação de densidade de cobertura para o vetor e consulta fornecidos, conforme publicado no artigo Relevance ranking for one to three term queries, de Charles L.A. Clark, Gordon V. Cormac e Elizabeth A. Tudhope, no periódico Information Processing and Management, de março de 2000. A densidade de cobertura é semelhante à pontuação da função ts_rank, exceto por ser considerada a proximidade dos lexemas correspondentes.

Esta função requer a informação de posição do lexema para realizar seu cálculo. Portanto, ignora qualquer lexema sem posição em tsvector. Se não existirem lexemas com posição na entrada, o resultado será zero. (Veja a Seção 12.4.1 para obter mais informações sobre a função strip, e as informações de posição em tsvector.)

Para estas duas funções, o argumento opcional weights (pesos) oferece a possibilidade de dar mais peso, ou menos peso, a certas palavras, dependendo de como estão rotuladas. As matrizes de peso especificam o peso de cada categoria de palavra, na ordem:

{D-weight, C-weight, B-weight, A-weight}

Se o argumento weights não for fornecido, então serão usados os valores padrão:

{0.1, 0.2, 0.4, 1.0}

Normalmente, os pesos são usados para marcar palavras de áreas específicas do documento, como o título ou um resumo inicial, para poderem ser tratadas com mais importância, ou menos importância, que as palavras no corpo do documento.

Como um documento mais longo tem uma chance maior de conter um termo da consulta, é razoável considerar o tamanho do documento. Por exemplo, um documento de cem palavras com cinco ocorrências de uma palavra de procura é provavelmente mais relevante do que um documento de mil palavras com cinco ocorrências. As duas funções de pontuação contêm o argumento opcional normalization, um número inteiro que especifica se e como o tamanho do documento deve afetar sua pontuação. Este número inteiro controla vários comportamentos e, por isto, é uma máscara de bits: é possível especificar um ou mais comportamentos usando | (por exemplo, 2|4).

  • 0 (o padrão) ignora o comprimento do documento

  • 1 divide a pontuação por 1 + o logaritmo do comprimento do documento

  • 2 divide a pontuação pelo comprimento do documento

  • 4 divide a pontuação pela média harmônica da distância entre as palavras (implementado apenas pela função ts_rank_cd)

  • 8 divide a pontuação pelo número de palavras únicas no documento

  • 16 divide a pontuação por 1 + o logaritmo do número de palavras únicas no documento

  • 32 divide a pontuação por ela mesma + 1

Se for especificado mais de um bit sinalizador, as transformações serão aplicadas na ordem listada acima.

É importante observar que as funções de pontuação não usam nenhuma informação global, por isto é impossível produzir uma normalização equilibrada para 1% ou 100%, como às vezes se deseja. A opção de normalização 32 (rank/(rank+1)) pode ser aplicada para manter todas as pontuações no intervalo de zero a um, mas é claro ser apenas uma mudança cosmética; não vai afetar a ordem dos resultados da procura.

A seguir está um exemplo que seleciona apenas as dez correspondências mais bem pontuadas:

SELECT title, ts_rank_cd(textsearch, query) AS rank
FROM apod, to_tsquery('neutrino|(dark & matter)') query
WHERE query @@ textsearch
ORDER BY rank DESC
LIMIT 10;

                     title                     |   rank
-----------------------------------------------+----------
 Neutrinos in the Sun                          |      3.1
 The Sudbury Neutrino Detector                 |      2.4
 A MACHO View of Galactic Dark Matter          |  2.01317
 Hot Gas and Dark Matter                       |  1.91171
 The Virgo Cluster: Hot Plasma and Dark Matter |  1.90953
 Rafting for Solar Neutrinos                   |      1.9
 NGC 4650A: Strange Galaxy and Dark Matter     |  1.85774
 Hot Gas and Dark Matter                       |   1.6123
 Ice Fishing for Cosmic Neutrinos              |      1.6
 Weak Lensing Distorts the Universe            | 0.818218

Este é o mesmo exemplo usando pontuação normalizada:

SELECT title, ts_rank_cd(textsearch, query, 32 /* rank/(rank+1) */ ) AS rank
FROM apod, to_tsquery('neutrino|(dark & matter)') query
WHERE  query @@ textsearch
ORDER BY rank DESC
LIMIT 10;

                     title                     |        rank
-----------------------------------------------+-------------------
 Neutrinos in the Sun                          | 0.756097569485493
 The Sudbury Neutrino Detector                 | 0.705882361190954
 A MACHO View of Galactic Dark Matter          | 0.668123210574724
 Hot Gas and Dark Matter                       |  0.65655958650282
 The Virgo Cluster: Hot Plasma and Dark Matter | 0.656301290640973
 Rafting for Solar Neutrinos                   | 0.655172410958162
 NGC 4650A: Strange Galaxy and Dark Matter     | 0.650072921219637
 Hot Gas and Dark Matter                       | 0.617195790024749
 Ice Fishing for Cosmic Neutrinos              | 0.615384618911517
 Weak Lensing Distorts the Universe            | 0.450010798361481

A pontuação pode ter um custo alto, porque requer a consulta do tsvector de cada documento que corresponde, que pode ser limitado por E/S, portanto lento. Infelizmente é quase impossível evitar, porque as consultas práticas geralmente resultam em um grande número de correspondências.

12.3.4. Realçando os resultados #

Para apresentar os resultados da procura, o ideal é mostrar uma parte de cada documento, e como ele se relaciona com a consulta. Normalmente, os motores de busca mostram fragmentos do documento com os termos da procura destacados. O PostgreSQL disponibiliza a função ts_headline que implementa esta funcionalidade.

ts_headline([ config regconfig, ] document text, query tsquery [, options text ]) returns text

A função ts_headline aceita um documento junto com uma consulta, e retorna um excerto do documento onde os termos da consulta são destacados. Especificamente, a função irá usar a consulta para selecionar fragmentos de texto relevantes e, em seguida, irá destacará todas as palavras que aparecem na consulta, mesmo que as posições destas palavras não correspondam às restrições da consulta. A configuração a ser usada para analisar o documento pode ser especificada pelo argumento config; se config for omitido, será usada a configuração default_text_search_config.

Se a cadeia de caracteres options for especificada, deverá consistir em uma lista separada por vírgulas de um ou mais pares option=value. As opções disponíveis são:

  • MaxWords, MinWords (inteiros): estes números determinam os cabeçalhos mais longos e mais curtos a serem produzidos. Os valores padrão são 35 e 15.

  • ShortWord (inteiro): as palavras desse tamanho, ou menores, serão descartadas no início e no final do cabeçalho, a menos que sejam termos da consulta. O valor padrão, igual a 3, elimina os artigos comuns em inglês.

  • HighlightAll (booleano): se for true, todo o documento será usado como cabeçalho, ignorando os três parâmetros anteriores. O padrão é false.

  • MaxFragments (inteiro): número máximo de fragmentos de texto a serem mostrados. O valor padrão zero seleciona um método de geração de cabeçalho não baseado em fragmentos. Um valor maior que zero seleciona a geração de cabeçalhos baseada em fragmentos (veja abaixo).

  • StartSel, StopSel (cadeias de caracteres): as cadeias de caracteres com as quais serão delimitadas as palavras da consulta que aparecem no documento, para distingui-las das outras palavras extraídas. Os valores padrão são <b> e </b>, que podem ser adequadas para a saída HTML (mas veja a advertência abaixo).

  • FragmentDelimiter (cadeia de caracteres): Quando mais de um fragmento for exibido, os fragmentos serão separados por esta cadeia de caracteres. O padrão é ... .

Aviso: Segurança contra Cross-site Scripting (XSS)

Não há garantia de que o resultado da função ts_headline seja seguro para inclusão direta em páginas da web. Quando HighlightAll é false (o padrão), algumas etiquetas XML simples são removidas do documento, mas isto não garante a remoção completa da marcação HTML. Portanto, isto não oferece uma defesa eficaz contra ataques como os de cross-site scripting (XSS) (injeção de scripts maliciosos) ao trabalhar com entradas não confiáveis. Para se proteger contra estes ataques, toda a marcação HTML deve ser removida do documento de entrada, ou deve ser usado um sanitizador de HTML na saída.

Esses nomes de opções são reconhecidos sem haver distinção entre letras maiúsculas e minúsculas. Devem ser colocadas aspas nos valores cadeia de caracteres, se contiverem espaços ou vírgulas.

Na geração de cabeçalhos não baseada em fragmentos, a função ts_headline localiza as correspondências para a query fornecida, e escolhe apenas uma para exibir, preferindo a correspondência que tenha o maior número de palavras da consulta dentro do tamanho permitido do cabeçalho. Na geração de cabeçalhos baseada em fragmentos, a função ts_headline localiza as correspondências da consulta e divide cada correspondência em fragmentos com não mais que MaxWords palavras cada, preferindo fragmentos com mais palavras da consulta e, quando possível, esticando os fragmentos para incluir palavras ao redor. O modo baseado em fragmentos é, portanto, mais útil quando as correspondências da consulta abrangem grandes seções do documento, ou quando se deseja exibir várias correspondências. Em qualquer um dos modos, se nenhuma correspondência da consulta puder ser identificada, um único fragmento das primeiras MinWords palavras do documento será exibido.

Por exemplo:

SELECT ts_headline('english',
  'The most common type of search
is to find all documents containing given query terms
and return them in order of their similarity to the
query.',
  to_tsquery('english', 'query & similarity'));

                        ts_headline
------------------------------------------------------------
 containing given <b>query</b> terms                       +
 and return them in order of their <b>similarity</b> to the+
 <b>query</b>.

SELECT ts_headline('english',
  'Search terms may occur
many times in a document,
requiring ranking of the search matches to decide which
occurrences to display in the result.',
  to_tsquery('english', 'search & term'),
  'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<, StopSel=>>');

                        ts_headline
------------------------------------------------------------
 <<Search>> <<terms>> may occur                            +
 many times ... ranking of the <<search>> matches to decide

-- Exemplos do tradutor abaixo

SELECT ts_headline('portuguese', $$o tipo mais comum de procura
é encontrar todos os documentos que
contenham determinados termos da consulta,
e devolvê-los em ordem de semelhança com a consulta.$$,
to_tsquery('portuguese', 'consulta & ordem'));

                            ts_headline
--------------------------------------------------------------------
 contenham determinados termos da <b>consulta</b>,                 +
 e devolvê-los em <b>ordem</b> de semelhança com a <b>consulta</b>.

SELECT ts_headline('portuguese', $$Os termos de procura podem
ocorrer muitas vezes em um documento,
exigindo a pontuação das correspondências da procura,
para decidir quais ocorrências serão exibidas no resultado.$$,
to_tsquery('portuguese', 'procura & resultado'),
'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<, StopSel=>>');

                            ts_headline
--------------------------------------------------------------------
 termos de <<procura>> podem                                       +
 ocorrer muitas vezes ... correspondências da <<procura>>,         +
 para decidir quais ocorrências ... serão exibidas no <<resultado>>

A função ts_headline usa o documento original, e não um resumo do tsvector, por isto pode ser lenta, devendo ser usada com cuidado.