11. MS SQL CTE (共通テーブル式)

 


👑 CTE (共通テーブル式)

前回、FROM句の中で一時テーブルを作る導出テーブルを学びましたね。今回は、それと並ぶ、あるいはそれ以上に強力で、プロのSQLエンジニアが最も愛用するテクニックの一つ「CTE(Common Table Expression:共通テーブル式)」を紹介します!


💡 第1章:CTEとは何か?なぜ使うのか?

1-1. CTEの正体

CTEは、クエリの先頭WITH句を使って定義する一時的な名前付きの結果セットです。導出テーブルと同様に一時的な仮想テーブルですが、構文が大きく異なります。

項目導出テーブル(Derived Table)CTE(共通テーブル式)
定義場所FROM句の中(括弧内)メインクエリのWITH句を使用)
再利用基本的に不可(同じ定義を何度も書く必要がある)一つのクエリ内で複数回参照可能
可読性複雑なクエリだと読みにくい複雑なクエリをステップ順に整理しやすい

1-2. CTEの基本構文

CTEは、必ずSELECT文の直前に定義します。

-- ① まず、「WITH [CTEの名前] AS」で定義を始める
WITH SalesDetails AS (
    -- ② 内部クエリ:この結果が一時テーブルとなる
    SELECT
        YEAR(OrderDate) AS OrderYear,
        SalesOrderID
    FROM
        Sales.SalesOrderHeader
)
-- ③ メインクエリ:FROM句でCTEの名前をテーブルのように呼び出す
SELECT
    OrderYear,
    COUNT(*) AS SalesCount
FROM
    SalesDetails -- ★ FROM句でSalesDetailsをテーブル名のように使う
WHERE
    OrderYear = 2006
GROUP BY
    OrderYear;

⚠️ 実行時の注意点: CTEを定義した後、必ず続けてメインクエリ(SELECT文)を実行する必要があります。WITHからSELECTの最後までをまとめてハイライトして実行しましょう。


🔄 第2章:CTEの最大メリット:再利用と自己結合

導出テーブルでは、同じ処理を複数回使いたい場合、その定義全体を何度も書く必要がありました。しかし、CTEなら一度定義すれば何度でも参照できます

2-1. 自己結合(Self Join)がスッキリ!

「今年の売上」と「去年の売上」を同じ行で比較したい場合、自分自身とテーブルを結合する自己結合が必要です。

【CTEを使った解決策】

  1. SalesByYearというCTEを一度だけ定義し、「年ごとの合計売上」を計算します。

  2. メインクエリで、そのSalesByYearを「CurrentYearSales」と「PriorYearSales」という別々の別名二回参照します。

WITH SalesByYear AS (
    -- ★ 定義はここだけ!年と合計売上を計算
    SELECT
        YEAR(OrderDate) AS SalesYear,
        SUM(TotalDue) AS TotalSales
    FROM
        Sales.SalesOrderHeader
    GROUP BY
        YEAR(OrderDate)
)
SELECT
    CY.SalesYear,
    CY.TotalSales AS AnnualSales,
    PY.TotalSales AS PriorYearSales -- 前年の売上
FROM
    SalesByYear AS CY               -- 💡 1回目:Current Yearとして参照
LEFT OUTER JOIN
    SalesByYear AS PY               -- 💡 2回目:Prior Yearとして参照
    ON CY.SalesYear - 1 = PY.SalesYear; -- 今年の年 - 1 = 前年の年で結合

👉 結果: 導出テーブルで同じクエリを二度書く必要がなくなり、非常にシンプルで間違いのないコードになりました!

2-2. 複数のCTEを連続で定義(処理の段階分け)

CTEは、コンマ(,)で区切ることで、複数の定義を連続で書くことができます。さらに、後のCTEは前のCTEを参照できます。これにより、複雑な処理をまるでプログラムのステップのように段階的に実行できます。

WITH S1 AS ( -- ステップ1: 年を切り出す
    SELECT
        YEAR(OrderDate) AS SalesYear, TotalDue
    FROM Sales.SalesOrderHeader
),
S2 AS ( -- ステップ2: S1の結果を使って集計する
    SELECT
        SalesYear,
        COUNT(*) AS SalesCount,
        SUM(TotalDue) AS AnnualSales
    FROM
        S1 -- ★ S1をFROM句で参照!
    GROUP BY SalesYear
)
-- 最終ステップ: S2の結果をフィルタリングして表示
SELECT
    SalesYear, SalesCount, AnnualSales
FROM
    S2 -- ★ S2をFROM句で参照!
WHERE
    SalesCount > 5000;

この「処理の段階分け」こそが、CTEがクエリの可読性を高める最大の理由です。複雑なクエリを書く際は、まずCTEを使う習慣をつけましょう!