web-dev-qa-db-fra.com

Postgres, la distance de Levenshtein et les requêtes imbriquées

J'ai une table de dB avec 3 000 000 rangées ou plus comme celle-ci:

|ID|T4|T16|T64|TL

où id = INTEGER (PK) et T4, T16, T64, TL sont des champs de texte. Chacun est plus grand long que le précédent, c'est-à-dire la longueur (T4)

Je souhaite comparer une ligne de cette table avec une jointure auto avec toutes les autres lignes basées sur la distance de Levenshtein (Extension FuzzyStrmatch) entre TL des lignes comparées et obtenir les résultats les mieux k correspondant à la plus petite distance de 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;

Bien sûr, cela serait extrêmement lent car il devra faire 3 000 000 calculs de Levenshtein.

Afin d'accélérer les requêtes, j'ai créé les "hachages" artificiels (champs T4, T16, T64). Donc, je veux vérifier initialement la distance de Levenshtein entre les champs T4. Si cette distance entre T4 est comprise entre une limite, je vérifie la distance de Levenshtein entre T16 et SO-ON. De cette façon, dans chaque étape, je devrais calculer moins de distances de Levenshtein qui par l'approche naïve.

La requête imbriquée est comme ça:

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;

La requête est beaucoup plus rapide que la naïve originale, car elle n'a besoin que d'accéder à 112851 rangées pour TL mais:

Le plan d'explication:

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) 

Comme vous voyez le problème, c'est que l'optimiseur de requête se joint/effondre les calculs de distance du Levenshtein entre T4, T16, T64, où sans l'effondrement, il serait probablement plus rapide, car il faudrait que moins de distances de Levenshtein devraient être effectuées pour T64. J'ai utilisé ensemble de_collapse_limit = 1; Pour éviter de s'effondrer à la même session, mais rien n'a changé. Existe-t-il un moyen d'appliquer cette fonctionnalité hiérarchique dans une requête Postgres; Gardez à l'esprit qu'aucun des T4 ... TL n'est indexé car je ne pense pas qu'il y ait un index pour accélérer les distances de Levenshtein. Toute suggestion pour améliorer encore les performances?

4
Alexandros

Je suggère d'utiliser un indice trigramme fourni par le module supplémentaire pg_trgm . Combinez cela avec la longueur de la chaîne pour obtenir une présélection valide.

  1. Déposez les colonnes T4, T16 Et T64 (Plus rapide dans une seule instruction) et exécutez VACUUM FULL Ou CLUSTER.

  2. Installez pg_trgm. Détails ici:

  3. Créez un index GIST sur tl et length(tl).

Il y a des détails à respecter. Réponses connexes sur dba.se impliquant pg_trgm:

4
Erwin Brandstetter