web-dev-qa-db-ja.com

複数のテキストフィールドでのパターンマッチングによる高速クエリ

20Mを超えるタプルを持つPostgresテーブルが1つあります。

first_name | last_name | email
-------------------------------------------
bat        | man       | [email protected]
arya       | vidal     | [email protected]
max        | joe       | [email protected]

使用しているレコードをフィルタリングするには:

SELECT *
  FROM people
WHERE (first_name || '' || last_name) ILIKE '%bat%man%' OR 
    first_name ILIKE '%bat%man%'  OR  
    last_name ILIKE '%bat%man%'   OR
    email ILIKE '%bat%man%'
    LIMIT 25 OFFSET 0

インデックスを使用した場合でも、結果が返されるまでに検索はほぼ1分かかります。
(first_name || '' || last_name)first_namelast_nameemailのインデックスがあります。

このクエリのパフォーマンスを向上させるにはどうすればよいですか?

7
Victor

パターンマッチングの種類には、トライグラムインデックスを使用するのが最適です。 最初にお読みください:

式_(first_name || '' || last_name)_にタイプミスがあると想定します。これは空の文字列では意味がなく、スペース文字を含む_(first_name || ' ' || last_name)_が本当に必要です。

どちらかの列がNULLになる可能性があると仮定すると、NULLセーフな連結が必要になります。簡単な解決策はconcat_ws()です。

ただし、その関数はIMMUTABLE(リンクされた回答の説明)ではないため、インデックス式で直接使用することはできません。 IMMUTABLE関数ラッパーを使用できます。

_CREATE OR REPLACE FUNCTION f_immutable_concat_ws(s text, t1 text, t2 text)
  RETURNS text AS
$func$
SELECT concat_ws(s, t1, t2)
$func$ LANGUAGE sql IMMUTABLE;
_

ラッパーはIMMUTABLEパラメーターのみを取るため、textにすることができます。
どちらにしても、これはより冗長ですが、内部オーバーヘッドが少なく、かなり高速です。

_CREATE OR REPLACE FUNCTION f_immutable_concat_ws1(s text, t1 text, t2 text)
  RETURNS text AS
$func$
SELECT CASE
         WHEN t1 IS NULL THEN t2
         WHEN t2 IS NULL THEN t1
         ELSE t1 || s || t2
       END
$func$ LANGUAGE sql IMMUTABLE;
_

または、ハードコードされたスペース文字を使用します。

_CREATE OR REPLACE FUNCTION f_concat_space(t1 text, t2 text)
  RETURNS text AS
$func$
SELECT CASE
         WHEN t1 IS NULL THEN t2
         WHEN t2 IS NULL THEN t1
         ELSE t1 || ' ' || t2
       END
$func$ LANGUAGE sql IMMUTABLE;
_

この機能に基づいて索引を作成することをお勧めします。

_CREATE INDEX people_special_gin_trgm_idx ON people
USING gin (f_concat_space(first_name, last_name) gin_trgm_ops, email gin_trgm_ops);
_

いくつかの考慮事項のために、2番目のインデックス列としてemailを追加しました。

インデックスの作成には、2,000万行の処理に時間がかかりますが、トップロード時ではなく、_CREATE INDEX CONCURRENTLY ..._を使用するのが最適です。 GINインデックスは、プレーンなbtreeインデックスよりもかなり大きく、維持にコストがかかります。必ず最新バージョンのPostgresを実行してください。最近のバージョンでは、GINインデックスに大きな改良が加えられています。

次に、少し調整して簡略化したクエリを高速かつ正確にする必要があります

_SELECT *
FROM   people
WHERE  f_concat_space(first_name, last_name) ILIKE '%bat%man%' OR
       email ILIKE '%bat%man%'
LIMIT  25;
_

このクエリには、oneインデックスのみが必要です。

パターンマッチングの基本:

13