### 13.2.15 WITH（公用表表达式）

[](<>)

公用表表达式 (CTE) 是一个命名的临时结果集，它存在于单个语句的范围内，稍后可以在该语句中引用，可能会多次引用。以下讨论描述了如何编写使用 CTE 的语句。

-   [公用表表达式](with.html#common-table-expressions)

-   [递归公用表表达式](with.html#common-table-expressions-recursive)

-   [限制公用表表达式递归](with.html#common-table-expressions-recursion-limits)

-   [递归公用表表达式示例](with.html#common-table-expressions-recursive-examples)

-   [公用表表达式与类似结构的比较](with.html#common-table-expressions-similar-constructs)

    有关 CTE 优化的信息，请参阅[第 8.2.2.4 节，“通过合并或实现优化派生表、视图引用和公用表表达式”](derived-table-optimization.html).

#### 其他资源

这些文章包含有关在 MySQL 中使用 CTE 的其他信息，包括许多示例：

-   [MySQL 8.0 Labs：\[递归\] MySQL (CTE) 中的通用表表达式](https://dev.mysql.com/blog-archive/mysql-8-0-labs-recursive-common-table-expressions-in-mysql-ctes/)

-   [MySQL 8.0 Labs: \[Recursive\] MySQL (CTE) 中的通用表表达式，第二部分 – 如何生成系列](https://dev.mysql.com/blog-archive/mysql-8-0-labs-recursive-common-table-expressions-in-mysql-ctes-part-two-how-to-generate-series/)

-   [MySQL 8.0 实验室：\[递归\] MySQL (CTE) 中的通用表表达式，第三部分 - 层次结构](https://dev.mysql.com/blog-archive/mysql-8-0-labs-recursive-common-table-expressions-in-mysql-ctes-part-three-hierarchies/)

-   [MySQL 8.0.1：\[递归\] MySQL 中的通用表表达式 (CTE)，第四部分 - 深度优先或广度优先遍历、传递闭包、循环避免](https://dev.mysql.com/blog-archive/mysql-8-0-1-recursive-common-table-expressions-in-mysql-ctes-part-four-depth-first-or-breadth-first-traversal-transitive-closure-cycle-avoidance/)

#### 公用表表达式

要指定公用表表达式，请使用[`和`](with.html)具有一个或多个逗号分隔的子句的子句。每个子条款都提供了一个子查询，该查询生成一个结果集，并将名称与子查询相关联。以下示例定义了名为`cte1`和`cte2`在里面[`和`](with.html)子句，并在顶层引用它们[`选择`](select.html)遵循[`和`](with.html)条款：

```
WITH
  cte1 AS (SELECT a, b FROM table1),
  cte2 AS (SELECT c, d FROM table2)
SELECT b, d FROM cte1 JOIN cte2
WHERE cte1.a = cte2.c;
```

在包含[`和`](with.html)子句中，可以引用每个 CTE 名称来访问对应的 CTE 结果集。

一个 CTE 名称可以在其他 CTE 中引用，从而可以基于其他 CTE 定义 CTE。

CTE 可以引用自身来定义递归 CTE。递归 CTE 的常见应用包括序列生成和分层或树结构数据的遍历。

公共表表达式是 DML 语句语法的可选部分。它们是使用定义的[`和`](with.html)条款：

```
with_clause:
    WITH [RECURSIVE]
        cte_name [(col_name [, col_name] ...)] AS (subquery)
        [, cte_name [(col_name [, col_name] ...)] AS (subquery)] ...
```

*`cte_name`*命名单个公用表表达式，并可在包含[`和`](with.html)条款。

这*`子查询`*部分`作为 （*`子查询`*)`被称为“CTE 的子查询”，是产生 CTE 结果集的原因。后面的括号`作为`是必要的。

如果公共表表达式的子查询引用它自己的名称，则它是递归的。这`递归的`如果有任何 CTE，则必须包含关键字[`和`](with.html)子句是递归的。有关详细信息，请参阅[递归公用表表达式](with.html#common-table-expressions-recursive).

给定 CTE 的列名确定如下：

-   如果 CTE 名称后面有一个带括号的名称列表，则这些名称是列名称：

    ```
    WITH cte (col1, col2) AS
    (
      SELECT 1, 2
      UNION ALL
      SELECT 3, 4
    )
    SELECT col1, col2 FROM cte;
    ```

    列表中的名称数必须与结果集中的列数相同。

-   否则，列名来自第一个的选择列表[`选择`](select.html)内`作为 （*`子查询`*)`部分：

    ```
    WITH cte AS
    (
      SELECT 1 AS col1, 2 AS col2
      UNION ALL
      SELECT 3, 4
    )
    SELECT col1, col2 FROM cte;
    ```

    一种[`和`](with.html)子句在这些情况下是允许的：

-   在。。。之初[`选择`](select.html), [`更新`](update.html)， 和[`删除`](delete.html)陈述。

    ```
    WITH ... SELECT ...
    WITH ... UPDATE ...
    WITH ... DELETE ...
    ```

-   在子查询的开头（包括派生表子查询）：

    ```
    SELECT ... WHERE id IN (WITH ... SELECT ...) ...
    SELECT * FROM (WITH ... SELECT ...) AS dt ...
    ```

-   紧接在前面[`选择`](select.html)对于包含 a 的语句[`选择`](select.html)陈述：

    ```
    INSERT ... WITH ... SELECT ...
    REPLACE ... WITH ... SELECT ...
    CREATE TABLE ... WITH ... SELECT ...
    CREATE VIEW ... WITH ... SELECT ...
    DECLARE CURSOR ... WITH ... SELECT ...
    EXPLAIN ... WITH ... SELECT ...
    ```

    只有一个[`和`](with.html)子句允许在同一级别。[`和`](with.html)其次是[`和`](with.html)不允许在同一级别，所以这是非法的：


```
WITH cte1 AS (...) WITH cte2 AS (...) SELECT ...
```

要使声明合法，请使用单个[`和`](with.html)用逗号分隔子句的子句：

```
WITH cte1 AS (...), cte2 AS (...) SELECT ...
```

但是，一个语句可以包含多个[`和`](with.html)子句如果它们出现在不同的级别：

```
WITH cte1 AS (SELECT 1)
SELECT * FROM (WITH cte2 AS (SELECT 2) SELECT * FROM cte2 JOIN cte1) AS dt;
```

一种[`和`](with.html)子句可以定义一个或多个公用表表达式，但每个 CTE 名称对于该子句必须是唯一的。这是非法的：

```
WITH cte1 AS (...), cte1 AS (...) SELECT ...
```

为了使语句合法，定义具有唯一名称的 CTE：

```
WITH cte1 AS (...), cte2 AS (...) SELECT ...
```

CTE 可以引用自身或其他 CTE：

-   自引用 CTE 是递归的。

-   CTE 可以引用前面定义的 CTE[`和`](with.html)子句，但不是后面定义的那些。

    该约束排除了相互递归的 CTE，其中`cte1`参考`cte2`和`cte2`参考`cte1`.其中一个引用必须是后面定义的 CTE，这是不允许的。

-   给定查询块中的 CTE 可以引用在更外层的查询块中定义的 CTE，但不能引用在更内层的查询块中定义的 CTE。

    为了解析对同名对象的引用，派生表隐藏了 CTE；和 CTE 隐藏基表，`暂时的`表和视图。名称解析是通过在同一查询块中搜索对象来进行的，然后在没有找到具有该名称的对象时依次进行到外部块。

    与派生表一样，CTE 不能包含 MySQL 8.0.14 之前的外部引用。这是 MySQL 8.0.14 中取消的 MySQL 限制，而不是 SQL 标准的限制。有关特定于递归 CTE 的其他语法注意事项，请参阅[递归公用表表达式](with.html#common-table-expressions-recursive).

#### 递归公用表表达式

递归公用表表达式是具有引用其自身名称的子查询的表达式。例如：

```
WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte WHERE n < 5
)
SELECT * FROM cte;
```

执行时，该语句会产生以下结果，即包含简单线性序列的单列：

```
+------+
| n    |
+------+
|    1 |
|    2 |
|    3 |
|    4 |
|    5 |
+------+
```

递归 CTE 具有以下结构：

-   这`和`子句必须以`带递归`如果有任何 CTE`和`子句是指它自己。（如果没有 CTE 引用它自己，`递归的`是允许的，但不是必需的。）

    如果你忘记了`递归的`对于递归 CTE，可能会出现此错误：

    ```
    ERROR 1146 (42S02): Table 'cte_name' doesn't exist
    ```

-   递归 CTE 子查询有两部分，由[`联合[全部]`](union.html)要么[`工会区别`](union.html)：

    ```
    SELECT ...      -- return initial row set
    UNION ALL
    SELECT ...      -- return additional row sets
    ```

    首先[`选择`](select.html)为 CTE 生成一个或多个初始行，并且不引用 CTE 名称。第二[`选择`](select.html)通过在其中引用 CTE 名称产生额外的行和递归`从`条款。当这部分不产生新行时，递归结束。因此，递归 CTE 由非递归[`选择`](select.html)部分后跟一个递归[`选择`](select.html)部分。

    每个[`选择`](select.html)部分本身可以是多个的并集[`选择`](select.html)陈述。

-   CTE 结果列的类型是从非递归的列类型推断出来的[`选择`](select.html)仅部分，并且列都可以为空。对于类型确定，递归[`选择`](select.html)部分被忽略。

-   如果非递归部分和递归部分由[`工会区别`](union.html), 重复行被消除。这对于执行传递闭包的查询很有用，以避免无限循环。

-   递归部分的每次迭代只对前一次迭代产生的行进行操作。如果递归部分有多个查询块，则每个查询块的迭代都按未指定的顺序安排，并且每个查询块对其前一次迭代或自前一次迭代结束以来由其他查询块产生的行进行操作。

    前面显示的递归 CTE 子查询具有这个非递归部分，它检索单行以生成初始行集：


```
SELECT 1
```

CTE 子查询也有这个递归部分：

```
SELECT n + 1 FROM cte WHERE n < 5
```

在每次迭代中，[`选择`](select.html)产生一个新值比值大一的行`n`从上一行设置。第一次迭代对初始行集（`1`) 并产生`1+1=2`;第二次迭代对第一次迭代的行集进行操作（`2`) 并产生`2+1=3`;等等。这一直持续到递归结束，这发生在`n`不再小于5。

如果 CTE 的递归部分为列生成的值比非递归部分更宽，则可能需要加宽非递归部分中的列以避免数据截断。考虑这个陈述：

```
WITH RECURSIVE cte AS
(
  SELECT 1 AS n, 'abc' AS str
  UNION ALL
  SELECT n + 1, CONCAT(str, str) FROM cte WHERE n < 3
)
SELECT * FROM cte;
```

在非严格 SQL 模式下，该语句产生以下输出：

```
+------+------+
| n    | str  |
+------+------+
|    1 | abc  |
|    2 | abc  |
|    3 | abc  |
+------+------+
```

这`字符串`列值都是`'abc'`因为非递归[`选择`](select.html)确定列宽。因此，更广泛的`字符串`递归产生的值[`选择`](select.html)被截断。

在严格的 SQL 模式下，该语句会产生错误：

```
ERROR 1406 (22001): Data too long for column 'str' at row 1
```

要解决此问题，使语句不会产生截断或错误，请使用[`投掷（）`](cast-functions.html#function_cast)在非递归[`选择`](select.html)使`字符串`列更宽：

```
WITH RECURSIVE cte AS
(
  SELECT 1 AS n, CAST('abc' AS CHAR(20)) AS str
  UNION ALL
  SELECT n + 1, CONCAT(str, str) FROM cte WHERE n < 3
)
SELECT * FROM cte;
```

现在该语句产生了这个结果，没有截断：

```
+------+--------------+
| n    | str          |
+------+--------------+
|    1 | abc          |
|    2 | abcabc       |
|    3 | abcabcabcabc |
+------+--------------+
```

列是按名称而不是位置访问的，这意味着递归部分中的列可以访问非递归部分中具有不同位置的列，如下 CTE 所示：

```
WITH RECURSIVE cte AS
(
  SELECT 1 AS n, 1 AS p, -1 AS q
  UNION ALL
  SELECT n + 1, q * 2, p * 2 FROM cte WHERE n < 5
)
SELECT * FROM cte;
```

因为`p`在一行中源自`q`在前一行中，反之亦然，正值和负值在输出的每个连续行中交换位置：

```
+------+------+------+
| n    | p    | q    |
+------+------+------+
|    1 |    1 |   -1 |
|    2 |   -2 |    2 |
|    3 |    4 |   -4 |
|    4 |   -8 |    8 |
|    5 |   16 |  -16 |
+------+------+------+
```

一些语法约束适用于递归 CTE 子查询：

-   递归的[`选择`](select.html)部分不得包含这些结构：

    -   聚合函数，例如`和（）`

    -   窗口函数

    -   `通过...分组`

    -   `订购方式`

    -   `清楚的`

        在 MySQL 8.0.19 之前，递归[`选择`](select.html)递归 CTE 的一部分也不能使用`限制`条款。MySQL 8.0.19 中取消了此限制，并且`限制`现在在这种情况下支持，以及可选的`抵消`条款。结果集上的效果和使用时一样`限制`在最外面`选择`, 但也更有效，因为它与递归一起使用`选择`一旦生成了所请求的行数，就停止生成行。

        这些约束不适用于非递归[`选择`](select.html)递归 CTE 的一部分。禁止`清楚的`仅适用于[`联盟`](union.html)会员；`工会区别`是允许的。

-   递归的[`选择`](select.html)部件必须仅引用一次 CTE，并且仅在其`从`子句，不在任何子查询中。它可以引用 CTE 以外的表并将它们与 CTE 连接起来。如果在这样的连接中使用，CTE 不能在`左连接`.

    这些约束来自 SQL 标准，而不是 MySQL 特定的排除`订购方式`,`限制`（MySQL 8.0.18 及更早版本），以及`清楚的`.

    对于递归 CTE，[`解释`](explain.html)递归的输出行[`选择`](select.html)零件展示`递归的`在里面`额外的`柱子。

    成本估算显示为[`解释`](explain.html)表示每次迭代的成本，它可能与总成本有很大不同。优化器无法预测迭代次数，因为它无法预测在什么时候迭代`在哪里`子句变为假。

    CTE 实际成本也可能受结果集大小的影响。产生许多行的 CTE 可能需要一个足够大的内部临时表，以便从内存中转换为磁盘格式，并且可能会遭受性能损失。如果是这样，增加允许的内存临时表大小可能会提高性能；看[第 8.4.4 节，“MySQL 中的内部临时表使用”](internal-temporary-tables.html).

#### 限制公用表表达式递归

对于递归 CTE，递归 CTE 很重要[`选择`](select.html)部分包括终止递归的条件。作为一种防止失控递归 CTE 的开发技术，您可以通过限制执行时间来强制终止：

-   这[`cte_max_recursion_depth`](server-system-variables.html#sysvar_cte_max_recursion_depth)系统变量强制限制 CTE 的递归级别数。服务器终止执行任何递归级别多于此变量值的 CTE。

-   这[`max_execution_time`](server-system-variables.html#sysvar_max_execution_time)系统变量强制执行超时[`选择`](select.html)在当前会话中执行的语句。

-   这[`MAX_EXECUTION_TIME`](optimizer-hints.html#optimizer-hints-execution-time)优化器提示强制执行每个查询的执行超时[`选择`](select.html)它出现的声明。

    假设在没有递归执行终止条件的情况下错误地编写了递归 CTE：


```
WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte
)
SELECT * FROM cte;
```

默认，[`cte_max_recursion_depth`](server-system-variables.html#sysvar_cte_max_recursion_depth)值为 1000，导致 CTE 在递归超过 1000 个级别时终止。应用程序可以更改会话值以适应其要求：

```
SET SESSION cte_max_recursion_depth = 10;      -- permit only shallow recursion
SET SESSION cte_max_recursion_depth = 1000000; -- permit deeper recursion
```

也可以设置全局[`cte_max_recursion_depth`](server-system-variables.html#sysvar_cte_max_recursion_depth)影响随后开始的所有会话的值。

对于执行并因此递归缓慢或在有理由设置的上下文中的查询[`cte_max_recursion_depth`](server-system-variables.html#sysvar_cte_max_recursion_depth)值非常高，防止深度递归的另一种方法是设置每个会话超时。为此，请在执行 CTE 语句之前执行如下语句：

```
SET max_execution_time = 1000; -- impose one second timeout
```

或者，在 CTE 语句本身中包含优化器提示：

```
WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte
)
SELECT /*+ SET_VAR(cte_max_recursion_depth = 1M) */ * FROM cte;

WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte
)
SELECT /*+ MAX_EXECUTION_TIME(1000) */ * FROM cte;
```

从 MySQL 8.0.19 开始，还可以使用`限制`在递归查询中强制将最大行数返回到最外层[`选择`](select.html)， 例如：

```
WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte LIMIT 10000
)
SELECT * FROM cte;
```

除了设置时间限制之外，您还可以这样做或代替设置时间限制。因此，以下 CTE 在返回一万行或运行一秒（1000 毫秒）后终止，以先发生者为准：

```
WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte LIMIT 10000
)
SELECT /*+ MAX_EXECUTION_TIME(1000) */ * FROM cte;
```

[](<>)[](<>)

如果没有执行时间限制的递归查询进入无限循环，您可以使用从另一个会话终止它[`终止查询`](kill.html).在会话本身中，用于运行查询的客户端程序可能会提供一种终止查询的方法。例如，在[**mysql**](mysql.html), 打字**控制+C**中断当前语句。

#### 递归公用表表达式示例

如前所述，递归公用表表达式 (CTE) 经常用于序列生成和遍历分层或树结构数据。本节展示了这些技术的一些简单示例。

-   [斐波那契数列生成](with.html#common-table-expressions-recursive-fibonacci-series)

-   [日期系列生成](with.html#common-table-expressions-recursive-date-series)

-   [分层数据遍历](with.html#common-table-expressions-recursive-hierarchy-traversal)

##### 斐波那契数列生成

斐波那契数列以两个数字 0 和 1（或 1 和 1）开始，之后的每个数字都是前两个数字的和。递归公用表表达式可以生成斐波那契数列，如果递归生成的每一行[`选择`](select.html)可以访问该系列的前两个号码。以下 CTE 使用 0 和 1 作为前两个数字生成一个 10 数字系列：

```
WITH RECURSIVE fibonacci (n, fib_n, next_fib_n) AS
(
  SELECT 1, 0, 1
  UNION ALL
  SELECT n + 1, next_fib_n, fib_n + next_fib_n
    FROM fibonacci WHERE n < 10
)
SELECT * FROM fibonacci;
```

CTE 产生以下结果：

```
+------+-------+------------+
| n    | fib_n | next_fib_n |
+------+-------+------------+
|    1 |     0 |          1 |
|    2 |     1 |          1 |
|    3 |     1 |          2 |
|    4 |     2 |          3 |
|    5 |     3 |          5 |
|    6 |     5 |          8 |
|    7 |     8 |         13 |
|    8 |    13 |         21 |
|    9 |    21 |         34 |
|   10 |    34 |         55 |
+------+-------+------------+
```

CTE 的工作原理：

-   `n`是一个显示列，指示该行包含`n`-第个斐波那契数。例如，第八个斐波那契数是13。

-   这个`小谎`列显示斐波那契数`n`.

-   这个`下一个谎言`列显示数字后的下一个斐波那契数`n`。此列将下一个系列值提供给下一行，以便该行可以在其列中生成前两个系列值的总和`小谎`柱

-   递归在`n`达到10岁。这是一个任意的选择，将输出限制为一小部分行。

    前面的输出显示了整个CTE结果。要仅选择其中的一部分，请添加适当的`哪里`条款到顶层[`选择`](select.html)。例如，要选择第8个斐波那契数，请执行以下操作：


```
mysql> WITH RECURSIVE fibonacci ...
       ...
       SELECT fib_n FROM fibonacci WHERE n = 8;
+-------+
| fib_n |
+-------+
|    13 |
+-------+
```

##### 日期序列生成

通用表表达式可以生成一系列连续的日期，这对于生成包含序列中所有日期的行（包括汇总数据中未表示的日期）的摘要非常有用。

假设销售编号表包含以下行：

```
mysql> SELECT * FROM sales ORDER BY date, price;
+------------+--------+
| date       | price  |
+------------+--------+
| 2017-01-03 | 100.00 |
| 2017-01-03 | 200.00 |
| 2017-01-06 |  50.00 |
| 2017-01-08 |  10.00 |
| 2017-01-08 |  20.00 |
| 2017-01-08 | 150.00 |
| 2017-01-10 |   5.00 |
+------------+--------+
```

此查询汇总了每天的销售额：

```
mysql> SELECT date, SUM(price) AS sum_price
       FROM sales
       GROUP BY date
       ORDER BY date;
+------------+-----------+
| date       | sum_price |
+------------+-----------+
| 2017-01-03 |    300.00 |
| 2017-01-06 |     50.00 |
| 2017-01-08 |    180.00 |
| 2017-01-10 |      5.00 |
+------------+-----------+
```

然而，该结果中包含的日期“洞”不在表中跨越的日期范围内。表示该范围内所有日期的结果可以使用递归CTE生成该日期集，并与`左连接`销售数据。

以下是生成日期范围系列的CTE：

```
WITH RECURSIVE dates (date) AS
(
  SELECT MIN(date) FROM sales
  UNION ALL
  SELECT date + INTERVAL 1 DAY FROM dates
  WHERE date + INTERVAL 1 DAY <= (SELECT MAX(date) FROM sales)
)
SELECT * FROM dates;
```

CTE产生以下结果：

```
+------------+
| date       |
+------------+
| 2017-01-03 |
| 2017-01-04 |
| 2017-01-05 |
| 2017-01-06 |
| 2017-01-07 |
| 2017-01-08 |
| 2017-01-09 |
| 2017-01-10 |
+------------+
```

CTE的工作原理：

-   非递归[`选择`](select.html)生成所跨越的日期范围内的最低日期`出售`桌子

-   递归生成的每一行[`选择`](select.html)将一天添加到上一行生成的日期。

-   递归在日期到达指定日期范围内的最高日期后结束`出售`桌子

    加入CTE`左连接`反对`出售`表为以下范围内的每个日期生成一行销售摘要：


```
WITH RECURSIVE dates (date) AS
(
  SELECT MIN(date) FROM sales
  UNION ALL
  SELECT date + INTERVAL 1 DAY FROM dates
  WHERE date + INTERVAL 1 DAY <= (SELECT MAX(date) FROM sales)
)
SELECT dates.date, COALESCE(SUM(price), 0) AS sum_price
FROM dates LEFT JOIN sales ON dates.date = sales.date
GROUP BY dates.date
ORDER BY dates.date;
```

输出如下所示：

```
+------------+-----------+
| date       | sum_price |
+------------+-----------+
| 2017-01-03 |    300.00 |
| 2017-01-04 |      0.00 |
| 2017-01-05 |      0.00 |
| 2017-01-06 |     50.00 |
| 2017-01-07 |      0.00 |
| 2017-01-08 |    180.00 |
| 2017-01-09 |      0.00 |
| 2017-01-10 |      5.00 |
+------------+-----------+
```

需要注意的几点：

-   查询是否效率低下，尤其是带有[`麦克斯（）`](aggregate-functions.html#function_max)为递归表中的每一行执行的子查询[`选择`](select.html)? [`解释`](explain.html)显示包含[`麦克斯（）`](aggregate-functions.html#function_max)只计算一次，并缓存结果。

-   使用[`聚结`](comparison-operators.html#function_coalesce)避免展示`无效的`在`总价`列中没有出现销售数据的天数`出售`桌子

##### 分层数据遍历

递归公共表表达式对于遍历形成层次结构的数据非常有用。考虑这些语句，创建一个小数据集，显示一个公司的每个雇员的雇员姓名和ID号，以及雇员经理的ID。顶级员工（CEO）的经理ID为`无效的`（没有经理）。

```
CREATE TABLE employees (
  id         INT PRIMARY KEY NOT NULL,
  name       VARCHAR(100) NOT NULL,
  manager_id INT NULL,
  INDEX (manager_id),
FOREIGN KEY (manager_id) REFERENCES employees (id)
);
INSERT INTO employees VALUES
(333, "Yasmina", NULL),  # Yasmina is the CEO (manager_id is NULL)
(198, "John", 333),      # John has ID 198 and reports to 333 (Yasmina)
(692, "Tarek", 333),
(29, "Pedro", 198),
(4610, "Sarah", 29),
(72, "Pierre", 29),
(123, "Adil", 692);
```

生成的数据集如下所示：

```
mysql> SELECT * FROM employees ORDER BY id;
+------+---------+------------+
| id   | name    | manager_id |
+------+---------+------------+
|   29 | Pedro   |        198 |
|   72 | Pierre  |         29 |
|  123 | Adil    |        692 |
|  198 | John    |        333 |
|  333 | Yasmina |       NULL |
|  692 | Tarek   |        333 |
| 4610 | Sarah   |         29 |
+------+---------+------------+
```

要为每个员工生成带有管理链的组织结构图（即从CEO到员工的路径），请使用递归CTE：

```
WITH RECURSIVE employee_paths (id, name, path) AS
(
  SELECT id, name, CAST(id AS CHAR(200))
    FROM employees
    WHERE manager_id IS NULL
  UNION ALL
  SELECT e.id, e.name, CONCAT(ep.path, ',', e.id)
    FROM employee_paths AS ep JOIN employees AS e
      ON ep.id = e.manager_id
)
SELECT * FROM employee_paths ORDER BY path;
```

CTE产生以下输出：

```
+------+---------+-----------------+
| id   | name    | path            |
+------+---------+-----------------+
|  333 | Yasmina | 333             |
|  198 | John    | 333,198         |
|   29 | Pedro   | 333,198,29      |
| 4610 | Sarah   | 333,198,29,4610 |
|   72 | Pierre  | 333,198,29,72   |
|  692 | Tarek   | 333,692         |
|  123 | Adil    | 333,692,123     |
+------+---------+-----------------+
```

CTE的工作原理：

-   非递归[`选择`](select.html)为CEO生成行（带有`无效的`经理ID）。

    这个`路径`柱子被加宽到`查尔（200）`确保有更长时间的空间`路径`递归函数生成的值[`选择`](select.html).

-   递归生成的每一行[`选择`](select.html)查找直接向上一行生成的员工报告的所有员工。对于每个这样的员工，该行包括员工ID和姓名，以及员工管理链。链是经理的链，末尾添加了员工ID。

-   当员工没有其他人向他们报告时，递归结束。

    要查找特定员工的路径，请添加`哪里`条款到顶层[`选择`](select.html)。例如，要显示Tarek和Sarah的结果，请修改该选项[`选择`](select.html)这样地：


```
mysql> WITH RECURSIVE ...
       ...
       SELECT * FROM employees_extended
       WHERE id IN (692, 4610)
       ORDER BY path;
+------+-------+-----------------+
| id   | name  | path            |
+------+-------+-----------------+
| 4610 | Sarah | 333,198,29,4610 |
|  692 | Tarek | 333,692         |
+------+-------+-----------------+
```

#### 常用表表达式与类似结构的比较

通用表表达式（CTE）在某些方面与派生表类似：

-   这两种构造都命名为。

-   这两种构造都存在于单个语句的范围内。

    由于这些相似性，CTE和派生表通常可以互换使用。作为一个简单的例子，这些语句是等价的：


```
WITH cte AS (SELECT 1) SELECT * FROM cte;
SELECT * FROM (SELECT 1) AS dt;
```

然而，与派生表相比，CTE有一些优势：

-   派生表在查询中只能被引用一次。CTE可以多次引用。要使用派生表结果的多个实例，必须多次派生该结果。

-   CTE可以是自引用的（递归的）。

-   一个CTE可以引用另一个CTE。

-   当CTE的定义出现在语句开头而不是嵌入其中时，它可能更容易阅读。

    CTE与使用创建的表类似[`创建[临时]表`](create-table.html)但不需要明确定义或删除。对于CTE，创建表不需要特权。
