Full-Text Search no PostgreSQL

Pesos por campo e ranking no PostgreSQL

setweight, ts_rank/ts_rank_cd e priorizar título sobre descrição.

Intermediário 45 min 35 pontos Leitura 0%

Nesta aula você vai

  • Aplicar setweight para priorizar título/assunto sobre corpo/sinopse
  • Ordenar com ts_rank e ts_rank_cd de forma consciente
  • Combinar ranking textual com desempates de negócio sem destruir relevância

Pesos por campo e ranking no PostgreSQL

Objetivos

Nesta aula você vai:

  • Montar search_vector com setweight (A/B/C/D)
  • Comparar ts_rank e ts_rank_cd no corpus real
  • Entregar uma ordenação que privilegia título sem ignorar filtros de negócio

Introdução

Nem todo match é igual. Na Estante Norte, um livro cujo título é Redes Neurais deve superar outro que só menciona a expressão no meio da sinopse. No PostgreSQL, isso se modela com pesos de campo no tsvector e funções de ranking na consulta.

Conteúdo

setweight: A > B > C > D

ALTER TABLE obras
  DROP COLUMN IF EXISTS search_vector;

ALTER TABLE obras
  ADD COLUMN search_vector tsvector
  GENERATED ALWAYS AS (
    setweight(to_tsvector('portuguese', coalesce(titulo, '')), 'A') ||
    setweight(to_tsvector('portuguese', coalesce(subtitulo, '')), 'B') ||
    setweight(to_tsvector('portuguese', coalesce(sinopse, '')), 'C') ||
    setweight(to_tsvector('portuguese', coalesce(tags_texto, '')), 'D')
  ) STORED;

CREATE INDEX idx_obras_search_gin ON obras USING gin (search_vector);
Peso Uso sugerido (Estante Norte) Uso sugerido (TicketFlow)
A título assunto
B subtítulo tags
C sinopse corpo
D tags auxiliares notas internas indexáveis

Os pesos são rótulos consumidos pelo ranking; sozinhos não filtram.

ts_rank e ts_rank_cd

SELECT id, titulo,
       ts_rank(search_vector, query) AS rank_basic,
       ts_rank_cd(search_vector, query) AS rank_cd
FROM obras,
     plainto_tsquery('portuguese', 'redes neurais') AS query
WHERE search_vector @@ query
ORDER BY rank_cd DESC
LIMIT 20;
  • ts_rank: considera frequência e pesos; variantes aceitam vetor de normalização
  • ts_rank_cd: cover density — favorece documentos em que os termos da consulta aparecem próximos/cobertos
-- Normalização (máscara de bits): ajuste fino; documente a escolha
SELECT id, titulo,
       ts_rank(search_vector, query, 32 /* rank/(rank+1) */) AS rank_norm
FROM obras,
     plainto_tsquery('portuguese', 'aprendizado máquina') AS query
WHERE search_vector @@ query
ORDER BY rank_norm DESC
LIMIT 20;

Consulte a documentação da sua versão para o significado exato de cada bit de normalização; o ponto pedagógico é: meça no corpus, não copie máscaras de fórum sem teste.

Ranking + regras de produto

SELECT o.id, o.titulo, o.publicado_em,
       ts_rank_cd(o.search_vector, q) AS rank
FROM obras AS o,
     websearch_to_tsquery('portuguese', 'postgresql desempenho') AS q
WHERE o.search_vector @@ q
  AND o.idioma = 'pt-BR'
  AND o.disponivel = true
ORDER BY rank DESC, o.publicado_em DESC
LIMIT 20;

TicketFlow:

SELECT t.id, t.assunto, t.status,
       ts_rank(t.search_vector, q) AS rank
FROM tickets AS t,
     plainto_tsquery('portuguese', 'timeout gateway pagamento') AS q
WHERE t.search_vector @@ q
  AND t.status IN ('aberto', 'em_andamento')
ORDER BY
  CASE t.status WHEN 'aberto' THEN 0 ELSE 1 END,
  rank DESC,
  t.aberto_em DESC
LIMIT 25;

Aqui o status influencia o desempate / prioridade operacional sem substituir o sinal textual.

Verificar pesos no vetor

SELECT titulo, search_vector
FROM obras
WHERE id = 42;
-- tokens aparecem como 'neural':A, 'sinops':C, etc.

Se tudo estiver com o mesmo peso, a expressão setweight não está aplicada ou a coluna não foi reconstruída.

Problema comum e solução

Problema: ordenar só por publicado_em ou id depois do @@, desperdiçando pesos e ranking.

Solução: ORDER BY ts_rank... DESC (ou ts_rank_cd) como critério principal; use frescor e status como desempate explícito.

Como analisar

Golden set + planos:

EXPLAIN (ANALYZE, BUFFERS)
SELECT id, titulo, ts_rank_cd(search_vector, q) AS rank
FROM obras,
     plainto_tsquery('portuguese', 'redes neurais') AS q
WHERE search_vector @@ q
ORDER BY rank DESC
LIMIT 10;

Compare o top-10 com e sem setweight (reconstruindo o vetor). Se a ordem não mudar, seus campos A/B/C estão pouco discriminativos ou a consulta só casa em um campo.

Resumo

  • setweight marca a origem do token (título vs sinopse)
  • ts_rank / ts_rank_cd transformam pesos e ocorrências em ordem útil
  • Combine ranking textual com desempates de negócio de forma explícita
  • Sem ORDER BY de rank, pesos não aparecem na UX