「PostgreSQLでJSONBというデータ型を見たけど、何に使うの?」
「JSONとJSONBって何が違うの?」
「どうやって使えばいいのか分からない」
そんな疑問を持っていませんか?
JSONBは、PostgreSQLで柔軟なデータを効率的に扱うための強力なデータ型です。商品カタログ、ユーザー設定、ログデータなど、構造が頻繁に変わるデータを管理するのに最適なんです。
この記事では、JSONBの基本から実践的な使い方まで、初心者の方にも分かりやすく説明します。
この記事で分かること:
- JSONBの基本的な概念
- JSONとJSONBの違い
- JSONBを使うメリット・デメリット
- テーブル作成とデータ挿入の方法
- データの検索方法と便利な演算子
- インデックスの作成方法
- 実践的な使用例
それでは、順番に見ていきましょう!
JSONBとは?基本を理解しよう

JSONBの定義
JSONB(JSON Binary)は、PostgreSQLデータベースで使われるデータ型の一つです。
簡単に言うと:
- JSONデータをバイナリ形式で保存する
- 検索や処理が非常に高速
- 柔軟なデータ構造を扱える
JSONBはこんな時に使う
例1:ECサイトの商品データ
商品によって属性が異なる場合:
本:著者、ページ数、出版社
電化製品:メーカー、色、サイズ、重さ
服:ブランド、色、サイズ、素材
これらを別々のカラムで管理するのは大変ですが、JSONBなら柔軟に対応できます。
例2:ユーザー設定
ユーザーごとに異なる設定を保存する場合:
ユーザーA:テーマ色、通知設定、言語
ユーザーB:テーマ色、通知設定、言語、タイムゾーン、フォントサイズ
新しい設定項目を追加しても、テーブル構造を変更する必要がありません。
例3:ログデータや監査記録
様々な形式のログを一つのテーブルで管理できます。
JSONとJSONBの違い
PostgreSQLには、JSON型とJSONB型の2つがあります。
JSON型の特徴
データの保存方法:
- テキスト形式でそのまま保存
- 入力されたJSON文字列を完全に保持
保存される内容:
- 空白やスペースも保持
- キーの順序も保持
- 重複したキーもすべて保存
処理速度:
- 書き込み(保存)は速い
- 読み込み・検索は遅い
- 毎回JSONを解析する必要がある
JSONB型の特徴
データの保存方法:
- バイナリ形式で保存
- 最適化された形で格納
保存される内容:
- 空白は削除される
- キーの順序は保持されない
- 重複したキーは最後の値だけ保持
処理速度:
- 書き込み(保存)は少し遅い
- 読み込み・検索は非常に速い
- インデックスを作成できる
比較表
| 項目 | JSON型 | JSONB型 |
|---|---|---|
| 保存形式 | テキスト | バイナリ |
| 空白の保持 | する | しない |
| キー順序の保持 | する | しない |
| 重複キーの扱い | すべて保持 | 最後の値のみ |
| 書き込み速度 | 速い | 少し遅い |
| 読み込み速度 | 遅い | 速い |
| 検索速度 | 遅い | 速い |
| インデックス | 作れない | 作れる |
| おすすめ度 | △ | ◎ |
どちらを使うべき?
ほとんどの場合、JSONB型を使うべきです。
JSONB型を使うべき理由:
- 検索・処理が高速
- インデックスが使える
- 実用的なメリットが多い
JSON型を使う場合:
- キーの順序を完全に保持する必要がある(非常に特殊なケース)
- 入力されたJSON文字列をそのまま保存したい(ほとんどない)
一般的には、JSONBを選んでおけば間違いありません。
JSONBのメリット・デメリット
メリット
1. 柔軟なデータ構造
テーブル構造を変更せずに、新しい属性を追加できます。
例:商品に新しい属性「環境配慮」を追加したい
→ テーブル構造はそのまま、JSONBデータに追加するだけ
2. 高速な検索
インデックスを使った高速検索が可能です。
3. 豊富な演算子
特定の値を検索したり、データを操作する便利な演算子が用意されています。
4. データの検証
保存時にJSON形式が正しいかチェックされます。
5. 省スペース
重複したキーは自動的に削除されるため、無駄なデータが保存されません。
6. NoSQLとRDBの良いとこ取り
柔軟性(NoSQL的)と信頼性(RDB的)を両立できます。
デメリット
1. 書き込みが少し遅い
バイナリ形式に変換するため、JSON型より保存が少し遅いです。
ただし、読み込みが圧倒的に速いので、トータルでは有利です。
2. キーの順序が保持されない
保存されたデータのキーの順序は保証されません。
(ほとんどのアプリケーションでは問題になりません)
3. 適切な設計が必要
何でもJSONBに入れると、データベース設計が複雑になる可能性があります。
適切な使い分けが重要:
- 頻繁に検索・結合する項目 → 通常のカラム
- 柔軟性が必要な項目 → JSONB
JSONBの基本的な使い方
テーブルを作成する
JSONBカラムを持つテーブルを作成します。
例1:商品テーブル
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price INTEGER NOT NULL,
details JSONB NOT NULL
);
解説:
id:商品ID(主キー)name:商品名price:価格details:商品の詳細情報(JSONB型)
例2:ユーザーテーブル
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL,
settings JSONB
);
データを挿入する
JSONB型のカラムにデータを挿入する方法です。
基本的な挿入
INSERT INTO products (name, price, details) VALUES
(
'ノートパソコン',
150000,
'{"brand": "TechCorp", "model": "X100", "specs": {"cpu": "Intel i7", "ram": "16GB", "storage": "512GB SSD"}}'::jsonb
);
解説:
- JSON文字列を
::jsonbでJSONB型に変換 - ネストした構造(
specsの中にさらにオブジェクト)も可能
複数行を挿入
INSERT INTO products (name, price, details) VALUES
('スマートフォン', 80000, '{"brand": "PhoneCo", "model": "S20", "color": "黒", "storage": "128GB"}'::jsonb),
('タブレット', 50000, '{"brand": "TabletInc", "model": "T10", "color": "白", "screen": "10.5インチ"}'::jsonb),
('ワイヤレスイヤホン', 15000, '{"brand": "AudioTech", "model": "WE100", "color": "青", "features": ["ノイズキャンセリング", "防水"]}'::jsonb);
配列を含むデータ
INSERT INTO users (email, settings) VALUES
('user@example.com', '{"theme": "dark", "notifications": ["email", "push"], "language": "ja"}'::jsonb);
データを検索する方法

