Como Funciona a Busca de Texto Completo
O recurso de busca de texto completo do PostgreSQL responde a uma pergunta enganosamente simples: este documento contém estas palavras, em algum sentido linguístico significativo, e quão bem ele corresponde em comparação com outros documentos?
Nos bastidores, essa pergunta é respondida por um pipeline de busca de texto que analisa, classifica e normaliza palavras antes que qualquer correspondência ou ranqueamento ocorra, o que diferencia a busca de texto completo de uma simples comparação de strings.
Esta página constrói o modelo conceitual por trás desse pipeline: como o texto bruto se torna um tsvector, por que a correspondência usa lemas em vez de substrings e como o ranqueamento transforma uma correspondência booleana em um conjunto de resultados ordenado.
Resumo
- A busca de texto completo converte o texto do documento e o texto da consulta em unidades linguísticas normalizadas chamadas lemas, depois corresponde e classifica os documentos com base nessas unidades, em vez de caracteres brutos.
- Por que Importa: Os usuários esperam que "correndo", "corre" e "correu" correspondam a uma busca por "correr", e a correspondência de string simples não pode fazer isso sem uma etapa explícita de normalização linguística.
- Conceitos Chave: parser, token, lema, dicionário,
tsvector,tsquery,ts_rank,ts_rank_cd. - Quando Usar: Recorra a este modelo mental quando uma consulta de busca não encontrar documentos que você espera que ela encontre, ou quando a ordem de ranqueamento parecer errada e você precisar entender o porquê.
- Limitações / Compensações: A mesma normalização que torna a busca tolerante a formas de palavras também a torna cega a substrings exatas, portanto, a busca de texto completo não pode substituir
LIKEpara correspondência de prefixo ou fragmentos de código. - Tópicos Relacionados: Mecânica de
tsvector/tsquery, detalhes internos do índice GIN, busca híbrida palavra-chave-mais-vetor, FTS do Postgres versus Elasticsearch.
Fundamentos
A busca de texto completo começa com um parser, um componente que divide o texto bruto em um fluxo de tokens classificados, em vez de uma lista plana de palavras.
O parser não apenas divide por espaços em branco; ele reconhece categorias como palavras, números, endereços de e-mail, URLs e compostos hifenizados, e etiqueta cada token com sua categoria.
Cada categoria de token é então roteada através de um dicionário, uma etapa de normalização plugável que pode reduzir uma palavra à sua raiz (stemming), descartá-la como palavra irrelevante (stop word) ou dobrá-la para uma grafia canônica.
A saída dessa etapa de dicionário é um lema (lexeme), a unidade normalizada que a busca de texto completo realmente indexa e corresponde. É por isso que "databases" e "database" se normalizam para o mesmo lema sob a configuração em inglês.
Um tsvector é simplesmente a coleção ordenada e deduplicada de lemas produzida para um pedaço de texto, carregando opcionalmente rótulos de peso e informações de posição para cada lema.
Um tsquery passa pelo mesmo pipeline de análise e normalização, o que explica exatamente por que uma string bruta construída manualmente pode produzir uma consulta que nunca corresponde a nada significativo.
A correspondência em si, expressa com o operador @@, é um teste booleano se a expressão de lemas da consulta é satisfeita pelos lemas presentes no tsvector do documento.
Como a correspondência ocorre em lemas normalizados em vez de texto bruto, a busca de texto completo é fundamentalmente uma operação linguística, não uma operação de comparação de caracteres.
Mecânicas e Interações
O pipeline de análise e normalização é executado em uma ordem estrita toda vez que um tsvector ou tsquery é construído, e entender essa ordem explica a maioria das perguntas do tipo "por que isso não correspondeu".
Primeiro, o parser tokeniza e classifica o texto bruto em tokens tipados; segundo, cada tipo de token é mapeado para uma lista ordenada de dicionários através de uma configuração de busca de texto como english ou simple; terceiro, o primeiro dicionário nessa lista que reconhece o token o normaliza, o descarta como palavra irrelevante ou o passa para o próximo dicionário na cadeia.
É por isso que trocar configurações muda os resultados sem alterar uma única linha de dados: simple pula o stemming inteiramente e é adequado para códigos de produto, enquanto english faz stemming agressivamente e se adequa à prosa.
Posições também importam, porque um tsvector retém onde cada lema ocorreu no texto original, o que possibilita operadores de frase como <-> no momento da consulta.
Pesos se sobrepõem às posições: um lema pode ser rotulado A, B, C ou D, tipicamente para marcar que veio de um título versus um corpo versus metadados, e as funções de ranqueamento leem esses rótulos para influenciar as pontuações.
-- As mesmas palavras, duas configurações, dois conjuntos de lemas diferentes
SELECT to_tsvector('simple', 'Running the databases');
-- 'databases':3 'running':1 'the':2
SELECT to_tsvector('english', 'Running the databases');
-- 'databas':3 'run':1O ranqueamento é onde a busca de texto completo passa de um sim/não booleano para um conjunto de resultados ordenado, e o PostgreSQL oferece duas funções de ranqueamento baseadas em diferentes teorias de relevância.
ts_rank é essencialmente uma pontuação de frequência de termo ponderada: conta quantos lemas da consulta corresponderam, considera seus pesos de posição e produz uma pontuação que trata as correspondências em qualquer lugar do documento de forma aproximadamente igual.
ts_rank_cd, a variante de densidade de cobertura, recompensa adicionalmente os documentos onde os lemas correspondentes aparecem próximos uns dos outros, com a teoria de que correspondências agrupadas indicam uma passagem mais focada e relevante.
Nenhuma das funções considera a normalização do comprimento do documento ou a raridade de termos em todo o corpus como o algoritmo BM25 de um motor de recuperação de informação completo faz, o que é uma compensação deliberada de simplicidade em troca de rodar inteiramente dentro do SQL.
Considerações Avançadas e Aplicações
O operador de correspondência @@ sozinho forçaria uma varredura sequencial em todos os tsvector das linhas, então a busca de texto completo depende de um índice GIN para tornar a correspondência rápida em escala, e o truque conceitual é o mesmo que todo motor de busca de índice invertido usa.
Um índice GIN inverte a relação entre documentos e lemas: em vez de armazenar "este documento contém estes lemas", ele armazena "este lema aparece nestes documentos", o que transforma uma consulta de correspondência em uma consulta rápida em vez de uma varredura por linha.
Essa inversão é precisamente o que distingue a busca de texto completo de LIKE '%term%' ou uma expressão regular, ambas as quais devem inspecionar os bytes reais de cada linha candidata porque nenhuma delas possui uma estrutura pré-construída conectando termos a linhas.
LIKE e regex são exatos em substring e sensíveis a maiúsculas/minúsculas/acentos por padrão, enquanto a busca de texto completo é linguisticamente normalizada e ciente da posição, então as duas técnicas resolvem problemas genuinamente diferentes e são frequentemente combinadas em vez de escolhidas exclusivamente.
Motores de busca externos como Elasticsearch são construídos sobre o mesmo conceito de índice invertido e frequência de termo, mas adicionam um ecossistema de analisadores muito mais rico, particionamento distribuído e modelos de relevância mais sofisticados, ao custo de executar e operar um segundo sistema fora do banco de dados transacional.
O ponto de decisão prático geralmente não é "qual técnica é mais inteligente", mas sim "o teto de relevância e escala de um índice invertido dentro do banco de dados atende às necessidades do produto sem pagar por um segundo cluster".
| Abordagem | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
Busca de texto completo (tsvector/GIN) | Normalização linguística, consistência transacional, sem sistema adicional | Menor ajuste de relevância do que motores de busca dedicados | Busca de prosa dentro de um aplicativo existente baseado em Postgres |
LIKE / regex | Correspondência exata de substring ou padrão, sem surpresas de normalização | Sem stemming, sem ranqueamento, baixo desempenho sem índices especializados | Códigos, IDs, consultas de fragmentos exatos |
| Motor de busca externo (Elasticsearch/OpenSearch) | Analisadores ricos, escala distribuída, ajuste avançado de relevância | Cluster separado para operar, atraso de consistência de escrita dupla ou CDC | Grandes corpus com infraestrutura de busca dedicada |
| Híbrido (FTS + embeddings) | Combina precisão lexical com recuperação semântica | Dois sistemas de pontuação para reconciliar e ajustar juntos | Busca que deve capturar sinônimos e paráfrases também |
Equívocos Comuns
- "Busca de texto completo é apenas LIKE com um índice" - é um pipeline linguístico construído sobre lemas analisados e normalizados, enquanto
LIKEé uma comparação de substring bruta sem qualquer normalização. - "Um tsvector armazena o texto original" - ele armazena um conjunto deduplicado e ordenado de lemas normalizados, mais pesos e posições opcionais, e as palavras originais não são recuperáveis dele em geral.
- "Um ts_rank mais alto sempre significa uma correspondência melhor para o usuário" -
ts_rankreflete a frequência de termos e rótulos de peso, não a relevância semântica, então um documento pode ter um ranqueamento alto enquanto perde a intenção real do usuário. - "ts_rank e ts_rank_cd sempre concordarão na ordenação" - eles codificam diferentes teorias de relevância, frequência de termos versus agrupamento de correspondências, então eles podem e produzem ordenações diferentes para a mesma consulta.
- "Stemming funciona da mesma forma para todas as línguas" - cada configuração de busca de texto tem sua própria cadeia de dicionários, então o stemming, as palavras irrelevantes e até mesmo os limites de tokenização diferem por configuração.
FAQs
Qual é a diferença entre um token e um lema?
Um token é um pedaço bruto e classificado de texto produzido pelo parser, enquanto um lema é o que resta após um dicionário normalizar, fazer stemming ou descartar esse token.
Por que buscar por "correr" também corresponde a "correndo" e "correu"?
O dicionário na configuração de busca de texto faz stemming de todas as três formas da palavra para o mesmo lema normalizado antes que qualquer coisa seja armazenada ou correspondida.
Por que a busca de texto completo não encontra uma substring como "gres" dentro de "postgres"?
A correspondência opera em lemas inteiros normalizados, não em substrings de caracteres, então fragmentos de palavras parciais exigem uma ferramenta diferente como pg_trgm ou LIKE.
O que determina se uma palavra se torna uma palavra irrelevante (stop word)?
A cadeia de dicionários para a configuração de busca de texto ativa decide, e configurações comuns como english descartam palavras de alta frequência como "the" e "and" por design.
Como a busca de texto completo sabe onde um lema estava no texto original?
O tsvector armazena inteiros de posição ao lado de cada lema, que é o que permite que operadores de frase e distância verifiquem a adjacência no momento da consulta.
Qual é a diferença prática entre ts_rank e ts_rank_cd?
ts_rank pontua com base na frequência de termos e rótulos de peso, enquanto ts_rank_cd recompensa adicionalmente documentos onde os lemas correspondentes aparecem agrupados próximos uns dos outros.
Por que a consulta precisa passar por to_tsquery em vez de uma string simples?
Um tsquery deve passar pelo mesmo pipeline de análise e normalização que o texto do documento, ou as palavras brutas da consulta nunca corresponderão aos lemas normalizados do documento.
Como a busca de texto completo é diferente de uma busca por expressão regular?
Uma expressão regular corresponde a padrões literais de caracteres sem consciência linguística, enquanto a busca de texto completo corresponde a lemas normalizados que carregam significado, independentemente da forma exata da palavra.
Por que as pessoas comparam a busca de texto completo do Postgres com o Elasticsearch?
Ambos são construídos sobre o mesmo conceito de índice invertido que conecta termos aos documentos que os contêm, mas o Elasticsearch adiciona analisadores mais ricos, escala distribuída e modelos de relevância mais avançados como um sistema separado.
A busca de texto completo entende significado ou sinônimos por conta própria?
Não, ela entende formas de palavras normalizadas através de stemming e dicionários, não significado conceitual, razão pela qual equipes a combinam com embeddings de vetor para recuperação semântica.
Por que um índice GIN torna a busca de texto completo rápida?
Ele inverte a relação de armazenamento de documento-para-lemas para lema-para-documentos, transformando uma correspondência em uma consulta direta em vez de uma varredura em todas as linhas.
Duas configurações diferentes podem produzir resultados de busca diferentes para o mesmo texto?
Sim, porque cada configuração tem suas próprias regras de parser e cadeia de dicionários, então simple e english podem normalizar a mesma entrada em conjuntos de lemas completamente diferentes.
Relacionados
- Noções Básicas de Busca de Texto Completo - a receita passo a passo para construir uma tabela pesquisável que esta página explica a teoria por trás
- tsvector & tsquery - mecânicas mais profundas sobre dicionários, operadores e funções de ranqueamento
- Índices GIN para FTS - como o índice invertido descrito aqui é construído e mantido na prática
- Busca Híbrida - combinando este modelo lexical com embeddings de vetor para recuperação semântica
- Melhores Práticas de Busca de Texto Completo - a lista de verificação operacional que complementa este modelo conceitual
Versões da Stack: Esta página foi escrita para PostgreSQL 18.4 (estável 18, manutenção 17), pgvector 0.8+, PostGIS 3.5+, pgbouncer 1.x, e Patroni 3.x.