web-dev-qa-db-ja.com

PostgreSQLで類似の文字列をすばやく見つける

テーブルに類似した文字列のランキングを作成する必要があります。

次の表があります

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

現在、私はpg_trgmモジュールを使用してsimilarity関数を提供していますが、効率の問題があります。 Postgresマニュアルが示唆するようなインデックスを作成しました

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

そして、私は次のクエリを実行しています:

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;

クエリは機能しますが、何百もの名前があると本当に遅くなります。さらに、SQLを少し忘れたのかもしれませんが、「列simが存在しません」エラーが発生せずにand sim > .8の条件を使用できない理由がわかりません。

クエリを高速化するためのヒントを教えてください。

31
cdarwin

Postgres 9.6 構成パラメータ _pg_trgm.similarity_threshold_ は関数set_limit()を置き換え、 show_limit()。関数は非推奨ですが、まだ機能します。

GINおよびGistインデックスのパフォーマンスも、Postgres 9.1以降、さまざまな方法で改善されました。


代わりに set_limit() および _%_演算子 を使用してください。どちらも _pg_trgm_ モジュールによって提供されます。

あなたがそれを持っている方法では、テーブルのすべての要素と他のすべての要素の間の類似性を計算する必要があります(ほとんどクロス結合)。テーブルに1000行ある場合、それはすでに1,000,000(!)の類似性計算です。 before これらは条件と照合してソートできます。代わりに試してください:

_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;
_

桁違いに速いが、それでも遅い。

前提条件(最初の文字の一致など)を追加して、可能なペアの数を制限したい場合がありますbeforeクロス結合(および一致する機能インデックスでサポート)。 クロス結合のパフォーマンスはO(N²)で低下します。


あなたの副次的な質問について:

_WHERE ... sim > 0.8
_

機能しないできないWHEREまたはHAVING句の出力列を参照するため。これは、SQL規格(やや紛らわしく、認められている)によるものです。これは、他の特定のRDBMSによってかなり緩く処理されます。

一方:

_ORDER BY sim DESC
_

Works出力列 can は_GROUP BY_および_ORDER BY_で使用できるため。詳細:

テストケース

古いテストサーバーで簡単なテストを実行して、私の主張を確認しました。
PostgreSQL 9.1.4。 _EXPLAIN ANALYZE_でかかった時間(ベスト5)。

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

GINインデックスを使用した最初のテスト:

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

Gistインデックスを使用した2回目のテスト:

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

新しいクエリ:

_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;
_

GINインデックスを使用、64ヒット:合計ランタイム:484.022ミリ秒
要旨インデックス、64ヒット:合計実行時間:248.772 ms

古いクエリ:

_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;
_

GINインデックスnot使用、64ヒット:合計ランタイム:6345.833ミリ秒
要旨インデックスnot使用、64ヒット:合計ランタイム:6335.975 ms

その他の点では同じ結果です。アドバイスはいいです。そして、これはちょうど1000行用です!

GINまたはGiST?

多くの場合、GINは優れた読み取りパフォーマンスを提供します。

ただし、この特定のケースではありません:

これはGistインデックスではかなり効率的に実装できますが、GINインデックスでは実装できません。

63