サーチ…
構文
WITH QueryName [(ColumnName、...)] AS(
SELECT ...
)
SELECT ... FROM QueryName ...;WITH RECURSIVE QueryName [(ColumnName、...)] AS(
SELECT ...
UNION [すべて]
SELECT ... FROM QueryName ...
)
SELECT ... FROM QueryName ...;
備考
公式ドキュメント: WITH句
共通表式は一時的な結果セットであり、複雑な副問合せの結果である可能性があります。 WITH句を使用して定義します。 CTEは可読性を向上させ、Temp TableとTable変数が作成されるTempDBデータベースではなくメモリに作成されます。
Common Table Expressionsの主な概念:
- 複雑なクエリ、特に複雑な結合やサブクエリを分割するために使用できます。
- クエリ定義をカプセル化する方法です。
- 次のクエリが実行されるまで持続します。
- 正しく使用すると、コードの品質/保守性と速度の両方が向上します。
- 同じステートメント内で結果の表を複数回参照することができます(SQLの重複を排除します)。
- ビューの一般的な使用が必要ない場合、ビューの代わりになることができます。つまり、定義をメタデータに格納する必要はありません。
- 定義されているときではなく、呼び出されたときに実行されます。 CTEがクエリで複数回使用されている場合、CTEは複数回実行されます(結果が異なる可能性があります)。
一時的なクエリ
これらはネストされたサブクエリと同じように動作しますが、構文は異なります。
WITH ReadyCars AS (
SELECT *
FROM Cars
WHERE Status = 'READY'
)
SELECT ID, Model, TotalCost
FROM ReadyCars
ORDER BY TotalCost;
ID | モデル | 総費用 |
---|---|---|
1 | フォードF-150 | 200 |
2 | フォードF-150 | 230 |
同等のサブクエリ構文
SELECT ID, Model, TotalCost
FROM (
SELECT *
FROM Cars
WHERE Status = 'READY'
) AS ReadyCars
ORDER BY TotalCost
再帰的に木に登る
WITH RECURSIVE ManagersOfJonathon AS (
-- start with this row
SELECT *
FROM Employees
WHERE ID = 4
UNION ALL
-- get manager(s) of all previously selected rows
SELECT Employees.*
FROM Employees
JOIN ManagersOfJonathon
ON Employees.ID = ManagersOfJonathon.ManagerID
)
SELECT * FROM ManagersOfJonathon;
イド | FName | LName | 電話番号 | マネージャーID | DepartmentId |
---|---|---|---|---|---|
4 | ジョンナトン | スミス | 1212121212 | 2 | 1 |
2 | ジョン | ジョンソン | 2468101214 | 1 | 1 |
1 | ジェームス | スミス | 1234567890 | ヌル | 1 |
値を生成する
ほとんどのデータベースには、臨時使用のための一連の数値を生成する固有の方法はありません。ただし、共通テーブル式を再帰で使用して、そのタイプの関数をエミュレートすることができます。
次の例では、数字1〜5の行を持つ列i
を持つNumbers
という共通テーブル式を生成します。
--Give a table name `Numbers" and a column `i` to hold the numbers
WITH Numbers(i) AS (
--Starting number/index
SELECT 1
--Top-level UNION ALL operator required for recursion
UNION ALL
--Iteration expression:
SELECT i + 1
--Table expression we first declared used as source for recursion
FROM Numbers
--Clause to define the end of the recursion
WHERE i < 5
)
--Use the generated table expression like a regular table
SELECT i FROM Numbers;
私 |
---|
1 |
2 |
3 |
4 |
5 |
この方法は、任意の番号間隔および他のタイプのデータとともに使用できます。
再帰的に列挙する列挙型
WITH RECURSIVE ManagedByJames(Level, ID, FName, LName) AS (
-- start with this row
SELECT 1, ID, FName, LName
FROM Employees
WHERE ID = 1
UNION ALL
-- get employees that have any of the previously selected rows as manager
SELECT ManagedByJames.Level + 1,
Employees.ID,
Employees.FName,
Employees.LName
FROM Employees
JOIN ManagedByJames
ON Employees.ManagerID = ManagedByJames.ID
ORDER BY 1 DESC -- depth-first search
)
SELECT * FROM ManagedByJames;
レベル | ID | FName | LName |
---|---|---|---|
1 | 1 | ジェームス | スミス |
2 | 2 | ジョン | ジョンソン |
3 | 4 | ジョンナトン | スミス |
2 | 3 | マイケル | ウィリアムズ |
再帰的CTEを使用したOracle CONNECT BY機能
OracleのCONNECT BY機能は、SQL標準の再帰的CTEを使用する際に組み込みのない、多くの便利で重要な機能を提供します。この例では、SQL Serverの構文を使用して、これらの機能を複製しています(完全性のためにいくつか追加しています)。 Oracleの開発者は、他のデータベースの階層的な問合せに欠けている多くの機能を見つけるのに最も役立ちますが、一般的に階層的な問合せで何ができるかを示すこともできます。
WITH tbl AS (
SELECT id, name, parent_id
FROM mytable)
, tbl_hierarchy AS (
/* Anchor */
SELECT 1 AS "LEVEL"
--, 1 AS CONNECT_BY_ISROOT
--, 0 AS CONNECT_BY_ISBRANCH
, CASE WHEN t.id IN (SELECT parent_id FROM tbl) THEN 0 ELSE 1 END AS CONNECT_BY_ISLEAF
, 0 AS CONNECT_BY_ISCYCLE
, '/' + CAST(t.id AS VARCHAR(MAX)) + '/' AS SYS_CONNECT_BY_PATH_id
, '/' + CAST(t.name AS VARCHAR(MAX)) + '/' AS SYS_CONNECT_BY_PATH_name
, t.id AS root_id
, t.*
FROM tbl t
WHERE t.parent_id IS NULL -- START WITH parent_id IS NULL
UNION ALL
/* Recursive */
SELECT th."LEVEL" + 1 AS "LEVEL"
--, 0 AS CONNECT_BY_ISROOT
--, CASE WHEN t.id IN (SELECT parent_id FROM tbl) THEN 1 ELSE 0 END AS CONNECT_BY_ISBRANCH
, CASE WHEN t.id IN (SELECT parent_id FROM tbl) THEN 0 ELSE 1 END AS CONNECT_BY_ISLEAF
, CASE WHEN th.SYS_CONNECT_BY_PATH_id LIKE '%/' + CAST(t.id AS VARCHAR(MAX)) + '/%' THEN 1 ELSE 0 END AS CONNECT_BY_ISCYCLE
, th.SYS_CONNECT_BY_PATH_id + CAST(t.id AS VARCHAR(MAX)) + '/' AS SYS_CONNECT_BY_PATH_id
, th.SYS_CONNECT_BY_PATH_name + CAST(t.name AS VARCHAR(MAX)) + '/' AS SYS_CONNECT_BY_PATH_name
, th.root_id
, t.*
FROM tbl t
JOIN tbl_hierarchy th ON (th.id = t.parent_id) -- CONNECT BY PRIOR id = parent_id
WHERE th.CONNECT_BY_ISCYCLE = 0) -- NOCYCLE
SELECT th.*
--, REPLICATE(' ', (th."LEVEL" - 1) * 3) + th.name AS tbl_hierarchy
FROM tbl_hierarchy th
JOIN tbl CONNECT_BY_ROOT ON (CONNECT_BY_ROOT.id = th.root_id)
ORDER BY th.SYS_CONNECT_BY_PATH_name; -- ORDER SIBLINGS BY name
CONNECT BYの機能と上記の説明と説明:
- 句
- CONNECT BY:階層を定義する関係を指定します。
- START WITH:ルートノードを指定します。
- 注文の順番:注文結果は適切です。
- パラメーター
- NOCYCLE:ループが検出されたときに分岐の処理を停止します。有効な階層はDirected Acyclic Graphsであり、循環参照はこの構造に違反します。
- 演算子
- PRIOR:ノードの親からデータを取得します。
- CONNECT_BY_ROOT:ノードのルートからデータを取得します。
- 疑似列
- LEVEL:ルートからのノードの距離を示します。
- CONNECT_BY_ISLEAF:子がないノードを示します。
- CONNECT_BY_ISCYCLE:循環参照を持つノードを示します。
- 関数
- SYS_CONNECT_BY_PATH:ルートからノードへのパスのフラット/連結表現を返します。
再帰的に日付を生成し、例としてチームのロストを含むように拡張
DECLARE @DateFrom DATETIME = '2016-06-01 06:00'
DECLARE @DateTo DATETIME = '2016-07-01 06:00'
DECLARE @IntervalDays INT = 7
-- Transition Sequence = Rest & Relax into Day Shift into Night Shift
-- RR (Rest & Relax) = 1
-- DS (Day Shift) = 2
-- NS (Night Shift) = 3
;WITH roster AS
(
SELECT @DateFrom AS RosterStart, 1 AS TeamA, 2 AS TeamB, 3 AS TeamC
UNION ALL
SELECT DATEADD(d, @IntervalDays, RosterStart),
CASE TeamA WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 1 END AS TeamA,
CASE TeamB WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 1 END AS TeamB,
CASE TeamC WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 1 END AS TeamC
FROM roster WHERE RosterStart < DATEADD(d, -@IntervalDays, @DateTo)
)
SELECT RosterStart,
ISNULL(LEAD(RosterStart) OVER (ORDER BY RosterStart), RosterStart + @IntervalDays) AS RosterEnd,
CASE TeamA WHEN 1 THEN 'RR' WHEN 2 THEN 'DS' WHEN 3 THEN 'NS' END AS TeamA,
CASE TeamB WHEN 1 THEN 'RR' WHEN 2 THEN 'DS' WHEN 3 THEN 'NS' END AS TeamB,
CASE TeamC WHEN 1 THEN 'RR' WHEN 2 THEN 'DS' WHEN 3 THEN 'NS' END AS TeamC
FROM roster
結果
つまり、1週目はTeamAがR&R、TeamBがDay Shift、TeamCがNight Shiftです。
共通テーブル式を使用するクエリのリファクタリング
総売上が20を超えるすべての商品カテゴリを取得するとします。
共通テーブル式がないクエリを次に示します。
SELECT category.description, sum(product.price) as total_sales
FROM sale
LEFT JOIN product on sale.product_id = product.id
LEFT JOIN category on product.category_id = category.id
GROUP BY category.id, category.description
HAVING sum(product.price) > 20
Common Table Expressionsを使用した同等のクエリ
WITH all_sales AS (
SELECT product.price, category.id as category_id, category.description as category_description
FROM sale
LEFT JOIN product on sale.product_id = product.id
LEFT JOIN category on product.category_id = category.id
)
, sales_by_category AS (
SELECT category_description, sum(price) as total_sales
FROM all_sales
GROUP BY category_id, category_description
)
SELECT * from sales_by_category WHERE total_sales > 20
共通表式を持つ複合SQLの例
「最上位のカテゴリ」から「最も安い製品」を検索するとします。
Common Table Expressionsを使用したクエリの例を次に示します
-- all_sales: just a simple SELECT with all the needed JOINS
WITH all_sales AS (
SELECT
product.price as product_price,
category.id as category_id,
category.description as category_description
FROM sale
LEFT JOIN product on sale.product_id = product.id
LEFT JOIN category on product.category_id = category.id
)
-- Group by category
, sales_by_category AS (
SELECT category_id, category_description,
sum(product_price) as total_sales
FROM all_sales
GROUP BY category_id, category_description
)
-- Filtering total_sales > 20
, top_categories AS (
SELECT * from sales_by_category WHERE total_sales > 20
)
-- all_products: just a simple SELECT with all the needed JOINS
, all_products AS (
SELECT
product.id as product_id,
product.description as product_description,
product.price as product_price,
category.id as category_id,
category.description as category_description
FROM product
LEFT JOIN category on product.category_id = category.id
)
-- Order by product price
, cheapest_products AS (
SELECT * from all_products
ORDER by product_price ASC
)
-- Simple inner join
, cheapest_products_from_top_categories AS (
SELECT product_description, product_price
FROM cheapest_products
INNER JOIN top_categories ON cheapest_products.category_id = top_categories.category_id
)
--The main SELECT
SELECT * from cheapest_products_from_top_categories