JSONBとは?PostgreSQLで柔軟なデータを扱う強力なデータ型を徹底解説

プログラミング・IT

「PostgreSQLでJSONBというデータ型を見たけど、何に使うの?」
「JSONとJSONBって何が違うの?」
「どうやって使えばいいのか分からない」

そんな疑問を持っていませんか?

JSONBは、PostgreSQLで柔軟なデータを効率的に扱うための強力なデータ型です。商品カタログ、ユーザー設定、ログデータなど、構造が頻繁に変わるデータを管理するのに最適なんです。

この記事では、JSONBの基本から実践的な使い方まで、初心者の方にも分かりやすく説明します。

この記事で分かること:

  • JSONBの基本的な概念
  • JSONとJSONBの違い
  • JSONBを使うメリット・デメリット
  • テーブル作成とデータ挿入の方法
  • データの検索方法と便利な演算子
  • インデックスの作成方法
  • 実践的な使用例

それでは、順番に見ていきましょう!


スポンサーリンク
  1. JSONBとは?基本を理解しよう
    1. JSONBの定義
    2. JSONBはこんな時に使う
  2. JSONとJSONBの違い
    1. JSON型の特徴
    2. JSONB型の特徴
    3. 比較表
    4. どちらを使うべき?
  3. JSONBのメリット・デメリット
    1. メリット
    2. デメリット
  4. JSONBの基本的な使い方
    1. テーブルを作成する
    2. データを挿入する
  5. データを検索する方法
    1. 基本的な検索演算子
  6. 便利な検索演算子
    1. 演算子5:@> (包含検索)
    2. 演算子6:<@ (被包含検索)
    3. 演算子7:? (キーの存在確認)
    4. 演算子8:?| (いずれかのキーの存在確認)
    5. 演算子9:?& (すべてのキーの存在確認)
  7. WHERE句での検索例
    1. 例1:特定の値で検索
    2. 例2:数値で比較
    3. 例3:複数の条件
    4. 例4:配列の中を検索
  8. インデックスを作成して検索を高速化
    1. GINインデックス(推奨)
    2. 特定キーのインデックス
  9. 実践的な使用例
    1. 例1:ECサイトの商品カタログ
    2. 例2:ユーザー設定管理
    3. 例3:ログデータの保存
  10. JSONBを使う際の注意点
    1. 注意点1:何でもJSONBに入れない
    2. 注意点2:検証が難しい
    3. 注意点3:パフォーマンスに注意
    4. 注意点4:マイグレーションが複雑
  11. よくある質問(FAQ)
    1. Q1. JSONとJSONBはどちらを使うべき?
    2. Q2. JSONBはどのバージョンのPostgreSQLから使える?
    3. Q3. JSONBに保存できるデータサイズに制限はある?
    4. Q4. 配列の中を検索できる?
    5. Q5. JSONBの値を更新する方法は?
    6. Q6. JSONBのキーを削除できる?
    7. Q7. JSONBで全文検索できる?
  12. まとめ:JSONBで柔軟なデータ管理を実現しよう
    1. この記事の重要ポイント
    2. 最後に

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のメリット:

  1. 柔軟なデータ構造
  2. 高速な検索(インデックス対応)
  3. 豊富な演算子
  4. データの検証
  5. NoSQLとRDBの良いとこ取り

主要な演算子:

  • -> :JSON型で値を取得
  • ->> :テキスト型で値を取得
  • @> :包含検索
  • ? :キーの存在確認
  • #>#>> :パス指定で値を取得

インデックス作成:

-- GINインデックス(推奨)
CREATE INDEX idx_details ON テーブル名 USING GIN (カラム名);

-- 特定キーのインデックス
CREATE INDEX idx_key ON テーブル名 ((カラム名->>'キー名'));

使用例:

  • ECサイトの商品カタログ
  • ユーザー設定管理
  • ログデータの保存
  • 構造が頻繁に変わるデータ

注意点:

  1. 何でもJSONBに入れない(適切な使い分け)
  2. データの検証が難しい(アプリ側で対応)
  3. 大きすぎるデータは避ける
  4. 適切なインデックスを作成する

最後に

JSONBは、PostgreSQLで柔軟なデータを効率的に扱うための強力なツールです。

こんな時にJSONBが役立ちます:

  • 商品カテゴリごとに異なる属性を管理したい
  • ユーザーごとに異なる設定を保存したい
  • ログや監査データを柔軟に記録したい
  • スキーマの頻繁な変更を避けたい

ただし、JSONBを使えば何でも解決するわけではありません。通常のカラムとJSONBを適切に使い分けることで、柔軟性とパフォーマンスを両立したデータベース設計が実現できます。

最初は小さく始めましょう:

  1. まずは簡単なテーブルでJSONBを試してみる
  2. 基本的な演算子(->->>@>)を使ってみる
  3. インデックスを作成して検索を高速化
  4. 徐々に複雑な構造にチャレンジ

この記事が、あなたのJSONB活用の第一歩になれば幸いです。

柔軟で効率的なデータベース設計を実現して、素晴らしいアプリケーションを作ってください!

コメント

タイトルとURLをコピーしました