複雑なクエリをすっきり整理!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を使うと階層構造の展開が容易になる。
- 大量データ処理時は一時テーブルとの使い分けを意識する。
参考リンク

0 件のコメント:
コメントを投稿