0Pricing
SQL Interview Prep · レッスン

JOINのファンアウトと行の増加

JOINによってどちらのテーブルよりも多くの行が返る理由と、面接での確認方法を学びます。

「JOINのファンアウトと行の増加」はCoddyKit上の無料SQL Interview Prepレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはSQL Interview Prep学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 SQL Interview Prepコースには全4レッスンが含まれています。

結合が行を返しすぎる場合

面接でよく出る、単純そうでいて本質的な質問があります。「結合によって、大きい方のテーブルより多くの行が返ることはありますか」という質問です。答えは「はい」で、この現象はファンアウトまたは行の乗算と呼ばれます。

「結合は単にテーブルを組み合わせるだけです」と答える候補者は、この点を見落としています。正確な行数を予測できる候補者は採用されます。このレッスンでは、その予測力を身につけます。

原因: 1対多の一致

ファンアウトは、左側の1行が右側の複数行と一致するときに発生します。一致ごとに別々の出力行が生成されます。

顧客と注文の例では、Adaという1人の顧客に2件の注文があります。結合は注文ごとに1行を出力するため、Adaは重複します。顧客のフィールドは繰り返され、異なるのは注文のフィールドだけです。

SELECT c.name, o.amount
FROM customers c
JOIN orders o ON o.customer_id = c.id;
-- Ada appears twice (she has 2 orders)
-- name | amount
-- Ada  | 50
-- Ada  | 20
-- Bob  | 99

出力行数を数える

出力行数は顧客数ではなく、左側の各行に対する一致数の合計になります。

  • Ada -> 2件の注文 -> 2行
  • Bob -> 1件の注文 -> 1行
  • Cleo -> 0件の注文 -> 0行(INNER JOINで除外)

顧客テーブルも3行ですが、合計は3行です。Adaの注文数を10件に変えると、結果は11行に増えます。

多対多では爆発的に増える

同じキーに対して両側に複数の一致があると、ファンアウトはさらに大きくなります。キーKが左側に3回、右側に4回現れる場合、そのキーについて結合は3 x 4 = 12行を生成します。

このようにして、一見小さな結合が数百万行に膨れ上がることがあります。面接官は、乗算に気付けるかを見るために、両側に重複キーがある問題をよく出します。

-- left has 3 rows with tag 'A', right has 4 rows with tag 'A'
SELECT l.id, r.id
FROM left_t l
JOIN right_t r ON r.tag = l.tag;
-- tag 'A' alone yields 3 * 4 = 12 output rows

集計の落とし穴

ここでは、面接官が最もよく仕掛けるバグを紹介します。注文をorder_itemsと結合して明細を取得し、その後で注文金額をSUMします。各注文が複数の明細行にファンアウトするため、注文金額が明細ごとに1回ずつ数えられます。

その結果、SUMは大幅に過大になります。クエリは正しそうに見え、実際に実行もできるため、危険なバグです。

-- BUG: order.amount duplicated across items
SELECT SUM(o.amount) AS total
FROM orders o
JOIN order_items i ON i.order_id = o.id;
-- a 3-item order counts o.amount 3 times

過大計上を確認する

ある注文の金額が100で、明細が3行あるとします。結合によって3行が生成され、それぞれに金額100が含まれます。SUM(o.amount)は100ではなく300を返します。

正しい粒度で集計することが解決策です。明細を合計するか、注文を別途重複なしで集計してください。子テーブルとのファンアウトした結合をまたいで、親の値を決してSUMしないでください。

o.id | o.amount | i.id
7    | 100      | 71
7    | 100      | 72
7    | 100      | 73
-- SUM(o.amount) = 300  (WRONG, should be 100)

解決策1: 子側を先に集計する

最も明快な解決策は、サブクエリまたはCTEで多側を事前集計し、各親が1つの集計済み行だけに一致するようにすることです。ファンアウトも過大計上も発生しません。

ここでは、結合する前に明細を注文ごとに1行へまとめているため、親の金額が重複することはありません。

