サーチ…


構文

  • 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


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow