前回の講義では、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"}'みたいなフィルタがめっちゃ速くなるよ!
GO TO FULL VERSION