KEMBAR78
JSONB型でpostgresをNoSQLっぽく使う | PDF
JSONB型でPostgres
をNoSQLっぽく使う
@tacke_jp
Mixi, Inc.
Nohana, Inc.
2015.10.16 第10回若手Webエンジニア交流会 LT
$ whoami
• @tacke_jp
• 株式会社ミクシィ / 株式会社ノハナ
• サーバーサイドとかネイティブアプリとかデータ
解析とか色々やってる器用貧乏エンジニア
• 最近はPostgreSQLで変態クエリを書くことに

ハマっています
(AD)We re Hiring!!
• 株式会社ノハナでは「家族の問題を解決
する」というミッションに共感して一緒
に働いてくれる仲間を募集しています
• 現在は、日本中のお子さんを持つご家庭
で、スマホに撮りためた子供の写真を形
に残すことのお手伝いをしています
• ご興味があればフリートーク時にお声が
けください!!
家族を笑顔にしまくってくれるスマホアプリエンジニアさんに会いたい!
https://www.wantedly.com/projects/25788
Postgres Meets Schemaless
スキーマレスのメリット
• マイグレーションなしに
フィールドを追加できる
• 入れ子オブジェクトのよう
な木構造のデータが扱える
RDBのメリット
• 枯れてる
• トランザクションが使える
• JOINが使える
• 豊富な関数群
Postgres※はJSONを値としてカラムにいれるようにすることで、
両者のいいとこ取りをした
(※ JSON型はPostgres9.3以降、JSONB型は9.4以降でサポート)
JSONファイルのimport
Line-delimited JSON file
// posts.json
{“id”:1,"tags":["book","haskell"],"title":"すごいH本読ん
だ","created_at":"2015-08-16T14:10:02Z"}⏎
{"id":2,"tags":["docker"],"title":"Dockerがどっかーにいっ
た件","options":{"private":true},"created_at":"Wed Oct
14 13:50:09 +0000 2015"}⏎
{"id":3,"tags":["lisp","scheme"],"title":"Schemeかわいい
よScheme","created_at":"2015-10-16T14:10:02+09"}⏎
COPY文でimport
create table post_json (
obj jsonb
);
-- JSONB型のカラムのみを持つテーブルを用意
copy post_json
from '/path/to/json/file/posts.json'
with csv quote e'x01' delimiter e'x02'
;
(http://adpgtech.blogspot.jp/2014/09/importing-json-data.html)
Bulk Insert
create table comment_json (
obj jsonb
);
insert into comment_json
values('{"id":1,"post_id":1,"comment":"わかる"}')
,('{"id":2,"post_id":2,"comment":"ウケる"}')
,('{"id":3,"post_id":3,"comment":"神"}')
;
JSONB型カラムへのクエリ
オブジェクトの中身を参照
select (obj->>'id')::integer as id
, obj->>'title' as title
, obj->>'created_at' as created_at
, obj->'tags'->>0 as first_tag
from post_json
;
-- 演算子 -> , ->> でオブジェクトや配列の中身
にアクセス
オブジェクトの中身を参照
id | title | created_at | first_tag
----+----------------------------+--------------------------------+-----------
1 | すごいH本読んだ | 2015-08-16T14:10:02Z | book
2 | Dockerがどっかーにいった件 | Wed Oct 14 13:50:09 +0000 2015 | docker
3 | SchemeかわいいよScheme | 2015-10-16T14:10:02+09 | lisp
(3 rows)
JOIN
select post_json.obj->>'title'
, comment_json.obj->>'comment'
from post_json
, comment_json
where true
and post_json.obj->>’id'
= comment_json.obj->>'post_id'
;
JOIN
?column? | ?column?
----------------------------+----------
すごいH本読んだ | わかる
Dockerがどっかーにいった件 | ウケる
SchemeかわいいよScheme | 神
(3 rows)
B-Tree Indexをそのまま張るのはNG
create index post_json_obj
on post_json using btree (obj);
create index comment_json_obj
on comment_json using btree (obj);
-- obj全体を = で比較する場合しかindexが使われない
式Index
create index post_json_id
on post_json using btree ((obj->>'id'));
create index comment_json_post_id
on comment_json using btree ((obj->>'post_id'));
Key名が決まらないならGIN※を
使おう
create index post_json_gin
on post_json using gin (obj jsonb_path_ops);
create index post_json_tags_gin
on post_json using gin (obj->'tags');
-- 配列に対するindexも貼れる
(※ GIN = Generalized Inverted Index)
その他
JSON配列に対する集約関数
select obj->>'id'
, ( select sum(length(value))
from jsonb_array_elements_text(obj->’tags')
)
from post_json
;
-- PostgresではSELECT式でサブクエリが使える
JSON配列に対する集約関数
?column? | sum
----------+-----
1 | 11
2 | 6
3 | 10
(3 rows)
SchemafulなテーブルへのINSERT
create table post (
id integer
, tags jsonb -- flatに出来ないものはjsonbに突っ込んじゃう
, title text
, created_at timestamptz
);
insert into post
select (jsonb_populate_record(null::post, obj)).*
from post_json
;
select *
from post;
SchemafulなテーブルへのINSERT
id | tags | title | created_at
----+---------------------+----------------------------+------------------------
1 | ["book", "haskell"] | すごいH本読んだ | 2015-08-16 23:10:02+09
2 | ["infra", "docker"] | Dockerがどっかーにいった件 | 2015-10-14 22:50:09+09
3 | ["lisp1", "scheme"] | SchemeかわいいよScheme | 2015-10-16 14:10:02+09
(3 rows)
PostgresのJSONB型まとめ
• JSONは1つの値として扱われる (1カラム = 1つのJSON)
• B-tree IndexやGINが貼れる
• jsonb_array_elements(_text) や jsonb_populate_record(set) は

リレーショナルな世界とJSONな世界との橋渡し役
• (ちなみに)9.5ではオブジェクトや配列に要素を追加したり削除
したりできるようになる
(参考: http://www.slideshare.net/hadoopxnttdata/postgresql-95-new-features-nttdata )
最後におまけ
こんなJSONがありまして
{"car":"T","cdr": {"car":"h","cdr": {"car":"a","cdr":
{"car":"n","cdr": {"car":"k","cdr": {"car":" ","cdr":
{"car":"y","cdr": {"car":"o","cdr": {"car":"u","cdr":
{"car":" ","cdr": {"car":"f","cdr": {"car":"o","cdr":
{"car":"r","cdr": {"car":" ","cdr": {"car":"l","cdr":
{"car":"i","cdr": {"car":"s","cdr": {"car":"t","cdr":
{"car":"e","cdr": {"car":"n","cdr": {"car":"i","cdr":
{"car":"n","cdr": {"car":"g","cdr": {"car":"!","cdr":
{"car":"!","cdr":{} }
}}} }}} }}} }}} }}} }}} }}} }}}
こんなクエリを実行すると
with recursive eval as (
select obj->>'car' as car
, obj->'cdr' as cdr
, 1 as depth
from lisp
union all
select cdr->>'car' as car
, cdr->'cdr' as cdr
, depth + 1 as depth
from eval
where (cdr->'cdr') is not null
)
select string_agg(car, '' order by depth) as msg
from eval
;
(https://gist.github.com/yuki-takeichi/c4812f7af46f7f84e958)
ありがとうございました!
msg
---------------------------
Thank you for listening!!
(1 row)

JSONB型でpostgresをNoSQLっぽく使う