📜  MySQL |递归 CTE(通用表表达式)

📅  最后修改于: 2021-09-08 15:50:11             🧑  作者: Mango

什么是 CTE?

在 MySQL 中,每个查询都会生成一个临时结果或关系。为了给这些临时结果集命名,使用了 CTE。

  • CTE 是使用WITH子句定义的。
  • 使用 WITH 子句,我们可以在一条语句中定义多个 CTE。
  • CTE 可以在属于同一 WITH 子句的其他 CTE 中引用,但这些 CTE 应该更早定义。
  • 每个 CTE 的范围都存在于定义它的语句中。

递归 CTE是一个子查询,它使用自己的名称来引用自己。

  • 递归 CTE 是使用WITH RECURSIVE子句定义的。
  • 递归 CTE 应该有一个终止条件。
  • 递归 CTE 用于生成和遍历分层或树结构数据的系列。

句法:

WITH RECURSIVE 
cte_name [(col1, col2, ...)]
AS ( subquery )
Select col1, col2, .. from cte_name;


cte_name: Name given to recursive subquery written in subquery block.
col1, col2, ...colN: The name given to columns generated by subquery.
subquery: A MySql query that refer to itself using cte_name as its own name.

此处, SELECT 语句中给出的列名应与 list 中提供的名称匹配,后跟cte_name

子查询块中提供的递归CTE结构:

Select col1, col2, ... coln from table_name        
UNION [ALL, DISTINCT]
Select col1, col2, ...coln from cte_name          
WHERE clause

递归 CTE 由一个非递归子查询和一个递归子查询组成 –

  • 第一个 select 语句是一个非递归语句,它为结果集提供初始行。
  • UNION [ALL, DISTINCT] 用于向先前的结果集中添加额外的行。 ALL 和 DISTINCT 关键字的使用用于在最后一个结果集中包含或消除重复行。
  • 第二个选择语句是一个递归语句,它迭代地产生结果集,直到 WHERE 子句中提供的条件为真。
  • 每次迭代产生的结果集以上次迭代产生的结果集为基表。
  • 当递归选择语句不产生任何额外的行时,递归结束。

例子:

  1. 考虑以下查询,该查询生成一系列前 5 个奇数 –
    询问:
    WITH RECURSIVE 
    odd_no (sr_no, n) AS
    (
    SELECT 1, 1 
    union all
    SELECT sr_no+1, n+2 from odd_no where sr_no < 5 
    )
    SELECT * FROM odd_no;  
    

    输出:

    +---------+-------+
    | sr_no   |  n    |
    +---------+-------+
    | 1       |  1    |
    | 2       |  3    |
    | 3       |  5    |
    | 4       |  7    |
    | 5       |  9    |
    +---------+-------+
    

    解释:

    上面的查询由两部分组成 – 非递归和递归。

    非递归部分 – 它将产生由名为“sr_no”和“n”的两列和单行组成的初始行。

    Query:
    SELECT 1, 1 
    
    Output:
    +---------+-------+
    | sr_no   |  n    |
    +---------+-------+
    | 1       |  1    |
    +---------+-------+
    

    递归部分-

    SELECT sr_no+1, n+2 from cte where odd_no < 5 
    

    它将向先前的输出添加行,直到满足终止条件即( sr_no < 5 )。

    当 sr_no 变为 5 时,条件变为假并且递归终止。

  2. 考虑下面的“bst”表——
    mysql> SELECT * FROM bst order by node;
    +------+-----------+
    | node | parent    | 
    +------+-----------+
    |  1   |  NULL     |
    |  2   |   1       |
    |  3   |   1       |
    |  4   |   2       |
    |  5   |   2       | 
    |  6   |   3       |
    |  7   |   3       |
    +------+-----------+
    

    上表“bst”由两列“node”和“parent”组成,它们给出了二叉搜索树中节点的值及其各自的父值。

    问题描述:我们必须在给定的“bst”中找到所有节点的路径。

    询问:

    WITH RECURSIVE
    cte ( node, path )
    AS
    ( SELECT node, cast ( 1 as char(30) )  
              FROM bst WHERE parent IS NULL
      UNION ALL
      SELECT bst.node,  CONCAT ( cte.path, '-->', bst.node ) 
             FROM cte JOIN bst ON cte.node = bst.parent
    )
    SELECT * FROM cte ORDER BY node;
    

    输出:

    +------+-----------+
    | node |   path    |
    +------+-----------+
    |  1   | 1         |
    |  2   | 1-->2     |
    |  3   | 1-->3     |
    |  4   | 1-->2-->4 |
    |  5   | 1-->2-->5 |
    |  6   | 1-->3-->6 |
    |  7   | 1-->3-->7 |
    +------+-----------+
    

    解释:

    这里,上面 CTE 中的非递归部分将只给出一行,其中包含一个根节点及其设置为 1 的路径。

    SELECT node, cast ( 1 as char(30) )  
              FROM bst WHERE parent IS NULL
    
    Output:
    +------+-----------+
    | node |   path    |
    +------+-----------+
    |  1   | 1         |
    +------+-----------+
    

    递归部分-

    SELECT bst.node,  CONCAT ( cte.path, '-->', bst.node ) 
             FROM cte JOIN bst ON cte.node = bst.parent
    

    递归 SELECT 语句将在 bst 中找到所有节点,其父节点是前一次迭代中产生的节点。

    当前一次迭代中产生的节点 ie(leaf node) 不包含 bst 中的任何子节点时,这样的迭代结束。