4. MS SQL 「JOIN(結合)」入門

 


🔗 「JOIN(結合)」入門

皆さん、こんにちは!

SQL学習において、今回学ぶ「JOIN(結合)」は最も重要なコンセプトです。データベースのデータは効率化のため、いくつものテーブルに分割されています。この分割された情報を一つの結果として取り出すためにJOINを使います。


💡 第1章:なぜJOINが必要なのか?(正規化の考え方)

1-1. データの「バラバラ収納」の理由

データベースでは、従業員の名前や住所、給与、部署など、すべての情報を一つの巨大なテーブルに入れることはしません。これを「正規化」といいます。

【例】

  • HumanResources.Employee テーブル:ID、職務タイトル、社会保障番号など

  • Person.Person テーブル:ID、First Name(名)Last Name(姓) など

もし、従業員テーブルから「ID」「職務タイトル」と「名前」を一緒に見たければ、別のテーブルに分かれている「名前」の情報を持ってくる必要があります。これがJOINの役割です。

1-2. JOINの役割

JOINとは、共通する列(キー) を使って、複数のテーブルの列一つの結果セットとして返す方法です。


🤝 第2章:INNER JOIN(内部結合)とは?

結合には4種類ありますが、今回は最もよく使う「INNER JOIN(内部結合)」を学びます。

2-1. INNER JOINの考え方:完全一致

INNER JOINは、2つのテーブルAとBがあったとき、「両方のテーブルに存在する行」だけを返す方法です。ベン図でいうと、2つの円が重なった中央の領域です。

テーブルAテーブルB結果(INNER JOIN)
🔑 結合キーの値が両方にある行両方にある行返される
🔑 結合キーの値がAにしかない返されない

2-2. 構文とテーブル別名(エイリアス)

JOINは、FROM句の直後に記述します。

SELECT
    [カラム名], ...
FROM
    [テーブルA] [別名A] -- ① 結合元テーブルと別名
INNER JOIN
    [テーブルB] [別名B] -- ② 結合するテーブルと別名
ON
    [別名A].[共通キー] = [別名B].[共通キー]; -- ③ 結合する条件(どの列同士が一致するか)

👉 テーブル別名 (Alias) の重要性: FROM Production.Product P のように、テーブル名に短い別名(PやPSなど) をつけることで、その後のクエリで P.Name のように簡潔に列を指定できます。


📝 第3章:INNER JOINの実践例

3-1. 【例1】製品名とカテゴリ名を結合する

Production.Productテーブル(製品情報)と、Production.ProductSubcategoryテーブル(サブカテゴリ名)を結合し、「製品の名前」と「サブカテゴリの名前」を一緒に表示します。

🔑 共通のキー: ProductSubcategoryID

SELECT
    P.Name AS ProductName,          -- Pテーブルから製品名
    PS.Name AS SubcategoryName      -- PSテーブルからサブカテゴリ名
FROM
    Production.Product P            -- ① 製品テーブルを P と命名
INNER JOIN
    Production.ProductSubcategory PS -- ② サブカテゴリテーブルを PS と命名
ON
    P.ProductSubcategoryID = PS.ProductSubcategoryID; -- ③ 結合キー

【解説】

  1. SQLはPの各行を見ます。

  2. 行にProductSubcategoryID(例:14)があれば、PSテーブルに行き、IDが14に一致する行を探します。

  3. 一致する行(マッチ) が見つかれば、そのPSの行にあるName(サブカテゴリ名)を、Pの行と結合して結果として返します。

  4. PテーブルでProductSubcategoryIDNULL(サブカテゴリがない製品)の場合は、PSテーブルに一致するIDが存在しないため、その製品行は結果に返されません(これがINNER JOINの特徴です!)。

3-2. 【例2】人物情報とメールアドレス、電話番号を結合する

一人の人物の「名・姓」と「メールアドレス」「電話番号」という、3つの異なるテーブルの情報を一度に結合します。

🔑 共通のキー: BusinessEntityID

SELECT
    P.FirstName,
    P.LastName,
    E.EmailAddress, -- Eテーブルからメールアドレス
    PP.PhoneNumber  -- PPテーブルから電話番号
FROM
    Person.Person P             -- 1. 人物テーブルを P と命名
INNER JOIN
    Person.EmailAddress E       -- 2. メールテーブルを E と結合
ON
    P.BusinessEntityID = E.BusinessEntityID -- 結合条件1

INNER JOIN
    Person.PersonPhone PP       -- 3. 電話テーブルを PP と結合
ON
    P.BusinessEntityID = PP.BusinessEntityID; -- 結合条件2

👉 ポイント: 必要な数だけINNER JOINを繰り返して記述できます。すべてのJOIN条件が満たされた行だけが結果として表示されます。


🖼️ 第4章:外部結合(OUTER JOIN)の考え方

