みなさんこんにちは!ヒロポンです!
LEFT JOIN で書いたのに、なぜか件数が減る。「あれ、全部残るはずやのに??」って夜中にログ追ったこと、あります。
原因はシンプルで、結合の種類を分かってなかっただけ。
SQL Server の結合は INNER / LEFT / RIGHT / FULL / CROSS の5種類。どれを選ぶかで結果の件数が変わるんですよね。ここが腹落ちしてないと集計がズレて、原因究明に時間を持っていかれます。
今回はSQL Server JOIN 種類の5つを、同じデータで件数がどう変わるか見せながら、1つずつ潰していきます。
そもそも JOIN とは — 2つのテーブルを横に突き合わせる
JOIN は、2つのテーブルをキーで突き合わせて横につなげる操作です。
たとえば「注文テーブル」と「顧客テーブル」。注文には顧客ID しか入ってないのに、画面には顧客名を出したい。そういう時、顧客ID をキーにして2つを結合します。
問題は「一致しなかった行をどうするか」。ここの扱いが、5種類の違いそのものなんですよね。
検証用データ(注文と顧客)
話を具体的にするために、このデータで統一します。
CREATE TABLE dbo.Customers (Id INT, Name NVARCHAR(20));
CREATE TABLE dbo.Orders (OrderId INT, CustomerId INT);
INSERT INTO dbo.Customers VALUES (1, N'田中'), (2, N'佐藤'), (3, N'鈴木');
INSERT INTO dbo.Orders VALUES (101, 1), (102, 1), (103, 2), (104, NULL);
わざと「はみ出す」データを2つ仕込んでます。
- 顧客3(鈴木)は注文が無い(顧客側にだけ存在)
- 注文104 は CustomerId が NULL(注文側にだけ存在・顧客不明)
この「はみ出し」が、結合の種類でどう扱われるか。ここを追っていきます。
INNER JOIN — 両方に一致する行だけ
一番基本のやつ。両方のテーブルにキーが一致する行だけを返します。
SELECT o.OrderId, c.Name
FROM dbo.Orders o
INNER JOIN dbo.Customers c ON o.CustomerId = c.Id;
結果は 3件(101→田中 / 102→田中 / 103→佐藤)。
注文104(CustomerId が NULL)と顧客3(鈴木・注文なし)は、相手がいないので消えます。「両方にある行だけ」。ここだけ覚えとけばいい。
LEFT JOIN と RIGHT JOIN — 片方を全部残す
LEFT JOIN は、左のテーブル(FROM 側)を全部残す。右に相手がいなければ、右の列は NULL で埋まります。
SELECT o.OrderId, c.Name
FROM dbo.Orders o
LEFT JOIN dbo.Customers c ON o.CustomerId = c.Id;
結果は 4件。INNER の3件に加えて、注文104 が Name = NULL で残ります(注文は全部残るから)。こんな感じで、左は1行も落ちません。
RIGHT JOIN はその逆。右のテーブル(Customers)を全部残す。
SELECT o.OrderId, c.Name
FROM dbo.Orders o
RIGHT JOIN dbo.Customers c ON o.CustomerId = c.Id;
結果は 4件。INNER の3件に加えて、顧客3(鈴木)が OrderId = NULL で残ります(顧客は全部残るから)。
実務では LEFT JOIN が圧倒的に多い。RIGHT はテーブルの順番を入れ替えれば LEFT で書けるので、こんな感じで「基本は LEFT に寄せる」と読みやすくなります。
FULL JOIN と CROSS JOIN — 全行、そして全組み合わせ
FULL JOIN は、両方のはみ出しを全部残す。LEFT と RIGHT を合体させたイメージですね。こんな感じで、どっちも落とさない。
SELECT o.OrderId, c.Name
FROM dbo.Orders o
FULL JOIN dbo.Customers c ON o.CustomerId = c.Id;
結果は 5件。一致した3件+注文104(Name NULL)+顧客3(OrderId NULL)。「どっちのテーブルからも1行も落としたくない」時に使います。
CROSS JOIN は毛色が違う。ON 句なしで全組み合わせ(直積)を作ります。
SELECT o.OrderId, c.Name
FROM dbo.Orders o
CROSS JOIN dbo.Customers c;
結果は 12件(注文4 × 顧客3)。全部の掛け合わせなので件数が一気に膨らむ。カレンダー表を作る時なんかに使いますが、業務でうっかり書くと件数爆発の事故になるので注意です。
5種類の結果を1枚で見る
同じデータで、件数がこう変わります。ここが今回の核心です!!

実行結果(5種類 + ハマり①の件数を SQL Server 2019 互換エンジンで実測):

