LIKE vs Full-Text Search: otimizando buscas no banco de dados
Entenda quando o
LIKEdeixa de escalar e como tokenização, análise linguística, ranking e Full-Text Search melhoram a performance e a relevância das pesquisas SQL.
Tempo estimado de leitura: 15 minutos
Autor: Mateus Gabriel Barbosa
Imagine um e-commerce com milhares ou milhões de produtos. O usuário acessa o campo de pesquisa e digita:
notebook com 16 GB de RAM e SSD de 1 TB
A primeira implementação costuma ser algo parecido com isto:
SELECT
p.ProductId,
p.Name,
p.Description
FROM dbo.Products AS p
WHERE p.Name LIKE N'%notebook%'
AND p.Description LIKE N'%16 GB%'
AND p.Description LIKE N'%RAM%'
AND p.Description LIKE N'%1 TB%'
AND p.Description LIKE N'%SSD%';
Em uma base pequena, essa consulta pode funcionar muito bem.
O problema aparece quando a tabela cresce, as descrições ficam maiores e várias pessoas começam a pesquisar ao mesmo tempo. A busca passa a consumir mais páginas, CPU e tempo de resposta. Além disso, ela continua sendo limitada: não calcula relevância, não trabalha bem com variações linguísticas e depende das sequências de caracteres aparecerem exatamente da forma esperada.
É nesse cenário que o Full-Text Search, ou pesquisa de texto completo, começa a fazer sentido.
Neste artigo, eu quero mostrar que Full-Text Search não é apenas um LIKE mais rápido. Ele é um mecanismo próprio de recuperação de informação, com processo de indexação, análise linguística, estrutura de armazenamento e ranking.
Os exemplos principais serão em SQL Server, mas no final também vou mostrar como a mesma ideia aparece no PostgreSQL, MySQL e Oracle Database.
O problema não é o LIKE
Antes de falar sobre as limitações do LIKE, é importante colocar cada recurso em seu lugar.
O LIKE continua sendo útil para correspondência de padrões.
Por exemplo:
SELECT
p.ProductId,
p.Name
FROM dbo.Products AS p
WHERE p.Name LIKE N'Notebook%';
Nesse caso, o começo do valor é conhecido.
Esse tipo de predicado pode ser SARGable — abreviação de Search Argument Able. Em outras palavras, o otimizador consegue utilizar o argumento de pesquisa para delimitar um intervalo no índice, em vez de percorrer todas as entradas.
Com um índice adequado e condições favoráveis de tipo de dado e collation, o SQL Server pode transformar essa busca em um intervalo e utilizar um Index Seek.
O cenário muda quando o curinga aparece no início:
WHERE p.Name LIKE N'%Notebook%';
Agora, o termo pode estar no começo, no meio ou no final do valor.
Um índice B-tree organiza as entradas pelo valor indexado, começando pela esquerda. Ele consegue localizar rapidamente os valores que começam com Notebook, mas não possui um caminho direto para descobrir quais textos contêm essa palavra em uma posição desconhecida.
Na prática, o plano pode apresentar:
Table Scan, caso a tabela seja um heap;Clustered Index Scan;Nonclustered Index Scan, se existir um índice menor que possa ser percorrido;- filtros e comparações de strings sobre as linhas lidas.
Por isso, dizer que todo LIKE gera Table Scan seria incorreto.
O ponto técnico é:
Um predicado como
LIKE '%termo%'normalmente deixa de ser SARGable para um índice B-tree convencional e impede umIndex Seekeficiente.
Isso não significa que o plano obrigatoriamente apresentará um Table Scan. O SQL Server também pode escolher um Clustered Index Scan ou um Nonclustered Index Scan. O ponto é que, sem um início conhecido, o mecanismo normalmente precisa examinar uma parte grande — ou até a totalidade — das entradas disponíveis para testar o padrão.
Por que eu digo que o LIKE é “burro”
Eu uso “burro” entre aspas porque o operador não está fazendo nada errado. Ele apenas executa a tarefa para a qual foi criado: comparar padrões de caracteres.
Para o LIKE, a pesquisa abaixo:
notebook com 16 GB de RAM e SSD de 1 TB
é apenas texto.
Ele não sabe que:
notebookidentifica um tipo de produto;16 GBestá relacionado à memória RAM;1 TBestá relacionado ao armazenamento;SSDdiferencia a tecnologia do armazenamento;- palavras como
com,deeenormalmente possuem pouco valor para relevância; - um resultado que contém todos os termos pode ser mais útil que outro com apenas parte deles;
- termos próximos podem representar uma correspondência melhor;
- uma ocorrência no nome pode ter mais importância que uma ocorrência perdida na descrição.
Também não existe ranking nativo de relevância:
WHERE p.Description LIKE N'%SSD%'
A condição responde basicamente:
A sequência de caracteres foi encontrada?
Sim ou não.
Para aceitar variações, a aplicação precisa enumerá-las manualmente:
WHERE p.Description LIKE N'%carro%'
OR p.Description LIKE N'%automóvel%'
OR p.Description LIKE N'%veículo%';
A mesma dificuldade aparece com singular e plural, conjugações, termos próximos e outras formas de escrever a mesma intenção.
O Full-Text Search trabalha em outro nível. Ele opera sobre palavras, frases, formas flexionais, posições, proximidade, stopwords, sinônimos configurados e ranking.
Essa é a diferença mais importante:
LIKEé correspondência de caracteres. Full-Text Search é recuperação textual.
O que é Full-Text Search
Full-Text Search é um mecanismo especializado em pesquisar conteúdo textual não estruturado ou semiestruturado.
Ele é útil para colunas como:
- nome e descrição de produtos;
- conteúdo de artigos;
- currículos;
- tickets de suporte;
- comentários e avaliações;
- bases de conhecimento;
- documentos;
- contratos;
- observações extensas.
A ideia principal é preparar o conteúdo antes da pesquisa.
Em vez de aguardar o usuário pesquisar e percorrer os textos procurando caracteres, o banco processa previamente as colunas e constrói uma estrutura otimizada.
O fluxo simplificado é:
Texto original
↓
Tokenização
↓
Análise linguística
↓
Aplicação de stopwords
↓
Índice invertido
↓
Consulta sobre os tokens
↓
Ranking e retorno dos documentos
Esse processamento antecipado possui custo, mas muda completamente o perfil das leituras futuras.
Sem Full-Text Index: o custo acontece em cada busca
Considere uma tabela com milhões de produtos:
SELECT
p.ProductId,
p.Name,
p.Description
FROM dbo.Products AS p
WHERE p.Name LIKE N'%notebook%'
AND p.Description LIKE N'%16 GB%'
AND p.Description LIKE N'%RAM%'
AND p.Description LIKE N'%1 TB%'
AND p.Description LIKE N'%SSD%';
Sem um mecanismo especializado, o SQL Server precisa localizar os textos candidatos e comparar cada padrão com seus valores.
O custo tende a crescer com:
quantidade de linhas
×
tamanho médio dos textos
×
número de padrões
×
quantidade de pesquisas
×
concorrência
Nesse exemplo, cada linha candidata pode passar por várias comparações:
notebook
16 GB
RAM
1 TB
SSD
Isso pode gerar:
- muitas leituras lógicas;
- pressão sobre o buffer pool;
- uso elevado de CPU em comparações de strings;
- maior duração das consultas;
- aumento da disputa por recursos;
- piora progressiva conforme o volume cresce.
Criar um índice B-tree convencional em Description não transforma automaticamente:
LIKE N'%16 GB%'
em um Index Seek.
O índice pode ser menor que o índice clustered e reduzir I/O em alguns planos, mas ele continua organizado pelo início do valor completo, não pelas palavras existentes dentro da descrição.
Com Full-Text Index: o custo é antecipado
Ao criar um Full-Text Index, o SQL Server lê o conteúdo existente, divide os textos em tokens, aplica regras linguísticas e constrói listas que relacionam os termos aos documentos em que aparecem.
Esse processo é chamado de population, também conhecido como crawl.
Depois que o índice está populado, a aplicação deixa de procurar cada sequência com múltiplos LIKE e passa a consultar os termos indexados:
SELECT
p.ProductId,
p.Name,
p.Description
FROM dbo.Products AS p
WHERE CONTAINS
(
(p.Name, p.Description),
N'"notebook" AND "16 GB" AND "RAM" AND "1 TB" AND "SSD"',
LANGUAGE 1046
);
Existe um detalhe fundamental:
Criar um Full-Text Index não acelera uma consulta que continua usando
LIKE.
A aplicação precisa utilizar:
CONTAINS;FREETEXT;CONTAINSTABLE;FREETEXTTABLE.
O Full-Text Index é separado dos índices B-tree convencionais. O otimizador não converte automaticamente LIKE '%termo%' em Full-Text Search, porque as duas operações possuem semânticas diferentes.
Por exemplo:
LIKE N'%book%'
pode encontrar a sequência book dentro de:
notebook
Uma pesquisa Full-Text pelo token book, porém, normalmente não encontra notebook, pois o índice trabalha com tokens, não com qualquer substring arbitrária.
Modelando o exemplo no SQL Server
Vou usar uma tabela de produtos que combina conteúdo textual e atributos estruturados:
CREATE TABLE dbo.Products
(
ProductId INT IDENTITY(1, 1) NOT NULL,
Name NVARCHAR(200) NOT NULL,
Description NVARCHAR(MAX) NULL,
Category NVARCHAR(50) NOT NULL,
RamGb SMALLINT NULL,
StorageGb INT NULL,
StorageType VARCHAR(10) NULL,
CreatedAt DATETIME2(0) NOT NULL
CONSTRAINT DF_Products_CreatedAt
DEFAULT SYSUTCDATETIME(),
CONSTRAINT PK_Products
PRIMARY KEY CLUSTERED (ProductId)
);
GO
Alguns registros poderiam ser:
INSERT INTO dbo.Products
(
Name,
Description,
Category,
RamGb,
StorageGb,
StorageType
)
VALUES
(
N'Notebook Pro 15',
N'Notebook para desenvolvimento com 16 GB de RAM, SSD de 1 TB e tela de 15 polegadas.',
N'Notebook',
16,
1024,
'SSD'
),
(
N'Notebook Gamer X',
N'Modelo gamer com 16 GB de memória RAM, placa de vídeo dedicada e SSD de 512 GB.',
N'Notebook',
16,
512,
'SSD'
),
(
N'Ultrabook Air',
N'Notebook leve com 8 GB de RAM, armazenamento SSD de 1 TB e bateria de longa duração.',
N'Notebook',
8,
1024,
'SSD'
),
(
N'Notebook Workstation',
N'Equipamento profissional com 32 GB de RAM e SSD NVMe de 1 TB.',
N'Notebook',
32,
1024,
'SSD'
);
GO
O SQL Server exige que a tabela tenha um índice único baseado em uma única coluna não nula para funcionar como chave do Full-Text Index.
Neste exemplo, a chave primária ProductId atende ao requisito.
Primeiro, crio o catálogo:
CREATE FULLTEXT CATALOG ProductsFullTextCatalog
AS DEFAULT;
GO
Depois, crio o índice:
CREATE FULLTEXT INDEX ON dbo.Products
(
Name LANGUAGE 1046,
Description LANGUAGE 1046
)
KEY INDEX PK_Products
ON ProductsFullTextCatalog
WITH
(
CHANGE_TRACKING = AUTO,
STOPLIST = SYSTEM
);
GO
O valor informado em LANGUAGE é um LCID (Locale Identifier). De acordo com a documentação oficial da Microsoft, 1046 corresponde a Português do Brasil, enquanto 2070 corresponde a Português de Portugal.
A view de catálogo sys.fulltext_languages contém uma linha para cada idioma cujo separador de palavras está registrado no SQL Server. Ela também expõe o LCID e o nome associado ao idioma.
Essa escolha influencia os recursos linguísticos disponíveis para indexação e consulta Full-Text, como word breakers, stemmers, stopwords e arquivos de thesaurus.
Com CHANGE_TRACKING = AUTO, o SQL Server realiza a população inicial e acompanha as alterações da tabela. Essa propagação ocorre em segundo plano, então uma alteração pode não ficar disponível para pesquisa no mesmo instante do commit.
O que acontece durante a construção do índice
Criar o Full-Text Index em uma tabela grande possui um custo inicial.
Durante a full population, o SQL Server precisa:
- Ler as linhas existentes.
- Extrair os valores das colunas indexadas.
- Separar o conteúdo em tokens.
- Aplicar componentes linguísticos.
- Identificar stopwords.
- Construir as listas invertidas.
- Persistir os fragmentos do índice.
A arquitetura envolve dois processos importantes:
sqlservr.exe, que executa o Database Engine;fdhost.exe, o Filter Daemon Host, responsável por executar filtros, word breakers e stemmers fora do processo principal.
Esse isolamento reduz o impacto de falhas ou consumo excessivo de recursos em filtros e componentes linguísticos — inclusive componentes de terceiros — sobre o processo principal do SQL Server. Ele não elimina todos os riscos operacionais, mas cria uma fronteira de segurança e estabilidade importante.
O fluxo conceitual é:
Tabela
↓
Full-Text Gatherer
↓
Word Breaker e Stemmer
↓
Stoplist
↓
Index Writer
↓
Índice invertido
Essa carga pode consumir CPU, memória e I/O.
Em produção, eu não trataria isso como um DDL qualquer executado no horário de pico. Para uma tabela grande, eu planejaria a criação e a população inicial para uma janela de menor utilização.
Também é possível criar o índice sem popular imediatamente:
CREATE FULLTEXT INDEX ON dbo.Products
(
Name LANGUAGE 1046,
Description LANGUAGE 1046
)
KEY INDEX PK_Products
ON ProductsFullTextCatalog
WITH
(
CHANGE_TRACKING = OFF,
NO POPULATION,
STOPLIST = SYSTEM
);
GO
Depois, a população pode ser iniciada em um momento controlado:
ALTER FULLTEXT INDEX ON dbo.Products
START FULL POPULATION;
GO
Portanto, a diferença de custo é clara:
Sem índice:
o trabalho pesado acontece repetidamente em cada pesquisa.
Com índice:
parte do trabalho é antecipada durante a construção
e mantida conforme os dados mudam.
Essa troca favorece cenários com muitas leituras e pesquisas recorrentes, mas aumenta o custo de armazenamento e manutenção.
Tokenização não é apenas separar por espaços
Considere a descrição:
Notebook para desenvolvimento com 16 GB de RAM e SSD NVMe de 1 TB
Uma representação simplificada seria:
notebookparadesenvolvimentocom16gbderamessdnvmede1tb
Isso é apenas uma representação didática. A tokenização real de números, abreviações, pontuação e termos compostos depende do idioma e dos componentes instalados.
Um tokenizador precisa lidar com textos como:
16GB
16 GB
1TB
1 TB
SSD NVMe
Wi-Fi 6
USB-C
Intel Core i7
Ryzen 7
O SQL Server utiliza componentes chamados word breakers, específicos por idioma, para identificar os limites das palavras.
Também utiliza stemmers, que trabalham com formas flexionais.
Eu consigo observar como uma expressão será interpretada usando:
SELECT
parser.display_term,
parser.occurrence,
parser.special_term,
parser.expansion_type,
parser.source_term
FROM sys.dm_fts_parser
(
N'"notebook com 16 GB de RAM e SSD de 1 TB"',
1046,
0,
0
) AS parser
ORDER BY
parser.occurrence,
parser.display_term;
Essa função é importante porque evita suposições.
Antes de depender de buscas por termos técnicos, números, unidades, hífens ou siglas, eu verifico como o SQL Server realmente tokeniza o conteúdo.
Stopwords: termos que pouco ajudam a diferenciar resultados
Palavras como estas aparecem em uma quantidade enorme de textos:
a
o
de
da
do
para
com
e
em
Elas normalmente possuem pouco valor para diferenciar um produto de outro.
Na frase:
Notebook com 16 GB de RAM e SSD de 1 TB
os termos mais relevantes são:
notebook
16
gb
ram
ssd
1
tb
Enquanto isto:
com
de
e
pode ser tratado como stopword.
O SQL Server utiliza stoplists para descartar termos comuns que não ajudam a pesquisa e evitam crescimento desnecessário do índice.
Isso reduz ruído, mas também exige cuidado.
Se uma palavra importante para o domínio estiver em uma stoplist, ela pode não participar da busca. Por isso, aplicações com vocabulário específico podem precisar de uma stoplist personalizada.
O índice invertido é o coração do Full-Text Search
A tabela armazena naturalmente a relação:
produto → descrição
Exemplo:
- Documento 1: Notebook com 16 GB de RAM e SSD de 1 TB.
- Documento 2: Notebook gamer com 16 GB de RAM e SSD de 512 GB.
- Documento 3: Notebook leve com 8 GB de RAM e SSD de 1 TB.
- Documento 4: Workstation com 32 GB de RAM e SSD NVMe de 1 TB.
O índice inverte essa relação:
token → produtos que contêm o token
Representação simplificada:
notebook: documentos 1, 2 e 3.16: documentos 1 e 2.ram: documentos 1, 2, 3 e 4.ssd: documentos 1, 2, 3 e 4.1: documentos 1, 3 e 4.tb: documentos 1, 3 e 4.gamer: documento 2.nvme: documento 4.
Uma estrutura mais completa também registra dados como:
- identificador do documento;
- coluna;
- frequência;
- posição das ocorrências.
Quando o usuário procura:
notebook AND 16 GB AND 1 TB AND SSD
o mecanismo pode combinar posting lists conceitualmente:
notebook → [1, 2, 3]
16 → [1, 2]
1 → [1, 3, 4]
tb → [1, 3, 4]
ssd → [1, 2, 3, 4]
A interseção aponta para:
[1]
Em vez de reler todas as descrições, o mecanismo começa pelos documentos associados aos termos.
É daí que vem boa parte do ganho de performance.
Como a performance muda com o índice criado
Sem Full-Text Index:
Consulta
↓
Leitura de muitas páginas
↓
Comparação de vários padrões com cada texto
↓
Filtro das linhas
Com Full-Text Index:
Consulta
↓
Localização dos tokens
↓
Leitura das posting lists
↓
Interseção ou união dos documentos
↓
Retorno das chaves
↓
Join com a tabela base
Unidade pesquisada
LIKE: caracteres.- Full-Text Search: tokens e frases.
Estrutura de acesso
LIKE: B-tree ou tabela percorrida.- Full-Text Search: índice invertido.
Busca por palavras
LIKE: simulada por padrões.- Full-Text Search: nativa.
Formas flexionais
LIKE: não.- Full-Text Search: conforme o idioma e o operador.
Stopwords
LIKE: não.- Full-Text Search: sim.
Proximidade entre termos
LIKE: não possui operador textual nativo.- Full-Text Search: sim.
Ranking
LIKE: não.- Full-Text Search: sim.
Substring arbitrária
LIKE: sim.- Full-Text Search: normalmente não.
Custo adicional de escrita
LIKE: apenas a manutenção dos índices relacionais existentes.- Full-Text Search: manutenção adicional do índice Full-Text.
Espaço adicional
LIKE: índices relacionais.- Full-Text Search: índice invertido e estruturas internas.
Eu evitaria prometer que toda consulta cairá de segundos para milissegundos.
O ganho depende de:
- quantidade de linhas;
- tamanho dos textos;
- frequência dos termos;
- número de correspondências;
- cache;
- hardware;
- fragmentação;
- ranking;
- filtros relacionais;
- concorrência.
Uma pesquisa por um termo raro tende a produzir uma posting list pequena:
thunderbolt → poucos produtos
Um termo muito comum pode produzir uma lista grande:
notebook → milhares ou milhões de produtos
O Full-Text Search evita comparar todo o conteúdo, mas ainda precisa combinar, ranquear, juntar e retornar os candidatos.
CONTAINS: busca controlada
CONTAINS é um predicado booleano. Ele informa se o documento corresponde à expressão.
SELECT
p.ProductId,
p.Name,
p.Description
FROM dbo.Products AS p
WHERE CONTAINS
(
(p.Name, p.Description),
N'"notebook" AND "16 GB" AND "RAM" AND "1 TB" AND "SSD"',
LANGUAGE 1046
);
Ele suporta:
- palavras;
- frases;
- operadores booleanos;
- prefixos;
- proximidade;
- formas flexionais;
- thesaurus;
- pesos.
Busca por prefixo:
WHERE CONTAINS
(
p.Name,
N'"note*"',
LANGUAGE 1046
);
Busca por proximidade:
WHERE CONTAINS
(
p.Description,
N'NEAR((SSD, NVMe), 3, TRUE)',
LANGUAGE 1046
);
Para termos técnicos e unidades, eu validaria a expressão com sys.dm_fts_parser antes de adotá-la em produção.
FREETEXT: uma interpretação mais livre
FREETEXT recebe uma frase de linguagem natural:
SELECT
p.ProductId,
p.Name,
p.Description
FROM dbo.Products AS p
WHERE FREETEXT
(
(p.Name, p.Description),
N'notebook para desenvolvimento com 16 GB de RAM e SSD de 1 TB',
LANGUAGE 1046
);
Enquanto CONTAINS oferece controle explícito sobre os operadores, FREETEXT quebra a entrada em termos e procura correspondências e expansões linguísticas aplicáveis.
Eu usaria:
CONTAINSquando a regra precisa ser precisa e controlada;FREETEXTquando o usuário digita uma frase mais livre e a aplicação aceita uma interpretação menos rígida.
Mesmo assim, Full-Text Search não deve ser confundido com busca vetorial.
Uma busca por:
notebook potente para programar
não é automaticamente equivalente a:
workstation móvel para desenvolvimento
Isso só acontece se houver tokens em comum, thesaurus configurado, expansão linguística aplicável ou outra camada de busca.
A “inteligência” do FTS é principalmente lexical e linguística, não uma compreensão semântica baseada em embeddings.
O que significa encontrar termos “próximos”
Quando falamos que uma busca é mais inteligente que LIKE, é fácil misturar recursos diferentes sob o mesmo nome.
Full-Text Search, busca fonética, fuzzy matching e busca vetorial não são a mesma coisa.
Cada técnica resolve um tipo de proximidade:
Maiúsculas e minúsculas
- Exemplo:
PostgreSQLepostgresql. - SQL Server FTS: sim.
Prefixo
- Exemplo:
note*encontrandonotebook. - SQL Server FTS: sim.
Formas flexionais
- Exemplo:
comprar,comprandoecomprou. - SQL Server FTS: sim, com stemmer.
Sinônimos configurados
- Exemplo:
carro,automóveleveículo. - SQL Server FTS: sim, com thesaurus.
Proximidade entre termos
- Exemplo:
SSDpróximo de1 TB. - SQL Server FTS: sim, com
NEAR.
Diferença de acentuação
- Exemplo:
memóriaememoria. - SQL Server FTS: depende da sensibilidade a acentos.
Som semelhante
- Exemplo:
SmitheSmythe. - SQL Server FTS: não.
- Alternativa:
SOUNDEXeDIFFERENCE.
Erro de digitação
- Exemplo:
notebokenotebook. - SQL Server FTS: não automaticamente.
Mudança de espaçamento
- Exemplo:
PostgreSQLePostgre SQL. - SQL Server FTS: não é garantido.
Significado sem palavras em comum
- Exemplo:
notebook potenteeworkstation móvel. - SQL Server FTS: não automaticamente.
Essa separação evita atribuir ao FTS capacidades que pertencem a outros mecanismos.
Maiúsculas e minúsculas
As consultas Full-Text do SQL Server não diferenciam maiúsculas de minúsculas.
Portanto, estas pesquisas são equivalentes para o mecanismo:
PostgreSQL
postgresql
POSTGRESQL
Isso já é uma vantagem sobre comparações de caracteres que dependem da collation utilizada pela coluna ou pela expressão.
Porém, não devemos confundir normalização de caixa com tolerância a erros.
PostgreSQL
PostgreSQ
continuam sendo tokens diferentes.
Prefixos
Uma busca por prefixo permite encontrar palavras que começam com determinado conjunto de caracteres:
SELECT
p.ProductId,
p.Name
FROM dbo.Products AS p
WHERE CONTAINS
(
p.Name,
N'"note*"',
LANGUAGE 1046
);
Essa consulta pode localizar termos como:
notebook
notebooks
O LIKE também pode trabalhar bem com um prefixo conhecido:
WHERE p.Name LIKE N'Note%';
Nesse cenário, um índice B-tree pode ser suficiente.
A vantagem do FTS aparece quando o prefixo faz parte de uma busca textual maior, combinado com outros tokens, proximidade e ranking.
Radiciação e formas flexionais
O termo mais usado pela documentação do SQL Server é stemming, realizado por componentes chamados stemmers.
O mecanismo pode expandir uma consulta para diferentes tempos e conjugações de verbos ou para singular e plural de substantivos, conforme as regras do idioma.
Exemplo:
SELECT
a.ArticleId,
a.Content
FROM dbo.Articles AS a
WHERE CONTAINS
(
a.Content,
N'FORMSOF(INFLECTIONAL, comprar)',
LANGUAGE 1046
);
Conforme o stemmer disponível para o idioma, a pesquisa pode considerar formas flexionais relacionadas, como:
comprar
comprando
comprou
compram
Com LIKE, seria necessário declarar variações manualmente:
WHERE a.Content LIKE N'%comprar%'
OR a.Content LIKE N'%comprando%'
OR a.Content LIKE N'%comprou%'
OR a.Content LIKE N'%compram%';
Além de crescer rapidamente, essa abordagem continua baseada em substrings e pode produzir falsos positivos.
Um cuidado de terminologia: stemming não é necessariamente o mesmo que lematização completa. O SQL Server documenta o recurso como geração de formas flexionais, e o resultado depende do idioma e do stemmer instalado.
FREETEXT e FREETEXTTABLE procuram formas flexionais dos termos por padrão. Em CONTAINS e CONTAINSTABLE, a expansão pode ser solicitada explicitamente com FORMSOF(INFLECTIONAL, ...).
Sinônimos configurados com thesaurus
Considere uma busca por:
carro
e documentos que utilizam:
automóvel
veículo
O LIKE '%carro%' não encontra esses documentos, pois as sequências de caracteres são diferentes.
No SQL Server, é possível configurar um thesaurus por idioma e declarar um conjunto de expansão com termos equivalentes para o domínio da aplicação.
Depois da configuração, uma consulta pode utilizar:
SELECT
a.ArticleId,
a.Content
FROM dbo.Articles AS a
WHERE CONTAINS
(
a.Content,
N'FORMSOF(THESAURUS, carro)',
LANGUAGE 1046
);
Conceitualmente, o thesaurus pode expandir a pesquisa:
carro
automóvel
veículo
Essa equivalência não é descoberta automaticamente por inteligência artificial. Ela precisa estar definida nos arquivos XML de thesaurus do SQL Server.
Isso é importante porque sinônimos dependem do contexto.
Em um catálogo, notebook e laptop podem ser tratados como equivalentes. Já ultrabook pode representar apenas uma categoria específica e não deveria necessariamente receber o mesmo peso.
FREETEXT e FREETEXTTABLE utilizam o thesaurus por padrão. CONTAINS e CONTAINSTABLE exigem FORMSOF(THESAURUS, ...) quando essa expansão é desejada.
Proximidade entre palavras
A presença dos mesmos termos não garante a mesma relevância.
Compare:
Notebook com SSD NVMe de 1 TB
com:
O notebook possui SSD. Entre os acessórios disponíveis,
há também um HD externo de 1 TB.
Os dois textos contêm SSD e 1 TB, mas no primeiro eles estão relacionados diretamente.
O Full-Text Search pode utilizar as posições armazenadas no índice:
SELECT
p.ProductId,
p.Name
FROM dbo.Products AS p
WHERE CONTAINS
(
p.Description,
N'NEAR((SSD, "1 TB"), 5, TRUE)',
LANGUAGE 1046
);
O LIKE consegue confirmar que as duas sequências aparecem, mas não possui um operador textual nativo para avaliar a distância e a ordem entre elas.
Busca fonética não é Full-Text Search
Busca fonética tenta localizar palavras que possuem som parecido, mesmo com grafias diferentes.
No SQL Server, isso pode ser feito com SOUNDEX e DIFFERENCE:
SELECT
SOUNDEX(N'Smith') AS SmithSoundex,
SOUNDEX(N'Smythe') AS SmytheSoundex,
DIFFERENCE(N'Smith', N'Smythe') AS Similarity;
A documentação oficial usa Smith e Smythe como exemplo de termos que geram o mesmo código Soundex.
Esse recurso é separado do Full-Text Search.
Também é importante conhecer suas limitações:
- o algoritmo do SQL Server é baseado em como palavras soam em inglês;
- o resultado é sensível à collation;
- nomes em português podem não produzir o resultado esperado;
SOUNDEXmantém a primeira letra no código.
Por isso, exemplos como Katia e Catia não devem ser tratados como equivalentes garantidos pelo SOUNDEX do SQL Server. Embora o som inicial possa ser parecido em português, as palavras começam com letras diferentes e o algoritmo é orientado ao inglês.
O SQL Server não oferece Metaphone como função T-SQL nativa tradicional. Quando esse tipo de correspondência é necessário, a solução pode exigir código da aplicação, CLR, uma tabela auxiliar de códigos fonéticos ou outro mecanismo de busca.
No PostgreSQL, a extensão fuzzystrmatch disponibiliza Soundex, Levenshtein, Metaphone e Double Metaphone, embora a própria documentação alerte sobre limitações dos algoritmos fonéticos tradicionais com textos multibyte e nomes não ingleses.
Tolerância a erros de digitação
Considere:
notebok
quando o conteúdo correto é:
notebook
O Full-Text Search tradicional do SQL Server não corrige automaticamente esse erro.
Da mesma forma, não é seguro assumir que:
PostgreSQL
Postgre SQL
PostgreSQl
Postgre
serão tratados como o mesmo termo. Diferenças apenas de maiúsculas e minúsculas são ignoradas, mas remoção de letras, inserção de espaços ou alteração dos limites dos tokens são outros problemas.
A primeira ferramenta de diagnóstico deve ser:
SELECT
parser.display_term,
parser.occurrence,
parser.special_term
FROM sys.dm_fts_parser
(
N'"PostgreSQL" OR "Postgre SQL"',
1046,
0,
0
) AS parser
ORDER BY
parser.occurrence;
Essa consulta mostra como o word breaker interpreta cada forma.
Para erros de digitação, as alternativas incluem:
- distância de edição;
- Jaro-Winkler;
- trigramas;
- dicionário de correções;
- normalização na aplicação;
- Elasticsearch ou OpenSearch;
- busca híbrida.
O SQL Server 2025 introduziu, ainda como recurso de visualização, funções como:
EDIT_DISTANCE
EDIT_DISTANCE_SIMILARITY
JARO_WINKLER_DISTANCE
JARO_WINKLER_SIMILARITY
Essas funções são complementares e não transformam o Full-Text Index em um índice fuzzy. Aplicá-las linha por linha sobre uma tabela grande também pode gerar bastante CPU e scans, então uma estratégia de candidatos, normalização ou indexação auxiliar continua sendo necessária.
No PostgreSQL, pg_trgm costuma ser uma alternativa forte para esse cenário, pois cria índices sobre trigramas e pode acelerar similaridade, LIKE e ILIKE.
O que o LIKE exigiria para simular tudo isso
Para reproduzir apenas parte dessas capacidades com LIKE, a consulta começaria a crescer:
WHERE p.Description LIKE N'%notebook%'
OR p.Description LIKE N'%notebooks%'
OR p.Description LIKE N'%laptop%'
OR p.Description LIKE N'%ultrabook%'
OR p.Description LIKE N'%notebok%';
Essa abordagem possui vários problemas:
- as variações precisam ser conhecidas antecipadamente;
- a lista cresce com o vocabulário;
- sinônimos ficam espalhados no código;
- erros de digitação são praticamente infinitos;
- não existe ranking linguístico;
- não existe proximidade nativa;
- múltiplos curingas iniciais aumentam o trabalho de leitura e CPU;
- alterações no vocabulário exigem deploy ou atualização de regras.
Full-Text Search centraliza parte dessa inteligência no mecanismo de pesquisa. Recursos que não pertencem ao FTS — como fonética e fuzzy matching — podem ser combinados de forma consciente, em vez de serem confundidos com uma única funcionalidade.
Uma arquitetura real pode ter várias camadas:
B-tree
→ filtros exatos e estruturados
Full-Text Search
→ tokens, frases, stemming, thesaurus, proximidade e ranking
Soundex ou algoritmo fonético
→ nomes com som semelhante
Trigramas ou distância de edição
→ erros de digitação e substrings aproximadas
Busca vetorial
→ proximidade de significado
O ponto não é substituir todas as buscas por FTS. É utilizar o mecanismo certo para cada tipo de correspondência.
CONTAINSTABLE: quando eu preciso de ranking
CONTAINS retorna verdadeiro ou falso.
CONTAINSTABLE retorna uma tabela com:
KEY, a chave do documento;RANK, uma medida relativa de relevância.
SELECT
p.ProductId,
p.Name,
p.Description,
ft.[RANK]
FROM CONTAINSTABLE
(
dbo.Products,
(Name, Description),
N'"notebook" AND "16 GB" AND "RAM" AND "1 TB" AND "SSD"',
LANGUAGE 1046,
50
) AS ft
INNER JOIN dbo.Products AS p
ON p.ProductId = ft.[KEY]
ORDER BY
ft.[RANK] DESC;
O último argumento limita o retorno aos 50 resultados mais bem ranqueados.
Isso pode reduzir a quantidade de correspondências que sai do mecanismo Full-Text e entra no restante do plano relacional.
O ranking é útil em:
- e-commerce;
- busca de artigos;
- bases de conhecimento;
- centrais de ajuda;
- pesquisas internas.
O valor de RANK serve para ordenar os resultados daquela consulta. Ele não representa uma porcentagem de certeza e não deve ser comparado diretamente entre pesquisas diferentes.
Full-Text Search não substitui uma boa modelagem
O exemplo:
notebook com 16 GB de RAM e SSD de 1 TB
possui duas naturezas de informação:
Texto não estruturado
notebook para desenvolvimento
leve
gamer
bateria de longa duração
uso profissional
Atributos estruturados
RAM = 16 GB
armazenamento = 1 TB
tipo = SSD
Para uma busca livre, o Full-Text Search ajuda a interpretar o texto.
Para filtros exatos, eu prefiro colunas tipadas:
SELECT
p.ProductId,
p.Name,
p.Description
FROM dbo.Products AS p
WHERE p.Category = N'Notebook'
AND p.RamGb = 16
AND p.StorageGb = 1024
AND p.StorageType = 'SSD'
AND CONTAINS
(
(p.Name, p.Description),
N'"desenvolvimento" OR "profissional"',
LANGUAGE 1046
);
Essa abordagem híbrida é mais previsível:
- B-tree para categoria e especificações;
- Full-Text Search para intenção e texto descritivo.
Um índice relacional pode dar suporte aos filtros:
CREATE INDEX IX_Products_SearchFilters
ON dbo.Products
(
Category,
RamGb,
StorageType,
StorageGb
)
INCLUDE
(
Name
);
GO
O Full-Text Search não substitui modelagem relacional.
Usar FTS para descobrir produtos que mencionam 16 GB é válido em texto livre. Porém, se a regra de negócio precisa garantir exatamente 16 GB de RAM, essa informação deveria existir em uma coluna própria.
Quais ônus o Full-Text Search traz
O ganho nas leituras não é gratuito.
Mais espaço em disco
O banco precisa armazenar tokens, referências aos documentos, frequências, posições e estruturas de manutenção.
Quanto mais conteúdo e colunas forem indexados, maior será o índice.
Escritas mais caras
Quando ocorre:
INSERT
UPDATE
DELETE
alterações nas colunas Full-Text precisam ser propagadas para o índice.
Com CHANGE_TRACKING = AUTO, isso acontece em segundo plano. Existe overhead de manutenção e pode haver uma pequena diferença entre o conteúdo salvo e o conteúdo disponível para pesquisa.
População inicial
Criar o índice em milhões de registros pode consumir CPU e I/O relevantes.
A operação deve ser planejada.
Configuração linguística
Idioma, stoplist, acentos, thesaurus, siglas e termos específicos do negócio influenciam os resultados.
Consultas amplas continuam custando
Termos muito frequentes, muitos operadores OR, prefixos amplos e ranking sobre grandes conjuntos ainda podem gerar consultas caras.
Complexidade operacional
A equipe passa a acompanhar:
- populações;
- alterações pendentes;
- falhas de crawl;
- fragmentos;
- crescimento;
- tempo de sincronização.
Como eu mediria a diferença de performance
Eu habilitaria:
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
GO
Primeiro, executaria a versão com LIKE:
SELECT
p.ProductId,
p.Name
FROM dbo.Products AS p
WHERE p.Name LIKE N'%notebook%'
AND p.Description LIKE N'%16 GB%'
AND p.Description LIKE N'%RAM%'
AND p.Description LIKE N'%1 TB%'
AND p.Description LIKE N'%SSD%';
GO
Depois, a versão Full-Text:
SELECT
p.ProductId,
p.Name
FROM dbo.Products AS p
WHERE CONTAINS
(
(p.Name, p.Description),
N'"notebook" AND "16 GB" AND "RAM" AND "1 TB" AND "SSD"',
LANGUAGE 1046
);
GO
E a versão limitada por ranking:
SELECT
p.ProductId,
p.Name,
ft.[RANK]
FROM CONTAINSTABLE
(
dbo.Products,
(Name, Description),
N'"notebook" AND "16 GB" AND "RAM" AND "1 TB" AND "SSD"',
LANGUAGE 1046,
50
) AS ft
INNER JOIN dbo.Products AS p
ON p.ProductId = ft.[KEY]
ORDER BY
ft.[RANK] DESC;
GO
SET STATISTICS TIME OFF;
SET STATISTICS IO OFF;
GO
Eu compararia:
- duração;
- CPU;
- leituras lógicas;
- plano de execução;
- termo raro e termo comum;
- cache frio e aquecido;
- quantidade de resultados;
- custo de
INSERTeUPDATE; - tempo para uma alteração aparecer na pesquisa.
A ressalva continua importante:
LIKEprocura sequências de caracteres; FTS procura tokens. O teste demonstra uma diferença de arquitetura e de semântica, não apenas qual query termina primeiro.
Quando Full-Text Search é essencial
Eu avaliaria seriamente o recurso nos seguintes cenários:
Catálogo de produtos
Pesquisa em nome, descrição e características, com ranking por relevância.
Blog e portal de conteúdo
Busca por palavras, frases, títulos e corpo dos artigos.
Central de ajuda
Pesquisa rápida em documentação, dúvidas e procedimentos internos.
Recrutamento
Busca em currículos por tecnologias, experiências e expressões.
Suporte
Pesquisa em tickets, históricos e soluções anteriores.
Sistemas jurídicos e documentais
Busca em contratos, pareceres e documentos extensos, respeitando requisitos de completude, auditoria e segurança.
Grandes campos textuais
Sempre que múltiplos LIKE '%termo%' aparecem repetidamente sobre grandes volumes e a aplicação precisa de palavras, frases ou relevância, FTS merece ser considerado.
Quando eu não usaria
Valores exatos
WHERE p.ProductId = 100
Use B-tree.
Filtros estruturados
WHERE p.RamGb = 16
AND p.StorageGb = 1024
Use colunas tipadas e índices relacionais.
Prefixo simples
WHERE p.Name LIKE N'Notebook%'
Um índice convencional pode ser suficiente.
Substring arbitrária
WHERE p.Sku LIKE N'%123%'
Full-Text Search normalmente não é a melhor opção.
Tabela pequena e pouca concorrência
A complexidade adicional pode não se pagar.
Busca semântica
Quando o objetivo é encontrar significado semelhante sem palavras em comum, busca vetorial ou híbrida pode ser mais adequada.
Pesquisa distribuída e independente
Quando a aplicação precisa de escalabilidade separada, facetas complexas, alta disponibilidade própria e integração com várias fontes, Elasticsearch ou OpenSearch podem ser escolhas melhores.
E nos outros bancos?
A ideia central é parecida: processar texto, gerar termos e consultar um índice invertido.
PostgreSQL
No PostgreSQL, o documento é transformado em tsvector e a consulta em tsquery.
CREATE INDEX products_search_idx
ON products
USING GIN
(
to_tsvector
(
'portuguese',
coalesce(name, '') || ' ' || coalesce(description, '')
)
);
Consulta:
SELECT
p.*
FROM products AS p
WHERE to_tsvector
(
'portuguese',
coalesce(p.name, '') || ' ' || coalesce(p.description, '')
)
@@ websearch_to_tsquery
(
'portuguese',
'notebook "16 GB" RAM "1 TB" SSD'
);
O parser separa tokens e os dicionários podem remover stopwords, normalizar palavras e produzir lexemas.
O GIN, Generalized Inverted Index, associa cada chave a uma posting list de linhas.
O PostgreSQL também oferece:
ts_rank;ts_rank_cd;- pesos de
AaD; - frases;
- dicionários;
- thesaurus;
ts_debug.
Para substring, erros leves de digitação e similaridade de caracteres, existe a extensão pg_trgm.
Ela divide os textos em trigramas, grupos de três caracteres, e pode criar índices GIN ou GiST capazes de acelerar operações como LIKE, ILIKE e pesquisas por similaridade.
O PostgreSQL também fornece a extensão fuzzystrmatch, com funções como Levenshtein, Soundex, Daitch-Mokotoff Soundex, Metaphone e Double Metaphone.
A diferença prática é:
Full-Text Search → palavras, lexemas e regras linguísticas
pg_trgm → substrings e similaridade entre caracteres
fuzzystrmatch → distância de edição e algoritmos fonéticos
Portanto, quando LIKE '%termo%' é realmente necessário ou a aplicação precisa tolerar pequenas diferenças de grafia, pg_trgm pode ser uma alternativa de infraestrutura. Esses recursos não substituem o FTS; resolvem categorias diferentes de busca.
MySQL
No MySQL, a interface é mais direta:
CREATE FULLTEXT INDEX products_search_idx
ON products(name, description);
Consulta em linguagem natural:
SELECT
p.*,
MATCH(p.name, p.description)
AGAINST
(
'notebook 16 GB RAM 1 TB SSD'
IN NATURAL LANGUAGE MODE
) AS score
FROM products AS p
WHERE MATCH(p.name, p.description)
AGAINST
(
'notebook 16 GB RAM 1 TB SSD'
IN NATURAL LANGUAGE MODE
)
ORDER BY
score DESC;
O InnoDB também utiliza um índice invertido e mantém informações de termos, documentos e posições.
O MySQL oferece modo natural, modo booleano, stopwords e configurações relacionadas ao tamanho dos tokens.
Oracle Database
No Oracle, o recurso é oferecido pelo Oracle Text:
CREATE INDEX products_search_idx
ON products(description)
INDEXTYPE IS CTXSYS.CONTEXT;
Consulta:
SELECT
p.*,
SCORE(1) AS score
FROM products p
WHERE CONTAINS
(
p.description,
'notebook AND RAM AND SSD AND 16 AND 1 TB',
1
) > 0
ORDER BY
SCORE(1) DESC;
O índice CONTEXT também utiliza uma estrutura invertida.
O Oracle Text oferece:
- operadores lógicos;
- proximidade;
- stemming;
- thesaurus;
- fuzzy matching;
- wildcard;
- pesquisa por seções;
- ranking.
A estratégia de sincronização depende do tipo de índice e da configuração. Ela pode ser manual, periódica ou vinculada ao commit, trazendo o mesmo trade-off entre atualização, escrita e disponibilidade da pesquisa.
Comparação rápida
SQL Server
- Estrutura: Full-Text Catalog e Full-Text Index.
- Consulta:
CONTAINSeFREETEXT. - Ranking:
CONTAINSTABLEeFREETEXTTABLE.
PostgreSQL
- Estrutura:
tsvectorcom índice GIN. - Consulta: operador
@@comtsquery. - Ranking:
ts_rankets_rank_cd.
MySQL
- Estrutura: índice
FULLTEXT. - Consulta:
MATCH ... AGAINST. - Ranking: score retornado por
MATCH.
Oracle
- Estrutura: Oracle Text com índice
CONTEXT. - Consulta:
CONTAINS. - Ranking:
SCORE.
Todos partem de uma ideia parecida, mas diferem em sintaxe, pipeline linguístico, manutenção e ranking.
Full-Text Search não é apenas uma otimização
A principal conclusão que eu tiro é que Full-Text Search não deve ser tratado apenas como técnica de performance.
Ele também muda a qualidade da busca.
Com LIKE, eu encontro padrões de caracteres.
Com Full-Text Search, eu posso trabalhar com:
- palavras;
- frases;
- proximidade;
- idioma;
- formas flexionais;
- thesaurus;
- ranking.
O ganho de performance vem do índice invertido. O ganho de produto vem da capacidade de retornar resultados mais relevantes.
Ao mesmo tempo, eu não criaria um Full-Text Index em toda coluna textual.
Ele possui ônus:
- armazenamento;
- custo de escrita;
- população inicial;
- monitoramento;
- possível atraso de sincronização;
- configuração linguística.
E ele não substitui uma boa modelagem.
Para uma pesquisa como:
notebook com 16 GB de RAM e SSD de 1 TB
a melhor solução pode ser híbrida:
Full-Text Search
para interpretar a intenção e o texto livre
+
índices relacionais
para filtrar RAM, armazenamento, categoria e tipo de SSD
A escolha correta depende da pergunta que a aplicação precisa responder.
Se a pergunta for:
Este produto possui exatamente 16 GB de RAM?
use uma coluna estruturada.
Se a pergunta for:
Quais notebooks para desenvolvimento mencionam
16 GB de RAM, SSD de 1 TB e uso profissional,
ordenados pelos resultados mais relevantes?
já estamos falando de recuperação de informação.
É aí que o Full-Text Search deixa de ser apenas uma otimização e passa a ser uma decisão de arquitetura.
Para discussão
Nos sistemas que você projeta hoje, a busca textual ainda depende de LIKE '%termo%' ou já utiliza Full-Text Search, Elasticsearch, OpenSearch, busca vetorial ou uma arquitetura híbrida?
Compartilhe nos comentários em que momento a solução adotada começou a apresentar limitações de performance, relevância ou escalabilidade.
Referências oficiais
Microsoft SQL Server
- Full-Text Search — SQL Server
- CREATE FULLTEXT INDEX
- Query with Full-Text Search
- Populate Full-Text Indexes
- CONTAINS
- CONTAINSTABLE
- sys.dm_fts_parser
- sys.fulltext_languages
- Configure and manage word breakers and stemmers
- Configure and manage thesaurus files
- SOUNDEX
- DIFFERENCE
- Fuzzy String Match — SQL Server 2025 preview
- Configure and manage stopwords and stoplists
- Improve the performance of full-text indexes
PostgreSQL
MySQL
- Full-Text Search Functions
- Natural Language Full-Text Searches
- Boolean Full-Text Searches
- InnoDB Full-Text Index Tables