SQLのJOINは、共通する列を条件にして、複数のテーブルを組み合わせる書き方です。一致した行だけが必要ならINNER JOIN、左のテーブルをすべて残したいならLEFT JOINを使います。

たとえば「顧客名と注文した商品を一緒に表示したい」ときに使います。この記事では顧客と注文の小さなデータで、書き方と結果の違いを見ていきましょう。

実行環境はSQL Server 2019 LocalDBです。練習用データの作成部分はSQL Server用なので、MySQLなどで試す場合は一時テーブルや文字列の書き方を読み替えてください。

INNER JOINとLEFT JOINの違い

書き方結果に残る行使う場面
INNER JOIN結合条件に一致した組み合わせだけ注文のある顧客と商品を表示する
LEFT JOIN左側の全行と、一致した右側の行注文のない顧客も表示する

INNER JOINは「内部結合」、LEFT JOINは「左外部結合」と呼ばれます。JOINだけならINNER JOIN、LEFT OUTER JOINはLEFT JOINと同じ意味です。

LEFT JOINでいう「左」は、FROMの直後に書いたテーブルです。右側に一致する行がないと、右側の列はNULLになります。NULLは、空文字や0とは別の値です。

実習用の顧客・注文データを用意する

SSMSで検証用の接続を開き、「新しいクエリ」に次のSQLを貼り付けて実行します。SSMSの準備がまだの場合は、SQL Serverの導入と接続方法を確認してください。

SET NOCOUNT ON;
DROP TABLE IF EXISTS #JoinOrders;
DROP TABLE IF EXISTS #JoinCustomers;

CREATE TABLE #JoinCustomers (
    CustomerID int PRIMARY KEY,
    CustomerName nvarchar(20) NOT NULL
);
CREATE TABLE #JoinOrders (
    OrderID int PRIMARY KEY,
    CustomerID int NOT NULL,
    ProductName nvarchar(20) NOT NULL,
    OrderStatus nvarchar(10) NOT NULL
);

INSERT INTO #JoinCustomers VALUES
    (1, N'サンプルA店'),
    (2, N'サンプルB店'),
    (3, N'サンプルC店');

INSERT INTO #JoinOrders VALUES
    (101, 1, N'ノート', N'確定'),
    (102, 1, N'ペン', N'未確定'),
    (103, 2, N'バッグ', N'確定');

先頭に#が付くテーブルは、この接続で使うローカル一時テーブルです。既存の顧客・注文テーブルは使いません。以降のSELECTは、作成したときと同じクエリウィンドウで実行してください。接続を閉じると一時テーブルも削除されます。

顧客は次の3件です。サンプルC店には注文がありません。

顧客ID顧客名
1サンプルA店
2サンプルB店
3サンプルC店

注文は次の3件です。CustomerIDで、どの顧客の注文なのかを表します。

注文ID顧客ID商品名注文状況
1011ノート確定
1021ペン未確定
1032バッグ確定

INNER JOINで一致した行だけ取得する

SELECT c.CustomerID AS 顧客ID, c.CustomerName AS 顧客名,
       o.OrderID AS 注文ID, o.ProductName AS 商品名
FROM #JoinCustomers AS c
INNER JOIN #JoinOrders AS o
    ON c.CustomerID = o.CustomerID
ORDER BY c.CustomerID, o.OrderID;

ONの条件で、顧客側と注文側のCustomerIDを対応させています。cとoはテーブルの別名で、c.CustomerIDのように書くと「どちらのテーブルの列か」が分かります。

結果は3行です。A店は注文が2件あるので2行、B店は1行になります。注文のないC店は含まれません。

INNER JOINで注文のある顧客だけが3行表示された結果
SSMSで実行したINNER JOINの結果。A店が2行、B店が1行で、C店は含まれません。

SELECTやORDER BYの基本は、SELECT文の使い方でも説明しています。この記事では結果を見比べやすいように、顧客ID・注文IDの順で並べています。

LEFT JOINで注文のない顧客も残す

INNER JOINをLEFT JOINに変えて実行してみましょう。

SELECT c.CustomerID AS 顧客ID, c.CustomerName AS 顧客名,
       o.OrderID AS 注文ID, o.ProductName AS 商品名
FROM #JoinCustomers AS c
LEFT JOIN #JoinOrders AS o
    ON c.CustomerID = o.CustomerID
ORDER BY c.CustomerID, o.OrderID;

結果は4行です。C店も残り、注文IDと商品名はNULLになります。A店には注文が2件あるので、LEFT JOINでも1行にはまとまりません。

