Eu tenho duas mesas.
O primeiro é uma tabela com prefixos
code name price
343 ek1 10
3435 nt 4
3432 ek2 2
O segundo é o registro de chamadas com números de telefone
number time
834353212 10
834321242 20
834312345 30
Preciso escrever um script que encontre o prefixo mais longo dos prefixos para cada registro e escreva todos esses dados na terceira tabela, assim:
number code ....
834353212 3435
834321242 3432
834312345 343
Para o número 834353212, devemos aparar '8' e, em seguida, encontrar o código mais longo da tabela de prefixos, seu 3435.
Sempre devemos soltar primeiro '8' e o prefixo deve estar no início.
Eu resolvi essa tarefa há muito tempo, de maneira muito ruim. Foi um script perl terrível, que faz muitas consultas para cada registro. Este script:
Pegue um número da tabela de chamadas, faça a substring do comprimento (número) para 1 => prefixo $ no loop
Faça a consulta: selecione count (*) em prefixos em que códigos como '$ prefix'
- Se contar> 0, pegue os primeiros prefixos e escreva na tabela
O primeiro problema é a contagem de consultas - é call_records * length(number)
. O segundo problema são LIKE
expressões. Receio que sejam lentos.
Tentei resolver o segundo problema:
CREATE EXTENSION pg_trgm;
CREATE INDEX prefix_idx ON prefix USING gist (code gist_trgm_ops);
Isso acelera cada consulta, mas não resolveu o problema em geral.
Agora tenho prefixos de 20k e números de 170k , e minha solução antiga é ruim. Parece que preciso de uma nova solução sem loops.
Apenas uma consulta para cada registro de chamada ou algo assim.
fonte
code
na primeira tabela é o mesmo prefixo mais tarde. Você poderia esclarecer isso? E algumas correções dos dados de exemplo e da saída desejada (para facilitar o acompanhamento do seu problema) também serão bem-vindas.Respostas:
Estou assumindo o tipo de dados
text
para as colunas relevantes.Solução "Simples"
Elementos chave:
DISTINCT ON
é uma extensão do Postgres do padrão SQLDISTINCT
. Encontre uma explicação detalhada para a técnica de consulta usada nesta resposta relacionada no SO .ORDER BY p.code DESC
escolhe a correspondência mais longa, porque'1234'
classifica depois'123'
(em ordem crescente).Violino simples do SQL .
Sem índice, a consulta seria executada por muito tempo (não esperou para vê-la terminar). Para tornar isso rápido, você precisa de suporte ao índice. Os índices trigramas que você mencionou, fornecidos pelo módulo adicional,
pg_trgm
são um bom candidato. Você precisa escolher entre o índice GIN e GiST. O primeiro caractere dos números é apenas ruído e pode ser excluído do índice, tornando-o um índice funcional.Nos meus testes, um índice GIN de trigrama funcional venceu a corrida sobre um índice GiST de trigrama (conforme o esperado):
Dbfiddle avançado aqui .
Todos os resultados de teste são de uma instalação de teste local do Postgres 9.1 com uma configuração reduzida: números de 17k e códigos de 2k:
Muito mais rápido ainda
Falha na tentativa com
text_pattern_ops
Quando ignoramos o primeiro caractere de ruído perturbador, ele se resume à correspondência básica de padrões ancorados à esquerda . Portanto, tentei um índice de árvore B funcional com a classe de operador
text_pattern_ops
(assumindo o tipo de colunatext
).Isso funciona de maneira excelente para consultas diretas com um único termo de pesquisa e faz com que o índice trigrama pareça ruim em comparação:
No entanto , o planejador de consultas não considerará esse índice para ingressar em duas tabelas. Eu já vi essa limitação antes. Ainda não tenho uma explicação significativa para isso.
Índices de árvore B parcial / funcional
A alternativa é usar verificações de igualdade em cadeias parciais com índices parciais. Isso pode ser usado em a
JOIN
.Como normalmente temos apenas um número limitado de
different lengths
prefixos, podemos criar uma solução semelhante à apresentada aqui com índices parciais.Digamos, temos prefixos que variam de 1 a 5 caracteres. Crie um número de índices funcionais parciais, um para cada comprimento de prefixo distinto:
Como esses são índices parciais , todos eles juntos são pouco maiores que um único índice completo.
Adicione índices correspondentes para números (levando em consideração o caractere de ruído principal):
Embora esses índices mantenham apenas uma substring e sejam parciais, cada um cobre a maior parte ou a totalidade da tabela. Portanto, eles são muito maiores juntos que um único índice total - exceto números longos. E eles impõem mais trabalho para operações de gravação. Esse é o custo para uma velocidade incrível.
Se esse custo for alto demais para você (o desempenho da gravação é importante / muitas operações de gravação / espaço em disco é um problema), você pode pular esses índices. O resto ainda é mais rápido, se não tão rápido quanto poderia ser ...
Se os números nunca forem mais curtos que os
n
caracteres, elimineWHERE
cláusulas redundantes de algumas ou de todas e também elimine aWHERE
cláusula correspondente de todas as consultas a seguir.CTE recursiva
Com toda a configuração até agora, eu esperava uma solução muito elegante com um CTE recursivo :
No entanto, embora essa consulta não seja ruim - ela é tão boa quanto a versão simples com um índice GIN de trigrama -, ela não fornece o que eu estava buscando. O termo recursivo é planejado apenas uma vez, portanto, ele não pode usar os melhores índices. Somente o termo não recursivo pode.
UNIÃO TUDO
Como estamos lidando com um pequeno número de recursões, podemos apenas explicá-las iterativamente. Isso permite planos otimizados para cada um deles. (Porém, perdemos a exclusão recursiva de números já bem-sucedidos. Portanto, ainda há espaço para melhorias, especialmente para uma variedade maior de comprimentos de prefixos)):
Um avanço, finalmente!
Função SQL
O agrupamento em uma função SQL remove a sobrecarga de planejamento da consulta para uso repetido:
Ligar:
Função PL / pgSQL com SQL dinâmico
Essa função plpgsql é muito parecida com a CTE recursiva acima, mas o SQL dinâmico
EXECUTE
força a consulta a ser planejada novamente para cada iteração. Agora ele faz uso de todos os índices personalizados.Além disso, isso funciona para qualquer faixa de tamanho de prefixo. A função usa dois parâmetros para o intervalo, mas eu a preparei com
DEFAULT
valores, portanto também funciona sem parâmetros explícitos:O passo final não pode ser envolvido facilmente na única função. Ou apenas chame assim:
Ou use outra função SQL como wrapper:
Ligar:
Um pouco mais lento devido à sobrecarga de planejamento necessária. Mas mais versátil que o SQL e mais curto para prefixos mais longos.
fonte
Uma string S é um prefixo de uma string T, se T estiver entre S e SZ, em que Z é lexicograficamente maior que qualquer outra string (por exemplo, 99999999 com 9's suficientes para exceder o maior número de telefone possível no conjunto de dados ou, às vezes, 0xFF funcionará).
O prefixo comum mais longo para qualquer T também é lexicograficamente máximo, portanto, um simples grupo by e max o encontrará.
Se isso for lento, provavelmente é devido às expressões calculadas, então você também pode tentar materializar p.code || '999999' em uma coluna na tabela de códigos com seu próprio índice, etc.
fonte