CodeGym /コース /SQL SELF /JSONBでの複雑なクエリの例

JSONBでの複雑なクエリの例

SQL SELF
レベル 34 , レッスン 2
使用可能

前回の講義では、JSONBの基本(作成、変更、データの取得)を勉強したよね。今回は本番!JSONBの本当の強さがわかる複雑なクエリに挑戦しよう。

ネットショップのカタログを想像してみて。各商品には基本情報(名前、ID)があるけど、スペックは全然違う:ノートPCならRAMやCPU、服ならサイズや素材、本なら著者やジャンル。全部を別テーブルで管理する?めんどくさいよね。JSONBなら?最高! でも、特定ブランドの商品を探したり、価格でソートしたり、カテゴリごとの統計を出したいときはどうする?普通のカラムじゃなくて、JSONの中に隠れてるデータをどうやって扱う?

今日は実際のシナリオをやってみるよ:シンプルなフィルタから、グループ化や集計を使った複雑なクエリまで。JSONBがPostgreSQLをどれだけ柔軟なツールに変えるか、実感できるはず!

JSONBデータのフィルタリング

フィルタリングはお茶の茶こしみたいなもんだよ:必要なものだけ残して、いらないものは捨てる。JSONBだともっと面白い!普通のカラムだけじゃなく、JSONの深いところにあるデータでもフィルタできるんだ。

JSONBフィルタ用の演算子:

  • @> — "JSONB-contains"。JSONBオブジェクトが指定したサブセットを含んでいるかチェックする。
  • ? — "キーが存在する"。指定したキーがJSONBオブジェクトにあるかチェックする。
  • ?| — "どれかのキーが存在する"。指定したキーのうち、どれか1つでもあればOK。
  • ?& — "全てのキーが存在する"。指定したキーが全部あるかチェックする。

例: キーとその値でフィルタする。 例えば、productsテーブルがあって、detailsカラムに商品のJSONB情報が入ってるとする:

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name TEXT,
    details JSONB
);

データ例:

INSERT INTO products (name, details) VALUES
('Laptop', '{"brand": "Dell", "price": 1200, "specs": {"ram": "16GB", "cpu": "i7"}}'),
('Smartphone', '{"brand": "Apple", "price": 800, "specs": {"ram": "4GB", "cpu": "A15"}}'),
('Tablet', '{"brand": "Samsung", "price": 500, "specs": {"ram": "8GB", "cpu": "Exynos"}}');

結果:

id name details
1 Laptop {"brand": "Dell", "price": 1200, "specs": {"ram": "16GB", "cpu": "i7"}}
2 Smartphone {"brand": "Apple", "price": 800, "specs": {"ram": "4GB", "cpu": "A15"}}
3 Tablet {"brand": "Samsung", "price": 500, "specs": {"ram": "8GB", "cpu": "Exynos"}}

brandが"Apple"の商品を探したいとき:

SELECT *
FROM products 
WHERE details @> '{"brand": "Apple"}';

結果:

id name details
2 Smartphone {"brand": "Apple", "price": 800, "specs": {"ram": "4GB", "cpu": "A15"}}

全てのspecsキーがある商品を探したいなら、?演算子を使う:

SELECT *
FROM products 
WHERE details ? 'specs';

結果:

id name details
1 Laptop {"brand": "Dell", "price": 1200, "specs": {"ram": "16GB", "cpu": "i7"}}
2 Smartphone {"brand": "Apple", "price": 800, "specs": {"ram": "4GB", "cpu": "A15"}}
3 Tablet {"brand": "Samsung", "price": 500, "specs": {"ram": "8GB", "cpu": "Exynos"}}

全部の行にdetailsフィールドとspecsキーが入ってるね。

JSONBデータのソート

たまに、普通のカラムじゃなくて、JSONBの中の値でソートしたいこともあるよね。そんな時は->>(テキスト値の抽出)とCAST(型変換)を使うと便利!

例:商品の価格でソートしてみよう:

SELECT *
FROM products 
ORDER BY (details->>'price')::INTEGER;

結果:

id name details
3 Tablet {"brand": "Samsung", "price": 500, "specs": {"ram": "8GB", "cpu": "Exynos"}}
2 Smartphone {"brand": "Apple", "price": 800, "specs": {"ram": "4GB", "cpu": "A15"}}
1 Laptop {"brand": "Dell", "price": 1200, "specs": {"ram": "16GB", "cpu": "i7"}}

JSONBデータのグループ化

グループ化はデータを集計したり、統計を出したりするのに便利!例えば、ブランドごとに商品数を知りたいとき。

例:ブランドごとの商品数を数えてみよう:

SELECT
    details->>'brand' AS brand,
    COUNT(*) AS product_count
FROM products
GROUP BY details->>'brand';

結果:

brand product_count
Dell 1
Apple 1
Samsung 1

実践例

フィルタとグループ化。 ブランドごとに価格が600より高い商品の数を数えてみよう:

SELECT
    details->>'brand' AS brand,
    COUNT(*) AS product_count
FROM products
WHERE (details->>'price')::INTEGER > 600
GROUP BY details->>'brand';

結果:

brand product_count
Dell 1
Apple 1

グループ化後のソート。 今度はブランドを商品数でソートしてみよう:

SELECT
    details->>'brand' AS brand,
    COUNT(*) AS product_count
FROM products
GROUP BY details->>'brand'
ORDER BY product_count DESC;

複合クエリ: フィルタ、ソート、グループ化

例えば、600より高い商品があるブランドを探して、各ブランドごとに一番安い商品を選びたいときはこうやる:

WITH filtered_products AS (
    SELECT *
    FROM products
    WHERE (details->>'price')::INTEGER > 600
)
SELECT
    details->>'brand' AS brand,
    MIN((details->>'price')::INTEGER) AS min_price
FROM filtered_products
GROUP BY details->>'brand'
ORDER BY min_price;

結果:

brand min_price
Apple 800
Dell 1200

よくあるミスとアドバイス

ミス: 演算子の使い間違い。 ->->>を混同しないでね。前者はオブジェクト、後者はテキスト値を返すよ。

ミス: パフォーマンス問題。 複雑なクエリをよく使うなら、JSONBカラムにGINインデックスを作ろう。

ミス: 型の問題。 JSONBから取り出した値は文字列だから、CASTを忘れずに!

インデックス作成例:

CREATE INDEX idx_products_details ON products USING GIN (details);

これでdetails @> '{"brand": "Apple"}'みたいなフィルタがめっちゃ速くなるよ!

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION