6. MS SQL GROUP BY句・HAVING句

 


🧑‍🤝‍🧑 GROUP BY句

皆さん、こんにちは!

前回、「集計関数SUM, COUNT, AVGなど)」を学び、テーブル全体の合計値などを出せるようになりました。しかし、「営業担当者ごとの合計売上」や「地域ごとの平均在庫数」のように、グループ別に集計したいですよね?

それを可能にするのが、今回学ぶ「GROUP BY句」です!


📐 第1章:GROUP BY句の基本とルール

1-1. GROUP BYとは?

GROUP BY句は、指定した列の同じ値を持つ行一つのグループにまとめ、そのグループごとに集計関数を実行させます。

1-2. SQL文における記述順序

GROUP BYは、WHERE句の直後に記述します。

SELECT
    [グループ化したい列],
    [集計関数(集計したい列)]
FROM
    [テーブル名]
WHERE
    [フィルタリング条件]
GROUP BY
    [グループ化したい列]; -- ★ WHEREの後、ORDER BYの前に置く

1-3. 🔑 最重要ルール:SELECT句の列の義務

GROUP BYを使う際、SELECT句に入れる列には厳しいルールがあります。

SELECT句のすべての列は、 ① GROUP BY句に含まれている または ② 集計関数の中に入っている (例: SUM(), COUNT() ) のどちらかを満たさなければならない。

【例】 SELECT SalesPersonID, SUM(TotalDue) とする場合、SalesPersonIDは集計関数に入っていないため、必ずGROUP BY SalesPersonIDと書く必要があります。


🔢 第2章:GROUP BYの実行例

2-1. 【例1】営業担当者ごとの合計売上を出す

Sales.SalesOrderHeaderテーブルから、各営業担当者(SalesPersonID)がどれだけの売上(TotalDue)を上げたかを合計します。

SELECT
    SalesPersonID,              -- ① グループの「名前」として表示
    SUM(TotalDue) AS TotalSales -- ② グループごとの「合計」を計算
FROM
    Sales.SalesOrderHeader
GROUP BY
    SalesPersonID;              -- ③ SalesPersonIDでグループ化

👉 結果: SalesPersonIDが同じ行は一つにまとめられ、そのグループのTotalDueが合計されます。NULLも一つのユニークなグループとして集計されます。

2-2. 【例2】製品ごとの合計在庫数と保管場所の数を数える

Production.ProductInventoryテーブルから、各製品(ProductID)の合計在庫と、何箇所に保管されているかを数えます。

SELECT
    ProductID,                  -- ① 製品ID
    SUM(Quantity) AS TotalInStock, -- ② 在庫数の合計
    COUNT(*) AS TotalLocations  -- ③ その製品IDの行数(=場所の数)
FROM
    Production.ProductInventory
GROUP BY
    ProductID;                  -- ④ ProductIDでグループ化

📊 第3章:複数列でのグループ化と結合(JOIN)

3-1. 複数列での階層的なグループ化

複数の列でグループ化すると、より詳細な集計が可能です。列の指定順が階層になります。

【例】 「地域(Territory)ごと」、さらに「その中の担当者ごと」の売上を合計する。(売上期間を2006年に限定)

SELECT
    TerritoryID,
    SalesPersonID,
    SUM(TotalDue) AS TotalSales
FROM
    Sales.SalesOrderHeader
WHERE
    OrderDate BETWEEN '2006-01-01' AND '2006-12-31' -- WHEREで計算対象の行を先に絞り込み
GROUP BY
    TerritoryID, SalesPersonID -- まずTerritoryIDで分け、次にSalesPersonIDで分ける
ORDER BY 1, 2;

3-2. JOINを使ってIDを名前に置き換える(実用的な応用)

IDではなく、名前でグループ化したい場合、JOINで関連テーブルを結合してからGROUP BYを使います。

【例】 2006年の売上を「地域名」と「担当者名」でグループ化

SELECT
    T.Name AS TerritoryName,    -- 地域名
    P.FirstName + ' ' + P.LastName AS SalesPersonName, -- 担当者のフルネーム (結合関数)
    SUM(SOH.TotalDue) AS TotalSales
FROM
    Sales.SalesOrderHeader SOH
-- 地域名を取得
INNER JOIN Sales.SalesTerritory T ON SOH.TerritoryID = T.TerritoryID
-- 担当者名を取得
INNER JOIN Sales.SalesPerson SP ON SOH.SalesPersonID = SP.BusinessEntityID
INNER JOIN Person.Person P ON SP.BusinessEntityID = P.BusinessEntityID
WHERE
    SOH.OrderDate BETWEEN '2006-01-01' AND '2006-12-31'
