【达梦数据库】UNION ALL 动态拼接 + LIMIT 分页实战:员工在职/离职统一查询
📌 前言
在实际业务中,我们经常遇到这样的需求:
- 查当前在职员工基本信息
- 当某个标志位
includeResigned == 1时,还要追加已离职员工数据 - 最终结果统一排序 + 分页返回
这个需求涉及三个核心知识点:ADD_MONTHS 时间计算、UNION ALL 纵向拼接、达梦 LIMIT 分页。本文以达梦数据库(DM8)为例,从思路到代码一步步拆解。
一、需求拆解
| 需求点 | 技术映射 |
|---|---|
| 近6个月入职的在职员工 | hire_date >= ADD_MONTHS(SYSDATE, -6) AND resign_date IS NULL |
| includeResigned==1 追加离职员工 | MyBatis <if> 动态控制 UNION ALL 子查询 |
| 统一排序分页 | 外层包装子查询 + ORDER BY + LIMIT ... OFFSET ... |
二、关键概念
2.1 UNION ALL = 纵向拼(加行)
【UNION ALL = 纵向拼(加行)】 【JOIN = 横向拼(加列)】
表A 表B 表A 表B JOIN结果
┌─────┐ ┌─────┐ ┌───┬───┐ ┌───┬────┐ ┌───┬───┬────┐
│ a1 │ │ b1 │ │id │nm │ │id │dept│ │id │nm │dept│
│ a2 │ + │ b2 │ = │ 1 │张三│ │ 1 │研发│ │ 1 │张三│研发│
│ a3 │ │ b3 │ │ 2 │李四│ │ 2 │测试│ │ 2 │李四│测试│
└─────┘ └─────┘ └───┴───┘ └───┴────┘ └───┴───┴────┘
2列+2列=4列
↓ UNION ALL 结果 (列数变多,行数不变)
┌─────┐
│ a1 │ ← 来自表A
│ a2 │ ← 来自表A
│ a3 │ ← 来自表A
│ b1 │ ← 来自表B
│ b2 │ ← 来自表B
│ b3 │ ← 来自表B
└─────┘
3行+3行=6行
(行数变多,列数不变)
2.2 为什么用 UNION ALL 而不是 UNION?
| 关键字 | 行为 | 性能 |
|---|---|---|
UNION | 合并后去重(隐式排序) | 慢 |
UNION ALL | 合并后保留所有行 | 快 |
两张表数据本身不重叠时,永远优先用
UNION ALL。
2.3 达梦分页语法
-- 推荐:简洁直观
LIMIT 20 OFFSET 0;
-- 备选:SQL:2008 标准写法(达梦8也支持)
OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY;
分页公式:OFFSET = (pageNum - 1) × pageSize
三、完整 SQL(达梦 DM8)
SELECT *
FROM (
/* ===== 基础查询:近6个月入职的在职员工 ===== */
SELECT emp_id,
emp_name,
dept_code,
hire_date,
'ACTIVE' AS emp_status
FROM t_employee
WHERE hire_date >= ADD_MONTHS(SYSDATE, -6)
AND resign_date IS NULL
UNION ALL
/* ===== 离职员工(includeResigned=1 时拼入) ===== */
SELECT emp_id,
emp_name,
dept_code,
hire_date,
'RESIGNED' AS emp_status
FROM t_employee_resigned
WHERE resign_date >= ADD_MONTHS(SYSDATE, -6)
) tmp
ORDER BY tmp.hire_date DESC
LIMIT 20 OFFSET 0; -- 每页20条,第1页
执行流程图
┌──────────────────────────────────────────────────────────┐
│ 最外层:ORDER BY hire_date DESC + LIMIT 分页 │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 子查询 tmp(UNION ALL 的结果集) │ │
│ │ │ │
│ │ ┌──────────────────┐ ┌──────────────────────┐ │ │
│ │ │ t_employee │ │ t_employee_resigned │ │ │
│ │ │ 近6月入职+在职 │ │ 近6月离职 │ │ │
│ │ │ ADD_MONTHS计算 │ │ (条件性拼入) │ │ │
│ │ └──────────────────┘ └──────────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
四、MyBatis 动态 SQL 集成
<select id="queryEmployees" resultType="com.example.entity.Employee">
SELECT *
FROM (
SELECT emp_id, emp_name, dept_code, hire_date,
'ACTIVE' AS emp_status
FROM t_employee
WHERE hire_date >= ADD_MONTHS(SYSDATE, -6)
AND resign_date IS NULL
<if test="includeResigned == 1">
UNION ALL
SELECT emp_id, emp_name, dept_code, hire_date,
'RESIGNED' AS emp_status
FROM t_employee_resigned
WHERE resign_date >= ADD_MONTHS(SYSDATE, -6)
</if>
) tmp
ORDER BY tmp.hire_date DESC
LIMIT #{pageSize} OFFSET #{offset}
</select>
Java 调用层:
int offset = (pageNum - 1) * pageSize;
Map<String, Object> params = new HashMap<>();
params.put("includeResigned", includeResigned);
params.put("pageSize", pageSize);
params.put("offset", offset);
List<Employee> list = employeeMapper.queryEmployees(params);
五、不同数据库分页语法对照
| 数据库 | 分页写法 |
|---|---|
| 达梦 DM8 | LIMIT m OFFSET n |
| MySQL | LIMIT m OFFSET n |
| PostgreSQL | LIMIT m OFFSET n |
| Oracle 12c+ | OFFSET n ROWS FETCH NEXT m ROWS ONLY |
| SQL Server | OFFSET n ROWS FETCH NEXT m ROWS ONLY |
如果你正在做 Oracle → 达梦迁移,只需替换分页语法,
ADD_MONTHS、SYSDATE、UNION ALL等用法基本一致。
六、避坑指南
| ⚠️ 常见坑 | 正确做法 |
|---|---|
| UNION ALL 两边列数/类型不一致 | 确保 SELECT 列数量、顺序、类型完全对应 |
| ORDER BY 写在子查询内部 | 子查询排序无意义且浪费性能,只在最外层排 |
| 用 UNION 代替 UNION ALL | 不需要去重就用 ALL,避免隐式排序开销 |
| OFFSET 写成页码 | OFFSET 是偏移行数,不是页码!需换算 |
| ADD_MONTHS 参数写反 | -6 是往前推6个月,+6 是往后推 |
| 常量列忘记加别名 | 'ACTIVE' AS emp_status 必须起别名,否则 UNION ALL 报错 |
七、总结
| 步骤 | 要点 |
|---|---|
| ① 时间范围 | ADD_MONTHS(SYSDATE, -6) 计算起始时间 |
| ② 纵向拼接 | UNION ALL 叠加行,列结构必须对齐 |
| ③ 条件控制 | MyBatis <if> 决定是否拼入第二段 |
| ④ 排序分页 | 外层包装 + ORDER BY + LIMIT ... OFFSET ... |

1891

被折叠的 条评论
为什么被折叠?



