2017-03-09 10 views
0

を私たちは、複数の文字列フィールドを持つレコードを含むミュージシャンテーブルを持っている、と言う:Postgresが検索インデックスとパフォーマンスLIKE逆

  • 「ジミ」、「ヘンドリックス」、「ギター」
  • 「フィル」、「コリンズは、 」、 "ドラム"
  • "スティング"、 ""、 "低音"
  • "リンゴ"、 "スター"、 "ドラム"
  • "ポール"、 "マッカートニー"、 "低音"
私が渡したい

は、長い文字列をPostgresは言う:

「ジミが彼のギターに光を設定し、ステージ上でながら すべてのドラムを粉砕するのが好きことが知られています。」

と私はすべての試合を持ってフィールドを返さ取得したい - 最初に最もマッチのために好ましい:

  • ジミ」、「ヘンドリックス」、「ギター
  • "フィル"、 "コリンズ"、 "ドラム"
  • "リンゴ"、 "スター"、 "ドラム"

私は...このようなクエリを構築する

select * from musicians where lowercase_string like '%'||firstname||'%' or lowercase_string like '%'||lastname||'%' or lowercase_string like '%'||instrument||'%' 

し、その後で結果をキャプチャするために(私の場合はルビーで)をループするよ、大文字小文字を区別しないように検索を必要とするのでほとんどのマッチ。

しかし、これはsqlステージ(1分+)では非常に遅いです。

私は小文字のGINインデックスを、hereのようにpg_trgmを使って追加しようとしましたが、類似のクエリが先頭に戻ってきていると思われます。

ありがとうございます!

+1

「長い文字列」に「ドラム」が含まれていて、「ドラム」の代わりに含まれている場合はどうなりますか? [全文検索](https://www.postgresql.org/docs/current/static/textsearch.html)は受け入れられますか? ([類似していますが、設定可能なランク付けシステム](https://www.postgresql.org/docs/current/static/textsearch-controls.html#TEXTSEARCH-RANKING)があります)。 – pozs

+0

私はそれがうまくいくと思います - 偽陽性は後でフィルターにかけることができます - しかし、 "テキスト"がポストグレスの外にあるとき、全文検索はどのように動作しますか?多分私はそれがどのように機能するか誤解していますか? – user1051849

+0

* postgres *の外で何を意味しますか?あなたの例では、 'lowercase_string'はpostgresの" inside "のようです:) - 明らかに、クエリパラメータ(クライアントに依存します)としてバインドするだけです。プリペアドステートメントでは、ほとんどのDBALでは 'ORDER BY ts_rank(to_tsvector(?)、plainto_tsquery(firstname)|| plainto_tsquery(lastname)|| ...) 'のように見えます。 – pozs

答えて

2

私のテストでは、は、すべてのであなたの質問に役立つかもしれないと思われます。他の索引タイプは、(I)LIKE/FTSベースの検索をスピードアップする可能性があります。

私は、クエリのすべては、以下の、彼らは照会されたときに「逆転」トライグラムインデックスを使用することを言及すべきである:表が(インデックス付けされて)文書が含まれている場合、そしてあなたのパラメータがクエリです。 (I)LIKE変種f.ex. 2〜3倍速くなります。

まず
select * 
from musicians 
where :input_string ilike '%' || firstname || '%' 
or  :input_string ilike '%' || lastname || '%' 
or  :input_string ilike '%' || instrument || '%' 

は、FTSは素晴らしいアイデアに見えたが、私のテストでもランク付けずに、それはLIKE(I)よりも60〜100倍遅いことを示しています。

これらのクエリは、私がテストしてみましたバリアント。 (したがって、これらのメソッドで結果を後処理する必要がない場合でも、これらは価値がありません)。

select * 
from musicians 
where to_tsvector(:input_string) @@ (plainto_tsquery(firstname) || plainto_tsquery(lastname) || plainto_tsquery(lastname)) 

はしかし、 ORDER BY rankはそれほどさらに遅くなることはありません:それは変LIKE(I)よりも70-120倍遅いです。 <%%>(PostgreSQLの9.6から入手可能):

