13.2.15 WITH (Common Table Expressions)WITH(公共表表达式)

A common table expression (CTE) is a named temporary result set that exists within the scope of a single statement and that can be referred to later within that statement, possibly multiple times. 公共表表达式(CTE)是一个命名的临时结果集,它存在于一条语句的范围内,以后可以在该语句中多次引用。The following discussion describes how to write statements that use CTEs.下面的讨论描述了如何编写使用cte的语句。

For information about CTE optimization, see Section 8.2.2.4, “Optimizing Derived Tables, View References, and Common Table Expressions with Merging or Materialization”.有关CTE优化的信息,请参阅第8.2.2.4节,“通过合并或物化优化派生表、视图引用和公共表表达式”

Additional Resources额外资源

These articles contain additional information about using CTEs in MySQL, including many examples:这些文章包含有关在MySQL中使用CTE的其他信息,包括许多示例:

Common Table Expressions公共表表达式

To specify common table expressions, use a WITH clause that has one or more comma-separated subclauses. 要指定公共表表达式,请使用具有一个或多个逗号分隔子类的WITH子句。Each subclause provides a subquery that produces a result set, and associates a name with the subquery. 每个子类都提供一个子查询来生成结果集,并将名称与子查询相关联。The following example defines CTEs named cte1 and cte2 in the WITH clause, and refers to them in the top-level SELECT that follows the WITH clause:以下示例在WITH子句中定义名为cte1cte2的CTE,并在WITH子句后面的顶级SELECT中引用它们:

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;

In the statement containing the WITH clause, each CTE name can be referenced to access the corresponding CTE result set.在包含WITH子句的语句中,可以引用每个CTE名称来访问相应的CTE结果集。

A CTE name can be referenced in other CTEs, enabling CTEs to be defined based on other CTEs.CTE名称可以在其他CTE中引用,从而可以基于其他CTE定义CTE。

A CTE can refer to itself to define a recursive CTE. CTE可以引用自身来定义递归CTE。Common applications of recursive CTEs include series generation and traversal of hierarchical or tree-structured data.递归CTE的常见应用包括层次或树结构数据的序列生成和遍历。

Common table expressions are an optional part of the syntax for DML statements. 公共表表达式是DML语句语法的可选部分。They are defined using a WITH clause:它们使用WITH子句定义:

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

cte_name names a single common table expression and can be used as a table reference in the statement containing the WITH clause.cte_name命名一个公共表表达式,可以在包含WITH子句的语句中用作表引用。

The subquery part of AS (subquery) is called the subquery of the CTE and is what produces the CTE result set. AS (subquery)的子查询部分称为“CTE的子查询”,是生成CTE结果集的部分。The parentheses following AS are required.AS后面的括号是必需的。

A common table expression is recursive if its subquery refers to its own name. 如果公共表表达式的子查询引用自己的名称,则该表达式是递归的。The RECURSIVE keyword must be included if any CTE in the WITH clause is recursive. 如果WITH子句中的任何CTE是递归的,则必须包含RECURSIVE关键字。For more information, see Recursive Common Table Expressions.有关详细信息,请参见递归公共表表达式

Determination of column names for a given CTE occurs as follows:确定给定CTE的列名如下:

  • If a parenthesized list of names follows the CTE name, those names are the column names:如果CTE名称后面有一个带圆括号的名称列表,则这些名称是列名:

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

    The number of names in the list must be the same as the number of columns in the result set.列表中的名称数必须与结果集中的列数相同。

  • Otherwise, the column names come from the select list of the first SELECT within the AS (subquery) part:否则,列名来自AS (subquery)部分中第一个SELECT的选择列表:

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

A WITH clause is permitted in these contexts:在下列情况下允许使用WITH子句:

  • At the beginning of SELECT, UPDATE, and DELETE statements.SELECTUPDATEDELETE语句的开头。

    WITH ... SELECT ...
    WITH ... UPDATE ...
    WITH ... DELETE ...
  • At the beginning of subqueries (including derived table subqueries):在子查询(包括派生表子查询)的开头:

    SELECT ... WHERE id IN (WITH ... SELECT ...) ...
    SELECT * FROM (WITH ... SELECT ...) AS dt ...
  • Immediately preceding SELECT for statements that include a SELECT statement:包含SELECT语句的SELECT语句的前一条语句:

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

