🧚 お知らせ!Beekeeper Studioは高速でモダンなオープンソースのデータベースGUIです ダウンロード
2025年3月23日 著者: Matthew Rathbone

PostgreSQLは、json型とjsonb型により、JSONデータの保存とクエリを強力にサポートしています。特にjsonb型はデータをバイナリ形式で保存するため、テキストベースのjson型と比べて、より効率的なインデックス作成とクエリが可能です。

このチュートリアルでは、PostgreSQLでよく使われるJSON操作を、アプリケーションでそのまま使える実践的な例とともに紹介します。

JSONデータの作成

まず、jsonbカラムを持つテーブルを作成し、サンプルデータを挿入しましょう。

CREATE TABLE customers (
    id SERIAL PRIMARY KEY,
    info JSONB
);

-- Insert a customer with basic information
INSERT INTO customers (info) VALUES (
    '{"name": "Alice Smith", "email": "alice@example.com", "age": 32, "addresses": [
        {"type": "home", "city": "San Francisco", "state": "CA"}, 
        {"type": "work", "city": "Oakland", "state": "CA"}
     ]}'
);

-- Insert another customer
INSERT INTO customers (info) VALUES (
    '{"name": "Bob Johnson", "email": "bob@example.com", "age": 45, "addresses": [
        {"type": "home", "city": "Seattle", "state": "WA"}
     ], "orders": [101, 102, 103]}'
);

基本的なJSON操作

JSONドキュメント全体の取得

SELECT info FROM customers WHERE id = 1;

結果:

{
  "age": 32,
  "name": "Alice Smith",
  "email": "alice@example.com",
  "addresses": [
    {"city": "San Francisco", "type": "home", "state": "CA"},
    {"city": "Oakland", "type": "work", "state": "CA"}
  ]
}

JSONから値を抽出する

PostgreSQLには、JSONから値を抽出するための演算子が複数用意されています。

-> 演算子で特定のフィールドを抽出する(JSONを返す)

-- Get the name field as JSON
SELECT info -> 'name' FROM customers;

結果:

"Alice Smith"
"Bob Johnson"

-» 演算子で特定のフィールドを抽出する(テキストを返す)

-- Get the name field as text
SELECT info ->> 'name' AS customer_name FROM customers;

結果:

customer_name
----------------
Alice Smith
Bob Johnson

ネストされたオブジェクトへのアクセス

-- Getting the first address (array element 0) city
SELECT info -> 'addresses' -> 0 ->> 'city' AS home_city FROM customers;

結果:

home_city
------------
San Francisco
Seattle

#> 演算子でパスを指定してアクセスする

-- Using the path syntax to get the same value
SELECT info #> '{addresses,0,city}' FROM customers;

結果:

"San Francisco"
"Seattle"

#» 演算子でパスを指定してテキストを取得する

-- Using the path syntax to get the same value as text
SELECT info #>> '{addresses,0,city}' AS home_city FROM customers;

結果:

home_city
------------
San Francisco
Seattle

JSONデータの変更

PostgreSQLには、JSONデータを変更するための関数がいくつか用意されています。

jsonb_setでフィールドを追加する

-- Add a new field 'premium_customer' to a customer
UPDATE customers
SET info = jsonb_set(info, '{premium_customer}', 'true')
WHERE id = 1;

- 演算子でフィールドを削除する

-- Remove the premium_customer field
UPDATE customers
SET info = info - 'premium_customer'
WHERE id = 1;

|| でJSONオブジェクトをマージする

-- Add multiple fields by merging objects
UPDATE customers
SET info = info || '{"status": "active", "last_login": "2025-03-23"}'::jsonb
WHERE id = 1;

ネストされたフィールドの更新

-- Update a nested field (home address state)
UPDATE customers
SET info = jsonb_set(
    info, 
    '{addresses,0,state}', 
    '"NY"'::jsonb
)
WHERE id = 1;

JSON配列のクエリ

JSON配列の操作はよく行われる処理です。

配列の長さを取得する

-- Get the number of addresses for each customer
SELECT 
    info ->> 'name' AS customer_name,
    jsonb_array_length(info -> 'addresses') AS address_count
FROM customers;

結果:

customer_name  | address_count
---------------+--------------
Alice Smith    | 2
Bob Johnson    | 1

特定の配列要素を抽出する

-- Get the second address (index 1) for Alice
SELECT info -> 'addresses' -> 1 FROM customers WHERE id = 1;

結果:

{"city": "Oakland", "type": "work", "state": "CA"}

配列を展開して個々の要素をクエリする

-- Unnest the addresses array to query individual addresses
SELECT 
    c.id,
    c.info ->> 'name' AS customer_name,
    jsonb_array_elements(c.info -> 'addresses') AS address
FROM customers c;

結果:

id | customer_name | address
---+---------------+------------------------------------------------------
1  | Alice Smith   | {"city": "San Francisco", "type": "home", "state": "NY"}
1  | Alice Smith   | {"city": "Oakland", "type": "work", "state": "CA"}
2  | Bob Johnson   | {"city": "Seattle", "type": "home", "state": "WA"}

展開した配列でフィルタリングする

-- Find customers with addresses in CA
SELECT DISTINCT
    c.id,
    c.info ->> 'name' AS customer_name
FROM customers c,
     jsonb_array_elements(c.info -> 'addresses') AS addr
WHERE addr ->> 'state' = 'CA';

結果:

id | customer_name
---+--------------
1  | Alice Smith

キーと値の存在チェック

キーが存在するか確認する

-- Find customers who have the 'orders' key
SELECT id, info ->> 'name' AS customer_name
FROM customers
WHERE info ? 'orders';

