🔗 「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つの円が重なった中央の領域です。
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; -- ③ 結合キー
【解説】
SQLは
Pの各行を見ます。行に
ProductSubcategoryID(例:14)があれば、PSテーブルに行き、IDが14に一致する行を探します。一致する行(マッチ) が見つかれば、その
PSの行にあるName(サブカテゴリ名)を、Pの行と結合して結果として返します。PテーブルでProductSubcategoryIDが NULL(サブカテゴリがない製品)の場合は、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) であれば、その情報も添えてね。従業員じゃなくても、お客様リストからは消さないで!」
4-2. RIGHT JOIN(右外部結合)とは
RIGHT JOINは、LEFT JOINの逆で、「JOINキーワードの後に指定したテーブル(右側のテーブル)」のすべての行を保持します。
💡 豆知識:LEFTとRIGHTは本質的に同じ
LEFT JOINとRIGHT 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) を付けて列を指定する習慣をつけましょう。これにより、クエリの可読性が大幅に向上します。
