Help us understand the problem. What is going on with this article?

JSON/JSONBと全文検索

More than 1 year has passed since last update.

はじめに

にゃーん
この記事は、PostgreSQL 10全部ぬこ Advent Calendar 2017 の12日目のエントリです。

JSON/JSONBとは

JSONを知らない人はたぶんいないとは思うけど、PostgreSQLではビルトインのデータタイプとして、JSON型をPostgreSQL 9.2からサポートしていた。
また、PostgreSQL 9.4からは検索時の効率を良くするバイナリ形式で格納する、JSONB型がサポートされた。

バージョン 主な追加機能
9.2 JSON型を導入。格納時のパース程度しかやってない。
9.3 JSON型に対する関数・演算子の大幅な追加。
9.4 バイナリJSON型(JSONB型)の追加。
9.5 jsonb_set(), jsonb_pretty()の追加。
9.6 jsonb_insert()、hstoreからの変換
10 全文検索対応、"-"演算子など

PostgreSQL全文検索

PostgreSQLで扱える全文検索機能として、現状以下のようなものがある。

名称 分類 検索方式 日本語対応 メモ
textsearch 本体機能 形態素 PostgreSQL本体機能
pg_trgm contrib N-gram 2文字以下の語で検索すると効率悪い
pg_bigm 外部製品 N-gram 2文字以下の語の検索に強い
textsearch_ja 外部製品 形態素 Mecab依存。現状、公式のメンテナがいない?
pgroonga 外部製品 形態素,N-gram 高機能・高速が売り。groongaのインストールが必要。

今回は、一番上の本体機能のtextsearchとJSON/JSONBの組合せで検証した。

検証モデル

こんなJSONデータを用意しておく。

{"name":"Work, Melos!", "authors":["Osamu Dasai"], "price":4.80}
{"name":"What's your name?", "authors":["Makoto Shinkai"], "price":6.00}
{"name":"PostgreSQL Administration is tired", "authors":["nuko_yokohama", "kingtomo", "masa_sae"], "price":34.00}
{"name":"Interview with Authors!", "authors":["hogehoge editors"], "price":8.3}
{"name":"PostgreSQL is wonderful", "authors":["nuko_yokohama"], "price":18.5}

で、以下の3つのテーブルに対して上記データをCOPY文でロードする。

ts=# \d books*
             Table "public.books_j"
 Column | Type | Collation | Nullable | Default
--------+------+-----------+----------+---------
 data   | json |           |          |

             Table "public.books_jb"
 Column | Type  | Collation | Nullable | Default
--------+-------+-----------+----------+---------
 data   | jsonb |           |          |

             Table "public.books_t"
 Column | Type | Collation | Nullable | Default
--------+------+-----------+----------+---------
 data   | text |           |          |

name を含むデータを全文検索する

books_t(テキスト型)、books_j(JSON型)、books_jb(JSONB型)のテーブルに対して、以下のようなクエリをそれぞれ実行してみる。

SELECT data FROM  <テーブル名> WHERE to_tsvector(data) @@ to_tsquery('name');
  • to_tscevtor()は全文検索するために文書を前処理しておく関数。実は単体でも実行可能(後述)
  • to_tsquery()は検索用の語を全文検索の問い合わせに変換する関数。

text型への検索

ts=# SELECT data FROM  books_t WHERE to_tsvector(data) @@ to_tsquery('name');
                                                       data
-------------------------------------------------------------------------------------------------------------------
 {"name":"Work, Melos!", "authors":["Osamu Dasai"], "price":4.80}
 {"name":"What's your name?", "authors":["Makoto Shinkai"], "price":6.00}
 {"name":"PostgreSQL Administration is tired", "authors":["nuko_yokohama", "kingtomo", "masa_sae"], "price":34.00}
 {"name":"Interview with Authors!", "authors":["hogehoge editors"], "price":8.3}
 {"name":"PostgreSQL is wonderful", "authors":["nuko_yokohama"], "price":18.5}
(5 rows)

