コマンド道場

CROSS JOIN の使い方

ひとことで言うと
受注 10

顧客 5

対応がある組み合わせ
10 通り
返ってくる組み合わせ(全部)
50 通り

緑のマスは「実際に対応がある組み合わせ」です。CROSS JOIN は対応の有無に関係なく、赤いマスも含めて全部返します。

CROSS JOIN は、左のすべての行と右のすべての行を総当たりで組み合わせます。 結果の件数は掛け算です。事故で起きると件数が爆発しますが、「全パターンの枠を先に作る」という正しい使い道もあります。

主要 7 製品で通用 そのまま使える: PostgreSQL / MySQL / MariaDB / SQLite / SQL Server / Oracle Database / IBM Db2

製品ごとの対応表(実測)を見る

このページの実行結果と図は、すべて本物のデータベースに同じ SQL を流して得たものです。手で書いた「たぶんこうなる」ではありません。

01件数は掛け算になる

結論: `CROSS JOIN` の結果は「左の行数 × 右の行数」です。

条件を書く場所がありません。ON は付けられず、付けようとすると構文エラーになります。総当たりなので絞り込む余地が無い、と考えると分かりやすいはずです。

10 行 × 5 行なら 50 行。小さいうちは平気ですが、1 万行 × 1 万行は 1 億行です。実務の表で不用意に書くと返ってきません。

1 行ずつ、何をしているか
  1. SELECT j.juchu_id, k.kokyaku_name何を出すか
  2. FROM juchu j左(全行)
  3. CROSS JOIN kokyaku k右(全行)と総当たり
  4. ORDER BY j.juchu_id, k.kokyaku_id並び順

ON を書く行がありません。ここが他の結合と決定的に違うところです。

件数だけ数えてみる
SELECT count(*) AS kensu
FROM juchu
CROSS JOIN kokyaku;
kensu
50

1

受注 10 行 × 顧客 5 行 = 50 行。掛け算そのものです。

中身を見る(1 件の受注に全顧客がぶら下がる)
SELECT j.juchu_id, k.kokyaku_name
FROM juchu j
CROSS JOIN kokyaku k
ORDER BY j.juchu_id, k.kokyaku_id;
juchu_idkokyaku_name
1001青葉工業
1001白樺物産
1001黒松システム
1001赤坂商会
1001三田商事
1002青葉工業

50 … うち先頭 6 行を表示

02カンマ区切りは CROSS JOIN と同じ

FROM a, b とカンマで並べる古い書き方は、`CROSS JOIN` とまったく同じ意味です。

つまり結合条件を WHERE に書き忘れると、そのまま総当たりになります。「意図しない CROSS JOIN」はこうして生まれます。

新しく書くときは `JOIN ... ON` を使ってください。 結合条件を書く場所が構文として決まっているので、書き忘れに気づけます。

カンマ区切りと CROSS JOIN の件数を並べる
SELECT count(*) AS kanma, (SELECT count(*) FROM juchu CROSS JOIN kokyaku) AS cross_join
FROM juchu, kokyaku;
kanmacross_join
5050

1

同じ数です。書き方が違うだけで、やっていることは同じです。

03正しい使い道は「全パターンの枠を作る」

総当たりが欲しい場面があります。代表例が集計表の枠づくりです。

「顧客 × 月」のように、データが 1 件も無い組み合わせも 0 として表に出したいとき、先に総当たりの枠を作っておいて、そこへ実績を外部結合します。こうすると「実績が無い月が行ごと抜ける」という問題が起きません。

ふつうに集計しただけでは、実績が無い組み合わせは行そのものが存在しません。グラフや帳票にすると、そこだけ歯抜けになります。枠を先に作るのは、この歯抜けを防ぐためです。

売れなかった商品を 0 件として並べたい、空いている時間枠を見つけたい、といった用途も同じ形です。「無いことを見せたい」ときに CROSS JOIN が要る、と覚えてください。

顧客 × 商品の全組み合わせを、買っていない分も 0 として出す
SELECT k.kokyaku_name, s.shohin_name, count(m.juchu_id) AS kensu
FROM kokyaku k
CROSS JOIN shohin s
LEFT JOIN juchu j ON j.kokyaku_id = k.kokyaku_id AND j.status = 'done'
LEFT JOIN juchu_meisai m ON m.juchu_id = j.juchu_id AND m.shohin_id = s.shohin_id
GROUP BY k.kokyaku_name, s.shohin_name
ORDER BY k.kokyaku_name, s.shohin_name;
kokyaku_nameshohin_namekensu
三田商事キーボード0
三田商事デスクチェア0
三田商事デスクライト0
三田商事ノート0
三田商事ボールペン0
三田商事付箋0
三田商事書棚0
白樺物産キーボード1

35 … うち先頭 8 行を表示

CROSS JOIN で枠を作り、実績を外部結合で乗せています。0 の行が消えないのがこの書き方の利点です。

04⚠️ よくある間違い:事故なのか意図なのかを、書き方で示す

件数が想定より多いとき、原因はたいてい結合条件の書き忘れです。

そして厄介なことに、カンマ区切りで書いてあると事故か意図かが読み取れません。書いた本人にしか分からない状態になります。

総当たりが意図なら `CROSS JOIN` と明示的に書いてください。 レビューする人が「これはわざとだ」と分かります。逆に CROSS JOIN と書いていないのに総当たりになっていたら、それは事故です。

気づき方: 結果の件数を、元の表の件数と比べてください。掛け算に近い数になっていたら総当たりが起きています。行数が多い表では返ってくる前に気づけないので、**まず count(*) だけ実行して件数を確かめる**のが安全です。

条件はあるが結合条件が無い(事故の典型)
SELECT j.juchu_id, k.kokyaku_name
FROM juchu j, kokyaku k
WHERE j.status = 'done'
ORDER BY j.juchu_id, k.kokyaku_id;
juchu_idkokyaku_name
1001青葉工業
1001白樺物産
1001黒松システム
1001赤坂商会
1001三田商事
1002青葉工業

40 … うち先頭 6 行を表示

WHERE があるので一見それらしく見えますが、顧客とつなぐ条件がありません。成立した受注 8 件 × 顧客 5 社で 40 行返っています。

自分で打ってみる

このページの例と同じデータが入った本物のデータベースを、この場(あなたのブラウザの中)で起動できます。打った SQL がサーバーへ送られることはありません。

押すと初回だけ約 9MB を読み込みます(2 回目以降はキャッシュされます)。

解いてみる

読んだだけでは書けるようになりません。実行結果で採点します。

関連するトピック

根拠(一次情報)