Only one WITH clause is permitted at the same level. 同一级别只允许一个WITH子句。WITH followed by WITH at the same level is not permitted, so this is illegal:不允许在同一级别WITH后跟WITH,因此这是非法的:

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

To make the statement legal, use a single WITH clause that separates the subclauses by a comma:要使语句合法,请使用一个WITH子句,该子句用逗号分隔子类:

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

However, a statement can contain multiple WITH clauses if they occur at different levels:但是,如果语句出现在不同的级别,则语句可以包含多个WITH子句:

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

A WITH clause can define one or more common table expressions, but each CTE name must be unique to the clause. WITH子句可以定义一个或多个公共表表达式,但每个CTE名称对于该子句都必须是唯一的。This is illegal:这是非法的:

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

To make the statement legal, define the CTEs with unique names:要使语句合法,请使用唯一名称定义CTE:

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

A CTE can refer to itself or to other CTEs:CTE可指自身或其他CTE:

  • A self-referencing CTE is recursive.自引用CTE是递归的。

  • A CTE can refer to CTEs defined earlier in the same WITH clause, but not those defined later.CTE可以引用前面同一WITH子句中定义的CTE,但不能引用后面定义的CTE。

    This constraint rules out mutually-recursive CTEs, where cte1 references cte2 and cte2 references cte1. 这个约束排除了相互递归的公共表表达式,其中cte1引用cte2cte2引用cte1One of those references must be to a CTE defined later, which is not permitted.其中一个引用必须是后面定义的CTE,这是不允许的。

  • A CTE in a given query block can refer to CTEs defined in query blocks at a more outer level, but not CTEs defined in query blocks at a more inner level.给定查询块中的CTE可以引用在更外层的查询块中定义的CTE,但不能引用在更内层的查询块中定义的CTE。

For resolving references to objects with the same names, derived tables hide CTEs; and CTEs hide base tables, TEMPORARY tables, and views. 为了解析对同名对象的引用,派生表隐藏CTE;CTE隐藏基表、临时表和视图。Name resolution occurs by searching for objects in the same query block, then proceeding to outer blocks in turn while no object with the name is found.名称解析是通过搜索同一查询块中的对象,然后依次转到外部块,而找不到具有名称的对象。

Like derived tables, a CTE cannot contain outer references prior to MySQL 8.0.14. 与派生表一样,CTE不能包含MySQL8.0.14之前的外部引用。This is a MySQL restriction that is lifted in MySQL 8.0.14, not a restriction of the SQL standard. 这是MySQL8.0.14中取消的MySQL限制,不是SQL标准的限制。For additional syntax considerations specific to recursive CTEs, see Recursive Common Table Expressions.有关特定于递归CTE的其他语法注意事项,请参阅递归公共表表达式

Recursive Common Table Expressions递归公共表表达式

A recursive common table expression is one having a subquery that refers to its own name. 递归公共表表达式具有引用其自身名称的子查询。For example:例如:

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

When executed, the statement produces this result, a single column containing a simple linear sequence:执行时,该语句将生成以下结果,即包含简单线性序列的单个列:

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