SELECT o.id, o.amount, i.item_count
FROM orders o
JOIN (
  SELECT order_id, COUNT(*) AS item_count
  FROM order_items
  GROUP BY order_id
) i ON i.order_id = o.id;

解決策2: COUNT(DISTINCT)と条件付き集計

ファンアウトした結合の後で集計する必要がある場合は、正しい粒度で数えるか合計してください。明細行ではなく注文を数えるには、COUNT(DISTINCT o.id)を使用します。

注意: SUM(DISTINCT o.amount)は安全な解決策ではありません。異なる2つの注文が同じ金額になることはあり、その場合は1つにまとめられてしまうためです。事前集計の方が信頼性は高くなります。

SELECT COUNT(DISTINCT o.id)   AS num_orders,
       COUNT(i.id)            AS num_items
FROM orders o
JOIN order_items i ON i.order_id = o.id;

ファンアウトを早期に検出する

面接官が好む簡単な診断方法があります。「1」の側になると想定している側で、結合キーが一意かどうかを確認してください。重複キー数が行数より少なければ、その側には重複があり、ファンアウトが発生します。

-- if this returns rows, order_id is NOT unique in order_items
SELECT order_id, COUNT(*) AS n
FROM order_items
GROUP BY order_id
HAVING COUNT(*) > 1;

COUNTで粒度を確認する

結合結果に対する集計を信頼する前に、行数が妥当か確認してください。手早い方法は、結合後の行数を、粒度になると想定しているテーブルの行数と比較することです。

結合に対するCOUNT(*)がordersのCOUNT(*)より大きければ、結合でファンアウトが発生しており、注文単位の集計は危険な状態です。この1行のチェックが、多くの面接での回答を救ってきました。

-- joined rows should equal order count if no fan-out
SELECT COUNT(*) AS joined_rows
FROM orders o
JOIN order_items i ON i.order_id = o.id;

SELECT COUNT(*) AS order_rows FROM orders;
-- joined_rows > order_rows  =>  fan-out present

ファンアウトは常にバグとは限らない

子側の行を1行ずつ表示したい場合もあります。注文ヘッダーとともにすべての明細行を一覧表示するなら、ファンアウトは正しい動作です。重要なのは、目的の粒度、つまり1つのエンティティから何行を生成すべきかを把握することです。

クエリを書く前に粒度を明確にしてください。「注文アイテムごとに1行」が必要なのか、「注文ごとに1行」が必要なのかによって、ファンアウトが機能になるかバグになるかが決まります。

確認問題

1対多の結合結果を予測してください。

まとめ: ファンアウトと行の乗算

覚えておくべきこと:

  • 結合は一致するペアごとに1行を生成するため、1対多の一致では「1」側が重複します。
  • 多対多のキーでは行数が乗算されます。そのキーについて、3 x 4 = 12行になります。
  • ファンアウトした結合をまたいで親の値を集計すると、合計や件数が過大になります。
  • 子側を事前集計するか、正しい粒度で数える・合計することで解決します(例: COUNT(DISTINCT))。
  • まず意図する粒度を明確にしてください。ファンアウトは、その粒度に反する場合にだけバグになります。

よくある質問

「JOINのファンアウトと行の増加」レッスンは無料ですか?

はい。「JOINのファンアウトと行の増加」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、SQL Interview Prepコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 SQL Interview Prepコースには全4レッスンが含まれています。

「JOINのファンアウトと行の増加」で何を学びますか?

JOINによってどちらのテーブルよりも多くの行が返る理由と、面接での確認方法を学びます。 ブラウザで直接実行するハンズオンコードでSQL Interview Prepを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

SQL Interview Prepを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのSQL Interview Prepは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「JOINのファンアウトと行の増加」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このSQL Interview Prepレッスンでコードを書いて実行できますか?

はい。すべてのSQL Interview Prepレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. INNER JOINが行を照合する仕組み
  2. JOINにおけるONとWHEREの違い
  3. JOINのファンアウトと行の増加
  4. 3つ以上のテーブルをJOINする
← SQL Interview Prepに戻る