text型の場合、JSONのキーと値を区別するわけではないので、"name"という名称のキーも、""What's your name?"という値に入っている"name"も区別なくヒットしてしまう。
"name"などのような汎用的な語がキーに使われている場合、検索のノイズとなってしまい些か具合が悪い。

json型への検索

ts=# SELECT data FROM  books_t WHERE to_tsvector(data) @@ to_tsquery('name');
                                                       data
-------------------------------------------------------------------------------------------------------------------
ts=# SELECT data FROM  books_j WHERE to_tsvector(data) @@ to_tsquery('name');
                                   data
--------------------------------------------------------------------------
 {"name":"What's your name?", "authors":["Makoto Shinkai"], "price":6.00}
(1 row)

jsonb型への検索

ts=# SELECT data FROM  books_jb WHERE to_tsvector(data) @@ to_tsquery('name');
                                    data
-----------------------------------------------------------------------------
 {"name": "What's your name?", "price": 6.00, "authors": ["Makoto Shinkai"]}
(1 row)

json/jsonb型の場合には、"name"というキーは検索対象とはならず、値に格納されている"name"のみがヒットする。
文章がJSON文書内に格納されている場合には、大変ありがたい機能だと言える。

textsearch_jaでもやってみた

最初のほうでtextsearch_jaは公式のメンテナがいないと書いたが、実は自分の環境にはtextsearch_jaのソースが残っているので、PostgreSQLがバージョンアップたびに、textseach_jaが動作するのかを確認していたりする。
一応、PostgreSQL 10でもそれなりに動作するっぽいので、json/jsonbとの連携を確認してみた。

こんなデータを用意して、さっきのテーブル(books_t, books_j)にロードしておく。

{"名前":"働けメロス", "著者":["ださいおさむ"], "価格":480}
{"名前":"貴様の名前は", "著者":["深海真言"], "価格":680}
{"名前":"PostgreSQLの運用管理は面倒だね", "著者":["王之友", "ぬこ@横浜","マサ冴木"], "価格":3400}
{"名前":"著者に聞いてみました!", "著者":["ほげほげ編集部"], "価格":800}
{"名前":"PostgreSQLはいいぞ", "著者":["ぬこ@横浜"], "価格":1800}

これを以下のようなクエリで検索する。

SELECT data FROM  <テーブル名> WHERE to_tsvector('japanese', data) @@ to_tsquery('japanese', '名前');

さっきとの違いは、to_tsvector()とto_tsquery()の第一引数に日本語での検索を示す"japanese"が追加されていること。この指定はtextsearch_jaをインストールすることで利用可能になる。

text型への検索

ts=# SELECT data FROM  books_t WHERE to_tsvector('japanese', data) @@ to_tsquery('japanese', '名前');
                                                data
----------------------------------------------------------------------------------------------------
 {"名前":"働けメロス", "著者":["ださいおさむ"], "価格":480}
 {"名前":"貴様の名前は", "著者":["深海真言"], "価格":680}
 {"名前":"PostgreSQLの運用管理は面倒だね", "著者":["王之友", "ぬこ@横浜","マサ冴木"], "価格":3400}
 {"名前":"著者に聞いてみました!", "著者":["ほげほげ編集部"], "価格":800}
 {"名前":"PostgreSQLはいいぞ", "著者":["ぬこ@横浜"], "価格":1800}
(5 rows)

json型への検索

ts=# SELECT data FROM  books_j WHERE to_tsvector('japanese', data) @@ to_tsquery('japanese', '名前');
                           data
----------------------------------------------------------
 {"名前":"貴様の名前は", "著者":["深海真言"], "価格":680}
(1 row)

英語文書での検証と同様に、"名前"というキーは無視され、値内の"名前"のみがヒットするようになっている。

おわりに

  • PostgreSQL 10からはJSON/JSONB文書内の値のみが検索対象となる。
  • とりあえず、textsearch_jaでもJSON/JSONB連携はできそう。

参考:該当するリリースノート

本エントリに関連するPostgreSQL 10リリースノートの記載です。

E.2.3.5. Data Types

  • AAdd full text search support for JSON and JSONB (Dmitry Dolgov)
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした