Day11 前半のゴール
「“JSONをそのままカラムに入れて、SQLで触れる”感覚を持つ」
今日からは、PostgreSQLの大きな武器のひとつ、JSONです。
SQLiteやMySQLでもJSONは扱えますが、PostgreSQLは「JSONをちゃんと“型”として扱える」レベルで強いです。
前半のゴールはこうです。
JSON型が「文字列ではなく、“中身を理解してくれる専用の型”」だと分かる。json と jsonb の違いを、実務でどう選ぶかのレベルで説明できる。
JSONカラムを持つテーブルを作るイメージが持てる。
ここではまず、「JSONをDBにどう置くか」「なぜPostgreSQLだと嬉しいのか」を丁寧に固めます。
JSON型とは何か
「“ただの文字列”ではなく、“構造を知っている箱”」
JSONそのものは、もう見慣れていると思います。
{
"name": "Taro",
"age": 25,
"tags": ["premium", "beta_user"],
"profile": {
"twitter": "@taro",
"location": "Tokyo"
}
}
これを、今まではアプリ側(JavaScriptやPythonなど)でパースして使っていたはずです。
DB側では、単なるTEXTとして保存していたかもしれません。
PostgreSQLのJSON型は、これを「ただの文字列」ではなく、「中身の構造を理解しているデータ」として扱えます。
JSONとして正しいかどうかをチェックしてくれる。
中のキーを指定して取り出したり、検索したりできる。
インデックスを張って、高速に検索できる(特にjsonb)。
つまり、「アプリだけで頑張っていたJSON処理の一部を、DB側に任せられる」ようになります。
json と jsonb の違い
「“そのまま保存”か、“検索しやすい形に変換して保存”か」
PostgreSQLには、JSON用の型が2つあります。
jsonjsonb
名前が似ていてややこしいですが、ざっくりこう覚えてください。
json
文字列としてのJSONを、そのままの形で保存する。
スペースやキーの順番も含めて、元のJSONを保持する。
jsonb(binary JSON)
JSONを内部的に“検索しやすいバイナリ形式”に変換して保存する。
キーの順番やスペースは保持しない(正規化される)。
インデックスを張ったり、検索したりするときに圧倒的に有利。
実務的には、ほぼ常に jsonb を選びます。
理由はシンプルで、「検索・更新・インデックスが強いから」です。
「元のJSON文字列の見た目(スペースやキー順)を絶対にそのまま残したい」という特殊な理由がない限り、jsonb を使う、というルールにしてしまって構いません。
JSONカラムを持つテーブルを作ってみる
「“柔軟な情報”を1カラムにまとめて持つ」
具体的なテーブルを1つ作ってみましょう。
ユーザーの基本情報は普通のカラムで持ちつつ、「追加情報」をJSONで持つイメージです。
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
extra JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
SQLここでのポイントを分解します。
extra JSONB
追加情報を入れるためのJSONBカラム。
何を入れるかはレコードごとに変えてOK。
DEFAULT '{}'::jsonb
デフォルト値として「空のオブジェクト」を入れている。
NULLではなく「空のJSON」として扱えるので、後で扱いやすい。
この extra に、例えばこんなJSONを入れられます。
ユーザーA:
{
"age": 25,
"tags": ["premium", "beta_user"],
"profile": {
"twitter": "@taro",
"location": "Tokyo"
}
}
ユーザーB:
{
"age": 30,
"tags": ["free"],
"settings": {
"newsletter": true
}
}
同じカラム extra に、違う構造のJSONを入れてもOKです。
ここが「スキーマが柔軟」というJSONの強みです。
なぜ“全部カラムにしないで”JSONにするのか
「変化しやすい情報・ユーザーごとにバラバラな情報」に向いている
「だったら最初から age や twitter もカラムにすればいいのでは?」と思うかもしれません。
それは半分正しくて、半分間違いです。
カラムにした方がいいもの
必ず全ユーザーが持っている。
アプリのロジックで頻繁に使う。
検索条件やJOINでよく使う。
JSONにした方がいいもの
ユーザーによって持っていたり持っていなかったりする。
仕様変更で増えたり減ったりしやすい。
「とりあえず柔軟に持っておきたい」情報。
例えば、「プロフィールのSNSアカウント」は、
最初はTwitterだけだったのが、後からInstagramやGitHubが増えるかもしれません。
最初から twitter, instagram, github というカラムを全部用意しておくと、
使わないカラムが大量にNULLになったり、仕様変更のたびにALTER TABLEが必要になったりします。
そういう「変化しやすい」「ユーザーごとにバラバラ」な情報を、extra JSONB にまとめて持つ、という設計がよく使われます。
PostgreSQLなら、そのJSONBに対しても「中身を見て検索する」ことができるので、
「柔軟さ」と「検索性」のバランスが取りやすいのが大きなメリットです。
JSONをINSERTしてみるイメージ
「アプリから渡しているJSONを、そのまま突っ込める」
実際にJSONをINSERTするSQLも見ておきましょう。
INSERT INTO users (email, name, extra)
VALUES (
'taro@example.com',
'Taro',
'{
"age": 25,
"tags": ["premium", "beta_user"],
"profile": {
"twitter": "@taro",
"location": "Tokyo"
}
}'::jsonb
);
SQLポイントは、JSON文字列を ::jsonb でキャストしているところです。
アプリ側からパラメータとして渡すときも、
「JSON文字列をそのまま渡して、DB側でjsonbとして受ける」という形になります。
ここでPostgreSQLは、「それが正しいJSONかどうか」をチェックしてくれます。
カンマの付け忘れや、ダブルクォートの閉じ忘れなどがあれば、INSERT時にエラーになります。
つまり、「JSONの構文チェックをDBに任せられる」ということです。
これは地味ですが、データの安全性という意味でかなり大きいポイントです。
JSON型を使うときの“最初の一歩の設計感覚”
「まずは“extra JSONB”を1カラム足してみる」
いきなり「全部JSONで持つ」ような設計に振る必要はありません。
むしろ、それはやりすぎです。
現実的な最初の一歩は、こうです。
基本的な情報(id, email, name, created_at など)は、普通のカラムで持つ。
「変化しやすい」「ユーザーごとにバラバラ」な情報だけ、extra JSONB に逃がす。
この「ハイブリッド構成」が、一番バランスが良いです。
Day11 前半では、「JSON型があるからといって、何でもかんでもJSONに突っ込むのではない」という感覚も、同時に持っておいてほしいところです。
PostgreSQLのJSONは強力ですが、「RDBのカラム設計」と「柔軟なJSON」を組み合わせてこそ真価を発揮します。
Day11 前半のまとめ
PostgreSQLのJSON型は、「JSONをただのTEXTとしてではなく、“中身の構造を理解している専用の型”として扱える」仕組みであり、構文チェックや中身の参照・検索・インデックスが可能になる。json は元の文字列の形(スペースやキー順)をそのまま保持するのに対し、jsonb は内部的に検索しやすいバイナリ形式に正規化して保存し、インデックスや検索に強いので、実務ではほぼ jsonb を選ぶ。
ユーザーの基本情報は通常のカラムで持ちつつ、「変化しやすくユーザーごとにバラバラな追加情報」を extra JSONB のようなカラムにまとめて持つ設計にすることで、RDBの堅さとJSONの柔軟さを両立できる――Day11 前半では、この「JSONをカラムとして持つ」「jsonbを基本にする」「柔軟な情報だけJSONに逃がす」という3つの感覚を押さえるところまでをゴールにする。
