SQLのJOINで件数が増える原因|重複と粒度のチェック手順(初心者向け)

JOINで件数が増える/原因と直し方 実務で使うデータ・AIスキル

結論: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なケース:

  • 金額が増えるのを“見た目だけ”直したい
  • 後工程で粒度が変わる可能性がある(だいたい壊れます)

実務で使える“最短テンプレ”(困ったらこれ)

  1. JOIN前後で COUNT(*) と COUNT(DISTINCT キー) を比較
  2. 結合キーの重複を GROUP BY HAVING COUNT(*) > 1 で確認
  3. 増えるキーを特定(どのキーが何倍か)
  4. 粒度を決める(注文単位/明細単位/顧客単位)
  5. 粒度が違うなら 集計してからJOIN

次に読む

  • 【ステップ2】SQL入門(基礎の復習)
  • データ分析の落とし穴7選(数字が合わない典型)
  • 【ステップ1】Excel基礎(粒度・集計の考え方の入り口)

タイトルとURLをコピーしました