2025年12月5日金曜日

SQL Server コモンテーブル式(CTE)の使い方と再帰クエリの実務活用

複雑なクエリをすっきり整理!SQL ServerのCTE(コモンテーブル式)と再帰クエリを徹底解説

SQL Serverで複雑なSELECT文を書くと、ネストが深くなり読みづらくなることがあります。
そんなときに便利なのがCTE(Common Table Expression:コモンテーブル式)です。
CTEを使うと、サブクエリを一時的な名前付きテーブルとして扱えるため、可読性が高く保守しやすいSQLを書くことができます。
この記事では、CTEの基本構文と再帰CTEの仕組み、実務での活用方法をわかりやすく解説します。

目次

CTE(コモンテーブル式)とは?

サブクエリを仮想テーブルとして扱う仕組み

CTEは、SELECT文の前に定義する一時的な結果セットです。
一度定義したCTEを、同一クエリ内で複数回参照できます。
サブクエリと似ていますが、名前を付けて再利用できる点が大きな特徴です。


WITH CTE名 AS (
    SELECT 列名 FROM テーブル WHERE 条件
)
SELECT * FROM CTE名;

「WITH句」でCTEを定義し、その後に通常のSELECT文で呼び出します。

CTEを使うメリット

  • クエリを論理的に分割でき、読みやすくなる
  • サブクエリを繰り返し使う場面で効率的
  • 再帰クエリ(ツリー構造や階層構造)を簡潔に記述できる

CTEの基本構文と使い方

基本構文


WITH CTE_Sales AS (
    SELECT CustomerID, SUM(TotalAmount) AS TotalSales
    FROM Orders
    GROUP BY CustomerID
)
SELECT c.CustomerName, s.TotalSales
FROM CTE_Sales AS s
JOIN Customers AS c ON c.CustomerID = s.CustomerID;

この例では、CTE_Sales で顧客ごとの売上を集計し、後続のSELECTで顧客情報と結合しています。

複数CTEを定義する

カンマで区切ることで、複数のCTEを連続定義できます。


WITH
CTE_Orders AS (
    SELECT * FROM Orders WHERE OrderDate >= '2025-01-01'
),
CTE_Customers AS (
    SELECT CustomerID, CustomerName FROM Customers
)
SELECT c.CustomerName, o.OrderDate
FROM CTE_Orders AS o
JOIN CTE_Customers AS c ON o.CustomerID = c.CustomerID;

サブクエリよりも見通しがよく、複雑なクエリを段階的に整理できます。

複数CTEの連結と入れ子の活用

CTEを段階的に処理する

複数CTEを組み合わせることで、集計 → フィルタリング → 結合 などを段階的に実装できます。


WITH
CTE_Base AS (
    SELECT CategoryID, COUNT(*) AS ProductCount
    FROM Products
    GROUP BY CategoryID
),
CTE_Filtered AS (
    SELECT * FROM CTE_Base WHERE ProductCount >= 10
)
SELECT c.CategoryName, f.ProductCount
FROM CTE_Filtered AS f
JOIN Categories AS c ON f.CategoryID = c.CategoryID;

このように「一時テーブル的に」中間処理を整理できるのがCTEの強みです。

再帰CTEの基本構造と実行の流れ

再帰CTEとは?

再帰CTE(Recursive CTE)は、CTE自身を再帰的に呼び出すことで、階層構造をたどる仕組みです。
組織ツリーや親子関係を持つテーブルをたどるときに活用されます。

構文パターン


WITH CTE_Employee AS (
    -- 基底部(初期レコード)
    SELECT EmployeeID, ManagerID, Name, 0 AS Level
    FROM Employees
    WHERE ManagerID IS NULL

    UNION ALL

    -- 再帰部(CTEを自己参照)
    SELECT e.EmployeeID, e.ManagerID, e.Name, c.Level + 1
    FROM Employees AS e
    INNER JOIN CTE_Employee AS c ON e.ManagerID = c.EmployeeID
)
SELECT * FROM CTE_Employee;

実行の流れは「基底部 → 再帰部 → 繰り返し → 結果結合」です。
ManagerIDがNULLの上位社員を起点に、下位階層を順に展開します。

再帰CTEの実務活用例(階層データの取得)

例:部署階層を展開する


WITH CTE_Department AS (
    SELECT DeptID, DeptName, ParentDeptID, 0 AS Level
    FROM Departments
    WHERE ParentDeptID IS NULL

    UNION ALL

    SELECT d.DeptID, d.DeptName, d.ParentDeptID, c.Level + 1
    FROM Departments AS d
    INNER JOIN CTE_Department AS c ON d.ParentDeptID = c.DeptID
)
SELECT REPLICATE(' ', Level * 2) + DeptName AS IndentedName
FROM CTE_Department
ORDER BY Level, DeptName;

結果は、階層に応じてインデントを付けたツリー構造として表示されます。
Excelのピボット展開のように階層データを1テーブルで管理できるのが再帰CTEの強みです。

例:メニュー構造やコメントツリー

WebアプリやECサイトでは、カテゴリ・コメント・メニューなど階層構造を持つデータが頻繁に登場します。
再帰CTEを使うと、アプリ側でループ処理せずにSQLだけで展開可能です。

CTEを使う際の注意点とパフォーマンス

  • CTEは一時的な論理ビュー:物理的な一時テーブルではなく、都度展開される。
  • 複雑すぎる再帰は制限あり:再帰深度はデフォルト100回。OPTION (MAXRECURSION n)で変更可。
  • パフォーマンスに注意:CTEは中間結果をキャッシュしないため、大量データには一時テーブルの方が効率的な場合も。
  • 再帰とORDER BYの組み合わせに注意:CTE内ではORDER BYは意味を持たず、最終SELECTで指定する。

まとめと次のステップ

学んだ内容の整理

  • CTEはサブクエリを名前付きの仮想テーブルとして扱う仕組み。
  • 複数CTEを組み合わせることで複雑なSQLを整理できる。
  • 再帰CTEを使うと階層構造の展開が容易になる。
  • 大量データ処理時は一時テーブルとの使い分けを意識する。

参考リンク

SQL Server 解説用イメージ

0 件のコメント:

コメントを投稿