Dada uma tabela assim:
caminho (ltree) |
---|
abc |
ab |
a |
de |
f |
Como eu escreveria uma consulta para retornar o caminho ltree correspondente mais longo, dada uma entrada?
Por exemplo:
(input) => expected output
(a.b.c) => a.b.c
(d.e.f) => d.e
(f.g.h) => f
(a.b) => a.b
Eu gostaria de poder usar isso para unir uma tabela contendo caminhos ltree em seu "caminho correspondente mais longo" em outra tabela de uma forma que tenha bom desempenho. Então, dada uma tabela contendo linhas de todos os itens inputs
do exemplo acima, como eu a juntaria à tabela para obter linhas com a "correspondência mais longa"?
Criar dados de teste...
Para aproximadamente 1.000 linhas na tabela "outros", procura o caminho correspondente mais longo na tabela "árvore" que contém 1 milhão de linhas.
Primeira tentativa: função de janela. 140ms sem LIMITE. Seria necessária alguma desduplicação na saída.
Segunda tentativa: comparação ltree pode ser usada, como '1.2.3'::ltree>'1.2'::ltree, então usar max() simplesmente retornaria o mais longo. Infelizmente max() não está implementado para ltree, mas você pode adicioná-lo . Mas sempre podemos usar LATERAL, que tem a vantagem de retornar a linha inteira caso você precise.
Este é mais rápido em 80 ms porque a classificação é movida dentro do loop aninhado e é um heapsort top-1. Portanto, 80 µs por linha para encontrar o caminho mais longo.
Terceiro: função de retorno de conjunto.
Resultado: 29ms, muito mais rápido.
No entanto, depende do fato de que o postgres usará as linhas de unnest_ltree() na ordem em que a função as retorna, o que não é garantido.
Quarta tentativa: junção manual
Resultado: 35ms com índice Gist no caminho, 16ms com índice btree no caminho.
No entanto, agora que adicionei um índice btree na coluna path, a primeira consulta contra-ataca. Como o pai mais longo (t.path) de um caminho (o.path) deve satisfazer t.path <= o.path, e devido à ordem de classificação dos caminhos, adicionar essa condição significa que o btree encontra a linha de destino imediatamente, enquanto a essência simplesmente retorna todos os ancestrais que precisam ser classificados. Portanto esta é a opção mais rápida, mas precisa de um índice extra.
Mas... o que aconteceria se a tabela "outros" fosse muito maior? Vamos tentar com 500 mil linhas.
Nesse caso, todos os métodos acima são afetados, demorando cerca de 3,5s, porque estão todos restritos a um tipo de plano de loop aninhado. Para um grande número de linhas em ambas as tabelas, uma junção de mesclagem seria uma opção muito melhor... Infelizmente o postgres não suporta ASOF JOIN que faria isso automaticamente, mas sempre podemos classificar!
Como isso fornece as linhas relacionadas próximas (uma sempre acima da outra), uma função de janela pode resolver isso.
Isso não usa nenhum índice e deve funcionar em tabelas grandes, mas no meu caso de teste leva quase o mesmo tempo que os anteriores.