結論:JOINで件数が増えるのは「キーが重複している」か「粒度が合っていない」
SQLでJOINした瞬間に件数や金額が増えるとき、原因はほぼ次のどちらかです。
- 結合キーが片側(または両側)で重複している
- JOINするテーブル同士の粒度が一致していない
DISTINCTで無理やり直すと「たまたま合ったように見える」だけで、後から別の集計で壊れやすいです。
この記事では、原因を最短で特定して、正しく直すための手順をまとめます。
この記事でわかること
- JOINで件数が増える代表原因(7パターン)
- 原因特定のチェック手順
- 正しい直し方例
まずは用語:粒度とは?
粒度=「1行が何を表しているか」です。
例:
- 注文テーブル:1行=注文(order_id)
- 注文明細テーブル:1行=注文×商品(order_id, product_id)
- 顧客テーブル:1行=顧客(customer_id)
粒度が違うもの同士をJOINすると、意図せず行が増えることがあります。
よくある原因7つ
原因1:1対多(One-to-Many)を意図せずJOINしている
注文(1行)に注文明細(複数行)をJOINすると、注文が明細数だけ増えます。
これは正しい挙動ですが、「注文数を数えたい」のに明細JOINしているのがミスです。
原因2:多対多(Many-to-Many)になっている(最も危険)
片側ももう片側もキーが重複していると、掛け算で爆増します。
例:
A側に同じcustomer_idが2行、B側にも同じcustomer_idが3行
→ JOINするとそのcustomer_idだけで 2×3=6行になります。
原因3:ディメンション(マスタ)側に重複がある
「マスタだから1キー1行だと思ったら、実は重複していた」パターン。
例:商品マスタに同一product_idが複数(版管理/誤登録)
原因4:JOINキーがズレている(トリム/大小文字/NULL/型)
表面上同じに見えても、実は一致していない → 別のキーでJOINしてしまい、結果が増えることがあります。
- スペース混入(’A ‘ と ‘A’)
- 大小文字(abc と ABC)
- 型が違う(文字列と数値)
- NULLが混ざっている
原因5:期間JOIN(有効期間JOIN)が複数マッチしている
start_date/end_dateなど日付を表すカラムでのJOINは、範囲が重なっていると複数ヒットして増えます。
原因6:ブリッジ(中間)テーブルが必要なのに直接JOINしている
顧客×施策、ユーザー×ロールなど、多対多関係は中間テーブルが必要です。
直接JOINすると増えがちです。
原因7:JOINした後の集計軸が変わっている(集計の粒度崩れ)
JOIN自体は正しくても、「金額を足す」ときの粒度が崩れて二重計上になります。
原因特定のチェック手順
本記事で使用するテーブル/項目一覧
1) orders(注文ヘッダ:粒度=注文1件=1行)
- order_id:注文ID(主キー。注文を一意に識別)
- customer_id:顧客ID(注文した顧客を識別。顧客マスタ等と結合に使う)
目的:注文単位(order_id単位)で件数・売上などを扱う起点テーブル
2) order_items(注文明細:粒度=注文×商品など=複数行/注文)
- order_id:注文ID(外部キー。ordersと結合するキー)
- amount:明細金額(行単位の金額。合計すると注文金額になる想定)
目的:注文を明細に分解したテーブル。order_id は重複するのが自然(1対多)
手順0:JOIN前後で「何が増えたか」を数値で把握する
-- JOIN前
SELECT COUNT(*) AS order_rows, COUNT(DISTINCT order_id) AS order_ids
FROM orders;
-- JOIN後
SELECT COUNT(*) AS joined_rows, COUNT(DISTINCT order_id) AS order_ids
FROM orders o
LEFT JOIN order_items i ON o.order_id = i.order_id;
ポイント:
- COUNT(*) と COUNT(DISTINCT キー) を両方見る
- 「行が増えた」のか「キーの種類が増えた」のかを切り分けます
手順1:結合キーが片側で重複してないか確認(超重要)
-- order_items側で order_id が重複してないか
SELECT order_id, COUNT(*) AS cnt
FROM order_items
GROUP BY order_id
HAVING COUNT(*) > 1
ORDER BY cnt DESC;
- ここで出る重複が「仕様(注文明細)として正しい」のか
- それとも「マスタ重複でおかしい」のか を判断します。
手順2:JOINキーが両側で重複していないか(多対多チェック)
-- A側(orders)のキー重複
SELECT order_id, COUNT(*) cnt
FROM orders
GROUP BY order_id
HAVING COUNT(*) > 1;
-- B側(items)のキー重複
SELECT order_id, COUNT(*) cnt
FROM order_items
GROUP BY order_id
HAVING COUNT(*) > 1;
両方出る場合、多対多の疑いが濃いです。
手順3:増えた“犯人キー”を特定する(差分を炙り出す)
SELECT o.order_id,COUNT(*) AS joined_cnt
FROM orders o
LEFT JOIN order_items i ON o.order_id = i.order_id
GROUP BY o.order_id
HAVING COUNT(*) > 1
ORDER BY joined_cnt DESC;
「どのorder_idで何倍になっているか」が分かると、調査が一気に楽になります。
手順4:粒度が一致しているかチェック(JOIN設計の本質)
自分に質問してください:
- 最終的に欲しい結果は 「注文単位」? 「注文明細単位」? 「顧客単位」?
粒度が「注文単位」なのに明細をJOINするなら、次の“直し方例”へ。
直し方例
直し方:先に集計してからJOINする(粒度を揃える)
「注文単位で売上を見たい」なら、明細を注文単位へ集計してからJOINします。
WITH items_agg AS (
SELECT
order_id,
SUM(amount) AS order_amount,
COUNT(*) AS line_cnt
FROM order_items
GROUP BY order_id
)
SELECT
o.order_id,
o.customer_id,
a.order_amount,
a.line_cnt
FROM orders o
LEFT JOIN items_agg a ON o.order_id = a.order_id;
DISTINCTは使っていい?(答え:原則“最後の手段”)
SELECT DISTINCT は「増えた理由を隠してしまう」ことがあります。
OKなケース:
- 重複が確実に同一内容で、業務上も同一とみなして良い
- かつ、どこで重複が発生しているか把握済み
NGなケース:
- 金額が増えるのを“見た目だけ”直したい
- 後工程で粒度が変わる可能性がある(だいたい壊れます)
実務で使える“最短テンプレ”(困ったらこれ)
- JOIN前後で COUNT(*) と COUNT(DISTINCT キー) を比較
- 結合キーの重複を GROUP BY HAVING COUNT(*) > 1 で確認
- 増えるキーを特定(どのキーが何倍か)
- 粒度を決める(注文単位/明細単位/顧客単位)
- 粒度が違うなら 集計してからJOIN
次に読む
- 【ステップ2】SQL入門(基礎の復習)
- データ分析の落とし穴7選(数字が合わない典型)
- 【ステップ1】Excel基礎(粒度・集計の考え方の入り口)
-160x90.png)



