Hummmm missão difícil e acredito que impossível, visto que o algoritmo de busca e o algoritmo de relevância do google possuí centenas regras e é algo incrível.
Então me resta falar um pouco sobre a busca textual que temos no PostgreSQL e o que conseguimos fazer com ela.
O PostgreSQL nos oferece o Full Text Search, busca textual, utilizando o tsearch2 e vou falar sobre:
- Busca Textual
- Operadores
- Busca por sinônimos
- Relevância
- Highlight no texto procurado
Busca Textual
Para quem quiser ir acompanhando e executando os exemplos, peguei uma relação de servidores, cargos e encargos do portal da transparência do município de Curitiba, que veio como .csv, então é tranquilo de carregar na base.
Baixado o arquivo, criar uma base de testes, no meu caso "busca" e uma tabela "servidores" com as colunas do cabeçalho do arquivo.
- Criar a tabela.
- Fazer a carga dos dados.
- Adicionar a extensão do tsearch2.
- Criar uma coluna para conter os dados que deseja ser pesquisado.
- Carregar a coluna fti com os dados no formato do tsvector
Nessa tabela escolhi as colunas que contem texto, porque as demais são número. Isso vai depender do seu negócio para saber em quais colunas a pesquisa deverá atuar.
- Criar o índice em cima da coluna fti para ser utilizado na busca textual.
Pode ser criado o índice em cima dessa coluna, ou em cima de uma função. Prefiro em cima da coluna, por ser mais simples de utilizar.
Índices:
Para a busca textual, podemos utilizar os seguintes tipos de índices: Gin ou Gist.
Gin: Índices determinísticos, bom para a busca e ruim para a criação. O tamanho do indice na versão PG 9.4 diminuiu consideravelmente.
Gist: De facil e rápida criação par o banco. Ele armazena o hash do tsvector e por armazenar o hash depois de encontrar o resultado ele tem que ler os registros retornados para garantir que estão dentro dos objetos buscados, então para bases pequenas ou poucos lexemas ele funciona bem, se não o Gin tende a ser melhor.
Pronto, a busca textual esta pronta, mas vamos ver um pouco do tsearch.
Para pesquisar você pode testar a função to_tsquery('professor&francisco');
- Criar a tabela.
CREATE TABLE servidores (NOME text, CARGO text, CARGO_COMISSAO text, MES text, ANO text, LOTACAO text, TOTAL_DESCONTOS text, TOTAL_LIQUIDO text, TOT_VANTAGENS text,MOTIVO text, DESCRICAO_EVENTO text, VALOR_EVENTO text, TIPO_EVENTO text);
Descrição da tabela servidores
- Fazer a carga dos dados.
COPY servidores FROM '/tmp/Servidores_Cargos_Encargos_-_Base_de_Dados.csv' DELIMITER ';';
- Adicionar a extensão do tsearch2.
CREATE EXTENSION tsearch2;
- Criar uma coluna para conter os dados que deseja ser pesquisado.
ALTER TABLE servidores ADD fti tsvector;
- Carregar a coluna fti com os dados no formato do tsvector
Nessa tabela escolhi as colunas que contem texto, porque as demais são número. Isso vai depender do seu negócio para saber em quais colunas a pesquisa deverá atuar.
UPDATE servidores SET fti = to_tsvector('portuguese', nome || ' ' || cargo || ' ' || lotacao || ' ' || descricao_evento || ' ' || tipo_evento );
- Criar o índice em cima da coluna fti para ser utilizado na busca textual.
Pode ser criado o índice em cima dessa coluna, ou em cima de uma função. Prefiro em cima da coluna, por ser mais simples de utilizar.
Índices:
Para a busca textual, podemos utilizar os seguintes tipos de índices: Gin ou Gist.
Gin: Índices determinísticos, bom para a busca e ruim para a criação. O tamanho do indice na versão PG 9.4 diminuiu consideravelmente.
Gist: De facil e rápida criação par o banco. Ele armazena o hash do tsvector e por armazenar o hash depois de encontrar o resultado ele tem que ler os registros retornados para garantir que estão dentro dos objetos buscados, então para bases pequenas ou poucos lexemas ele funciona bem, se não o Gin tende a ser melhor.
CREATE INDEX idx_seridores_fti ON servidores USING gin(fti);
Pronto, a busca textual esta pronta, mas vamos ver um pouco do tsearch.
Para pesquisar você pode testar a função to_tsquery('professor&francisco');
SELECT to_tsquery('professor+francisco'); to_tsquery -------------------------- 'professor' & 'fracisc'
Ele gerou os lexemas de pesquisa para as palavras professor e francisco, com estes lexemas é possível ele utilizar o índice e fazer a pesquisa textual de forma rápida.
Agora vamos testar a pesquisa na nossa base, mas como realizo a pesquisa?? Utilizo o operador igual(=)? Utilizo o like?
O operador de busca textual é o @@, então temos:
SELECT * FROM servidores WHERE fti @@ to_tsquery('professor&francisco');
Aqui é o momento onde da para entender porque é mais fácil criar a coluna fti, pois agora vc só precisa utilizar o operador em cima dela. Caso você só vá indexar uma coluna, acredito ser melhor já criar o indice em cima dessa coluna e usar o @@ para comparar com as palavras pesquisadas.
Na consulta ele vai retornar todas as linhas que contenham as palavras professor e francisco, em alguma das colunas nome, cargo, lotacao, descricao_evento e tipo_evento.
Isto é praticamente um LIKE %% com UNION pra cada coluna!!
Mas logo na primeira linha para mim retornou:
nome: Francisco Galdino Bezerra
cargo: Professor de Educação Infantil
Então não funcionou ??? Trouxe uma mulher ao invés de um homem??
Sim, ele funcionou. Mas como a busca textual utiliza o tsearch2 e o mesmo é baseado em radicais, o retorno serão todos os radicais que contemplem a pesquisa.
Para a sua pesquisa poder colocar direto a sua entrada do campo texto da aplicação, fazemos:
SELECT * FROM servidores WHERE fti @@ to_tsquery(replace('professor francisco', ' ', '&'));
A consulta também pode ser feita usando a função plainto_tsquery, que troca os espaços pelo &:
SELECT * FROM servidores WHERE fti @@ plainto_tsquery('professor francisco');
Feito, busca textual pronta para uso! =)
Operadores
O operador de pesquisa é o @@ e para utilizar nos lexemas temos:
Também aceita parenteses, então da para deixar a lógica como necessário.- & (and) condicional de e
- | (or) condicional de ou
- ! (not) negação do termo pesquisado
Busca por sinônimos
Agora se queremos procurar por um professor e ele é cadastrado em sua função como educador, podemos definir sinônimos.Exemplos:
- professor = educador
- automóvel = carro
- parana = pr
Então para isso temos de definir um dicionário de sinônimos da seguinte forma:
Criar o arquivo dentro do diretório do tsearch:
> /usr/share/postgresql/9.4/tsearch_data/celepar_sinonimos.syn
Popular o dicionário de sinônimos, onde no nosso exemplo iremos dizer que professor é o mesmo que educador, então adiciona essa linha ao arquivo.
educador professor
Se fizermos agora o teste de como é professor temos:
select to_tsquery('educador'); to_tsquery -------------- 'educ'
Agora vamos carregar o dicionário de sinônimos:
Com o comando ts_debug ele irá mostrar de onde vem o resultado, os dicionários envolvidos e o lexema.
CREATE TEXT SEARCH DICTIONARY celepar_sinonimos ( TEMPLATE = synonym, SYNONYMS = celepar_sinonimos); ALTER TEXT SEARCH CONFIGURATION portuguese ALTER MAPPING FOR asciiword WITH celepar_sinonimos, portuguese_stem;
SELECT to_tsquery('educador'); to_tsquery -------------- 'professor'
Com o comando ts_debug ele irá mostrar de onde vem o resultado, os dicionários envolvidos e o lexema.
SELECT * FROM ts_debug('educador'); alias | description | token | dictionaries | dictionary | lexemes -----------+-----------------+-----------+-------------------------------------+-------------------+------------ asciiword | Word, all ASCII | educador | {celepar_sinonimos,portuguese_stem} | celepar_sinonimos | {professor} (1 row)
Agora o PG já sabe que a palavra educador e professor são a mesma coisa e podem retornar o mesmo na pesquisa.
Vamos atualizar o nosso indice para isso funcionar na nossa pesquisa:
UPDATE servidores SET fti = to_tsvector('portuguese', nome || ' ' || cargo || ' ' || lotacao || ' ' || descricao_evento || ' ' || tipo_evento );
E agora a pesquisa ira retornar tendo professor ou educador.
Relevância
Para nossa alegria o tsearch2 já possui dois algoritmos de relevância: o ts_rank e o ts_rank_cd.
O ts_rank é considerada a função padrão de rankeamento e retornará o valor em relação a frequência que encontra os lexemas. O ts_rank_cd usa o algoritmo de Rankig da Densidade de Cobertura, que é parecido com o algoritmo do ts_rank, mas leva em consideração a proximidade no texto dos lexemas procurados.
No exemplo eu irei mostrar o resultado de ambos, mas irei utilizar o ts_rank_cd, pois para mim interessa a proximidade das palavras. Então quando procurado Francisco Summa ele irá deixar no topo os registros q encontrem as duas palavras próximas no texto.
Uma coisa muito interessante, mas não vou entrar no mérito neste post, é que no rankeamento você pode ao indexar os seus dados definir até quatro pesos e o resultado irá levar em consideração os pesos que você definiu. Para o exemplo que estou fazendo eu poderia definir que se encontrar no nome o lexema vale o dobro do que encontrar no cargo, ou até mesmo para cada um dos campos que indexei, ele ter o seu peso de acordo com a lógica do negócio.
SELECT DISTINCT ts_rank_cd(fti, to_tsquery(replace('maria pereira silva professora',' ','|'))) AS Relevancia, nome, cargo, lotacao FROM servidores WHERE fti @@ to_tsquery(replace('maria pereira silva professora',' ','|')) ORDER BY relevancia DESC LIMIT 10;
Highlight no texto procurado
Uma função que pode ser útil e facilitar a vida do desenvolvedor, é retornar os dados procurados já no formato do html com a tag de <b>bold</b> nos lexemas que foram procurados.
Para isso função basta passar o texto e o tsquery dos elementos procurados.
SELECT DISTINCT ts_rank_cd(fti, to_tsquery(replace('maria pereira silva professora',' ','|'))) AS Relevancia ,ts_headline(nome||' '||cargo||' '||lotacao||' '||descricao_evento||' '||tipo_evento, to_tsquery(replace('maria pereira silva professora',' ','|')))
Acredito que com isso da para dar uma ótima orientada sobre busca textual e para utilizar de forma fácil, apenas fazer uma função que irá receber o dado pesquisado e já coloca nos três locais do SQL.
Acredito que na semana que vem eu acrescente aqui.
Referências:
http://www.postgresql.org/docs/9.4/static/textsearch.html
http://linuxgazette.net/164/sephton.html
http://www.postgresql.org/docs/9.4/static/textsearch-dictionaries.html#TEXTSEARCH-SYNONYM-DICTIONARY
http://www.postgresql.org/docs/9.4/static/textsearch-controls.html#TEXTSEARCH-HEADLINE
Nenhum comentário:
Postar um comentário