news 2026/5/22 4:40:48

CTE+阶段式递归:用公共表表达式搞定复杂业务逻辑,告别SQL难题!

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CTE+阶段式递归:用公共表表达式搞定复杂业务逻辑,告别SQL难题!

📌 今日关键词:CTE、公共表表达式、递归查询、阶段式递归、WITH、树形结构

大家好,我是数据库小学妹👋

前面我们学过子查询、窗口函数这些进阶技能。今天我要分享一个让我"相见恨晚"的功能 ——CTE(公共表表达式)+ 递归

为什么这么说?因为我一开始遇到树形结构数据(部门层级、商品分类、组织架构)的时候,子查询套子查询,写到最后自己都绕晕了,性能还差。后来发现了CTE+递归这个组合,SQL写得清爽多了!

今天小学妹就带你从CTE基础到递归实战,一步步把这个技能掌握。


一、CTE 是什么?告别嵌套地狱

啥是CTE?你就理解成给一段查询结果起个名字,后面想用直接写名字就行。

就像 Excel 里给某个区域起名,后面公式里直接用那个名,不用每次都重写那片区域。

基础语法

WITHemployee_cteAS(SELECTid,name,manager_id,salaryFROMemployeesWHEREmanager_idISNULL)SELECT*FROMemployee_cte;

WITH 后面就是 CTE 的名字,AS 括号里是查询内容。最后用这个临时名字来查。

💡CTE 只在这次查询里有效,查完就没了,不会污染数据库。

CTE vs 子查询:有啥区别?

场景CTE子查询
代码可读性清爽,一层一层嵌套多了看瞎眼
复用性一个 CTE 多地方引用每次都要重写
调试方便,单独查 CTE麻烦,拆开要重寫
性能差不多差不多

用子查询套多了自己都看不下去,CTE 就是来解决这个问题的。


二、CTE 嵌套着用:复杂查询变简单

CTE 最实用的地方是可以一个接一个写,像搭积木一样。

💻 实战:部门薪资统计 + 排名

要把部门总薪资、平均薪资、排名全算出来,拆成三级 CTE:

WITHdepartment_salaryAS(SELECTdepartment_id,SUM(salary)astotal_salaryFROMemployeesGROUPBYdepartment_id),average_salaryAS(SELECTdepartment_id,total_salary,total_salary/COUNT(*)asavg_salaryFROMdepartment_salary),department_rankAS(SELECTdepartment_id,avg_salary,RANK()OVER(ORDERBYavg_salaryDESC)asrankFROMaverage_salary)SELECT*FROMdepartment_rank;

第一层算总薪资,第二层算平均,第三层加排名。一层一层往下走,每层干一件事,逻辑清清楚楚。

💡CTE 之间可以互相引用。后面的 CTE 可以直接用前面 CTE 的名字,就像引用表一样。

配合 CASE WHEN 做数据分类

WITHemployee_dataAS(SELECTid,name,salary,CASEWHENsalary>10000THEN'高薪'WHENsalary>5000THEN'中薪'ELSE'低薪'ENDassalary_levelFROMemployees),high_salary_employeesAS(SELECT*FROMemployee_dataWHEREsalary_level='高薪')SELECT*FROMhigh_salary_employees;

第一层先分类,第二层再筛选。写起来比嵌套子查询顺多了。


三、递归 CTE:处理树形结构的神器

递归 CTE 是 CTE 的进阶用法,专门用来查层级数据 —— 组织架构、商品分类、审批流程这些场景太常用了!

语法结构

WITHRECURSIVE recursive_cteAS(-- 基础查询(起点)SELECTid,name,manager_id,1aslevelFROMemployeesWHEREmanager_idISNULLUNIONALL-- 递归部分(自己调用自己)SELECTe.id,e.name,e.manager_id,r.level+1FROMemployees eINNERJOINrecursive_cte rONe.manager_id=r.id)SELECT*FROMrecursive_cte;

分两部分:

  1. 基础查询:先找到"起点"(没有上级的节点)
  2. 递归部分:用起点往下找,一层一层查,找不到新数据就停

💡 递归的逻辑就像你查家谱:先找到太爷爷(起点),然后一层层往下找子子孙孙。

💻 实战:部门层级查询

WITHRECURSIVE department_treeAS(SELECTid,name,parent_id,1aslevelFROMdepartmentsWHEREparent_idISNULLUNIONALLSELECTd.id,d.name,d.parent_id,dt.level+1FROMdepartments dINNERJOINdepartment_tree dtONd.parent_id=dt.id)SELECT*FROMdepartment_tree;

跑出来的结果:

idnameparent_idlevel
1总部NULL1
2销售部12
3技术部12
4UI 组23
5前端组23
6后端组33

以前实现这个要写存储过程或者复杂的自连接,现在一行 WITH RECURSIVE 搞定。做权限树、商品分类的同学,这个技能必须有!


四、阶段式递归的实战场景

📚 场景一:商品分类树

和部门层级类似,就是把部门换成商品:

WITHRECURSIVE product_treeAS(SELECTid,name,parent_id,1aslevelFROMproductsWHEREparent_idISNULLUNIONALLSELECTp.id,p.name,p.parent_id,pt.level+1FROMproducts pINNERJOINproduct_tree ptONp.parent_id=pt.id)SELECT*FROMproduct_tree;

📚 场景二:数据溯源

排查数据问题时经常要用 —— 找到某个记录的来源,一层一层往上找:

WITHRECURSIVE data_traceAS(SELECTid,data,parent_id,1astrace_levelFROMaudit_logWHEREid=12345UNIONALLSELECTa.id,a.data,a.parent_id,dt.trace_level+1FROMaudit_log aINNERJOINdata_trace dtONa.id=dt.parent_id)SELECT*FROMdata_trace;

