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.
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_vectorcomsetweight(A/B/C/D) - Comparar
ts_rankets_rank_cdno 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çãots_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
setweightmarca a origem do token (título vs sinopse)ts_rank/ts_rank_cdtransformam pesos e ocorrências em ordem útil- Combine ranking textual com desempates de negócio de forma explícita
- Sem
ORDER BYde rank, pesos não aparecem na UX