「件数が想定と違う」時は、まずこの表で「自分はどれを使ってるか」を確認する。これが一番早いです。
ハマりポイント: 知らないと件数がズレるやつ
結合まわりで業務SEがよく踏むのは、この2つ。
① LEFT JOIN したのに WHERE で右列を条件にすると、実質 INNER になる
正直、俺が夜中に溶かしたのがこれです。LEFT JOIN で全部残したつもりが、WHERE に右テーブルの列を書いた瞬間、NULL 行が消えて実質 INNER JOINになってた。
-- ❌ LEFT のつもりが、WHERE で右列を条件にすると NULL 行が落ちる
SELECT o.OrderId, c.Name
FROM dbo.Orders o
LEFT JOIN dbo.Customers c ON o.CustomerId = c.Id
WHERE c.Name = N'田中'; -- c.Name が NULL の注文104 は、この条件で除外される
WHERE c.Name = '田中' は、Name が NULL の行を問答無用で落とします(NULL は条件に一致しないので)。せっかく LEFT で残した NULL 行が、ここで消える。あなたもこれ、やったことあるでしょ?
直し方。右テーブルへの絞り込みは WHERE じゃなく ON 句に書く。
-- ✅ 右列の条件は ON 句へ。LEFT の NULL 行は残る
SELECT o.OrderId, c.Name
FROM dbo.Orders o
LEFT JOIN dbo.Customers c ON o.CustomerId = c.Id AND c.Name = N'田中';
教訓はLEFT JOIN で右テーブルを絞るなら、条件は ON に書く。WHERE に書くと、LEFT が INNER に化けます。
② 結合キーに NULL があると、一致しない
これもハマりやすいやつ。SQL の NULL は「値が不明」なので、NULL = NULL は成立しません(真にならない)。
今回の注文104 は CustomerId が NULL。だから顧客テーブルに「Id が NULL の顧客」を入れても、o.CustomerId = c.Id では結合されない。NULL 同士は等しくない。ん?NULL なのに一致せえへんの??ってなるやつです。
「結合キーに NULL が混じるデータ」を扱う時は、ISNULL(CustomerId, -1) みたいに NULL を実在しない値へ置き換えてから結合する。もしくは、そもそも結合キーに NULL を入れない設計にする。どっちかが安全です。
俺の現場での考え方
流通系の基幹システムの保守をやってた頃、結合の種類を意識せずコピペで JOIN を書いてる箇所が山ほどあって、件数が合わない調査でよく時間を取られてました。
そこから落とし込んだのはシンプルで、こう決めてます。
- 基本は INNER。「相手がいない行も残したい」時だけ LEFT に切り替える
- LEFT を書いたら、右テーブルの条件は必ず
ON句(WHEREに書かない) - 結合キーに NULL がありうるなら、結合前に潰す
「件数が想定と違う」と思ったら、まず結合の種類と WHERE の位置を見る。これだけで調査時間がガクッと減ります。
まとめ
要点はこれだけ。
INNER=両方に一致する行だけ /LEFT=左を全部 /RIGHT=右を全部 /FULL=両方の全行 /CROSS=全組み合わせ- 使う種類で件数が変わる。「想定と違う」ならまず種類を疑う
LEFT JOINの右列条件はWHEREではなくONに書く(でないと実質 INNER)- 結合キーの NULL は一致しない。事前に潰すか設計で避ける
SQL Server の結合や実行計画をもっと深く押さえたいなら、RDB の仕組み系の解説書を1冊持っておく。この手の「なんで件数が違う?」を調べる時間が、まるっと浮きます。
このガイド、結合クエリを書く時の手元メモにでも使ってください。
よくある質問
Q1. LEFT JOIN と LEFT OUTER JOIN は違いますか?
同じです。OUTER は省略できるキーワードで、LEFT JOIN と LEFT OUTER JOIN はまったく同じ動きをします。RIGHT / FULL も同様に OUTER を省略できます。INNER も省略して単に JOIN と書けますが、意図を明示するために書いておくのが無難です。
Q2. 結合したら件数が増えました。なぜですか?
結合キーが「1対多」になってるのが原因のことが多いです。今回の例でも、顧客1(田中)に注文が2件あるので、田中は結果に2行出ます。1対多の結合では、多い側の件数に膨らむ。想定より増えたら、結合キーの重複を疑ってください。重複排除は SQL Server DISTINCT の3つの罠 も参考になります。
Q3. INNER JOIN と WHERE での結合、どちらを使うべきですか?
WHERE a.Id = b.Id の書き方(暗黙結合)でも INNER JOIN と同じ結果になりますが、INNER JOIN ... ON の明示結合を推奨します。結合条件と絞り込み条件が分離されて読みやすく、LEFT/RIGHT への変更もしやすい。古いコードで暗黙結合を見かけたら、明示結合に直しておくと保守が楽になります。
次に読むべき記事





以上!
同じ「LEFT のはずが件数減った」で溶かした人いたら、どんどんシェア待ってるぜ!!
執筆者
バイブス父さん — 業務 SE 7 年 (SIer 正社員 2 / フリーランス 5)。 現職は SEO 直轄部の AI アドバイザー兼 PL、 副業で中小 SIer の CTO。 SIer の正社員からフリーランスに転じ、 複数のエージェント経由で案件を回してきた経験ベースで「業務 SE 視点」 の技術 + キャリア記事を書いています。
🐦 X: @hiro_progra0524 (日々の現場メモ更新中)
📝 About Me で経歴詳細を見る