select * 
from  musicians 
where to_tsvector(:input_string) @@ (plainto_tsquery(firstname) || plainto_tsquery(lastname) || plainto_tsquery(lastname)) 
order by ts_rank(to_tsvector(:input_string), plainto_tsquery(firstname) || plainto_tsquery(lastname) || plainto_tsquery(lastname)) 

はその後、最後の努力のために、私は(かなり新しい)「という単語の類似度」トライグラムモジュールの演算子を試してみました。

select * 
from musicians 
where :input_string %> firstname 
or  :input_string %> lastname 
or  :input_string %> instrument 

select * 
from musicians 
where firstname <% :input_string 
or  lastname <% :input_string 
or  instrument <% :input_string 

これらは、(I)LIKE変異体よりも約50~70倍遅いFTSよりもやや速かった。

(一部作業)rextester:それはPostgreSQLの9.5に対して実行されるので、9.6演算子は、明らかに、ここで実行されません。

更新フルワードマッチはあなたのための十分なされている場合は、実際にインデックスを使用できるようにするには、クエリを逆にすることができます。あなたは、クエリ「解析」する必要がありますけれども(別名「長い文字列を」。):

with long_string(ls) as (
    values (:input_string) 
), 
words(word) as (
    select s 
    from long_string, regexp_split_to_table(ls, '[^[:alnum:]]+') s 
    where s <> '' 
) 
select musicians.* 
from  musicians, words 
where firstname ilike word 
or  lastname ilike word 
or  instrument ilike word 
group by musicians.id 

注:私はすべての完全な単語のためのクエリを解析されました。他にもいくつかのロジックを持たせることも、クライアント側で解析することもできます。それは(I)LIKE(ここでは、完全な単語の一致を探しているとして、我々は、とにかくそれらを必要としません)とトライグラムインデックスよりもはるかに高速であるよう

デフォルト、btree指数は、ここに輝く:

with long_string(ls) as (
    values (:input_string) 
), 
words(word) as (
    select s 
    from long_string, regexp_split_to_table(lower(ls), '[^[:alnum:]]+') s 
    where s <> '' 
) 
select musicians.* 
from  musicians, words 
where lower(firstname) = word 
or  lower(lastname) = word 
or  lower(instrument) = word 
group by musicians.id 

http://rextester.com/PSABJ6745

あなたもマッチ順で

sum((lower(firstname) = word)::int 
    + (lower(lastname) = word)::int 
    + (lower(instrument) = word)::int) 
+0

ありがとう@pozs - 私の実験は同じ結論に達した:インデックスはここで私を助けることはできませんとLIKEが遅いオプションの中で最速です... – user1051849

+0

@ user1051849単語があなたに十分に一致する場合は、それ自身を照会して(f.ex.を空白で単語に分割)、次に「firstname」ilike「It」またはlastname ilike「is''のように単語ごとに一致させようとします。または、この時点で、フィルタリングのためにデフォルトの 'btree'インデックスと等価演算子' = 'を使用することができます。 – pozs

2

ilikeオプションのようなものでマッチカウントを得ることができますing:

with long_string (ls) as (values 
    ('It is known that Jimi liked to set light to his guitar and smash up all the drums while on stage.') 
) 
select musicians.*, matches 
from 
    musicians 
    cross join 
    long_string 
    cross join lateral 
    (select 
     (ls ilike format ('%%%s%%', first_name) and first_name != '')::int + 
     (ls ilike format ('%%%s%%', last_name) and last_name != '')::int + 
     (ls ilike format ('%%%s%%', instrument) and instrument != '')::int 
     as matches 
    ) m 
where matches > 0 
order by matches desc 
; 
first_name | last_name | instrument | matches 
------------+-----------+------------+--------- 
Jimi  | Hendrix | Guitar  |  2 
Phil  | Collins | Drums  |  1 
Ringo  | Starr  | Drums  |  1 
+0

+1、 'first_name!= '''部分は良いキャッチであり、合計で、後処理コストを削減するための素晴らしいアイデアです。ただし、クエリ自体のコストを削減することはできません。 – pozs