JSONBデータを検索する便利な演算子を紹介します。
基本的な検索演算子
演算子1:-> (JSON型で値を取得)
特定のキーの値をJSON型で取得します。
構文:
SELECT カラム名->'キー名' FROM テーブル名;
例:
-- brandを取得(JSON型で返る)
SELECT details->'brand' FROM products;
結果:
"TechCorp"
"PhoneCo"
"TabletInc"
ダブルクォーテーション付きのJSON型で返ってきます。
演算子2:->> (テキスト型で値を取得)
特定のキーの値をテキスト型で取得します。
構文:
SELECT カラム名->>'キー名' FROM テーブル名;
例:
-- brandを取得(テキスト型で返る)
SELECT details->>'brand' FROM products;
結果:
TechCorp
PhoneCo
TabletInc
ダブルクォーテーションなしのテキスト型で返ってきます。
使い分け:
- さらにJSONの中を辿りたい →
->を使う - 最終的な値が欲しい →
->>を使う
ネストしたデータへのアクセス
->を連続して使うと、ネストした値にアクセスできます。
例:
-- specsの中のcpuを取得
SELECT details->'specs'->>'cpu' FROM products WHERE name = 'ノートパソコン';
結果:
Intel i7
演算子3:#> (パスで値を取得・JSON型)
配列形式のパスを指定して、一気に深い階層の値を取得します。
構文:
SELECT カラム名#>'{キー1, キー2, キー3}' FROM テーブル名;
例:
-- specsの中のcpuを取得(パス指定)
SELECT details#>'{specs, cpu}' FROM products WHERE name = 'ノートパソコン';
結果:
"Intel i7"
JSON型(ダブルクォーテーション付き)で返ります。
演算子4:#>> (パスで値を取得・テキスト型)
#>のテキスト版です。
例:
-- specsの中のcpuを取得(テキスト型)
SELECT details#>>'{specs, cpu}' FROM products WHERE name = 'ノートパソコン';
結果:
Intel i7
テキスト型で返ります。
便利な検索演算子
演算子5:@> (包含検索)
左側のJSONBが右側のJSONBを含んでいるかチェックします。
使い方:
-- brandがTechCorpの商品を検索
SELECT * FROM products WHERE details @> '{"brand": "TechCorp"}';
結果:
ノートパソコンのレコードが返ります。
配列の中を検索:
-- featuresに「ノイズキャンセリング」を含む商品を検索
SELECT * FROM products WHERE details @> '{"features": ["ノイズキャンセリング"]}';
演算子6:<@ (被包含検索)
左側のJSONBが右側のJSONBに含まれているかチェックします。
@>の逆です。
使い方:
SELECT * FROM products WHERE '{"brand": "TechCorp"}' <@ details;
これは@>と同じ結果になります。
演算子7:? (キーの存在確認)
トップレベルに特定のキーが存在するかチェックします。
使い方:
-- colorキーが存在する商品を検索
SELECT * FROM products WHERE details ? 'color';
結果:
スマートフォン、タブレット、ワイヤレスイヤホンが返ります。
演算子8:?| (いずれかのキーの存在確認)
指定したキーのいずれかが存在するかチェックします。
使い方:
-- colorまたはsizeキーが存在する商品を検索
SELECT * FROM products WHERE details ?| array['color', 'size'];
演算子9:?& (すべてのキーの存在確認)
指定したキーがすべて存在するかチェックします。
使い方:
-- brandとmodelキーが両方存在する商品を検索
SELECT * FROM products WHERE details ?& array['brand', 'model'];
WHERE句での検索例
実際の検索クエリの例をいくつか紹介します。
例1:特定の値で検索
-- brandがPhoneCoの商品を検索
SELECT name, price FROM products WHERE details->>'brand' = 'PhoneCo';
例2:数値で比較
-- priceが100000以上の商品を検索(通常のカラムで検索)
SELECT name, details->>'brand' AS brand FROM products WHERE price >= 100000;
例3:複数の条件
-- brandがTechCorpで、かつstorageに「SSD」を含む商品
SELECT name FROM products
WHERE details->>'brand' = 'TechCorp'
AND details->'specs'->>'storage' LIKE '%SSD%';
例4:配列の中を検索
-- featuresに「防水」を含む商品
SELECT name FROM products
WHERE details->'features' @> '["防水"]'::jsonb;
インデックスを作成して検索を高速化
JSONBの大きな利点は、インデックスを作成できることです。
GINインデックス(推奨)
GIN(Generalized Inverted Index)は、JSONB全体に対してインデックスを作成します。
作成方法:
CREATE INDEX idx_products_details ON products USING GIN (details);
メリット:
- 任意のキーの検索が高速化される
@>、?、?|、?&などの演算子が高速化される
こんな時に使う:
- JSONBの中の様々なキーを検索する
- 包含検索(
@>)を頻繁に使う
特定キーのインデックス
特定のキーだけにインデックスを作成することもできます。
作成方法:
-- brandキーだけにインデックスを作成
CREATE INDEX idx_products_brand ON products ((details->>'brand'));
メリット:
- 特定のキーの検索がさらに高速化
- インデックスサイズが小さい
デメリット:
- そのキー以外は高速化されない
使い分け:
- よく検索するキーが決まっている → 特定キーのインデックス
- 様々なキーを検索する → GINインデックス
実践的な使用例

