在企业级应用开发中,复杂的业务统计需求往往需要编写大量代码进行数据处理。本文将通过 Oracle 的DECODE函数与分组函数的巧妙结合,展示如何用一条 SQL 语句实现原本需要 3000 行代码的复杂计算逻辑,尤其针对企业组织架构中的部门级请假数据统计场景。
CREATE TABLE t_dept (
dept_id NUMBER PRIMARY KEY, -- 部门ID(主键)
dept_name VARCHAR2(50) NOT NULL, -- 部门名称
parent_dept_id NUMBER, -- 上级部门ID(根部门为0)
create_time DATE DEFAULT SYSDATE -- 创建时间
);
-- 插入示例数据(三级部门架构)
INSERT INTO t_dept (dept_id, dept_name, parent_dept_id) VALUES (1, '集团总部', 0);
INSERT INTO t_dept (dept_id, dept_name, parent_dept_id) VALUES (2, '技术研发部', 1);
INSERT INTO t_dept (dept_id, dept_name, parent_dept_id) VALUES (3, '产品运营部', 1);
INSERT INTO t_dept (dept_id, dept_name, parent_dept_id) VALUES (4, '后端开发组', 2);
INSERT INTO t_dept (dept_id, dept_name, parent_dept_id) VALUES (5, '前端开发组', 2);
CREATE TABLE t_leave (
user_id VARCHAR2(20) PRIMARY KEY, -- 人员账号(主键)
user_name VARCHAR2(30) NOT NULL, -- 人员姓名
dept_id NUMBER NOT NULL, -- 所属部门ID
leave_days NUMBER(5,1), -- 请假天数(保留1位小数)
leave_type VARCHAR2(20), -- 请假类型(年假/事假/病假等)
leave_status VARCHAR2(10), -- 请假状态('完成'/'进行中')
apply_time DATE DEFAULT SYSDATE -- 申请时间
);
-- 插入模拟数据(覆盖不同部门/类型/状态)
INSERT INTO t_leave VALUES
('U001', '张三', 4, 3.5, '年假', '完成', TO_DATE('2025-06-01', 'YYYY-MM-DD')),
('U002', '李四', 5, 2.0, '事假', '进行中', TO_DATE('2025-06-10', 'YYYY-MM-DD')),
('U003', '王五', 3, 1.5, '病假', '完成', TO_DATE('2025-06-15', 'YYYY-MM-DD')),
('U004', '赵六', 4, 5.0, '年假', '进行中', TO_DATE('2025-06-20', 'YYYY-MM-DD')),
('U005', '周七', 2, 1.0, '事假', '完成', TO_DATE('2025-06-05', 'YYYY-MM-DD')),
('U006', '吴八', 3, 3.0, '年假', '进行中', TO_DATE('2025-06-25', 'YYYY-MM-DD'));
DECODE(expression, search1, result1, search2, result2, ..., default)
-- 将状态编码转为中文描述
SELECT
user_name,
leave_type,
DECODE(leave_status, '完成', '已完成', '进行中', '处理中', '未知状态') AS status_desc
FROM t_leave;
-- 输出结果:
-- 张三 年假 已完成
-- 李四 事假 处理中
-- 王五 病假 已完成
需求:统计不同请假类型的占比(按部门维度)
SELECT
dept_id,
SUM(DECODE(leave_type, '年假', leave_days, 0)) AS annual_leave_days,
SUM(DECODE(leave_type, '事假', leave_days, 0)) AS personal_leave_days,
SUM(DECODE(leave_type, '病假', leave_days, 0)) AS sick_leave_days
FROM t_leave
GROUP BY dept_id;
-- 关键逻辑:
-- 针对每个部门,按请假类型累加对应天数
-- 非目标类型自动返回0,避免NULL影响SUM计算
传统写法:
SUM(CASE WHEN leave_status = '完成' THEN 1 ELSE 0 END)
DECODE 等价写法:
SUM(DECODE(leave_status, '完成', 1, 0))
优势:语法更简洁,执行效率相当(Oracle 会自动优化)
从根部门(集团总部,dept_id=1)开始,统计每个子部门的:
WITH dept_hierarchy AS (
-- 递归查询获取所有子部门(包含自身)
SELECT dept_id, dept_name, parent_dept_id
FROM t_dept
WHERE dept_id = 1 -- 根部门ID
UNION ALL
SELECT d.dept_id, d.dept_name, d.parent_dept_id
FROM t_dept d
JOIN dept_hierarchy dh ON d.parent_dept_id = dh.dept_id
)
SELECT
dh.dept_name AS 部门名称,
-- 请假类型统计(年假/事假/病假,其他类型自动归为0)
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 进行中请假数
FROM dept_hierarchy dh
LEFT JOIN t_leave tl ON dh.dept_id = tl.dept_id
GROUP BY dh.dept_id, dh.dept_name
ORDER BY dh.dept_id;
部门名称 |
年假总天数 |
事假总天数 |
病假总天数 |
完成请假数 |
进行中请假数 |
集团总部 |
0.0 |
0.0 |
0.0 |
0 |
0 |
技术研发部 |
4.5 |
1.0 |
0.0 |
1 |
1 |
产品运营部 |
1.5 |
0.0 |
3.0 |
1 |
1 |
后端开发组 |
3.5 |
0.0 |
0.0 |
1 |
1 |
前端开发组 |
0.0 |
2.0 |
0.0 |
0 |
1 |
1.递归 CTE(WITH 子句):构建部门层级关系,实现从根部门到所有子部门的遍历
2.DECODE 与 SUM 结合:
横向扩展统计维度(类型 / 状态)
自动处理 NULL 值(非匹配项返回 0)
3.LEFT JOIN 优化:确保无数据部门也能显示(如集团总部本身无请假记录)
特性 |
DECODE |
CASE WHEN |
语法简洁度 |
★★★★★ |
★★★☆☆ |
执行效率 |
同等(Oracle 内部优化) |
同等 |
类型支持 |
仅等值比较 |
支持范围 / 表达式比较 |
可读性 |
简单条件更优 |
复杂逻辑更清晰 |
适用场景:当统计条件为明确的等值匹配时,DECODE 能显著简化代码,尤其适合多维度聚合场景。
通过 DECODE 函数与分组函数的组合,我们实现了:
1.代码量锐减:原本需要 Java/Python 编写的多层循环 + 条件判断,浓缩为单条 SQL
2.性能提升:数据库层直接完成复杂计算,减少数据传输开销
3.维护便利:业务逻辑集中在 SQL 层,无需修改应用代码即可调整统计规则
最佳实践:
对于需要处理组织架构 + 多维度统计的业务场景,这种写法能带来指数级的开发效率提升。下次遇到类似需求时,不妨先尝试用 SQL 构建数据模型,或许能发现更优雅的解决方案。本章内容有个问题,在下个文章会给大家答疑,并且进行进一步的扩展逻辑优化,期待与您一起进步。