LIKE、SIMILAR TO、またはPostgreSQLの正規表現を使用したパターンマッチング


94

BまたはDで始まる人の名前を探す単純なクエリを作成する必要がありました。

SELECT s.name 
FROM spelers s 
WHERE s.name LIKE 'B%' OR s.name LIKE 'D%'
ORDER BY 1

パフォーマンスを向上させるためにこれを書き換える方法があるかどうか疑問に思っていました。だから私は避けることができますor/またはlike


なぜ書き直そうとしているのですか?パフォーマンス?清eat?されてs.nameインデックス化?
マーティンスミス

パフォーマンスのために書きたいのですが、s.nameにはインデックスが付けられていません。
ルーカスカウフマン

8
先行するワイルドカードを使用せずに検索し、追加の列を選択しないのでname、パフォーマンスを重視する場合、ここでインデックスを使用すると便利です。
マーティンスミス

回答:


161

クエリはほぼ最適です。構文はそれほど短くなりませんし、クエリはそれほど速くなりません:

SELECT name
FROM   spelers
WHERE  name LIKE 'B%' OR name LIKE 'D%'
ORDER  BY 1;

本当に構文短縮したい場合は、ブランチを含む正規表現を使用します

...
WHERE  name ~ '^(B|D).*'

または、文字クラスを使用してわずかに高速化:

...
WHERE  name ~ '^[BD].*'

インデックスなしのクイックテストはSIMILAR TO、どちらの場合よりも高速な結果をもたらします。
適切なBツリーインデックスが適切に配置されていれば、LIKEこのレースで桁違いに勝ちます。

マニュアルのパターンマッチングの基本を読んでください。

優れたパフォーマンスのためのインデックス

パフォーマンスに関心がある場合は、大きなテーブル用に次のようなインデックスを作成します。

CREATE INDEX spelers_name_special_idx ON spelers (name text_pattern_ops);

この種のクエリを桁違いに高速化します。ロケール固有のソート順には特別な考慮事項が適用されます。マニュアルの演算子クラスの詳細をお読みください。標準の "C"ロケールを使用している場合(ほとんどの人はそうではありません)、プレーンインデックス(既定の演算子クラスを使用)でできます。

このようなインデックスは、左アンカーパターン(文字列の先頭からの一致)にのみ適しています。

SIMILAR TOまたは、基本的な左アンカー式の正規表現もこのインデックスを使用できます。ただし、ブランチまたは文字クラスは使用ません(少なくともPostgreSQL 9.0のテストでは)。(B|D)[BD]

Trigramの一致またはテキスト検索では、特別なGINまたはGiSTインデックスが使用されます。

パターンマッチング演算子の概要

  • LIKE~~)はシンプルで高速ですが、機能に制限があります。
    ILIKE~~*)大文字と小文字を区別しないバリアント。
    pg_trgmは両方のインデックスサポートを拡張します。

  • ~ (正規表現の一致)は強力ですが、より複雑であり、基本的な表現以外のものでは遅くなる場合があります。

  • SIMILAR TOただで無意味。特異なハーフブリードLIKEと正規表現。私は決して使いません。下記参照。

  • は、追加モジュールによって提供される「類似性」演算子ですpg_trgm。下記参照。

  • @@テキスト検索演算子です。下記参照。

pg_trgm-トライグラムのマッチング

以降ではPostgreSQL 9.1あなたは、拡張を容易にすることができるpg_trgmインデックスのためのサポートを提供するためにあらゆる LIKE / ILIKEパターン(ととの簡単な正規表現パターンを~GINか、GiSTインデックスを使用して)。

詳細、例、リンク:

pg_trgmこれらの演算子も提供します:

  • % -「類似性」演算子
  • <%(交換子:%>)-Postgres 9.6以降の「word_similarity」演算子
  • <<%(交換子:%>>)-Postgres 11以降の「strict_word_similarity」演算子

テキスト検索

個別のインフラストラクチャおよびインデックスタイプとの特別なタイプのパターンマッチングです。辞書とステミングを使用し、特に自然言語の場合に文書内の単語を検索するための優れたツールです。

