type
status
date
slug
summary
tags
category
icon
password

前言

Common Table Expression (CTE) 是一個在大部分的 RDBMS 都有的特性,例如:ORACLE、SQL SERVER、POSTGERSQL 這些都早已支援,而 MySQL 直到 8.0 才引入了此功能,接下來就讓我們來一起了解 CTE 是什麼以及怎麼使用吧!

什麼是 CTE ?

CTE 是一個命名的臨時結果集,僅在單個 SQL 語句的執行範圍內存在,並且可以多次引用。
除了多次引用的特性,可以看到 CTE 看起來很像子查詢,實際上 CTE 也可以用普通的子查詢來實現取代,例如以下範例:
但和子查詢 (subquery) 相比,CTE 還具有以下優勢
可以多次引用
假設我們有以下表格:
我們想比較每一天和前一天相比銷售量的變化:
在這個例子中我們可以看到傳統的子查詢我們必須進行 2 次 GROUP BY 的子查詢才能進行兩天的比對,而 cte 只需要 1 次,這就是 cte 的可重複引用特點。
  • 可讀性更佳
  • 可以遞迴

怎麼用 CTE ?

WITH 語句

使用 WITH 語句來建構 CTE,以下為其結構和範例:

WITH 語句的用法與限制

以下是可以使用 WITH 語句的地方:
  • 在 SELECT,UPDATE 和 DELETE 語句的開頭使用
    • 在子查詢的開頭使用
      • 語句中包含 SELECT 語句,並且必須在 SELECT 前方使用
        以下是 WITH 語句的一些限制:
        • 在同一個層級中只允許包含一個 WITH 子句,如下範例是不合法的:
          • 如果要使其合法,請使用逗號分隔多個 cte 的定義
            或者是當在不同層級中就可以再使用其他 WITH 子句,如下範例是合法的:
        • CTE 可以引用在其之前定義的另一個 CTE,但不能引用在其之後定義的另一個 CTE。
          • 這是為了避免產生相互遞規的 CTE,例如 CTE1 引用 CTE2 並且 CTE2 引用 CTE1 的情況。

        CTE 中的欄位命名

        可以透過在 cte_name 後方括號定義欄位名稱
        否則會以第一個 SELECT 語句中的欄位名稱為主

        Recursive CTE 怎麼用?

        一個 Recursive CTE 會是以下結構:
        • 在 cte 名稱前方以 WITH RECRUSIVE 為開頭
        • cte 內部的第一個 SELECT 語句用來生成初始的 row
        • 使用 UNION ALL 或 UNION DISTINCT 來分隔 2 個 SELECT 語句
        • cte 內部的第二個 SELECT 語句需要在 FROM 中引用 cte 用來產生額外的 row
        接著讓我們看一下以下範例:
        在以上範例中我們透過 SELECT 1 產生了初始的行,隨後在第2個語句中 n(1) < 5 產出了 SELECT 2 的結果,並繼續遞迴 n(2) < 5 產生了 SELECT 3 的結果以此類推,直到 n(5) < 5 不成立終止的此語法的遞迴產出了 1~5 的結果集。

        注意事項

        1. 遞迴 CTE 產生的結果集,其欄位的型態由初始語句也就是第一個 SELECT 語句決定,以下範例:
          1. 可以看到因為 str 欄位的初始值為 abc ,因此該欄位被定義為 CHAR(3) 後續產生的 str 欄位超過 3 個字符從而導致截斷的狀況,可以透過再初始語句添加 CAST() 函數指定型態來避免。
        1. 遞迴 SELECT 子句(也就是第2部分的 SELECT) 不可以包含以下結構:
          1. SQL 標準限制
            • Aggregate function
            • Window function
            • GROUP BY
            MySQL 限制
            • ORDER BY
            • DISTINCT
            • LIMIT ? OFFSET ? (Before MySQL 8.0.19)
            注意:在 MySQL 8.0.19 之後的版本取消了不可使用 LIMIT ? OFFSET ? 的限制,其對結果集的影響與在 CTE 外層編寫 LIMIT ? OFFSET ? 相同,但相比之下會更有效率,因為當遞迴 SELECT 產生所需的行數後會立即停止繼續遞迴產生後續的行數。
        1. 遞迴 SELECT 子句中必須且只能在 FROM 子句中引用 1 次 CTE,而不是在其他子查詢內。
        1. 遞迴 SELECT 子句可以引用其他的 TABLE 進行 JOIN,但是 CTE 不能為用 LEFT JOIN 的右側。
        1. EXPLAN 顯示的成本估算代表每次迭代的成本,因為優化器無法預測迭代次數,因此無法顯示總成本。

        限制遞迴 CTE

        1. 透過以下 system variable 限制遞迴 CTE 的執行
            • max_execution_time:以毫秒為單位,當 SELECT 語句執行時間超過將 timeout。
        1. 透過 optimizer hint 限制單個語句的遞迴 CTE 執行
          1. 從 MySQL 8.0.19 開始可以透過 LIMIT 限制遞迴 CTE 產生的行數

            範例

            Date Series Generation
            初始資料如下:
            列出 2017/01/03 ~ 2017/01/10 每一天的銷售額 (銷後額為 0 也需要顯示):
            Hierarchical Data Traversal
            初始資料如下:
            列出所有員工從最上層的主管到自身的路徑圖:
            CTE 運作過程如下事例:

            實踐

            取得 GROUP BY 最後一筆的完整資料

            參考