Oracle 递归 + Decode + 分组函数实现复杂树形统计进阶(第二课)

发布于:2025-06-30 ⋅ 阅读:(19) ⋅ 点赞:(0)

在上篇文章基础上,我们进一步解决层级数据递归汇总问题 —— 让上级部门的统计结果自动包含所有下级部门数据(含多级子部门),并新增请假天数大于 3 天的统计维度。通过递归 CTE、DECODE函数与分组函数的深度结合,实现真正意义上的树形结构数据聚合。

一、业务需求升级:层级汇总与新增统计维度

核心目标

  1. 递归汇总:上级部门数据包含所有直属 / 非直属下级部门数据(如集团总部需汇总技术研发部、产品运营部及其子部门数据)
  2. 新增统计项:统计每个部门(含各级上级)的 "请假天数 > 3 天" 的记录数
  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 (

-- 初始层:每个部门自身作为祖先

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

)
  • 核心逻辑:为每个部门生成从自身到所有后代的层级路径,ancestor_id表示当前统计的顶层部门(如子部门 4 的 ancestor_id 可为自身 4、父部门 2、根部门 1)
  • 递归方向:从父部门到子部门的向下递归,确保每个子部门关联到所有上级祖先

2. 关联请假表与递归 CTE

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的所有请假记录

三、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. 全维度统计表达式(整合新旧需求)

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天请假数

四、终极 SQL:递归汇总全层级数据

完整实现代码如下:

相信我,如果你能学会下面这个SQL的写法或者能看懂,那么你对ORACLE类似逻辑的处理已经达到极高的水平,这个SQL我认为有95%的人会看不懂。如果你们都会了欢迎留言打脸。因为这个SQL的实现我在7年前专门拿出来给全公司技术人员进行过培训,留了一个类似的作业,结果1个完成的都没有。你也可以考虑下,如果不用SQL来实现,而是让你去通过代码去实现这个需求的统计,你需要写多少代码来实现,需要多少时间?


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关联子部门的实际数据,确保上级部门能获取所有下级数据

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逻辑,相信我,肯定会对你在实际工作中有巨大帮助。欢迎关注留言,期待与您一起进步。


网站公告

今日签到

点亮在社区的每一天
去签到