プレフィックスマッチングもサポートされています。

だけでなく、フレーズ検索のPostgres 9.6以降:

マニュアル概要と、演算子と機能の概要を検討してください。

ファジー文字列マッチングのための追加ツール

追加のモジュールfuzzystrmatchにはさらにいくつかのオプションがありますが、パフォーマンスは一般的に上記のすべてより劣っています。

特に、levenshtein()関数のさまざまな実装が役立つ場合があります。

なぜ正規表現(~)は常により速いのですSIMILAR TOか?

答えは簡単です。SIMILAR TO式は内部的に正規表現に書き換えられます。したがって、すべてのSIMILAR TO式には、少なくとも 1つの高速な正規表現があります(式を書き換えるオーバーヘッドを節約します)。SIMILAR TO これまでに使用してもパフォーマンスは向上しません。

とにかくLIKE~~)で実行できる簡単な式はより高速LIKEです。

SIMILAR TOSQL標準の初期のドラフトで終わったため、PostgreSQLでのみサポートされています。彼らはまだそれを取り払っていない。しかし、それを削除し、代わりに正規表現の一致を含める計画があります-またはそう聞いた。

EXPLAIN ANALYZEそれを明らかにします。自分でテーブルを試してみてください!

EXPLAIN ANALYZE SELECT * FROM spelers WHERE name SIMILAR TO 'B%';

明らかに:

