Quando uma função retorna texto, mas você precisa de linhas: explorando o Full Text Search no PostgreSQL

By 29 de outubro de 2026Artigos

O Full Text Search do PostgreSQL oferece recursos para pesquisar documentos, ranquear resultados e também destacar os trechos mais relevantes de um texto. Entre essas funcionalidades está a ts_headline, que pode ser usada para gerar um resumo do conteúdo encontrado e destacar os termos que correspondem à busca.

Mas o que acontece quando você precisa de mais do que um único texto de retorno?

Imagine uma tabela de livros em que cada registro possui o conteúdo de um capítulo ou parágrafo. Ao realizar uma busca por uma determinada palavra, pode ser interessante retornar não apenas o registro encontrado, mas cada trecho relevante do conteúdo como uma linha independente.

É aí que surge uma situação interessante: a função ts_headline consegue retornar vários fragmentos, mas o resultado ainda é um único valor do tipo texto.

O papel do ts_headline

No PostgreSQL, a função ts_headline recebe um documento e uma consulta de texto e retorna um trecho do documento no qual os termos encontrados são destacados. Ela também permite configurar como esses trechos serão gerados.

Entre as opções disponíveis estão:

  • MaxWords e MinWords: Número máximo e mínimo de palavras em cada trecho exibido
  • HighlightAll: Quando True, o resultado será o trecho completo com o texto pesquisado destacado, ignorando os parâmetros acima 
  • MaxFragments: Máximo de fragmentos de texto a serem exibidos
  • FragmentDelimiter: Texto que será utilizado para separar os diferentes fragmentos no resultado (ex.: $)

Por exemplo, podemos configurar a função para retornar vários fragmentos separados pelo caractere $:

  SELECT 
    ts_headline(
        content,
        to_tsquery('english', 'happy'),
        'MaxFragments=3
         FragmentDelimiter=$'
    )
FROM mytable
WHERE ts_content @@ to_tsquery('english', 'happy');

Entendendo a Consulta:

  • content: Coluna do tipo text com o texto original não processado onde a marcação será aplicada
  • ts_content: Coluna do tipo tsvector pré-processada (GENERATED ALWAYS AS (to_tsvector(‘english’, content)) STORED), usada na cláusula WHERE. Necessário a criação de um índice GIN sobre ela (CREATE INDEX … USING gin (ts_content)), para garantir melhor desempenho nas buscas
  • to_tsquery: Converte a string informada em uma consulta estruturada (tsquery) no idioma correspondente

O resultado pode ser algo semelhante a:

Este é um fragmento com <b>happy</b>$outro fragmento com <b>happy</b>$e mais um fragmento com <b>happy</b>

Para quem precisa apenas exibir o conteúdo, isso pode ser suficiente. Mas, se a aplicação precisa trabalhar com cada fragmento individualmente, ainda existe um problema: todos eles continuam dentro de uma única string.

Transformando o texto em linhas

Uma alternativa é utilizar a função regexp_split_to_table.

Ela recebe um texto e uma expressão regular que define onde o conteúdo deve ser dividido. Diferentemente de funções que retornam um único valor, regexp_split_to_table transforma cada parte resultante da divisão em uma linha.

Como já definimos um delimitador específico para o ts_headline, podemos utilizá-lo para separar os fragmentos:

SELECT
regexp_split_to_table(
ts_headline(
content,
to_tsquery('english', 'happy'),
'MaxFragments=3
 FragmentDelimiter=$'
   ),
   '$'
)
FROM mytable WHERE ts_content @@ to_tsquery('english', 'happy');

Nesse caso, o ts_headline continua responsável por encontrar e destacar os trechos relevantes, enquanto o regexp_split_to_table transforma o resultado em múltiplas linhas.

Em vez de receber:

Este é um fragmento com <b>happy</b>$outro fragmento com <b>happy</b>$e mais um fragmento com <b>happy</b>

passamos a ter algo conceitualmente como:

Este é um fragmento com <b>happy</b>
outro fragmento com <b>happy</b>
e mais um fragmento com <b>happy</b>

Onde cada fragmento agora é apresentado em linhas.

Essa combinação é útil justamente porque cada função resolve uma parte diferente do problema: uma trabalha com a busca e a geração dos trechos relevantes, enquanto a outra reorganiza o resultado para que ele possa ser tratado como um conjunto de linhas.

Por que isso é interessante?

Esse tipo de situação aparece quando o resultado de uma função SQL não está no formato mais conveniente para o restante da consulta ou para a aplicação que vai consumir os dados.

No caso do Full Text Search, podemos pensar em três etapas:

  1. Encontrar os documentos relevantes: O operador @@ verifica se o tsvector corresponde à consulta.
  1. Gerar os fragmentos: O ts_headline utiliza a consulta para produzir trechos relevantes e destacar os termos encontrados.
  1. Transformar o resultado em linhas: O regexp_split_to_table utiliza o delimitador definido anteriormente para transformar o texto em registros individuais.

Essa separação de responsabilidades deixa a consulta mais flexível e permite que os fragmentos sejam tratados individualmente em etapas posteriores.

Um cuidado importante com ts_headline

Existe, porém, um ponto importante quando trabalhamos com essa abordagem: o custo da própria função ts_headline.

A documentação do PostgreSQL destaca que ts_headline trabalha sobre o documento original, e não simplesmente sobre o tsvector, o que pode tornar a operação custosa. Por isso, seu uso deve ser pensado com cuidado, especialmente quando existe uma quantidade grande de documentos correspondentes à consulta.

Uma estratégia comum é primeiro encontrar e ordenar os resultados relevantes e só depois aplicar a geração dos trechos aos registros que realmente serão apresentados. Dessa forma, evitamos executar o processamento de destaque em um conjunto muito maior do que o necessário.

Quando o formato do resultado também importa

Esse exemplo mostra uma característica interessante do PostgreSQL: muitas vezes, o desafio não está em encontrar uma função que faça exatamente tudo o que precisamos, mas em combinar funções que resolvem etapas diferentes do problema.

O Full Text Search já oferece os mecanismos para identificar documentos relevantes e gerar destaques. Quando precisamos transformar esse resultado em uma estrutura diferente, outras funções SQL podem entrar em cena para reorganizar os dados.

Nesse caso, ts_headline e regexp_split_to_table trabalham juntos para transformar um texto com vários fragmentos em um conjunto de linhas que pode ser manipulado individualmente.

É um exemplo simples, mas que mostra como explorar as funções nativas do PostgreSQL pode evitar processamento adicional na aplicação e resolver problemas de transformação diretamente na consulta.

Receba mais conteúdos como este!

Assine a nossa newsletter e acompanhe conteúdos técnicos, análises e discussões sobre PostgreSQL, arquitetura e engenharia de dados.

[Quero receber os próximos conteúdos]

Share