ショッピンサイトなどを開発していると中間一致で検索したくなりますよね。
でも、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するには
のいずれかが必要です。