A recursive CTE has this structure:递归CTE具有以下结构:

  • The WITH clause must begin with WITH RECURSIVE if any CTE in the WITH clause refers to itself. 如果WITH子句中的任何CTE引用自身,则WITH子句必须以WITH RECURSIVE开头。(If no CTE refers to itself, RECURSIVE is permitted but not required.)(如果没有CTE引用自身,则允许加RECURSIVE,但不是必需的。)

    If you forget RECURSIVE for a recursive CTE, this error is a likely result:如果您忘记递归CTE的RECURSIVE,则可能会出现以下错误:

    ERROR 1146 (42S02): Table 'cte_name' doesn't exist
  • The recursive CTE subquery has two parts, separated by UNION [ALL] or UNION DISTINCT:递归CTE子查询有两部分,由UNION [ALL]UNION DISTINCT分隔:

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

    The first SELECT produces the initial row or rows for the CTE and does not refer to the CTE name. 第一个SELECT生成CTE的初始行,不引用CTE名称。The second SELECT produces additional rows and recurses by referring to the CTE name in its FROM clause. 第二个SELECT生成额外的行,并通过在其FROM子句中引用CTE名称来递归。Recursion ends when this part produces no new rows. 当该部分不产生新行时,递归结束。Thus, a recursive CTE consists of a nonrecursive SELECT part followed by a recursive SELECT part.因此,递归CTE由非递归SELECT部分和后面跟着的递归SELECT部分组成。

    Each SELECT part can itself be a union of multiple SELECT statements.每个SELECT部分本身可以是多个SELECT语句的并集。

  • The types of the CTE result columns are inferred from the column types of the nonrecursive SELECT part only, and the columns are all nullable. CTE结果列的类型仅从非递归SELECT部分的列类型推断出来,并且这些列都可以为空。For type determination, the recursive SELECT part is ignored.对于类型确定,将忽略递归SELECT部分。

  • If the nonrecursive and recursive parts are separated by UNION DISTINCT, duplicate rows are eliminated. 如果用UNION DISTINCT分隔非递归部分和递归部分,则消除重复行。This is useful for queries that perform transitive closures, to avoid infinite loops.这对于执行可传递闭包的查询非常有用,以避免无限循环。

  • Each iteration of the recursive part operates only on the rows produced by the previous iteration. 递归部分的每次迭代仅对上一次迭代生成的行进行操作。If the recursive part has multiple query blocks, iterations of each query block are scheduled in unspecified order, and each query block operates on rows that have been produced either by its previous iteration or by other query blocks since that previous iteration's end.如果递归部分有多个查询块,则每个查询块的迭代都按未指定的顺序进行调度,并且每个查询块对上一次迭代或上一次迭代结束后其他查询块生成的行进行操作。

The recursive CTE subquery shown earlier has this nonrecursive part that retrieves a single row to produce the initial row set:前面显示的递归CTE子查询有一个非递归部分,它检索一行以生成初始行集:

SELECT 1

The CTE subquery also has this recursive part:CTE子查询还有这个递归部分:

SELECT n + 1 FROM cte WHERE n < 5

At each iteration, that SELECT produces a row with a new value one greater than the value of n from the previous row set. 在每次迭代中,该SELECT将生成一个新值大于上一行集中n值的行。The first iteration operates on the initial row set (1) and produces 1+1=2; the second iteration operates on the first iteration's row set (2) and produces 2+1=3; and so forth. 第一次迭代对初始行集(1)进行运算,产生1+1=2;第二次迭代对第一次迭代的行集(2)进行运算,产生2+1=3;依此类推。This continues until recursion ends, which occurs when n is no longer less than 5.这种情况一直持续到递归结束,当n不再小于5时发生。

If the recursive part of a CTE produces wider values for a column than the nonrecursive part, it may be necessary to widen the column in the nonrecursive part to avoid data truncation. 如果CTE的递归部分为列生成比非递归部分更宽的值,则可能需要在非递归部分加宽列以避免数据截断。Consider this statement:考虑一下这个说法:

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;

In nonstrict SQL mode, the statement produces this output:在非严格SQL模式下,该语句生成以下输出:

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

The str column values are all 'abc' because the nonrecursive SELECT determines the column widths. str列值都是'abc',因为非递归SELECT决定列宽。Consequently, the wider str values produced by the recursive SELECT are truncated.因此,递归SELECT生成的更宽的str值被截断。

In strict SQL mode, the statement produces an error:在严格SQL模式下,该语句会产生错误:

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

To address this issue, so that the statement does not produce truncation or errors, use CAST() in the nonrecursive SELECT to make the str column wider:要解决此问题,以便语句不会产生截断或错误,请在非递归SELECT中使用CAST()使str列更宽:

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;

