Digamos que eu tenha uma tabela chamada words
com muitos registros.
As colunas são id
e name
.
Na words
tabela tenho por exemplo:
'systematic', 'سلام','gear','synthesis','mysterious', etc.
NB: também temos palavras utf8. Como consultar com eficiência para ver quais palavras incluem
letras 's'
e (todas elas)?'m'
'e'
A saída seria:
systematic,mysterious
Não tenho ideia de como fazer uma coisa dessas. Deve ser eficiente porque nosso servidor sofreria caso contrário.
Uma abordagem fácil seria considerar a matriz de letras correspondente a cada palavra e pesquisar dentro dela com o
@>
operador de matriz (contém). Isso funciona independentemente das posições das letras, conforme mostrado no exemplo do manual , ou seja,ARRAY[1,4,3] @> ARRAY[3,1]
é verdade.Esta matriz pode ser facilmente obtida com
regexp_split_to_array(name, '')
.[ EDIT : de acordo com a resposta de @Erwin ,
string_to_array(name, NULL)
é mais rápido, então é melhor usar isso. É uma substituição imediata no resto da resposta]Aqui está uma demonstração que primeiro materializa a matriz como uma coluna em uma tabela de teste contendo uma mistura de palavras em inglês e francês (~ 511.000 linhas, comprimento médio = 13 caracteres) e, em seguida, uma segunda tabela de teste sem adicionar a matriz como uma coluna.
Para encontrar um número relativamente grande de palavras:
Isso faz uma varredura sequencial conforme mostrado por EXPLAIN ANALYZE:
Mas podemos indexar o array com um índice GIN:
E então é bem mais rápido:
Podemos até evitar a materialização do array como uma coluna indexando diretamente a expressão, já que o postgres suporta índices funcionais.
Em seguida, a pesquisa deve ser feita exatamente com a mesma expressão e o índice é usado:
Consulte Tipos de índices GiST e GIN no manual para obter advertências sobre esses tipos de índices.
Caso de teste
Eu construí um caso de teste meio realista:
Palavras minúsculas com 3 a 25 letras, alguns caracteres adicionais como substitutos para letras não ASCII. Palavras mais curtas são mais comuns e algumas letras são mais comuns do que outras. Usando
random()^2
para obter uma distribuição de dados inclinada.Usando isso, executei vários testes para comparar a abordagem de @Daniel com algumas alternativas.
word LIKE ALL(arr)
Achei esta consulta surpreendentemente eficaz, mesmo sem índice:
Para fazê-lo funcionar, você preencheria suas letras com
%
. Ou seja:a
->%a%
e forme um array como acima.Quase tão rápido com uma varredura sequencial em 100 mil linhas quanto outras soluções baseadas em um índice GIN. Também testei com 10k e 40k linhas e no Postgres 9.1. Resultados semelhantes.
Isso vai se deteriorar com mesas ainda maiores. Mas se sua tabela contiver 100 mil linhas ou menos (e não tiver outras colunas grandes), essa consulta simples pode ser tudo o que você precisa.
índice funcional
string_to_array()
Em primeiro lugar, use
string_to_array(word, NULL)
) em vez deregexp_split_to_array(word, '')
. Achei 5 a 6 vezes mais rápido no Postgres 9.1 e 2 a 3 vezes mais rápido no Postgres 9.3. Afeta principalmente a manutenção do índice, mas também, em menor grau, o desempenho da consulta.Qualquer uma dessas expressões resulta em uma matriz não classificada com possíveis duplicatas.
variantes ineficazes
A remoção de duplicatas da matriz é ineficaz, pois o algoritmo GIN faz isso sozinho. Citando o manual:
Incluo essa variante ineficaz no violino de qualquer maneira para provar o ponto e também como prova de conceito - como usar uma
IMMUTABLE
função definida pelo usuário em um índice:Um índice em uma matriz distinta e classificada também mostra os mesmos tempos de pesquisa, embora seja mais caro. Eu não incluí.
-> Demonstração do SQLfiddle.
Dobrar letras
No entanto, uma
IMMUTABLE
função como a demonstrada acima pode ser usada para dobrar letras. Um caso de uso típico seria com ounaccent
módulo para remover acentos (sinais diacríticos), ou seja, também localizaré
,è
etc. ao procurare
.Esta seria uma solução muito eficaz:
Explicação detalhada nesta resposta relacionada em SO (não deixe de lê-la):
O PostgreSQL oferece suporte a agrupamentos “insensíveis a acentos”?
Testei localmente no Postgres 9.1 e 9.3, mas não consigo incluir no violino, onde não consigo instalar módulos adicionais.
O Postgresql tem muitas opções de indexação. Ele também possui alguns componentes poderosos construídos sobre essas opções de indexação.
Um desses recursos é a pesquisa de texto completo. Consulte http://www.postgresql.org/docs/9.3/static/textsearch.html