什么是CTE
CTE(Common Table Expression公共表表达式)是一个临时的结果集,它在SQL语句执行期间存在,并且可以在包含它的SQL查询中被引用。它提供了一种更加清晰和灵活的方式来编写复杂的SQL查询,允许你将查询分解成更小的、可管理的部分,并且可以在同一查询中多次引用。它是以WITH语句开始的,后跟CTE的名称、可选的列名列表,以及一个AS关键字后跟一个括号,括号中包含CTE的查询。
MySQL从版本8.0开始支持CTE。
以下是一个使用CTE的简单SQL查询示例:
scss
WITH [RECURSIVE]
cte_name [(col_name [, col_name] ...)] AS (subquery)
[, cte_name [(col_name [, col_name] ...)] AS (subquery)] ...
• cte_name: CTE名称,后续使用
• col_name: 列名,未指定时从subquery中推断
• subquery: subquery部分产生结果集,括号为必须
sql
WITH cte (col1, col2) AS
(
SELECT 1, 2
UNION ALL
SELECT 3, 4
)
SELECT col1, col2 FROM cte;
+------+------+
| col1 | col2 |
+------+------+
| 1 | 2 |
| 3 | 4 |
+------+------+
什么是RECURSIVE CTE
RECURSIVE CTE(递归公用表表达式)是一种特殊类型的CTE,用于执行递归操作.递归基本语法如下:
sql
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 |
+------+
• WITH后必须包含RECURISIVE
• subquery中,SELECT 1为初始查询(递归入口),SELECT n + 1 FROM cte WHERE n < 5为递归部分,查询终止条件(递归出口)为递归部分结果为空; 每次迭代只会处理上一次迭代产生的结果
以上两个例子看完还是不懂?没关系,下面我们看看其在OneTerm的实战。
场景1: 查询资产的目录结构
在OneTerm中,每个资产节点都包含一个节点名称属性,即为资产所属节点目录的路径,如图中test/测试组1,很自然的想到这种查询需要自顶向下的递归过程。
简单起见,示例表中仅包含需要用到的字段
sql
CREATE DATABASE IF NOT EXISTS test;
CREATE TABLE IF NOT EXISTS test.node (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(64) NOT NULL DEFAULT '',
`parent_id` INT NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
INSERT INTO test.node (id, name, parent_id)
VALUES(1, "node1", 0);
INSERT INTO test.node (id, name, parent_id)
VALUES(2, "node2", 1);
INSERT INTO test.node (id, name, parent_id)
VALUES(3, "node3", 1);
INSERT INTO test.node (id, name, parent_id)
VALUES(4, "node4", 2);
INSERT INTO test.node (id, name, parent_id)
VALUES(5, "node5", 2);
INSERT INTO test.node (id, name, parent_id)
VALUES(6, "node6", 5);
select * from test.node;
+----+-------+-----------+
| id | name | parent_id |
+----+-------+-----------+
| 1 | node1 | 0 |
| 2 | node2 | 1 |
| 3 | node3 | 1 |
| 4 | node4 | 2 |
| 5 | node5 | 2 |
| 6 | node6 | 5 |
+----+-------+-----------+
递归CTE查询:
sql
WITH RECURSIVE cte AS (
SELECT id,
name,
0 AS depth
FROM test.node
WHERE id = 1
UNION ALL
SELECT t.id,
CONCAT(cte.name, '->', t.name),
cte.depth + 1
FROM cte
INNER JOIN test.node AS t ON cte.id = t.parent_id
)
SELECT *
FROM cte;
+------+----------------------------+-------+
| id | name | depth |
+------+----------------------------+-------+
| 1 | node1 | 0 |
| 2 | node1->node2 | 1 |
| 3 | node1->node3 | 1 |
| 4 | node1->node2->node4 | 2 |
| 5 | node1->node2->node5 | 2 |
| 6 | node1->node2->node5->node6 | 3 |
+------+----------------------------+-------+
场景2: 查询每个节点目录下包含的资产个数
与场景1相对,资产树每个节点需要展示他所包含的资产个数,资产个数不仅要包含当前节点直接所属的资产个数,同时要包含所有子节点的资产个数。此时就不能简单使用group by来统计。同样的还是得使用递归查询来统计。与场景1不同的是,我们需要自底向上的递归。
这个过程其实和算法中对树的统计是一样的,场景1需要求出根节点到每个节点的路径,场景2需要求出每个节点的子节点的权值和+该节点本身权值
sql
WITH RECURSIVE cte AS (
SELECT parent_id
FROM test.node
UNION ALL
SELECT t.parent_id
FROM cte
INNER JOIN test.node AS t ON cte.parent_id = t.id
)
SELECT parent_id,
count(*) AS cnt
FROM cte
GROUP BY parent_id;
+-----------+-----+
| parent_id | cnt |
+-----------+-----+
| 0 | 6 |
| 1 | 5 |
| 2 | 3 |
| 5 | 1 |
+-----------+-----+
参考