Now the statement produces this result, without truncation:现在,语句将生成以下结果,而不进行截断:

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

Columns are accessed by name, not position, which means that columns in the recursive part can access columns in the nonrecursive part that have a different position, as this CTE illustrates:列是按名称而不是位置访问的,这意味着递归部分中的列可以访问非递归部分中具有不同位置的列,如此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;

Because p in one row is derived from q in the previous row, and vice versa, the positive and negative values swap positions in each successive row of the output:因为一行中的p来自前一行中的q,反之亦然,所以正负值在输出的每个连续行中交换位置:

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

Some syntax constraints apply within recursive CTE subqueries:一些语法约束适用于递归CTE子查询:

  • The recursive SELECT part must not contain these constructs:递归SELECT部分不能包含以下构造:

    • Aggregate functions such as SUM()聚合函数,如SUM()

    • Window functions窗口函数

    • GROUP BY

    • ORDER BY

    • DISTINCT

    Prior to MySQL 8.0.19, the recursive SELECT part of a recursive CTE also could not use a LIMIT clause. 在MySQL 8.0.19之前,递归CTE的递归SELECT部分也不能使用LIMIT子句。This restriction is lifted in MySQL 8.0.19, and LIMIT is now supported in such cases, along with an optional OFFSET clause. MySQL 8.0.19取消了这个限制,在这种情况下,LIMIT和可选的OFFSET子句都得到了支持。The effect on the result set is the same as when using LIMIT in the outermost SELECT, but is also more efficient, since using it with the recursive SELECT stops the generation of rows as soon as the requested number of them has been produced.对结果集的影响与在最外层的SELECT中使用LIMIT时相同,但效率更高,因为在递归SELECT中使用LIMIT会在生成请求的行数后立即停止行的生成。

    These constraints do not apply to the nonrecursive SELECT part of a recursive CTE. 这些约束不适用于递归CTE的非递归SELECT部分。The prohibition on DISTINCT applies only to UNION members; UNION DISTINCT is permitted.禁止使用DISTINCT只适用于UNION成员;允许UNION DISTINCT

  • The recursive SELECT part must reference the CTE only once and only in its FROM clause, not in any subquery. 递归SELECT部分只能引用CTE一次,并且只能在其FROM子句中引用,而不能在任何子查询中引用。It can reference tables other than the CTE and join them with the CTE. 它可以引用CTE以外的表,并将它们与CTE连接起来。If used in a join like this, the CTE must not be on the right side of a LEFT JOIN.如果在这样的连接中使用,则CTE不能位于LEFT JOIN的右侧。

These constraints come from the SQL standard, other than the MySQL-specific exclusions of ORDER BY, LIMIT (MySQL 8.0.18 and earlier), and DISTINCT.这些约束来自SQL标准,而不是特定于MySQL的ORDER BYLIMIT(MySQL 8.0.18及更早版本)和DISTINCT排除。

For recursive CTEs, EXPLAIN output rows for recursive SELECT parts display Recursive in the Extra column.对于递归CTE,EXPLAIN递归SELECT部分的输出行在Extra列中显示Recursive

Cost estimates displayed by EXPLAIN represent cost per iteration, which might differ considerably from total cost. EXPLAIN显示的成本估算表示每次迭代的成本,这可能与总成本有很大的不同。The optimizer cannot predict the number of iterations because it cannot predict at what point the WHERE clause becomes false.优化器无法预测迭代次数,因为它无法预测WHERE子句在什么时候变为false

CTE actual cost may also be affected by result set size. CTE实际成本也可能受到结果集大小的影响。A CTE that produces many rows may require an internal temporary table large enough to be converted from in-memory to on-disk format and may suffer a performance penalty. 产生许多行的CTE可能需要一个足够大的内部临时表,以便从内存格式转换为磁盘格式,并且可能会受到性能损失。If so, increasing the permitted in-memory temporary table size may improve performance; see Section 8.4.4, “Internal Temporary Table Use in MySQL”.如果是这样,增加内存中允许的临时表大小可以提高性能;请参阅第8.4.4节,“MySQL中的内部临时表使用”

