結合の実践演習
「商品ごとの売上を出して」と言われて内部結合で書くと、1 個も売れていない商品が一覧から消えます。売れ筋の順位は見えますが、「どれが売れていないのか」という、たいてい本当に知りたいほうの情報が落ちています。
レポートの 1 行が何を表すかを、先に決める
今回作るのは「商品ごとに 1 行」のレポートです。商品が 5 件あるなら、注文が 0 件の商品も含めて 5 行出るのが正解になります。1 行が何を表すかを決めれば、主軸のテーブルが決まります。主軸を FROM の側に置き、そこから関連するテーブルを広げていきます。
いきなり集計まで書かず、まず結合だけを書いて 1 度実行してみてください。
SQL クエリ
-- 段階 1 結合だけ書いて、右が NULL の行が残るかを確かめる
SELECT b.title, r.rating
FROM books b
LEFT JOIN reviews r ON b.id = r.book_id;ここで相手のいない行が NULL を伴って並んでいれば、土台は正しく組めています。行数が足りなければ、結合の種類を間違えています。
空欄と 0 は、読み手にとって別のもの
集計の対象になる行が 1 つも無いグループでは、SUM も AVG も 0 ではなく NULL を返します。レポートに空欄が並ぶと、集計漏れなのか本当に 0 なのかが読み手に伝わりません。COALESCE(SUM(...), 0) の形で包み、0 だと言い切るところまでを SQL の仕事にします。
なお COUNT だけは対象が無くても 0 を返すので、包む必要がありません。集計関数によって振る舞いが違う点は覚えておいてください。
GROUP BY の書き漏らしは、環境が変わってから出る
SQL クエリ
-- title が GROUP BY に無い。標準の設定ではエラーになる
SELECT b.id, b.title, COUNT(r.id)
FROM books b
LEFT JOIN reviews r ON b.id = r.book_id
GROUP BY b.id;SELECT に並べた列のうち、集計関数で包んでいないものは、すべて GROUP BY にも書きます。MySQL の緩い設定では書き漏らしても動いてしまうため、手元で通ったクエリが本番で落ちる、という形で表面化しがちです。最初から全部書く癖を付けておくのが安全です。
並べ替えのキーは複数指定できます。1 つ目が同点になったときに順序が実行のたびに入れ替わると差分が取りづらいので、2 つ目に
idのような一意な列を足しておくと結果が安定します。
テーブル構造
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(50),
price INT
);
CREATE TABLE orders (
id INT PRIMARY KEY,
order_date DATE
);
CREATE TABLE order_items (
id INT PRIMARY KEY,
order_id INT,
product_id INT,
quantity INT
);
INSERT INTO products VALUES
(1, 'マウス', 2500),
(2, 'キーボード', 8000),
(3, 'モニター', 35000),
(4, 'ヘッドセット', 6000),
(5, 'ウェブカメラ', 4500);
INSERT INTO orders VALUES
(1001, '2026-05-01'),
(1002, '2026-05-03'),
(1003, '2026-05-10');
INSERT INTO order_items VALUES
(1, 1001, 1, 3),
(2, 1001, 2, 1),
(3, 1002, 3, 1),
(4, 1003, 1, 2);期待される出力
| product_id | product_name | total_quantity | total_revenue |
|---|---|---|---|
| 3 | モニター | 1 | 35000 |
| 1 | マウス | 5 | 12500 |
| 2 | キーボード | 1 | 8000 |
| 4 | ヘッドセット | 0 | 0 |
| 5 | ウェブカメラ | 0 | 0 |