LEFT JOINでC店とNULLの注文欄を含め4行表示された結果
SSMSで実行したLEFT JOINの結果。注文のないC店も残り、注文IDと商品名がNULLになります。

JOINは「左の1行につき結果も1行」とは限りません。一致する注文が2件あれば2行になります。行が増えたときは、まず結合する列と、相手側の同じ値が何件あるかを確認しましょう。

LEFT JOINの条件はONとWHEREで結果が変わる

「確定した注文だけを表示したい。ただし注文のない顧客も残したい」という場合は、注文の条件をONに足します。

ONに条件を書く:顧客をすべて残す

SELECT c.CustomerID AS 顧客ID, c.CustomerName AS 顧客名,
       o.OrderID AS 注文ID, o.ProductName AS 商品名
FROM #JoinCustomers AS c
LEFT JOIN #JoinOrders AS o
    ON c.CustomerID = o.CustomerID
   AND o.OrderStatus = N'確定'
ORDER BY c.CustomerID, o.OrderID;

結合相手を「顧客IDが同じ、かつ注文が確定している行」に限定します。結果はA店・B店・C店の3行で、C店の注文欄はNULLです。

WHEREに条件を書く:条件を満たさない行を除く

SELECT c.CustomerID AS 顧客ID, c.CustomerName AS 顧客名,
       o.OrderID AS 注文ID, o.ProductName AS 商品名
FROM #JoinCustomers AS c
LEFT JOIN #JoinOrders AS o
    ON c.CustomerID = o.CustomerID
WHERE o.OrderStatus = N'確定'
ORDER BY c.CustomerID, o.OrderID;

こちらは結合した結果をWHEREで絞り込みます。C店の注文状況はNULLなので「確定」という条件を満たさず、結果から外れます。結果はA店とB店の2行です。

ONで絞ると3行、WHEREで絞ると2行になる実行結果
上がONで絞った3行、下がWHEREで絞った2行です。C店が残るかどうかを見比べてください。
条件を書く場所この例の結果C店
ON3行残る
WHERE2行残らない

これは右側の注文状況を条件にした場合の違いです。WHEREを使うと必ずLEFT JOINがINNER JOINになる、という意味ではありません。残したい行を決めてから、条件を書く場所を選びましょう。

注文のない顧客だけを探す

LEFT JOINで結合したあと、右側の注文IDがNULLになった行を取り出します。

SELECT c.CustomerID AS 顧客ID, c.CustomerName AS 顧客名
FROM #JoinCustomers AS c
LEFT JOIN #JoinOrders AS o
    ON c.CustomerID = o.CustomerID
WHERE o.OrderID IS NULL
ORDER BY c.CustomerID;

この例では、顧客IDが3のサンプルC店だけが表示されます。注文IDは主キーでNULLを許可していないため、一致する注文がなかったかどうかを判断できます。

NULLの判定にはIS NULLを使います。「= NULL」では、この結果を取り出せません。また、NULLを許可する商品名などで判定すると、注文が存在する行まで拾うことがあるため注意しましょう。

JOINでつまずいたときの確認ポイント

「列名があいまいです」と出る

両方のテーブルにあるCustomerIDなどは、c.CustomerID、o.CustomerIDのように別名を付けて指定します。表示したい列が、どちらのテーブルにあるかも確認してください。

予想より行数が増える

同じ顧客に注文が複数あれば、その組み合わせの数だけ行が返ります。結合条件の列が違っていないか、複数列で対応させる必要がないかを確認します。理由を確かめずにDISTINCTで消すと、必要な注文までまとめてしまうことがあります。

LEFT JOINなのに顧客が消える

右側の列をWHEREで絞り込んでいないか見直します。全顧客を残して注文の結合条件だけを変えたい場合は、この記事のONとWHEREの例を比べてみてください。

一時テーブルが見つからない

別のクエリウィンドウへ移ったり、接続し直したりすると、作成した一時テーブルは使えません。同じ接続で準備用SQLを実行してから、JOINの例を試してください。

まとめ

一致した組み合わせだけならINNER JOIN、左側の全行を残すならLEFT JOINです。まずは結果が3行と4行に分かれる理由を、顧客と注文の対応で確認してみましょう。

LEFT JOINで右側のデータを絞り込むときは、ONとWHEREの違いも大切です。「結合する相手を絞る」のか、「結合後の行を絞る」のかを意識すると、意図した結果を作りやすくなります。

結合の種類と条件の詳しい仕様は、Microsoft公式のJOIN解説も参考になります。

実行確認:2026年10月27日、SQL Server 2019 LocalDBで掲載例の結果を確認しました。画像はSSMS 22の実際の結果画面から、表の部分だけを撮影しています。