PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Trouver rapidement des chaînes similaires avec PostgreSQL

La façon dont vous l'avez, la similarité entre chaque élément et tous les autres éléments de la table doit être calculée (presque une jointure croisée). Si votre tableau comporte 1 000 lignes, c'est déjà 1 000 000 (!) calculs de similarité, avant ceux-ci peuvent être vérifiés par rapport à l'état et triés. Balance terriblement.

Utilisez SET pg_trgm.similarity_threshold et le % opérateur à la place. Les deux sont fournis par le pg_trgm module. De cette façon, un index trigramme GiST peut être utilisé à bon escient.

Le paramètre de configuration pg_trgm.similarity_threshold remplacé les fonctions set_limit() et show_limit() dans Postgres 9.6. Les fonctions obsolètes fonctionnent toujours (à partir de Postgres 13). De plus, les performances des index GIN et GiST se sont améliorées à bien des égards depuis Postgres 9.1.

Essayez plutôt :

SET pg_trgm.similarity_threshold = 0.8;  -- Postgres 9.6 or later
  
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.

pg_trgm.similarity_threshold est une option "personnalisée", qui peut être gérée comme n'importe quelle autre option. Voir :

  • Interroger un paramètre (paramètre postgresql.conf) comme "max_connections"

Vous pouvez limiter le nombre de paires possibles en ajoutant des conditions préalables (comme la correspondance des premières lettres) avant jointure croisée (et la prendre en charge avec un index fonctionnel correspondant). Les performances d'une jointure croisée se détériore avec O(N²) .

Cela ne fonctionne pas car vous ne pouvez pas faire référence aux colonnes de sortie dans WHERE ou HAVING clauses :

WHERE ... sim > 0.8

C'est selon la norme SQL (qui est gérée 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é dans GROUP BY et ORDER BY . Voir :

  • PostgreSQL réutilise le résultat du calcul dans la requête select

Cas de test

J'ai exécuté un test rapide sur mon ancien serveur de test pour vérifier mes revendications.
PostgreSQL 9.1.4. Temps pris avec EXPLAIN ANALYZE (meilleur des 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 index 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;

Indice GIN utilisé, 64 hits :temps d'exécution total :484,022 ms
Indice GIST utilisé, 64 hits :temps d'exécution total :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;

Indice GIN pas utilisé, 64 hits :temps d'exécution total :6 345,833 ms
Indice GIST non utilisé, 64 hits :temps d'exécution total :6 335,975 ms

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

GIN ou GiST ?

GIN fournit souvent des performances de lecture supérieures :

  • Différence entre l'indice GiST et GIN

Mais pas dans ce cas particulier !

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

  • Index multicolonne sur 3 champs avec des types de données hétérogènes