【実務・中級編】 JSONB型 – PostgreSQL

現場で差がつく!PostgreSQLのJSONB型、使いこなせてる?

どうも、皆さん!データベース周りの話ならお任せあれ、〇〇(←あなたのブログ名とか入れてね)の△△です。
今日は、PostgreSQLのちょっとニッチだけど、知っておくと「お、こいつできるな」って思われること間違いなしの「JSONB型」について、現場で役立つ実践的な話をしようと思います。

「JSON?そんなのアプリケーション側で扱えばいいじゃん?」って思ってるそこのあなた!
ちょっと待ってください。PostgreSQLに任せられること、結構あるんですよ。特にJSONB型は、まさに「痒いところに手が届く」機能なんです。

なんでJSONB?そもそもJSON型との違いって?

まず、PostgreSQLにはJSON型とJSONB型の2つがあります。
簡単に言うと、

  • JSON型: 入力されたJSONテキストをそのまま保存します。構造チェックはするけど、加工はしないイメージ。
  • JSONB型: 入力されたJSONをバイナリ形式に変換して保存します。ちょっと手間はかかるけど、後々が全然違う!

「バイナリ形式?何がいいの?」って思いますよね。
ここがJSONBの肝なんです。バイナリ形式で保存することで、

1. 高速な検索: JSONの構造を解析して、特定のキーや値に素早くアクセスできるようになります。
2. インデックス作成: なんと、JSONBの中身に対してもインデックスが貼れるんです!これは、検索パフォーマンスを劇的に向上させる強力な武器になります。
3. 部分更新: JSON全体を一度読み込んで書き換えるのではなく、必要な部分だけを効率的に更新できます。
4. 重複キーの削除: JSONBでは、重複するキーがあった場合、後から出てきたものを優先します。これも地味に便利。

ぶっちゃけ、ほとんどの場合でJSONB型を使うことをおすすめします。JSON型を使うのは、本当に「入力されたそのままのテキストが欲しい」って、なかなかないケースだけだと思いますよ。

現場でJSONB型、どう使う?具体的なユースケース

じゃあ、具体的にどんな場面でJSONB型が活躍するんでしょうか?いくつか例を挙げてみましょう。

1. 設定情報やメタデータの保存

例えば、ユーザーごとに異なる設定情報を持たせたい場合。
「このユーザーは通知をメールで受け取りたい」「このユーザーはダークモードにしたい」みたいな、ちょっと複雑な条件を管理するのに便利です。

CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50),
settings JSONB
);

INSERT INTO users (username, settings) VALUES
(‘alice’, ‘{“notifications”: {“email”: true, “sms”: false}, “theme”: “dark”}’),
(‘bob’, ‘{“notifications”: {“email”: false, “sms”: true}, “theme”: “light”}’);

この`settings`カラムがJSONB型です。

2. 外部APIからのレスポンスの保存

頻繁に変わる、あるいは構造が複雑な外部APIからのレスポンスをそのまま保存しておきたいとき。
後から分析したり、デバッグしたりするのに役立ちます。

CREATE TABLE api_logs (
id SERIAL PRIMARY KEY,
api_endpoint VARCHAR(255),
response_data JSONB,
created_at TIMESTAMP DEFAULT NOW()
);

— 例:外部APIから取得したデータ
INSERT INTO api_logs (api_endpoint, response_data) VALUES
(‘/users/123’, ‘{“user_id”: 123, “name”: “Charlie”, “address”: {“city”: “Tokyo”, “zip”: “100-0001”}}’);

3. 可変長の属性やタグの管理

商品情報で、共通の属性に加えて、商品ごとに異なる属性(例えば、衣類なら「素材」、電化製品なら「保証期間」など)を持たせたい場合。
あるいは、ブログ記事にタグを付けたりするのに、JSONBで配列として保存すると便利です。

CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
attributes JSONB
);

INSERT INTO products (name, attributes) VALUES
(‘T-Shirt’, ‘{“color”: “red”, “size”: “M”, “material”: “cotton”}’),
(‘Laptop’, ‘{“brand”: “AwesomePC”, “storage”: “512GB SSD”, “warranty_years”: 2}’);

CREATE TABLE articles (
id SERIAL PRIMARY KEY,
title VARCHAR(255),
tags JSONB — タグは配列で保存
);

INSERT INTO articles (title, tags) VALUES
(‘PostgreSQL入門’, ‘[“database”, “postgresql”, “jsonb”]’),
(‘React Hooks徹底解説’, ‘[“javascript”, “react”, “frontend”]’);

JSONB型の強力なクエリ機能

ここからが本番!JSONB型が「ただのデータ型」じゃない理由。それは、その強力なクエリ機能にあります。

JSONB演算子たち