結果:

id | customer_name
---+--------------
2  | Bob Johnson

配列内の値を確認する

-- Find customers with a specific order number
SELECT id, info ->> 'name' AS customer_name
FROM customers
WHERE info -> 'orders' ? '101';

結果:

id | customer_name
---+--------------
2  | Bob Johnson

いずれかの配列要素が一致するか確認する

-- Find customers who have an address in Oakland (using path operators)
SELECT id, info ->> 'name' AS customer_name
FROM customers
WHERE info @> '{"addresses": [{"city": "Oakland"}]}';

結果:

id | customer_name
---+--------------
1  | Alice Smith

JSONデータのインデックス作成

大規模なJSONデータセットを扱う本番データベースでは、適切なインデックス作成が不可欠です。

包含クエリ用のGINインデックスを作成する

-- Create a GIN index for faster containment operations
CREATE INDEX idx_customers_info ON customers USING GIN (info);

JSONフィールド専用のインデックスを作成する

-- Create an index on the 'email' field for fast lookups
CREATE INDEX idx_customers_email ON customers ((info ->> 'email'));

クエリでインデックス付きフィールドを使う

-- This query can use the index we created above
SELECT id, info ->> 'name' AS customer_name
FROM customers
WHERE info ->> 'email' = 'alice@example.com';

JSONとテーブルの相互変換

JSONを行に変換する(jsonb_to_record)

-- Extract JSON fields into a tabular structure
SELECT 
    id,
    rec.name,
    rec.email,
    rec.age
FROM 
    customers,
    jsonb_to_record(info) AS rec(
        name text,
        email text,
        age int
    );

結果:

id | name         | email             | age
---+--------------+-------------------+-----
1  | Alice Smith  | alice@example.com | 32
2  | Bob Johnson  | bob@example.com   | 45

ネストされた配列をテーブルに変換する(jsonb_to_recordset)

-- Extract addresses from all customers into a table
SELECT 
    c.id,
    c.info ->> 'name' AS customer_name,
    addr.type AS address_type,
    addr.city,
    addr.state
FROM 
    customers c,
    jsonb_to_recordset(c.info -> 'addresses') AS addr(
        type text,
        city text,
        state text
    );

結果:

id | customer_name | address_type | city          | state
---+---------------+--------------+---------------+------
1  | Alice Smith   | home         | San Francisco | NY
1  | Alice Smith   | work         | Oakland       | CA
2  | Bob Johnson   | home         | Seattle       | WA

クエリ結果をJSONに変換する

-- Aggregate customer orders into a JSON array
SELECT
    c.id,
    c.info ->> 'name' AS customer_name,
    json_agg(o.order_id) AS orders
FROM 
    customers c
    CROSS JOIN LATERAL (
        SELECT jsonb_array_elements_text(c.info -> 'orders') AS order_id
    ) o
GROUP BY c.id, c.info ->> 'name';

結果:

id | customer_name | orders
---+---------------+----------
2  | Bob Johnson   | ["101", "102", "103"]

パフォーマンスに関する考慮事項

PostgreSQLでJSONを扱う際は、次のパフォーマンス上の考慮事項に注意してください。

  1. jsonではなくjsonbを使う: ほとんどの操作でjsonb型の方が高いパフォーマンスを発揮します。

  2. 戦略的にインデックスを作成する: 包含クエリにはGINインデックスを使い、頻繁にクエリするフィールドには専用のインデックスを作成しましょう。

  3. 非正規化は慎重に検討する: JSONを使えばデータモデルを非正規化できますが、過度なネストはクエリのパフォーマンスに影響します。

  4. クエリパターンに注意する: 複雑なJSONパス式や、大きな配列の頻繁な展開はコストの高い操作になり得ます。

  5. 配列のサイズを制限する: 非常に大きなJSON配列は、展開時にパフォーマンス問題を引き起こす可能性があります。

  6. 必要なフィールドだけを取得する: JSONドキュメント全体を返すのではなく、必要なフィールドだけを抽出しましょう。

まとめ

PostgreSQLのJSON機能は、半構造化データを保存・クエリする必要があるアプリケーションにとって優れた選択肢です。このチュートリアルで紹介した演算子と関数を使えば、成熟したリレーショナルデータベースの性能と信頼性を活かしながら、JSONデータを効果的に扱えます。

複雑なデータモデルでは、ハイブリッドなアプローチを検討してください。固定スキーマや頻繁にクエリするデータには構造化されたテーブルを使い、柔軟でスキーマレスなデータや、リレーショナルにモデル化しにくいネスト構造にはJSONを使います。

これらの例を実際に試してみませんか?Beekeeper Studioを開いて、今すぐPostgreSQLのJSON機能を体験してみましょう。

Beekeeper Studioは無料のオープンソースのデータベースGUIです

これまで使った中で最高のSQLクエリ&エディタツールです。データベースの管理に必要なものがすべて揃っています。 - ⭐⭐⭐⭐⭐ Mit

Beekeeper Studioは高速で直感的、そして使いやすいツールです。多数のデータベースに対応し、Windows、Mac、Linuxで快適に動作します。

BeekeeperのLinux版は100%フル機能。手抜きも機能の妥協も一切ありません。

Beekeeper Studio 利用者の声

★★★★★
「Beekeeper Studioは、これまでのSQLワークフローを完全に置き換えてくれました。高速で直感的、データベース作業がまた楽しくなりました。」
— Alex K.、データベース開発者
★★★★★
「多くのデータベースGUIを試してきましたが、Beekeeperは機能とシンプルさのバランスが絶妙です。とにかくちゃんと動きます。」
— Sarah M.、フルスタックエンジニア