web-dev-qa-db-ja.com

多くの列でのPostgreSQL全文検索

指定した文字列に基づいてレコードを検索する際のアドバイスが必要です。

検索文字列には、これらの列の値を含めることができます。この文字列の値は、正しい順序で指定された厳密に同一である必要はありません。また、この文字列の一部の列の値が欠落している場合があります。

検索文字列の例:

22 Karntner Wien

そして、例えば、上位5件の類似レコードの結果を取得します。

全文検索を使うべきだと思いますが、今までに使ったことがありません。進め方を教えていただけますか?

6
Denis Stephanov

クエリとインデックスにこの式をお勧めします。

_SELECT * FROM tbl
WHERE  to_tsvector('simple', f_concat_ws(' ', country, city, street, house_nr, postcode))
    @@ plainto_tsquery('simple', '22 Kärntner Wien');
_

上記のカスタム関数f_concat_ws()に注意してください。これは、concat_ws()STABLEではなくIMMUTABLEであるためです。作成する必要があります最初

_CREATE OR REPLACE FUNCTION f_concat_ws(text, VARIADIC text[])
  RETURNS text LANGUAGE sql IMMUTABLE AS 'SELECT array_to_string($2, $1)';
_

concat_ws() のドロップイン置換として使用できますが、実際のテキストデータのみを入力として受け入れる(これにより、不正を行わずにIMMUTABLEにすることができます。 、効果的に)。詳細な説明(読んでください!):

VARIADICについて:

多くの列の場合、これは短くて高速です。あなたはcouldなしで実行しますが、構文はかなり冗長になります(- joanoloの回答 を参照)。

これと一致するインデックス:

_CREATE INDEX tbl_adr_fts_idx ON tbl USING GIN (
       to_tsvector('simple', f_concat_ws(' ', country, city, street, house_nr, postcode)));
_

国際住所データを扱っているので、notenglishテキスト検索構成を使用してください。ステミングはnamesにはほとんど意味がなく、サンプルデータのほとんどは、最初は英語でさえありません。代わりにsimple構成を使用してください。 2つのパラメーターを持つフォームが必要です-以下を参照してください。

文字列を連結し、より高価な関数to_tsvector()onceを呼び出します。 concat_ws()を使用して、可能なNULL値をエレガントに処理します。全体的に安く、また短くなります。

私がコメントしたように、全文検索ではファジーマッチングのサポートが制限されていますが、プレフィックスマッチングには見過ごされがちな機能があります。

したがって、それが'Kärntner'であるか'Kärnten'であるか、および'Straße'であるかどうか不明な場合は、' strasse 'または' Strabe '(バグのあるサンプルデータのように)が、2番目のWordが最初のWordに続くことがわかっている場合、次のことができます。

... @@ to_tsquery('simple', '22 & Kärnt:* <-> Stra:* & Wien')

_<->_はフレーズ検索演算子であり、Postgres9.6が必要です。

また、発音区別符号も無視したい場合('ä' <> 'a')、ミックスにunaccent()を追加します。個別のfunctionとして使用するか、または dictionaryとして追加できます。 をテキスト検索構成に追加します。最初に拡張機能をインストールする必要があります...

一般的なPostgresインストールでのパターンマッチングオプションの概要:

JoanoloはすでにFTSに関するいくつかの基本情報と詳細についてはマニュアルへのリンクを提供しています。

あなたのコメントに対処する

このインデックスを追加しようとしていますが、エラーが発生します。

_ERROR: functions in index expression must be marked IMMUTABLE
_

関数には2つのバリアントがありますto_tsvector()- "function overloading "を参照してください。 1つ目はtextのみを使用し、2つ目はregconfigtextを使用します。自分で見て:

_SELECT proname, provolatile, proargtypes[0]::regtype, proargtypes[1]::regtype
FROM   pg_proc
WHERE  proname = 'to_tsvector';
_

2番目のみがIMMUTABLEであり、インデックス式で直接使用できます。 'simple'上記の例では、テキスト検索構成(regconfig)です。

もっと重要なこと、私の見落とし:concat_ws()(私が最初のバージョンで持っていた)はSTABLEだけで、 IMMUTABLE。上記に必要な手順を追加しました。

関連:

7

これがあなたのテーブルといくつかのデータであると想像しましょう:

CREATE TABLE t
(
    country text,
    city text,
    street text,
    house_number text,
    post_code text
) ;

INSERT INTO t
VALUES
   ('Österreich', 'Vienna', 'HauptStrasse', '123', '12345'),   
   ('France', 'Paris', 'Rue du Midi', '12A', '01234'),   
   ('España', 'Barcelona', 'Passeig de Gràcia', '32', '08001'),   
   ('United Kingdom', 'London', 'Oxford Street', '20', 'W1D 1AS'),
   ('Nederland', 'Amsterdam', 'Leidsekruisstraat', '6-8', '1017 RH') ;

[注:それを確認してくださいhttp://rextester.com/DOJN8533]

PostgreSQLを使用して複数の列に対して全文検索を実行する方法(「english」がFTS構成の名前であると想定)は、次のようなクエリを使用することです。

SELECT
    *
FROM
    t
WHERE
    (
        to_tsvector('english', coalesce(country, ''))      || 
        to_tsvector('english', coalesce(city, ''))         || 
        to_tsvector('english', coalesce(street, ''))       || 
        to_tsvector('english', coalesce(house_number, '')) ||
        to_tsvector('english', coalesce(post_code, '')) 
    ) @@ plainto_tsquery('english', 'Amsterdam') ;

Where節の意味:

 (this tsvector = document) @@ /* matches */  (this tsquery = query)

tsvectorは、PostgreSQLがテキストについて変換されたデータ(たとえば、すべて小文字、カンマを除いて、単語を特定してリストするなど)を格納するために使用する特別なデータ型です。 tsqueryは、ドキュメントの特性を確認する方法です(たとえばこの_and_を含む)。

||演算子はtsvectorを結合します(「一緒に追加する」としましょう)。

物事をスピードアップしたい場合は、次のように定義された1つの機能インデックスが必要です。

CREATE INDEX ts_idx 
    ON t USING Gist ( 
    (
        to_tsvector('english', coalesce(country, '')) || 
        to_tsvector('english', coalesce(city, '')) || 
        to_tsvector('english', coalesce(street, '')) || 
        to_tsvector('english', coalesce(house_number, '')) ||
        to_tsvector('english', coalesce(post_code, ''))
    ) 
) ;

全文検索 に関するドキュメントを注意深くチェックする必要があります。可能性はたくさんあるので少々怖いですが、時間をかける価値はあります。

多数の場合に結果を整理するには、ts_rank関数を使用してORDER BYにしてから制限する必要があります。

3
joanolo