PostgreSQLは、JSONB専用の演算子をたくさん用意してくれています。いくつか代表的なものを紹介しましょう。

  • `->`: 指定したキーの値を取得(結果はJSONB型)
  • `->>`: 指定したキーの値を取得(結果はテキスト型)
  • `#>`: 指定したパスの要素を取得(結果はJSONB型)
  • `#>>`: 指定したパスの要素を取得(結果はテキスト型)
  • `@>`: 左辺のJSONBが右辺のJSONBを含むか?(存在チェックに便利)
  • `?`: 指定したキーが存在するか?
  • `?|`: 指定したキーのいずれかが存在するか?
  • `?&`: 指定したキーのすべてが存在するか?

具体的なクエリ例

先ほどの`users`テーブルを例に見てみましょう。

「alice」さんの設定を取得したい

SELECT settings FROM users WHERE username = ‘alice’;
— 結果:{“notifications”: {“email”: true, “sms”: false}, “theme”: “dark”} (JSONB型)

「alice」さんの`theme`設定を取得したい

— ->> 演算子でテキスト型として取得
SELECT settings ->> ‘theme’ FROM users WHERE username = ‘alice’;
— 結果:dark (テキスト型)

— -> 演算子でJSONB型として取得(この場合はオブジェクトなのであまり意味がないかも)
SELECT settings -> ‘theme’ FROM users WHERE username = ‘alice’;
— 結果:”dark” (JSONB型、文字列リテラルになる)

「bob」さんが`sms`通知を有効にしているか検索したい

— ネストしたキーも指定できます
SELECT username FROM users WHERE settings -> ‘notifications’ ->> ‘sms’ = ‘true’;
— 結果:bob

— より短く書くなら #>> 演算子
SELECT username FROM users WHERE settings #>> ‘{notifications,sms}’ = ‘true’;
— 結果:bob

「alice」さんが`email`通知を有効にしているユーザーを検索したい

`@>` 演算子がここで大活躍します!

SELECT username FROM users WHERE settings @> ‘{“notifications”: {“email”: true}}’;
— 結果:alice

これは、「`settings`カラムが `{“notifications”: {“email”: true}}` という構造を含んでいるか?」というクエリになります。
部分的な条件で検索できるのが、JSONBの強力なところですね。

`tags`カラムに「postgresql」が含まれる記事を検索したい

`articles`テーブルの`tags`カラム(JSONB配列)を例に。

SELECT title FROM articles WHERE tags @> ‘[“postgresql”]’;
— 結果:PostgreSQL入門

配列の中に特定の要素が含まれているか、というチェックも `@>` でできます。

インデックスでさらに高速化!

ここまで見てきて、「便利だけど、データが増えたら遅くならない?」って心配になった方もいるかもしれません。
ご安心ください!JSONB型はインデックスにも対応しています。

PostgreSQLには、JSONB専用のインデックスタイプがいくつかあります。

  • GIN (Generalized Inverted Index): JSONBのキーや値全体をインデックス化します。`@>`, `?`, `?|`, `?&` 演算子を使った検索が高速になります。

CREATE INDEX idx_gin_users_settings ON users USING GIN (settings);

`@>` 演算子を多用するような、JSONBの「部分一致」や「包含」検索をよく行う場合に最適です。

  • B-tree Index: 特定のキーやパスの値をB-treeインデックスにすることも可能です。`=` や `>` などの比較演算子を使った検索が速くなります。

— 特定のキーの値で検索したい場合
CREATE INDEX idx_btree_users_theme ON users ((settings ->> ‘theme’));
SELECT username FROM users WHERE settings ->> ‘theme’ = ‘dark’;

この場合、`settings ->> ‘theme’` のように、インデックス対象の式を `()` で囲む必要があります(式ツリーインデックス)。

どちらのインデックスを使うかは、どのようなクエリをよく実行するかによって変わってきます。
まずはGINインデックスで試してみて、パフォーマンスを見ながら必要に応じてB-treeインデックスも検討するのが良いでしょう。

まとめ:JSONB型、現場でどんどん使っていこう!

どうでしたか?PostgreSQLのJSONB型、結構パワフルでしょ?
「JSONをDBに?いやいや…」って思ってた人も、ちょっと見方が変わったんじゃないでしょうか。

  • 設定情報、メタデータ、APIレスポンス、可変属性など、柔軟なデータ構造を扱いたいときに最適。
  • 専用の演算子が豊富で、柔軟かつ効率的なクエリが可能。
  • GINインデックスを使えば、検索パフォーマンスも劇的に向上。

もちろん、何でもかんでもJSONBにするのは本末転倒ですが、
「このデータ、リレーショナルモデルで表現するのちょっと面倒だな…」
「頻繁に構造が変わる、あるいは固定できないデータなんだよな…」
そんな時は、ぜひJSONB型を検討してみてください。

現場でデータベースを触る上で、JSONB型を使いこなせるかどうかで、開発のスピードや効率が大きく変わってくるはずです。
ぜひ、皆さんのプロジェクトでもJSONB型を積極的に活用してみてくださいね!

何か質問があれば、気軽にコメントでください!
それでは、また次の記事でお会いしましょう!

コメント

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