💡 这个是"向上追溯",和前面的"向下展开"方向相反。核心区别在 JOIN 条件上:向下查是子.parent_id = 父.id,向上查是父.id = 子.parent_id

📚 场景三:多阶段业务逻辑拆解

客户分层这种需求,拆成几步更清楚:

WITHstage1AS(SELECTid,name,email,created_atFROMcustomersWHEREstatus='active'),stage2AS(SELECTc.id,c.name,SUM(o.amount)astotal_spentFROMstage1 cLEFTJOINorders oONc.id=o.customer_idGROUPBYc.id,c.name),stage3AS(SELECTid,name,total_spent,CASEWHENtotal_spent>10000THEN'VIP'WHENtotal_spent>5000THEN'普通 VIP'WHENtotal_spent>1000THEN'新客户'ELSE'潜在客户'ENDascustomer_levelFROMstage2)SELECT*FROMstage3;

第一层筛活跃客户,第二层算消费总额,第三层打标签。每一步干干净净,改逻辑也方便。

📚 场景四:审批流程追踪

WITHRECURSIVE approval_traceAS(SELECTid,process_id,user_id,status,1asstageFROMapprovalsWHEREprocess_id='P12345'ANDstatus='pending'UNIONALLSELECTa.id,a.process_id,a.user_id,a.status,at.stage+1FROMapprovals aINNERJOINapproval_trace atONa.process_id=at.process_idANDa.id=at.next_approval_id)SELECT*FROMapproval_trace;

这个在公司内部系统里很常用,查一条审批流到了哪一步、还有谁需要审批。


五、CTE + 窗口函数:强强联合

CTE 和窗口函数不冲突,经常混着用。CTE 负责拆分逻辑,窗口函数负责排名聚合。

比如同时做部门统计和员工排名:

WITHemployee_cteAS(SELECTid,name,department_id,salary,COUNT(*)OVER(PARTITIONBYdepartment_id)asdept_count,SUM(salary)OVER(PARTITIONBYdepartment_id)asdept_totalFROMemployees),ranked_employeesAS(SELECTid,name,department_id,salary,ROW_NUMBER()OVER(PARTITIONBYdepartment_idORDERBYsalaryDESC)asrankFROMemployee_cte)SELECT*FROMranked_employees;

六、新手避坑指南

❌ 坑一:忘记加递归限制

不加限制的话,万一数据有环,查起来就停不了了:

-- 错误示例:无限递归WITHRECURSIVE infinite_loopAS(SELECTid,nameFROMdepartmentsUNIONALLSELECTid,nameFROMinfinite_loop)SELECT*FROMinfinite_loop;
-- ✅ 正确写法:加递归限制WITHRECURSIVE safe_loopAS(SELECTid,name,1aslevelFROMdepartmentsUNIONALLSELECTid,name,sl.level+1FROMdepartments dINNERJOINsafe_loop slONd.parent_id=sl.idWHEREsl.level<10)SELECT*FROMsafe_loop;

❌ 坑二:用了 UNION 而不是 UNION ALL

UNION 要去重,多一层开销。递归 CTE 里基本都用 UNION ALL。

❌ 坑三:递归字段没建索引

递归字段(比如 parent_id、manager_id)一定要建索引,不然递归查询会慢到怀疑人生。

CREATEINDEXidx_parent_idONdepartments(parent_id);CREATEINDEXidx_manager_idONemployees(manager_id);

❌ 坑四:MySQL 版本不支持

CTE 是 MySQL 8.0 才有的功能!如果你还在用 5.7,升级或者用其他方式替代。


七、今日学习心得

  1. CTE 让复杂查询变清爽,一层一层写,比嵌套子查询好维护多了
  2. 递归 CTE 是树形数据的好工具,组织架构、商品分类、审批流程都能用
  3. 阶段式拆解是写 SQL 的好习惯,复杂业务拆成几步,每步干净利落
  4. 注意加递归限制和建索引,这两个坑我踩过,别让大家再踩了
  5. CTE + 窗口函数组合起来,能处理更多场景

👋 我是数据库小学妹,一个用设计师思维学数据库的转行人。我们一起,把复杂的技术变得简单有趣!💕


本文为个人学习总结,所有示例基于 MySQL 8.0+。如果你的版本低于 8.0,CTE 功能不可用,建议升级或使用其他方式替代。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/22 4:33:04

circumflex 语法高亮功能详解:让评论和文章更易读的终极指南

circumflex 语法高亮功能详解&#xff1a;让评论和文章更易读的终极指南 【免费下载链接】circumflex &#x1f33f; Its Hacker News in your terminal 项目地址: https://gitcode.com/gh_mirrors/ci/circumflex circumflex 是一个强大的终端 Hacker News 浏览器&#…

作者头像 李华
网站建设 2026/5/22 4:26:22

Octopress草稿管理终极指南:如何高效使用publish和unpublish命令

Octopress草稿管理终极指南&#xff1a;如何高效使用publish和unpublish命令 【免费下载链接】octopress Octopress 3.0 – Jekylls Ferrari 项目地址: https://gitcode.com/gh_mirrors/oct/octopress Octopress草稿管理功能是博客写作工作流中的重要组成部分&#xff0…

作者头像 李华
网站建设 2026/5/22 4:22:17

Istio安全防护完全手册:认证授权与证书管理最佳实践

Istio安全防护完全手册&#xff1a;认证授权与证书管理最佳实践 【免费下载链接】istio-handbook Istio服务网格进阶实战 项目地址: https://gitcode.com/gh_mirrors/is/istio-handbook 在微服务架构中&#xff0c;网络通信的安全性至关重要。Istio作为强大的服务网格解…

作者头像 李华