4-1. LEFT JOIN(左外部結合)とは

LEFT JOINは、結合の際に「FROM句で先に指定したテーブル(左側のテーブル)」のすべての行を保持し、結合を行います。

テーブルA(左)テーブルB(右)結果
全行保持結合キーが一致した行のみ返すテーブルAの情報はすべて残る
🔑 一致するキーがある一致するデータ⭕ データが揃う
🔑 一致するキーがないデータなしNULL(ヌル)になる

【イメージ】お客様リスト(A) は全部見たい。もしその人が 従業員(B) であれば、その情報も添えてね。従業員じゃなくても、お客様リストからは消さないで!」

4-2. RIGHT JOIN(右外部結合)とは

RIGHT JOINは、LEFT JOINの逆で、「JOINキーワードの後に指定したテーブル(右側のテーブル)」のすべての行を保持します。

💡 豆知識:LEFTとRIGHTは本質的に同じ LEFT JOINRIGHT JOINは、テーブルの記述順序を変えれば全く同じ結果になります。

  • テーブルA LEFT JOIN テーブルB

  • テーブルB RIGHT JOIN テーブルA この2つは同じ意味です。そのため、通常はLEFT JOINを使うことに統一し、左側のテーブルを主役として扱うことが一般的です。


📝 第5章:LEFT JOINの実践例

5-1. 【例】すべての製品とそのカテゴリ名を表示する

INNER JOINの例を思い出してください。サブカテゴリIDがない製品は、結果から除外されていました。LEFT JOINを使えば、その製品も残せます。

🔑 共通のキー: ProductSubcategoryID

SELECT
    P.Name AS ProductName,
    PS.Name AS SubcategoryName
FROM
    Production.Product P          -- ① 左側 (主役) : すべての製品情報
LEFT OUTER JOIN                   -- ※ OUTER は省略可 (LEFT JOINでOK)
    Production.ProductSubcategory PS -- ② 右側 : サブカテゴリ情報
ON
    P.ProductSubcategoryID = PS.ProductSubcategoryID; -- 結合条件

👉 結果: 504行すべてが返されます。

  • サブカテゴリIDが一致する製品 SubcategoryNameに名前が入る

  • サブカテゴリIDが NULL の製品 SubcategoryNameの列に NULL が表示されるが、製品情報自体は残る。

5-2. 複数テーブルの結合とNULL値対策

営業案件(SalesOrderHeader)の情報を見たいとき、営業担当者(SalesPerson)が設定されていない注文もあります(SalesPersonIDがNULL)。

このような場合も、LEFT JOINを使えば「すべての注文情報」を失わずに結合できます。

SELECT
    SOH.SalesOrderNumber,
    P.FirstName,
    P.LastName,
    SOH.TotalDue AS SalesAmount
FROM
    Sales.SalesOrderHeader SOH      -- ① 主役は「すべての注文」
LEFT JOIN
    Sales.SalesPerson SP            -- ② 営業担当者テーブル
ON
    SOH.SalesPersonID = SP.BusinessEntityID -- 注文IDと担当者IDを結合
LEFT JOIN
    HumanResources.Employee E       -- ③ 担当者情報から従業員情報へ
ON
    SP.BusinessEntityID = E.BusinessEntityID
LEFT JOIN
    Person.Person P                 -- ④ 従業員情報から氏名テーブルへ
ON
    E.BusinessEntityID = P.BusinessEntityID;
-- 💡 SalesPersonIDがNULLの注文でも、SOHの情報はすべて保持される。
--    FirstName/LastNameの列には NULL が表示される。

📝 第6章:結合とフィルタリング(WHERE/ORDER BY)の連携

LEFT JOINなどで取得した複雑な結果に対して、WHERE句ORDER BY句を適用するのは、通常のクエリと同じです。

6-1. WHERE句による絞り込み

WHERE句すべてのJOINが完了した後に適用されます。

【例】 「上記のすべての注文のうち、テリトリー名が 'Northwest' のものに絞り込みたい」

-- 既存のLEFT JOINクエリ...
LEFT JOIN Sales.SalesTerritory T ON SOH.TerritoryID = T.TerritoryID
WHERE
    T.Name = 'Northwest' -- 結合後のテリトリー列を使って絞り込み!

6-2. ORDER BY句による並び替え

【例】 「絞り込んだ結果を、SalesAmount(売上金額)の降順に並び替えたい」

-- 既存のWHERE句クエリ...
ORDER BY
    SOH.TotalDue DESC; -- 総額(TotalDue)で降順に並び替え

👉 ポイント: 複雑なクエリを書くときは、どのテーブルからデータが来ているかを明確にするため、テーブル別名(例: SOH.TotalDue を付けて列を指定する習慣をつけましょう。これにより、クエリの可読性が大幅に向上します。