Eu tenho uma tabela de banco de dados com 3.000.000 linhas ou mais assim:
|ID|T4|T16|T64|TL
onde ID =INTEGER (PK) e T4,T16,T64,TL são campos de texto. Cada um é maior em comprimento que o anterior, ou seja, LENGTH(T4)
Desejo comparar uma linha desta tabela com um SELF JOIN com todas as outras linhas com base na distância levenshtein (extensão fuzzystrmatch) entre TL das linhas comparadas e obter os melhores resultados de k que correspondem à menor distância levenstein.
SELECT n1.*,n2.*
FROM (SELECT * FROM dbtable WHERE ID =110) n1,
dbtable n2
ORDER BY levenshtein_less_equal(n1.TL,n2.TL,LENGTH(n1.TEXT)/4) LIMIT 100;
Claro que isso seria extremamente lento porque teria que fazer 3.000.000 cálculos de levenshtein.
Para agilizar as consultas, criei os "hashes" artificiais (campos T4,T16,T64). Então, eu quero verificar inicialmente a distância levenshtein entre os campos T4. Se esta distância entre T4 estiver dentro de algum limite, então verifico a distância levenshtein entre T16 e assim por diante. Dessa forma, em cada etapa, eu precisaria calcular menos distâncias levenshtein do que na abordagem ingênua.
A consulta aninhada é assim:
SELECT n5.*,levenshtein_less_equal(n5.n1_tl,n5.n2_tl,LENGTH(n5.n1_tl)/4)
FROM
(SELECT n4.*
FROM
(SELECT n3.*
FROM
(SELECT
n1.id AS n1_id,
n1.t4 AS n1_t4,
n1.t16 AS n1_t16,
n1.t64 AS n1_t64,
n1.tl AS n1_tl,
n2.id AS n2_id,
n2.t4 AS n2_t4,
n2.t16 AS n2_t16,
n2.t64 AS n2_t64,
n2.tl AS n2_tl
FROM (SELECT id,t4,t16,t64,tl FROM dbTable WHERE id=110) AS n1,dbTable AS n2
WHERE
n1.id<>n2.id
AND levenshtein_less_equal(n1.t4,n2.t4,LENGTH(n1.t4)) <= LENGTH(n1.t4)/2
) n3
WHERE levenshtein_less_equal(n3.n1_t16,n3.n2_t16,LENGTH(n3.n1_t16)/2) < LENGTH(n3.n1_t16)/2
) AS n4
WHERE levenshtein_less_equal(n4.n1_t64,n4.n2_t64,LENGTH(n4.n1_t64)/2) < LENGTH(n4.n1_t64)/2)
AS n5
ORDER BY levenshtein_less_equal(n5.n1_tl,n5.n2_tl,LENGTH(n5.n1_tl)/4)
LIMIT 100;
A consulta é muito mais rápida que a ingênua original, pois precisa acessar apenas 112851 linhas para TL, mas:
O PLANO DE EXPLICAÇÃO:
Limit (cost=872281.90..872282.15 rows=100 width=1168)
-> Sort (cost=872281.90..872564.03 rows=112851 width=1168)
Sort Key: (levenshtein_less_equal(dbTable.tl, n2.tl, (length(dbTable.tl) / 4)))
-> Nested Loop (cost=0.00..867968.81 rows=112851 width=1168)
Join Filter: ((dbTable.id <> n2.id) AND (levenshtein_less_equal(dbTable.t4, n2.t4, length(dbTable.t4)) <= (length(dbTable.t4) / 2)) AND (levenshtein_less_equal(dbTable.t16, n2.t16, (length(dbTable.t16) / 2)) < (length(dbTable.t16) / 2)) AND (levenshtein_less_equal(dbTable.t64, n2.t64, (length(dbTable.t64) / 2)) < (length(dbTable.t64) / 2)))
-> Index Scan using dbTable_pkey on dbTable (cost=0.00..8.60 rows=1 width=584)
Index Cond: (id = 110)
-> Seq Scan on dbTable n2 (cost=0.00..699529.82 rows=3046982 width=584)
Como você pode ver, o problema é que o otimizador de consulta junta/recolhe os cálculos de distância levenshtein entre T4, T16, T64, onde sem recolher seria provavelmente mais rápido, porque menos distâncias levenshtein precisariam ser feitas para T64. Eu usei SET from_collapse_limit=1; para evitar o colapso na mesma sessão, mas nada mudou. Existe uma maneira de impor essa funcionalidade hierárquica em uma consulta Postgres; Lembre-se de que nenhum dos T4...TL é indexado porque não acho que haja um Ãndice para acelerar as distâncias levenshtein. Alguma sugestão para melhorar ainda mais o desempenho?
Sugiro usar um Ãndice de trigrama fornecido pelo módulo adicional pg_trgm . Combine isso com o comprimento da string para obter uma pré-seleção válida.
Solte as colunas
T4
,T16
eT64
(mais rápido em uma única instrução) e executeVACUUM FULL
ouCLUSTER
.Instalar
pg_trgm
. Detalhes aqui:Crie um Ãndice GiST em
tl
elength(tl)
.Há alguns detalhes a serem observados. Respostas relacionadas em dba.SE envolvendo
pg_trgm
: