Posted at

Rails + PostgreSQLでIndexを利用した中間一致検索する

More than 1 year has passed since last update.

ショッピンサイトなどを開発していると中間一致で検索したくなりますよね。

でも、Elasticsearch入れるほどでも無いし...という場合の簡単な方法を紹介します。

試した環境は


  • Ubuntu 16.04

  • Ruby 2.3.3

  • Rails 5.0.0

  • PostgreSQL 9.5.5 (apt-get install)

です。


pg_trgmの有効化

まずはサクッとpg_trgmを有効にしましょう。PostgreSQLに接続するユーザはSUPERUSER権限を持つ必要があります。

なければ、

psql -U postgres -c 'ALTER USER USER_FROM_RAILS WITH SUPERUSER'

こんな感じで権限与えます。

Railsでpg_trgmを有効にするにはrails g migration EnablePgTrgmExtensionして、


db/migrate/yyyyMMddhhmmss_enable_pg_trgm_extension.rb

class Hoge < ActiveRecord::Migration[5.0]

def change
enable_extension 'pg_trgm'
end
end

からの、rake db:migrateください。


インデックス作成

これもrails g migration AddIndexProductsOnNameして、


db/migrate/yyyyMMddhhmmss_add_index_to_product_on_name.rb

class AddIndexProductsOnName < ActiveRecord::Migration[5.0]

def up
add_index :products,
'name gist_trgm_ops',
using: :gist,
name: :index_products_on_name
end

def down
remove_index :products,
name: :index_products_on_name
end
end


からの、rake db:migrateください。


中間一致検索してみる

Likeで検索するだけです。サンプルでは小文字大文字無視したいのでiLikeで検索します。rails cで試してみて下さい。

Product.where("name ilike ?", "%silk%")

like検索なんだからあたりまえ?そうですね、index使っているか確認しましょう。

Product.where("name ilike ?", "%silk%").explain

Product Load (0.5ms) SELECT "products".* FROM "products" WHERE (name ilike '%silk%')
=> EXPLAIN for: SELECT "products".* FROM "products" WHERE (name ilike '%silk%')
QUERY PLAN
------------------------------------------------------------------------------------------
Bitmap Heap Scan on products (cost=4.17..12.63 rows=4 width=152)
Recheck Cond: ((name)::text ~~* '%silk%'::text)
-> Bitmap Index Scan on index_products_on_name (cost=0.00..4.17 rows=4 width=0)
Index Cond: ((name)::text ~~* '%silk%'::text)
(4 rows)

Bitmap Index ScanなのでIndex検索できてますね!


注意

簡単にできそうと紹介してましたが、pg_trgmで英数字以外をIndexするには

のいずれかが必要です。