例1:ECサイトの商品カタログ
テーブル設計:
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
category VARCHAR(50) NOT NULL,
price INTEGER NOT NULL,
attributes JSONB NOT NULL
);
-- インデックス作成
CREATE INDEX idx_products_attributes ON products USING GIN (attributes);
データ挿入:
-- 本
INSERT INTO products (name, category, price, attributes) VALUES
('プログラミング入門', '本', 3000,
'{"author": "山田太郎", "pages": 350, "publisher": "技術出版", "isbn": "978-4-xxx"}'::jsonb);
-- 電化製品
INSERT INTO products (name, category, price, attributes) VALUES
('ノートPC', '電化製品', 120000,
'{"brand": "TechCorp", "cpu": "Intel i7", "ram": "16GB", "color": "シルバー"}'::jsonb);
-- 服
INSERT INTO products (name, category, price, attributes) VALUES
('Tシャツ', '服', 2500,
'{"brand": "FashionBrand", "size": "M", "color": "青", "material": "綿100%"}'::jsonb);
検索クエリ例:
-- 著者が「山田太郎」の本を検索
SELECT name, price FROM products
WHERE category = '本' AND attributes->>'author' = '山田太郎';
-- CPUがIntel i7の電化製品を検索
SELECT name, price FROM products
WHERE category = '電化製品' AND attributes->>'cpu' = 'Intel i7';
-- サイズがMの服を検索
SELECT name, price, attributes->>'color' AS color FROM products
WHERE category = '服' AND attributes->>'size' = 'M';
例2:ユーザー設定管理
テーブル設計:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
preferences JSONB DEFAULT '{}'::jsonb
);
-- インデックス作成
CREATE INDEX idx_users_preferences ON users USING GIN (preferences);
データ挿入:
INSERT INTO users (email, name, preferences) VALUES
('user1@example.com', 'ユーザー1',
'{"theme": "dark", "language": "ja", "timezone": "Asia/Tokyo", "notifications": {"email": true, "push": false}}'::jsonb),
('user2@example.com', 'ユーザー2',
'{"theme": "light", "language": "en", "fontSize": "large"}'::jsonb);
設定を更新:
-- ユーザー1のテーマをlightに変更
UPDATE users
SET preferences = jsonb_set(preferences, '{theme}', '"light"')
WHERE email = 'user1@example.com';
-- 新しい設定項目を追加
UPDATE users
SET preferences = preferences || '{"darkMode": true}'::jsonb
WHERE email = 'user1@example.com';
設定を検索:
-- ダークテーマを使っているユーザーを検索
SELECT name, email FROM users
WHERE preferences->>'theme' = 'dark';
-- メール通知をオンにしているユーザー
SELECT name, email FROM users
WHERE preferences->'notifications'->>'email' = 'true';
例3:ログデータの保存
テーブル設計:
CREATE TABLE activity_logs (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
action VARCHAR(50) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
details JSONB NOT NULL
);
CREATE INDEX idx_logs_details ON activity_logs USING GIN (details);
CREATE INDEX idx_logs_user_id ON activity_logs (user_id);
データ挿入:
-- ログイン
INSERT INTO activity_logs (user_id, action, details) VALUES
(1, 'login', '{"ip": "192.168.1.100", "device": "iPhone", "browser": "Safari"}'::jsonb);
-- 商品購入
INSERT INTO activity_logs (user_id, action, details) VALUES
(1, 'purchase', '{"product_id": 123, "price": 5000, "payment_method": "クレジットカード"}'::jsonb);
-- 設定変更
INSERT INTO activity_logs (user_id, action, details) VALUES
(1, 'settings_change', '{"changed_fields": ["theme", "language"], "old_values": {"theme": "light"}, "new_values": {"theme": "dark"}}'::jsonb);
ログを検索:
-- 特定ユーザーの購入履歴
SELECT created_at, details->>'product_id' AS product_id, details->>'price' AS price
FROM activity_logs
WHERE user_id = 1 AND action = 'purchase'
ORDER BY created_at DESC;
-- iPhoneからのログイン
SELECT user_id, created_at, details->>'ip' AS ip
FROM activity_logs
WHERE action = 'login' AND details->>'device' = 'iPhone';
JSONBを使う際の注意点
注意点1:何でもJSONBに入れない
適切な使い分けが重要です。
通常のカラムにすべき:
- 頻繁に検索・結合する項目
- 必須の項目
- 厳密な型チェックが必要な項目
例:ユーザーのメールアドレス、商品の価格
JSONBにすべき:
- 商品ごとに異なる属性
- ユーザーごとに異なる設定
- ログデータ
- 頻繁に変更される構造
注意点2:検証が難しい
JSONBは柔軟な反面、データの整合性を保つのが難しいです。
対策:
- アプリケーション側でバリデーションを行う
- CHECK制約を使う(PostgreSQL拡張が必要)
注意点3:パフォーマンスに注意
大量データを扱う場合:
- 適切なインデックスを作成する
- クエリのパフォーマンスをテストする
JSONB内に大きなデータを保存しない:
- 画像やファイルは別テーブルで管理
- URLやIDだけをJSONBに保存
注意点4:マイグレーションが複雑
JSONBの構造を変更する場合、既存データの更新が必要です。
対策:
- データ構造の変更は慎重に行う
- バージョン管理の仕組みを考える
よくある質問(FAQ)
Q1. JSONとJSONBはどちらを使うべき?
A. ほとんどの場合、JSONBを使うべきです。
JSONB型は:
- 検索・処理が高速
- インデックスを作成できる
- 実用的なメリットが多い
JSON型を使うのは、キーの順序を完全に保持する必要がある非常に特殊なケースだけです。
Q2. JSONBはどのバージョンのPostgreSQLから使える?
A. PostgreSQL 9.4以降で使用できます。
現在のほとんどのPostgreSQLバージョンで利用可能です。
Q3. JSONBに保存できるデータサイズに制限はある?
A. はい、制限があります。
- 通常は約1GBまで(TOASTという仕組みで大きなデータを扱える)
- ただし、大きすぎるデータは避けるべき
- 実用的には数MB以内が推奨
Q4. 配列の中を検索できる?
A. はい、検索できます。
-- featuresの配列に「防水」が含まれるか
SELECT * FROM products
WHERE details->'features' @> '["防水"]'::jsonb;
配列の特定の要素にアクセス:
-- features配列の最初の要素
SELECT details->'features'->0 FROM products;
Q5. JSONBの値を更新する方法は?
A. jsonb_set関数を使います。
-- brandをNewBrandに変更
UPDATE products
SET details = jsonb_set(details, '{brand}', '"NewBrand"')
WHERE id = 1;
新しいキーを追加:
-- 新しいキーを追加
UPDATE products
SET details = details || '{"warranty": "2年"}'::jsonb
WHERE id = 1;
Q6. JSONBのキーを削除できる?
A. はい、-演算子で削除できます。
-- colorキーを削除
UPDATE products
SET details = details - 'color'
WHERE id = 1;
複数のキーを削除:
-- colorとsizeを削除
UPDATE products
SET details = details - '{color, size}'::text[]
WHERE id = 1;
Q7. JSONBで全文検索できる?
A. はい、工夫すれば可能です。
-- detailsのすべての値からテキストを抽出して検索
SELECT * FROM products
WHERE details::text LIKE '%Intel%';
ただし、パフォーマンスを考えると:
- 全文検索用のカラムを別に作る
- または、PostgreSQLの全文検索機能(tsvector)を使う
のがおすすめです。
まとめ:JSONBで柔軟なデータ管理を実現しよう
JSONBの基本から実践的な使い方まで、詳しく説明してきました。
この記事の重要ポイント
JSONBとは:
- PostgreSQLで使える柔軟なデータ型
- JSONデータをバイナリ形式で保存
- 検索・処理が高速
- インデックスが作成できる
JSONとJSONBの違い:
- JSON型:テキスト形式、書き込み速い、読み込み遅い
- JSONB型:バイナリ形式、書き込み少し遅い、読み込み非常に速い
- 推奨:ほとんどの場合JSONB型を使うべき
JSONBのメリット:
- 柔軟なデータ構造
- 高速な検索(インデックス対応)
- 豊富な演算子
- データの検証
- NoSQLとRDBの良いとこ取り
主要な演算子:
->:JSON型で値を取得->>:テキスト型で値を取得@>:包含検索?:キーの存在確認#>、#>>:パス指定で値を取得
インデックス作成:
-- GINインデックス(推奨)
CREATE INDEX idx_details ON テーブル名 USING GIN (カラム名);
-- 特定キーのインデックス
CREATE INDEX idx_key ON テーブル名 ((カラム名->>'キー名'));
使用例:
- ECサイトの商品カタログ
- ユーザー設定管理
- ログデータの保存
- 構造が頻繁に変わるデータ
注意点:
- 何でもJSONBに入れない(適切な使い分け)
- データの検証が難しい(アプリ側で対応)
- 大きすぎるデータは避ける
- 適切なインデックスを作成する
最後に
JSONBは、PostgreSQLで柔軟なデータを効率的に扱うための強力なツールです。
こんな時にJSONBが役立ちます:
- 商品カテゴリごとに異なる属性を管理したい
- ユーザーごとに異なる設定を保存したい
- ログや監査データを柔軟に記録したい
- スキーマの頻繁な変更を避けたい
ただし、JSONBを使えば何でも解決するわけではありません。通常のカラムとJSONBを適切に使い分けることで、柔軟性とパフォーマンスを両立したデータベース設計が実現できます。
最初は小さく始めましょう:
- まずは簡単なテーブルでJSONBを試してみる
- 基本的な演算子(
->、->>、@>)を使ってみる - インデックスを作成して検索を高速化
- 徐々に複雑な構造にチャレンジ
この記事が、あなたのJSONB活用の第一歩になれば幸いです。
柔軟で効率的なデータベース設計を実現して、素晴らしいアプリケーションを作ってください!


コメント