web-dev-qa-db-fra.com

Trouver rapidement des chaînes similaires avec PostgreSQL

J'ai besoin de créer un classement de chaînes similaires dans un tableau.

J'ai le tableau suivant

create table names (
name character varying(255)
);

Actuellement, j'utilise le module pg_trgm qui offre la fonction similarity, mais j'ai un problème d'efficacité. J'ai créé un index comme le le manuel de Postgres le suggère :

CREATE INDEX trgm_idx ON names USING Gist (name Gist_trgm_ops);

et j'exécute la requête suivante:

select (similarity(n1.name, n2.name)) as sim, n1.name, n2.name
from names n1, names n2
where n1.name != n2.name and similarity(n1.name, n2.name) > .8
order by sim desc;

La requête fonctionne, mais est vraiment lente lorsque vous avez des centaines de noms. De plus, j'ai peut-être oublié un peu de SQL, mais je ne comprends pas pourquoi je ne peux pas utiliser la condition and sim > .8 sans obtenir l'erreur "La colonne sim n'existe pas".

J'aimerais avoir un indice pour accélérer la requête.

31
cdarwin

Dans Postgres 9.6 le paramètre de configuration pg_trgm.similarity_threshold a remplacé les fonctions set_limit() et show_limit(). Les fonctions sont obsolètes mais fonctionnent toujours.

Les performances des index GIN et Gist se sont également améliorées de plusieurs manières depuis Postgres 9.1.


Utilisez set_limit() et % opérateur à la place. Les deux sont fournis par le module pg_trgm .

De la façon dont vous l'avez, la similitude entre chaque élément et chaque autre élément de la table doit être calculée (presque une jointure croisée). Si votre table contient 1 000 lignes, c'est déjà 1 000 000 (!) Calculs de similitude, avant ceux-ci peuvent être vérifiés par rapport à la condition et triés. Essayez plutôt:

SET pg_trgm.similarity_threshold = 0.8; -- Postgres 9.6 or later
-- SELECT set_limit(0.8);               -- for older versions

SELECT similarity(n1.name, n2.name) AS sim, n1.name, n2.name
FROM   names n1
JOIN   names n2 ON n1.name <> n2.name
               AND n1.name % n2.name
ORDER  BY sim DESC;

Plus rapide par ordre de grandeur, mais toujours lent.

Vous voudrez peut-être restreindre le nombre de paires possibles en ajoutant des conditions préalables (comme faire correspondre les premières lettres) avant la jonction croisée (et la prendre en charge avec un index fonctionnel correspondant) . Les performances d'une jointure croisée se détériorent avec O (N²) .


Quant à votre question subsidiaire:

WHERE ... sim > 0.8

Ne fonctionne pas car vous ne pouvez pas faire référence aux colonnes de sortie dans WHERE ou HAVING clauses. Cela est conforme au standard SQL (un peu déroutant, accordé) - qui est géré de manière assez lâche par certains autres SGBDR.

D'autre part:

ORDER BY sim DESC

Fonctionne car les colonnes de sortie peuvent être utilisées dans GROUP BY Et ORDER BY. Détails:

Cas de test

J'ai effectué un test rapide sur mon ancien serveur de test pour vérifier mes réclamations.
PostgreSQL 9.1.4. Temps pris avec EXPLAIN ANALYZE (Meilleur de 5).

CREATE TEMP table t AS 
SELECT some_col AS name FROM some_table LIMIT 1000;  -- real life test strings

Première série de tests avec indice GIN:

CREATE INDEX t_gin ON t USING gin(name gin_trgm_ops);  -- round1: with GIN index

Deuxième série de tests avec index Gist:

DROP INDEX t_gin;
CREATE INDEX t_Gist ON t USING Gist(name Gist_trgm_ops);

Nouvelle requête:

SELECT set_limit(0.8);

SELECT similarity(n1.name, n2.name) AS sim, n1.name, n2.name
FROM   t n1
JOIN   t n2 ON n1.name <> n2.name
           AND n1.name % n2.name
ORDER  BY sim DESC;

Index GIN utilisé, 64 hits: durée totale d'exécution: 484,022 ms
Index Gist utilisé, 64 hits: durée d'exécution totale: 248,772 ms

Ancienne requête:

SELECT (similarity(n1.name, n2.name)) as sim, n1.name, n2.name
FROM   t n1, t n2
WHERE  n1.name != n2.name
AND    similarity(n1.name, n2.name) > 0.8
ORDER  BY sim DESC;

Index GIN non utilisé, 64 hits: durée totale d'exécution: 6345.833 ms
Index Gist non utilisé, 64 hits: durée totale d'exécution: 6335,975 ms

Sinon, des résultats identiques. Les conseils sont bons. Et c'est pour seulement 1000 lignes!

GIN ou GiST?

GIN offre souvent des performances de lecture supérieures:

Mais pas dans ce cas particulier:

Cela peut être implémenté assez efficacement par les index Gist, mais pas par les index GIN.

63
Erwin Brandstetter