GROUP BY
    T.Name,                     -- 地域名でグループ化
    P.FirstName + ' ' + P.LastName; -- 担当者フルネームでグループ化

👉 ポイント: SELECT句でフルネームを結合関数FirstName + ' ' + LastName)として定義した場合、GROUP BY句でも全く同じ関数P.FirstName + ' ' + P.LastName)を指定する必要があります。


🛡️HAVING句

GROUP BYグループ化した結果を、さらに特定の条件で絞り込むための強力なツールです!


⚔️ 第4章:HAVING句の役割とWHERE句との違い

4-1. HAVING句とは?

HAVING句は、GROUP BYで作成されたグループに対して、集計関数の結果をもとにフィルタリング(絞り込み) を行うために使用されます。

4-2. WHERE句との決定的な違い

項目WHERE句HAVING句
対象個々の行(Row)グループ(Group)
使う条件通常の列の値集計関数の結果 (例: SUM(), COUNT())
評価タイミングGROUP BYGROUP BY

💡 大事なこと: WHERE句では、SUM(TotalDue)のような集計関数は使えません。集計結果を絞り込むときは、必ずHAVING句を使います。

4-3. SQL文における記述順序

HAVING句は、GROUP BY句の直後に書かれます。

SELECT
    [グループ化したい列], [集計関数(...)]
FROM
    [テーブル名]
WHERE
    [個々の行を絞り込む条件] -- 処理順:2番目
GROUP BY
    [グループ化したい列] -- 処理順:3番目
HAVING
    [グループを集計結果で絞り込む条件] -- 処理順:4番目
ORDER BY
    [並び替え] -- 処理順:最後

🔎 第5章:HAVING句の活用例

5-1. 【例1】売上が400万ドル以上の地域を抽出する

まず、地域(テリトリー)ごとの合計売上を出してから、その売上額が400万ドル以上のグループだけを残します。

SQL
SELECT
    ST.Name AS TerritoryName,
    SUM(SOH.TotalDue) AS TotalSales2006
FROM
    Sales.SalesOrderHeader SOH
INNER JOIN
    Sales.SalesTerritory ST ON SOH.TerritoryID = ST.TerritoryID
WHERE
    SOH.OrderDate BETWEEN '2006-01-01' AND '2006-12-31' -- ★ 行のフィルタリング(2006年分に絞る)
GROUP BY
    ST.Name
HAVING
    SUM(SOH.TotalDue) > 4000000 -- ★ グループのフィルタリング(合計が400万ドル超のグループだけを残す)
ORDER BY
    TotalSales2006 DESC;

👉 結果: 400万ドル以上の売上を達成した地域(グループ)だけが結果として表示されます。

5-2. 【例2】所属製品が15個以上のサブカテゴリを抽出する

製品の数が少ないサブカテゴリを結果から除外します。ここでいう「グループ」はサブカテゴリです。

SELECT
    PS.Name AS SubcategoryName,
    COUNT(*) AS ProductCount
FROM
    Production.Product P
INNER JOIN
    Production.ProductSubcategory PS ON P.ProductSubcategoryID = PS.ProductSubcategoryID
GROUP BY
    PS.Name
HAVING
    COUNT(*) >= 15 -- ★ 製品数(COUNTの結果)が15以上のグループだけを残す

🔬 第6章:HAVING句の高度な使い方

6-1. 複数条件の適用(AND/OR)

WHERE句と同様に、HAVING句でもANDORといった論理演算子を使って、複数の集計条件を組み合わせることができます。

【例】 「合計売上200万ドル以上」かつ「合計注文数75件以上」の担当者を抽出する。

SELECT
    SalesPersonID,
    SUM(TotalDue) AS TotalSalesAmount,
    COUNT(*) AS TotalSalesCount
FROM
    Sales.SalesOrderHeader
WHERE
    SalesPersonID IS NOT NULL -- WHEREで行を絞り込む(NULLの担当者を除外)
    AND OrderDate BETWEEN '2006-01-01' AND '2006-12-31'
GROUP BY
    SalesPersonID
HAVING
    SUM(TotalDue) >= 2000000 -- 条件1: 売上が200万以上
    AND
    COUNT(*) >= 75;          -- 条件2: 注文数が75件以上

👉 ポイント: このクエリでは、SELECT句に入れたすべての集計関数(SUM(TotalDue)COUNT(*))をHAVING句の条件に使っています。

これで、SQLの主要な6つの句(SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY)すべてを学びました!これらの句を使いこなせば、データベースから必要な情報を思い通りに引き出し、分析することができるようになります。