Limiting Common Table Expression Recursion限制公共表表达式递归

It is important for recursive CTEs that the recursive SELECT part include a condition to terminate recursion. 对于递归CTE来说,递归SELECT部分包含终止递归的条件是很重要的。As a development technique to guard against a runaway recursive CTE, you can force termination by placing a limit on execution time:作为一种防止失控的递归CTE的开发技术,您可以通过限制执行时间来强制终止:

  • The cte_max_recursion_depth system variable enforces a limit on the number of recursion levels for CTEs. cte_max_recursion_depth系统变量强制限制CTE的递归级别数。The server terminates execution of any CTE that recurses more levels than the value of this variable.服务器终止任何比该变量的值递归更多级别的CTE的执行。

  • The max_execution_time system variable enforces an execution timeout for SELECT statements executed within the current session.max_execution_time系统变量为当前会话中执行的SELECT语句强制执行超时。

  • The MAX_EXECUTION_TIME optimizer hint enforces a per-query execution timeout for the SELECT statement in which it appears.MAX_EXECUTION_TIME优化器提示对出现在其中的SELECT语句强制每个查询执行超时。

Suppose that a recursive CTE is mistakenly written with no recursion execution termination condition:假设在没有递归执行终止条件的情况下错误地编写了递归CTE:

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

By default, cte_max_recursion_depth has a value of 1000, causing the CTE to terminate when it recurses past 1000 levels. 默认情况下,cte_max_recursion_depth的值为1000,当它递归超过1000级时,导致cte终止。Applications can change the session value to adjust for their requirements:应用程序可以更改会话值以适应其需求:

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

You can also set the global cte_max_recursion_depth value to affect all sessions that begin subsequently.您还可以设置全局cte_max_recursion_depth值以影响随后开始的所有会话。

For queries that execute and thus recurse slowly or in contexts for which there is reason to set the cte_max_recursion_depth value very high, another way to guard against deep recursion is to set a per-session timeout. 对于执行缓慢从而递归的查询,或者在有理由将cte_max_recursion_depth值设置得非常高的上下文中,另一种防止深度递归的方法是设置每个会话超时。To do so, execute a statement like this prior to executing the CTE statement:为此,请在执行CTE语句之前执行如下语句:

SET max_execution_time = 1000; -- impose one second timeout

Alternatively, include an optimizer hint within the CTE statement itself:或者,在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;

Beginning with MySQL 8.0.19, you can also use LIMIT within the recursive query to impose a maximum nuber of rows to be returned to the outermost SELECT, for example:从MySQL8.0.19开始,您还可以在递归查询中使用LIMIT来设置要返回到最外层SELECT的最大行数,例如:

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

You can do this in addition to or instead of setting a time limit. 除了设置时间限制之外,您还可以这样做,也可以不设置时间限制。Thus, the following CTE terminates after returning ten thousand rows or running for one thousand seconds, whichever occurs first:因此,以下CTE在返回一万行或运行一千秒后终止,以先发生的为准:

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

If a recursive query without an execution time limit enters an infinite loop, you can terminate it from another session using KILL QUERY. 如果没有执行时间限制的递归查询进入无限循环,可以使用KILL QUERY从另一个会话终止它。Within the session itself, the client program used to run the query might provide a way to kill the query. 在会话本身中,用于运行查询的客户机程序可能会提供终止查询的方法。For example, in mysql, typing Control+C interrupts the current statement.例如,在mysql中,键入Control+C会中断当前语句。

Recursive Common Table Expression Examples递归公共表表达式示例

As mentioned previously, recursive common table expressions (CTEs) are frequently used for series generation and traversing hierarchical or tree-structured data. 如前所述,递归公共表表达式(cte)经常用于序列生成和遍历层次结构或树结构的数据。This section shows some simple examples of these techniques.本节展示了这些技术的一些简单示例。

Fibonacci Series Generation斐波那契级数生成

