在上篇文章基础上,我们进一步解决层级数据递归汇总问题 —— 让上级部门的统计结果自动包含所有下级部门数据(含多级子部门),并新增请假天数大于 3 天的统计维度。通过递归 CTE、DECODE函数与分组函数的深度结合,实现真正意义上的树形结构数据聚合。
-- 新增判断逻辑:请假天数>3天标记
DECODE(SIGN(leave_days - 3), 1, 1, 0) AS over_3_days_flag
-- SIGN函数说明:返回1(正数)、0(零)、-1(负数),简化条件判断
WITH dept_ancestor AS (
-- 初始层:每个部门自身作为祖先
SELECT
dept_id,
dept_name,
parent_dept_id,
dept_id AS ancestor_id -- 核心字段:标记当前部门的顶层祖先
FROM t_dept
UNION ALL
-- 递归层:向下遍历子部门,继承祖先ID
SELECT
d.dept_id,
d.dept_name,
d.parent_dept_id,
da.ancestor_id -- 子部门继承父部门的祖先ID
FROM t_dept d
JOIN dept_ancestor da ON d.parent_dept_id = da.dept_id
)
SELECT
da.ancestor_id, -- 统计的目标部门(上级部门)
da_dept.dept_name, -- 目标部门名称
tl.dept_id AS child_dept_id -- 实际产生数据的子部门ID(用于验证层级)
FROM dept_ancestor da
LEFT 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的所有请假记录
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优势:通过数值比较简化条件表达式,执行效率更高
SELECT
da_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天请假数
WITH dept_ancestor AS (
-- 构建部门层级关系,获取每个部门的所有祖先路径
SELECT
dept_id,
dept_name,
parent_dept_id,
dept_id AS ancestor_id -- 初始祖先为自身
FROM t_dept
UNION ALL
-- 递归向下遍历子部门,继承祖先ID
SELECT
d.dept_id,
d.dept_name,
d.parent_dept_id,
da.ancestor_id -- 子部门的祖先与父部门一致
FROM t_dept d
JOIN dept_ancestor da ON d.parent_dept_id = da.dept_id
),
-- 提取祖先部门的基础信息(避免重复计算)
ancestor_info AS (
SELECT DISTINCT ancestor_id, dept_name
FROM dept_ancestor
)
SELECT
ai.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 ai
LEFT JOIN dept_ancestor da ON ai.ancestor_id = da.ancestor_id
LEFT JOIN t_leave tl ON da.dept_id = tl.dept_id -- 关联子部门请假数据
GROUP BY ai.ancestor_id, ai.dept_name
ORDER 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关联子部门的实际数据,确保上级部门能获取所有下级数据
类型统计:按leave_type分类累加天数
状态统计:按leave_status分类计数
数值判断:通过SIGN函数简化 "大于 3 天" 的条件转换
按ancestor_id分组,确保每个上级部门汇总其所有后代(包括多级子部门)的数据
LEFT JOIN确保无数据部门(如根部门若自身无数据)仍能显示统计结果
特性 |
上篇文章(单部门统计) |
本文(递归层级统计) |
统计范围 |
仅当前部门或指定子部门 |
包含所有下级部门(多级递归) |
递归方向 |
单向向下(固定根部门) |
双向关联(每个部门可作为祖先) |
核心字段 |
dept_id直接分组 |
ancestor_id递归分组 |
新增功能 |
无 |
请假天数 > 3 天统计、层级汇总 |
-- 为部门层级关系创建索引
CREATE INDEX idx_dept_parent ON t_dept(parent_dept_id);
-- 为请假表关联字段创建索引
CREATE INDEX idx_leave_dept ON t_leave(dept_id);
ALTER SESSION SET MAX_RECURSION_DEPTH = 2000; -- 默认1000层
通过递归 CTE 构建层级关系+DECODE 实现条件聚合+分组函数完成数据汇总,我们实现了:
1、真正的层级递归统计:上级部门自动包含所有下级数据,支持任意深度的组织架构
2、多维度复杂计算:在单个 SQL 中完成类型统计、状态分类、数值判断等多重逻辑
3、代码极简主义:相比传统 Java 递归 + 多层循环,SQL 代码量减少 90% 以上,且执行效率更高
这种方案特别适合组织架构复杂、层级统计频繁的企业级应用(如人力资源管理、财务成本分摊等场景)。掌握递归与DECODE的组合使用,能让你在处理树形数据时如虎添翼,真正发挥 Oracle 数据库的原生优势。如果你能学会这种SQL逻辑,相信我,肯定会对你在实际工作中有巨大帮助。欢迎关注留言,期待与您一起进步。