...  
Seq Scan on spelers  (cost= ...  
  Filter: (name ~ '^(?:B.*)$'::text)

SIMILAR TO正規表現(~)で書き換えられました。

この特定の場合の究極のパフォーマンス

しかし、EXPLAIN ANALYZEより多くを明らかにする。上記のインデックスを設定して試してください:

EXPLAIN ANALYZE SELECT * FROM spelers WHERE name ~ '^B.*;

明らかに:

...
 ->  Bitmap Heap Scan on spelers  (cost= ...
       Filter: (name ~ '^B.*'::text)
        ->  Bitmap Index Scan on spelers_name_text_pattern_ops_idx (cost= ...
              Index Cond: ((prod ~>=~ 'B'::text) AND (prod ~<~ 'C'::text))

内部的には、ロケールに対応していないインデックス(とtext_pattern_opsや使用してロケールC:)シンプルな左アンカー表現は、これらのテキストパターン演算子を使用して書き換えられ~>=~~<=~~>~~<~。これは~~~などSIMILAR TOです。

またはを持つvarchar型のインデックスについても同様です。varchar_pattern_opscharbpchar_pattern_ops

したがって、元の質問に適用すると、これは可能な限り速い方法です:

SELECT name
FROM   spelers  
WHERE  name ~>=~ 'B' AND name ~<~ 'C'
    OR name ~>=~ 'D' AND name ~<~ 'E'
ORDER  BY 1;

もちろん、隣接するイニシャルを検索する必要がある場合は、さらに単純化できます。

WHERE  name ~>=~ 'B' AND name ~<~ 'D'   -- strings starting with B or C

~またはを単純に使用する場合の~~利点はわずかです。パフォーマンスが最重要要件ではない場合は、標準の演算子に固執する必要があります-既に質問にあるものに到達します。


OPには名前のインデックスはありませんが、もしあれば、元のクエリに2つの範囲シークとsimilarスキャンが含まれることを知っていますか?
マーティンスミス

2
@MartinSmith:EXPLAIN ANALYZE2つのビットマップインデックススキャンを示す簡単なテスト。複数のビットマップインデックススキャンをかなり迅速に組み合わせることができます。
アーウィンブランドステッター

ありがとう。だから、交換すると任意の走行距離があるだろうORUNION ALLか、交換するname LIKE 'B%'name >= 'B' AND name <'C'Postgresのでは?
マーティンスミス

1
@MartinSmith:ありUNIONませんが、はい、範囲を1つのWHERE句に結合するとクエリが高速化されます。回答にさらに追加しました。もちろん、ロケールを考慮する必要があります。ロケール対応の検索は常に低速です。
アーウィンブランドステッター

2
@a_horse_with_no_name:期待しない。GINインデックスを使用したpg_tgrmの新しい機能は、汎用テキスト検索の扱いです。開始時に固定された検索は、それよりもすでに高速です。
アーウィンブランドステッター

11

テーブルに列を追加する方法について。実際の要件に応じて:

person_name_start_with_B_or_D (Boolean)

person_name_start_with_char CHAR(1)

person_name_start_with VARCHAR(30)

PostgreSQLはSQL Serverの基本テーブルの計算列をサポートしていませんが、トリガーを介して新しい列を維持できます。明らかに、この新しい列にはインデックスが付けられます。

または、式のインデックスを使用すると、同じように安くなります。例えば:

CREATE INDEX spelers_name_initial_idx ON spelers (left(name, 1)); 

条件の式に一致するクエリは、このインデックスを利用できます。

この方法では、データが作成または修正されたときにパフォーマンスヒットが発生するため、アクティビティの少ない環境(つまり、読み取りより書き込みがはるかに少ない環境)にのみ適しています。


8

試すことができます

SELECT s.name
FROM   spelers s
WHERE  s.name SIMILAR TO '(B|D)%' 
ORDER  BY s.name

しかし、上記の式または元の式のいずれかがPostgresで検索可能かどうかはわかりません。

提案されたインデックスを作成する場合、これが他のオプションとどのように比較されるかについても興味があります。

SELECT name
FROM   spelers
WHERE  name >= 'B' AND name < 'C'
UNION ALL
SELECT name
FROM   spelers
WHERE  name >= 'D' AND name < 'E'
ORDER  BY name

1
それは機能し、1.25だった1.19のコストを得ました。ありがとう!
ルーカスカウフマン

2

同様のパフォーマンスの問題に直面して、私が過去に行ったことは、最後の文字のASCII文字を増やし、BETWEENを実行することです。これにより、LIKE機能のサブセットに対して最高のパフォーマンスが得られます。もちろん、特定の状況でのみ機能しますが、たとえば名前で検索している超大規模なデータセットでは、パフォーマンスがひどいものから許容できるものになります。


2

非常に古い質問ですが、この問題の別の迅速な解決策を見つけました。

SELECT s.name 
FROM spelers s 
WHERE ascii(s.name) in (ascii('B'),ascii('D'))
ORDER BY 1

関数ascii()は文字列の最初の文字のみを調べるため。


1
これはインデックスを使用します(name)か?
ypercubeᵀᴹ

2

イニシャルの確認には、"char"(二重引用符で)キャストすることがよくあります。ポータブルではありませんが、非常に高速です。内部的には、単純にテキストをデトーストし、最初の文字を返します。タイプが1バイト固定長であるため、「char」比較操作は非常に高速です。

SELECT s.name 
FROM spelers s 
WHERE s.name::"char" =ANY( ARRAY[ "char" 'B', 'D' ] )
ORDER BY 1

へのキャスト"char"ascii()@ Sole021によるスリューションよりも高速ですが、UTF8互換(またはその他のエンコード)ではなく、単に最初のバイトを返すため、比較がプレーン7と比較される場合にのみ使用してください。ビットASCII文字。


1

そのような場合に対処するための、まだ言及されていない2つの方法があります。

  1. 部分(またはパーティション-手動でフルレンジ用に作成された場合)インデックス-データのサブセットのみが必要な場合(たとえば、メンテナンス中またはレポートの一時的な場合)に最も便利です。

    CREATE INDEX ON spelers WHERE name LIKE 'B%'
  2. テーブル自体のパーティショニング(パーティショニングキーとして最初の文字を使用)-この手法は、PostgreSQL 10+(苦痛の少ないパーティショニング)および11+(クエリ実行中のパーティションプルーニング)で特に考慮する価値があります。

さらに、テーブル内のデータがソートされている場合、BRINインデックス(最初の文字)を使用することでメリットが得られます。


-4

おそらく、1文字の比較をより高速に実行します。

SUBSTR(s.name,1,1)='B' OR SUBSTR(s.name,1,1)='D'

1
あんまり。 column LIKE 'B%'列で部分文字列関数を使用するよりも効率的です。
ypercubeᵀᴹ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.