A Fibonacci series begins with the two numbers 0 and 1 (or 1 and 1) and each number after that is the sum of the previous two numbers. 斐波那契级数从两个数字0和1(或1和1)开始,之后的每个数字是前两个数字的和。A recursive common table expression can generate a Fibonacci series if each row produced by the recursive SELECT has access to the two previous numbers from the series. 如果递归SELECT生成的每一行都可以访问序列中的前两个数字,则递归公共表表达式可以生成斐波那契序列。The following CTE generates a 10-number series using 0 and 1 as the first two numbers:以下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;

The CTE produces this result: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 |
+------+-------+------------+

How the CTE works:CTE的工作原理:

  • n is a display column to indicate that the row contains the n-th Fibonacci number. n是一个显示列,表示该行包含第n个斐波那契数。For example, the 8th Fibonacci number is 13.例如,第8个斐波那契数是13。

  • The fib_n column displays Fibonacci number n.fib_n列显示斐波那契数n

  • The next_fib_n column displays the next Fibonacci number after number n. next_fib_n列显示数字n之后的下一个斐波那契数字。This column provides the next series value to the next row, so that row can produce the sum of the two previous series values in its fib_n column.此列将下一个系列值提供给下一行,以便该行可以在其fib_n列中生成前两个系列值的总和。

  • Recursion ends when n reaches 10. 递归在n达到10时结束。This is an arbitrary choice, to limit the output to a small set of rows.这是一个任意的选择,将输出限制为一小部分行。

The preceding output shows the entire CTE result. 前面的输出显示了整个CTE结果。To select just part of it, add an appropriate WHERE clause to the top-level SELECT. 要只选择其中的一部分,请在顶层SELECT中添加适当的WHERE子句。For example, to select the 8th Fibonacci number, do this:例如,要选择第8个斐波那契数,请执行以下操作:

mysql> WITH RECURSIVE fibonacci ...
       ...
SELECT fib_n FROM fibonacci WHERE n = 8;
+-------+
| fib_n |
+-------+
|    13 |
+-------+
Date Series Generation日期序列生成

A common table expression can generate a series of successive dates, which is useful for generating summaries that include a row for all dates in the series, including dates not represented in the summarized data.公共表表达式可以生成一系列连续的日期,这对于生成包含序列中所有日期行的摘要(包括摘要数据中未表示的日期)非常有用。

Suppose that a table of sales numbers contains these rows:假设销售编号表包含以下行:

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 |
+------------+--------+

This query summarizes the sales per day:此查询汇总了每天的销售额:

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 |
+------------+-----------+

However, that result contains holes for dates not represented in the range of dates spanned by the table. 但是,该结果包含表所跨越的日期范围内未表示的日期的“洞”。A result that represents all dates in the range can be produced using a recursive CTE to generate that set of dates, joined with a LEFT JOIN to the sales data.可以使用递归CTE生成表示该范围内所有日期的结果,以生成该日期集,并与销售数据进行LEFT JOIN

Here is the CTE to generate the date range series:以下是生成日期范围系列的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;

The CTE produces this result: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 |
+------------+

How the CTE works:CTE的工作原理:

  • The nonrecursive SELECT produces the lowest date in the date range spanned by the sales table.非递归SELECT生成sales表所跨越的日期范围中的最低日期。

  • Each row produced by the recursive SELECT adds one day to the date produced by the previous row.递归SELECT生成的每一行在前一行生成的日期的基础上加上一天。

  • Recursion ends after the dates reach the highest date in the date range spanned by the sales table.递归在日期到达sales表所跨越的日期范围内的最高日期之后结束。

Joining the CTE with a LEFT JOIN against the sales table produces the sales summary with a row for each date in the range:将CTE与sales表的LEFT JOIN连接起来,生成销售汇总,其中包含范围内每个日期的行:

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;

The output looks like this:输出如下所示:

+------------+-----------+
| 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 |
+------------+-----------+

