Quantcast
Channel: Adventures in the Land of Binaries
Viewing all articles
Browse latest Browse all 34

Full Text Search em português no PostgreSQL

$
0
0


Full Text Searching (ou simplesmente busca textual ou FTS) é uma poderosa ferramenta disponível em bancos de dados que visa aumentar a probabilidade de sucesso nas consultas efetuadas pelos usuários.



Operadores de busca textual existem nos SGBDs há anos. O PostgreSQL oferece, além do LIKE, o ILIKE (versão que desconsidera a capitalização) e operadores baseados em expressão regular (i.e., ~ e ~*) para tipos de dados textuais. Entretanto, tais operadores falham ao não prover muitas propriedades essenciais requeridas por sistemas de informações modernos, tais como suporte linguístico, ranking dos resultados e indexação [1].

Na Universidade de Moscou foi criado o projeto Tsearch2 [2], no qual os russos Oleg Bartunov e Teodor Sigaev desenvolveram um motor do tipo full text inteiramente integrado ao SGBD PostgreSQL. Além da referida solução de busca textual, foram criados dois poderosos mecanismos de indexação: GiST e GIN.



O Tsearch2 residiu no diretório contrib do PostgreSQL até a sua versão 8.1, sendo finalmente incorporado ao core do SGBD na versão 8.3, tornando-se a opção default para FTS nele.



Para ilustrar, tomemos como exemplo o clássico cadastro de municípios brasileiros. A missão é efetuar buscas textuais (neste caso pelo nome dos municípios) usando FTS.

Eis uma tabela no PostgreSQL populada com as cidades:


nome | uf
---------------------+----
Abadia de Goiás | GO
Abadia dos Dourados | MG
Abadiânia | GO
Abaeté | MG
Abaetetuba | PA
Abaiara | CE
Abaíra | BA
Abaré | BA
Abatiá | PR
Abdon Batista | SC
...


A ideia é fazer com que buscas aproximadas possam ser efetuadas pelo usuário, ou seja, algo mais "poderoso" que um falho operador LIKE.

A primeira modificação será criar uma coluna adicional, chamada de "busca", a fim de armazenar os vetores já normalizados dos termos para ser usada posteriormente nas consultas.


ALTER TABLE municipios ADD busca tsvector;


Eis a estrutura da tabela após a execução do comando:


Table "public.municipios"
Column | Type | Modifiers
--------+-----------------------+-----------
nome | character varying(50) |
uf | character(2) |
busca | tsvector |


Como o foco deste estudo é usar o idioma português, e considerando que o servidor PostgreSQL esteja configurado com a codificação UTF-8 (i.e., Unicode), precisaremos criar algumas funções adicionais. Baseando-se em [3] e [4], criei essas funções:


CREATE FUNCTION to_ascii(bytea, name) RETURNS text AS
'to_ascii_encname'
LANGUAGE internal STRICT;

CREATE FUNCTION simples(texto varchar) RETURNS varchar AS
'select lower(to_ascii(convert_to($1, ''latin1''), ''latin1''))'
LANGUAGE sql IMMUTABLE STRICT;


Veja o que essa nova função simples() é capaz de fazer:


brasil=# select simples('Pinhão com Açaí');
simples
----------------
pinhao com acai
(1 row)


Ou seja, trata-se de uma função de transformação que normaliza o texto recebido como argumento. Ela será essencial para os passos a seguir.

Podemos usar a função simples() em conjunto com to_tsvector() a fim de produzir o vetor de lexemas (termos processados a serem usados nas buscas). Veja:


brasil=# select to_tsvector(simples('Pinhão com Açaí'));
to_tsvector
-------------------
'aca':3 'pinha':1
(1 row)


Voltando à tabela de municípios, execute o comando SQL a seguir para popular a recém-criada coluna "busca" com o vetor transformado em cada uma de suas tuplas.


UPDATE municipios SET busca = to_tsvector(simples(nome));


Pronto! Agora que os textos foram pré-processados, basta executar as consultas SQL usando o operador especial @@ no PostgreSQL. Veja um exemplo de uso no qual o usuário digitou o texto "sao mateus" na aplicação. A consulta SQL fica assim:


brasil=# select nome, uf from municipios
brasil=# where busca @@ plainto_tsquery(simples('sao mateus'));
nome | uf
------------------------+----
São Mateus | ES
São Mateus do Maranhão | MA
São Mateus do Sul | PR
(3 rows)


Note que além do vetor (coluna "busca" do tipo tsvector) é preciso especificar um tipo tsquery, fornecido pelas funções to_tsquery() e plainto_tsquery(). A função simples() foi usada novamente para realizar a transformação do texto informado.

Ótimo! É só isso?

Não... Funcionou, mas o desempenho não pode ser garantido. Na realidade a busca com essa abordagem será mais feliz que se usássemos um ardiloso LIKE "%sao mateus%". (Jogue a primeira pedra quem nunca encontrou um monstrinho desses em uma aplicação!).

Vejamos como o SGBD está tratando a consulta SQL acima:


brasil=# explain select nome, uf from municipios
brasil=# where busca @@ plainto_tsquery(simples('sao mateus'));
QUERY PLAN
-------------------------------------------------------------
Seq Scan on municipios (cost=0.00..163.60 rows=6 width=16)
Filter: (busca @@ plainto_tsquery('sao mateus'::text))
(2 rows)


Ops, uma varredura sequencial (também conhecido por full scan ou sequential scan) está sendo efetuada! Se você não sabe o que é isso, consulte o seu DBA. Se ele for teu amigo, não irá te xingar.

Bom, e como podemos corrigir isso? Criando um índice! :D

Neste caso específico do FTS no PostgreSQL, temos a escolha de usar uma das duas opções de índices, GiST ou GIN (lembra dos russos?). Cada uma tem a sua aplicação. Escolhi o GIN por esta apresentar maior precisão para esta aplicação. Veja como criar o índice:


CREATE INDEX municipios_gidx ON municipios USING gin(busca);


E agora, será que funcionou? Roda o EXPLAIN novamente! Veja:


brasil=# explain select nome, uf from municipios
brasil=# where busca @@ plainto_tsquery(simples('sao mateus'));
QUERY PLAN
------------------------------------------------------------------------------
Bitmap Heap Scan on municipios (cost=4.30..23.49 rows=6 width=16)
Recheck Cond: (busca @@ plainto_tsquery('sao mateus'::text))
-> Bitmap Index Scan on municipios_gidx (cost=0.00..4.30 rows=6 width=0)
Index Cond: (busca @@ plainto_tsquery('sao mateus'::text))
(4 rows)


Sucesso! Agora a busca textual com FTS está devidamente indexada no PostgreSQL.

Referências


[1] PostgreSQL 8.3 Documentation - Full Text Search

[2] Tsearch2 - full text search extension for PostgreSQL

[3] PostgreSQL: TO_ASCII & UTF8

[4] PostgreSQL 8.3 Documentation - String Functions and Operators

Viewing all articles
Browse latest Browse all 34