SQL入门:CET-简化复杂查询的利器
一、CET 的核心概念
CET(Common Table Expression,通用表表达式)是 SQL 中通过WITH
关键字定义的临时结果集,仅在当前 SQL 语句的执行周期内有效,用于简化复杂查询逻辑。
本质与特性:
- 临时性:不存储在磁盘,仅在查询执行时动态生成,执行结束后立即释放,避免资源占用。
- 可读性:通过命名将复杂子查询模块化,让逻辑更直观(如
UserOrders
比嵌套子查询更易理解)。 - 递归能力:支持递归查询(需
RECURSIVE
关键字),是处理树形结构(如组织架构)、序列生成的唯一简洁方式。 - 作用域:仅在定义它的 SQL 语句中有效,无法被其他独立查询调用(区别于临时表)。
二、CET 的使用场景与语法
1. 非递归 CET(基础场景)
适用于将复杂查询拆分为多个临时结果集,避免多层嵌套,常见于多表关联、数据过滤、统计分析等场景。
(1)单 CET 语法
WITH 结果集名称 (列名1, 列名2, ...) -- 列名可选,默认继承子查询列名
AS (-- 子查询:生成临时结果集的逻辑SELECT 列1, 列2 FROM 表名 WHERE 过滤条件
)
-- 主查询:引用CET进行分析
SELECT * FROM 结果集名称;
示例:筛选 2024 年订单金额超 1000 的用户消费记录
WITH HighValueOrders (订单ID, 用户ID, 金额) AS (SELECT order_id, user_id, amountFROM ordersWHERE order_date >= '2024-01-01' AND amount > 1000
)
SELECT 用户ID, SUM(金额) AS 总消费
FROM HighValueOrders
GROUP BY 用户ID;
(2)多 CET 语法(逻辑串联)
多个 CET 用逗号分隔,后续 CET 可引用前面的 CET,实现逻辑分层。
示例:分析用户年度消费与复购情况
WITH
-- 步骤1:筛选2024年订单
Year2024Orders AS (SELECT order_id, user_id, amount, order_dateFROM ordersWHERE order_date BETWEEN '2024-01-01' AND '2024-12-31'
),
-- 步骤2:统计用户年度总消费(引用步骤1)
UserTotalSpend AS (SELECT user_id, SUM(amount) AS total_spendFROM Year2024OrdersGROUP BY user_id
),
-- 步骤3:筛选复购用户(订单数≥2,引用步骤1)
RepeatUsers AS (SELECT user_id, COUNT(order_id) AS order_countFROM Year2024OrdersGROUP BY user_idHAVING COUNT(order_id) >= 2
)
-- 主查询:关联用户信息,输出结果
SELECT u.user_name, uts.total_spend, ru.order_count
FROM users u
JOIN UserTotalSpend uts ON u.user_id = uts.user_id
JOIN RepeatUsers ru ON u.user_id = ru.user_id;
2. 递归 CET(高级场景)
用于处理树形结构(如部门层级、分类关系)或序列生成(如日期序列),必须包含两部分:
- 锚点成员:递归的起始数据(如顶级部门)。
- 递归成员:引用自身 CET 的查询,定义层级关联逻辑(如子部门关联父部门),需用
UNION ALL
连接。
语法结构
WITH RECURSIVE 递归结果集名称 (列1, 列2, 层级列)
AS (-- 1. 锚点成员:起始数据(非递归查询)SELECT dept_id, dept_name, parent_id, 1 AS levelFROM departmentsWHERE parent_id = 0 -- 假设parent_id=0为顶级部门UNION ALL -- 必须用UNION ALL连接-- 2. 递归成员:引用自身,生成下一层数据SELECT d.dept_id, d.dept_name, d.parent_id, r.level + 1 AS levelFROM departments dJOIN 递归结果集名称 r ON d.parent_id = r.dept_id -- 子部门关联父部门
)
-- 主查询:获取所有层级数据
SELECT dept_name, level FROM 递归结果集名称 ORDER BY level;
示例:查询部门层级(含 3 级部门)
假设departments
表数据:
dept_id | dept_name | parent_id |
1 | 总部 | 0 |
2 | 技术部 | 1 |
3 | 研发组 | 2 |
4 | 测试组 | 2 |
5 | 产品部 | 1 |
执行递归 CET 后结果:
dept_name | level |
总部 | 1 |
技术部 | 2 |
产品部 | 2 |
研发组 | 3 |
测试组 | 3 |
三、CET 与其他临时结果集的区别
特性 | CET(WITH 子句) | 子查询(嵌套 / 派生表) | 临时表(CREATE TEMP TABLE) |
作用范围 | 仅当前 SQL 语句有效 | 仅所在查询块有效(如 FROM 子句 | 会话内有效(可多查询复用) |
可读性 | 高(模块化命名) | 低(多层嵌套难理解) | 中(需单独定义) |
递归能力 | 支持(需 RECURSIVE) | 不支持 | 不支持 |
性能(常规场景) | 与子查询相当(数据库优化) | 与 CET 相当 | 略高(可建索引) |
适用场景 | 复杂查询拆解、递归逻辑 | 简单嵌套查询 | 需多次复用临时数据的场景 |
四、CET 的优化技巧
CET 的性能优化需结合数据库执行计划,核心目标是减少临时结果集的大小和计算复杂度。
1. 非递归 CET 优化
-
提前过滤数据:在 CET 子查询中尽量通过
WHERE
、LIMIT
等条件减少结果集行数,避免后续关联时处理冗余数据。
-- 优化前:CET返回所有订单,再过滤
WITH AllOrders AS (SELECT * FROM orders)
SELECT * FROM AllOrders WHERE amount > 1000;-- 优化后:CET内部直接过滤,结果集更小
WITH HighValueOrders AS (SELECT * FROM orders WHERE amount > 1000)
SELECT * FROM HighValueOrders;
-
避免重复计算:将重复使用的计算逻辑(如
SUM(amount)
)放入 CET,减少重复执行。 -
合理使用索引:对 CET 子查询中
WHERE
、JOIN
涉及的字段(如order_date
、user_id
)建立索引,加速筛选和关联。
2. 递归 CET 优化(重点)
递归查询容易因层级过深或数据量大导致性能问题,需针对性优化:
-
限制最大层级:通过
level < N
避免无限递归(如WHERE level < 10
),尤其适用于数据可能存在循环引用的场景(如A→B→A
)。 -
追踪路径避免重复:用字符串记录路径(如
path = CONCAT(parent_path, ',', dept_id)
),通过NOT LIKE
排除已访问节点:
WITH RECURSIVE DeptTree AS (SELECT dept_id, parent_id, CAST(dept_id AS CHAR(100)) AS path FROM departments WHERE parent_id = 0UNION ALLSELECT d.dept_id, d.parent_id, CONCAT(t.path, ',', d.dept_id)FROM departments dJOIN DeptTree t ON d.parent_id = t.dept_idWHERE t.path NOT LIKE CONCAT('%', d.dept_id, '%') -- 排除已出现的节点
)
SELECT * FROM DeptTree;
-
索引优化:对递归关联的字段(如
parent_id
)建立索引,加速子查询与 CET 的连接。 -
拆分大递归:若层级超过 100 级,可拆分为多次小递归查询,避免单次递归占用过多内存。
五、常见问题与解决方案
-
递归 CET 报错 “列数不匹配”
- 原因:锚点成员与递归成员返回的列数或数据类型不一致。
- 解决:确保两部分列名、数量、类型完全一致(如锚点返回
dept_id, level
,递归也必须返回相同结构)。
-
递归查询陷入死循环
- 原因:数据存在循环引用(如
A.parent_id = B.dept_id
且B.parent_id = A.dept_id
)。 - 解决:通过
level
限制最大层级,或用路径追踪排除重复节点。
- 原因:数据存在循环引用(如
-
CET 性能比子查询差
- 原因:数据库优化器未正确识别 CET 逻辑,导致执行计划低效。
- 解决:查看执行计划(如
EXPLAIN ANALYZE
),对关键字段加索引;复杂场景可尝试将 CET 改写为子查询对比性能。
六、总结
CET 是简化复杂 SQL 的核心工具,非递归 CET 适合拆解多层逻辑,递归 CET 则是处理树形结构的唯一高效方式。使用时需注意:
- 优先通过命名和分层提升可读性;
- 递归查询必须控制层级,避免死循环;
- 结合索引和数据过滤优化性能,必要时通过执行计划分析瓶颈。