Some points to note:需要注意的几点:

  • Are the queries inefficient, particularly the one with the MAX() subquery executed for each row in the recursive SELECT? 查询是否效率低下,特别是对递归SELECT中的每一行执行MAX()子查询的查询?EXPLAIN shows that the subquery containing MAX() is evaluated only once and the result is cached.EXPLAIN显示包含MAX()的子查询只计算一次,结果被缓存。

  • The use of COALESCE() avoids displaying NULL in the sum_price column on days for which no sales data occur in the sales table.使用COALESCE()可以避免在sales表中没有销售数据的日期的sum_price列中显示NULL

Hierarchical Data Traversal分层数据遍历

Recursive common table expressions are useful for traversing data that forms a hierarchy. 递归公共表表达式对于遍历形成层次结构的数据非常有用。Consider these statements that create a small data set that shows, for each employee in a company, the employee name and ID number, and the ID of the employee's manager. 考虑这些语句,创建一个小数据集,显示一个公司的每个雇员的雇员姓名和ID号,以及雇员经理的ID。The top-level employee (the CEO), has a manager ID of NULL (no manager).顶级员工(CEO)的经理ID为NULL(无经理)。

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);

The resulting data set looks like this:结果数据集如下所示:

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 |
+------+---------+------------+

To produce the organizational chart with the management chain for each employee (that is, the path from CEO to employee), use a recursive CTE:要使用每个员工的管理链(即从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;

The CTE produces this output: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     |
+------+---------+-----------------+

How the CTE works:CTE的工作原理:

  • The nonrecursive SELECT produces the row for the CEO (the row with a NULL manager ID).非递归SELECT生成CEO的行(管理ID为NULL的行)。

    The path column is widened to CHAR(200) to ensure that there is room for the longer path values produced by the recursive SELECT.path列被加宽为CHAR(200),以确保有空间容纳递归SELECT生成的较长path值。

  • Each row produced by the recursive SELECT finds all employees who report directly to an employee produced by a previous row. 递归SELECT生成的每一行查找直接向上一行生成的员工报告的所有员工。For each such employee, the row includes the employee ID and name, and the employee management chain. 对于每个这样的员工,该行包括员工ID和姓名,以及员工管理链。The chain is the manager's chain, with the employee ID added to the end.链是管理者的链,其末尾添加了雇员ID。

  • Recursion ends when employees have no others who report to them.当员工没有其他人向他们报告时,递归结束。

To find the path for a specific employee or employees, add a WHERE clause to the top-level SELECT. 要查找特定雇员的路径,请在顶层SELECT中添加WHERE子句。For example, to display the results for Tarek and Sarah, modify that SELECT like this:例如,要显示Tarek和Sarah的结果,请修改SELECT如下:

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         |
+------+-------+-----------------+

Common Table Expressions Compared to Similar Constructs常用表表达式与类似结构的比较

Common table expressions (CTEs) are similar to derived tables in some ways:公共表表达式(CTE)在某些方面与派生表类似:

  • Both constructs are named.两个构造都命名为。

  • Both constructs exist for the scope of a single statement.两个构造都存在于单个语句的作用域中。

Because of these similarities, CTEs and derived tables often can be used interchangeably. 由于这些相似性,CTE和派生表通常可以互换使用。As a trivial example, these statements are equivalent:举个简单的例子,这些语句是等价的:

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

However, CTEs have some advantages over derived tables:但是,与派生表相比,CTE有一些优势:

  • A derived table can be referenced only a single time within a query. 派生表只能在查询中引用一次。A CTE can be referenced multiple times. CTE可以多次引用。To use multiple instances of a derived table result, you must derive the result multiple times.若要使用派生表结果的多个实例,必须多次派生该结果。

  • A CTE can be self-referencing (recursive).CTE可以是自引用的(递归的)。

  • One CTE can refer to another.一个CTE可以引用另一个CTE。

  • A CTE may be easier to read when its definition appears at the beginning of the statement rather than embedded within it.当CTE的定义出现在语句的开头而不是嵌入其中时,它可能更容易阅读。

CTEs are similar to tables created with CREATE [TEMPORARY] TABLE but need not be defined or dropped explicitly. CTE类似于用CREATE [TEMPORARY] TABLE创建的表,但不需要显式定义或删除。For a CTE, you need no privileges to create tables.对于CTE,创建表不需要任何权限。