在上篇文章基础上,我们进一步解决层级数据递归汇总问题 —— 让上级部门的统计结果自动包含所有下级部门数据(含多级子部门),并新增请假天数大于 3 天的统计维度。通过递归 CTE、DECODE函数与分组函数的深度结合,实现真正意义上的树形结构数据聚合。
一、业务需求升级:层级汇总与新增统计维度
核心目标
- 递归汇总:上级部门数据包含所有直属 / 非直属下级部门数据(如集团总部需汇总技术研发部、产品运营部及其子部门数据)
- 新增统计项:统计每个部门(含各级上级)的 "请假天数 > 3 天" 的记录数
- 兼容原有指标:保留请假类型天数统计、状态分类统计
数据模型扩展(无需修改表结构,新增计算逻辑)
-- 新增判断逻辑:请假天数>3天标记DECODE(SIGN(leave_days - 3), 1, 1, 0) AS over_3_days_flag-- SIGN函数说明:返回1(正数)、0(零)、-1(负数),简化条件判断
二、关键技术升级:双向递归 CTE 构建层级关系
1. 递归 CTE 重构:获取每个部门的所有后代部门
WITH dept_ancestor AS (-- 初始层:每个部门自身作为祖先SELECTdept_id,dept_name,parent_dept_id,dept_id AS ancestor_id -- 核心字段:标记当前部门的顶层祖先FROM t_deptUNION ALL-- 递归层:向下遍历子部门,继承祖先IDSELECTd.dept_id,d.dept_name,d.parent_dept_id,da.ancestor_id -- 子部门继承父部门的祖先IDFROM t_dept dJOIN dept_ancestor da ON d.parent_dept_id = da.dept_id)
- 核心逻辑:为每个部门生成从自身到所有后代的层级路径,ancestor_id表示当前统计的顶层部门(如子部门 4 的 ancestor_id 可为自身 4、父部门 2、根部门 1)
- 递归方向:从父部门到子部门的向下递归,确保每个子部门关联到所有上级祖先
2. 关联请假表与递归 CTE
SELECTda.ancestor_id, -- 统计的目标部门(上级部门)da_dept.dept_name, -- 目标部门名称tl.dept_id AS child_dept_id -- 实际产生数据的子部门ID(用于验证层级)FROM dept_ancestor daLEFT JOIN t_dept da_dept ON da.ancestor_id = da_dept.dept_id -- 关联祖先部门信息LEFT JOIN t_leave tl ON da.dept_id = tl.dept_id -- 关联子部门请假数据-- 示例输出:祖先部门1(集团总部)会关联到子部门2、3、4、5的所有请假记录
三、DECODE 函数进阶:多维度条件聚合
1. 新增 "请假 > 3 天" 统计
SUM(DECODE(SIGN(tl.leave_days - 3), 1, 1, 0)) AS over_3_days_count-- 等价于:SUM(CASE WHEN tl.leave_days > 3 THEN 1 ELSE 0 END)-- DECODE优势:通过数值比较简化条件表达式,执行效率更高
2. 全维度统计表达式(整合新旧需求)
SELECTda_dept.dept_name AS 部门名称,-- 请假类型统计(含下级部门)SUM(DECODE(tl.leave_type, '年假', tl.leave_days, 0)) AS 年假总天数,SUM(DECODE(tl.leave_type, '事假', tl.leave_days, 0)) AS 事假总天数,SUM(DECODE(tl.leave_type, '病假', tl.leave_days, 0)) AS 病假总天数,-- 状态统计SUM(DECODE(tl.leave_status, '完成', 1, 0)) AS 完成请假数,SUM(DECODE(tl.leave_status, '进行中', 1, 0)) AS 进行中请假数,-- 新增统计:请假>3天SUM(DECODE(SIGN(tl.leave_days - 3), 1, 1, 0)) AS 超3天请假数
四、终极 SQL:递归汇总全层级数据
完整实现代码如下:
相信我,如果你能学会下面这个SQL的写法或者能看懂,那么你对ORACLE类似逻辑的处理已经达到极高的水平,这个SQL我认为有95%的人会看不懂。如果你们都会了欢迎留言打脸。因为这个SQL的实现我在7年前专门拿出来给全公司技术人员进行过培训,留了一个类似的作业,结果1个完成的都没有。你也可以考虑下,如果不用SQL来实现,而是让你去通过代码去实现这个需求的统计,你需要写多少代码来实现,需要多少时间?
WITH dept_ancestor AS (-- 构建部门层级关系,获取每个部门的所有祖先路径SELECTdept_id,dept_name,parent_dept_id,dept_id AS ancestor_id -- 初始祖先为自身FROM t_deptUNION ALL-- 递归向下遍历子部门,继承祖先IDSELECTd.dept_id,d.dept_name,d.parent_dept_id,da.ancestor_id -- 子部门的祖先与父部门一致FROM t_dept dJOIN dept_ancestor da ON d.parent_dept_id = da.dept_id),-- 提取祖先部门的基础信息(避免重复计算)ancestor_info AS (SELECT DISTINCT ancestor_id, dept_nameFROM dept_ancestor)SELECTai.dept_name AS 部门名称,-- 请假类型汇总(含所有子部门)SUM(DECODE(tl.leave_type, '年假', tl.leave_days, 0)) AS 年假总天数,SUM(DECODE(tl.leave_type, '事假', tl.leave_days, 0)) AS 事假总天数,SUM(DECODE(tl.leave_type, '病假', tl.leave_days, 0)) AS 病假总天数,-- 状态汇总SUM(DECODE(tl.leave_status, '完成', 1, 0)) AS 完成请假数,SUM(DECODE(tl.leave_status, '进行中', 1, 0)) AS 进行中请假数,-- 新增统计项SUM(DECODE(SIGN(tl.leave_days - 3), 1, 1, 0)) AS 超3天请假数FROM ancestor_info aiLEFT JOIN dept_ancestor da ON ai.ancestor_id = da.ancestor_idLEFT JOIN t_leave tl ON da.dept_id = tl.dept_id -- 关联子部门请假数据GROUP BY ai.ancestor_id, ai.dept_nameORDER BY ai.ancestor_id;
执行结果解析(新增示例数据后)
部门名称 | 年假总天数 | 事假总天数 | 病假总天数 | 完成请假数 | 进行中请假数 | 超 3 天请假数 |
集团总部 | 8.5 | 2.0 | 3.0 | 2 | 3 | 2 |
技术研发部 | 4.5 | 2.0 | 0.0 | 1 | 2 | 2 |
产品运营部 | 1.5 | 0.0 | 3.0 | 1 | 1 | 0 |
后端开发组 | 3.5 | 0.0 | 0.0 | 1 | 1 | 1 |
前端开发组 | 0.0 | 2.0 | 0.0 | 0 | 1 | 1 |
核心逻辑拆解
1、递归 CTE 双向关联:
向上:每个部门作为祖先,向下遍历所有子部门(ancestor_id固定为顶层部门)
向下:通过da.dept_id = tl.dept_id关联子部门的实际数据,确保上级部门能获取所有下级数据
2、DECODE 的多维应用:
类型统计:按leave_type分类累加天数
状态统计:按leave_status分类计数
数值判断:通过SIGN函数简化 "大于 3 天" 的条件转换
3、分组策略:
按ancestor_id分组,确保每个上级部门汇总其所有后代(包括多级子部门)的数据
LEFT JOIN确保无数据部门(如根部门若自身无数据)仍能显示统计结果
五、与上篇文章的核心区别
特性 | 上篇文章(单部门统计) | 本文(递归层级统计) |
统计范围 | 仅当前部门或指定子部门 | 包含所有下级部门(多级递归) |
递归方向 | 单向向下(固定根部门) | 双向关联(每个部门可作为祖先) |
核心字段 | dept_id直接分组 | ancestor_id递归分组 |
新增功能 | 无 | 请假天数 > 3 天统计、层级汇总 |
六、性能优化与注意事项
1. 索引优化建议
-- 为部门层级关系创建索引CREATE INDEX idx_dept_parent ON t_dept(parent_dept_id);-- 为请假表关联字段创建索引CREATE INDEX idx_leave_dept ON t_leave(dept_id);
2. 大数据量处理
- 若部门层级超过 1000 层,需调整 Oracle 递归限制:
ALTER SESSION SET MAX_RECURSION_DEPTH = 2000; -- 默认1000层
3. DECODE vs CASE WHEN 扩展
- 复杂范围判断(如BETWEEN)建议用CASE WHEN,等值判断优先用DECODE
- 多层嵌套时注意DECODE的参数顺序(严格按匹配顺序执行)
七、总结:树形数据统计的终极解决方案
通过递归 CTE 构建层级关系+DECODE 实现条件聚合+分组函数完成数据汇总,我们实现了:
1、真正的层级递归统计:上级部门自动包含所有下级数据,支持任意深度的组织架构
2、多维度复杂计算:在单个 SQL 中完成类型统计、状态分类、数值判断等多重逻辑
3、代码极简主义:相比传统 Java 递归 + 多层循环,SQL 代码量减少 90% 以上,且执行效率更高
这种方案特别适合组织架构复杂、层级统计频繁的企业级应用(如人力资源管理、财务成本分摊等场景)。掌握递归与DECODE的组合使用,能让你在处理树形数据时如虎添翼,真正发挥 Oracle 数据库的原生优势。如果你能学会这种SQL逻辑,相信我,肯定会对你在实际工作中有巨大帮助。欢迎关注留言,期待与您一起进步。