直接用join追踪多级审批状态容易出错,因审批流是链式依赖而非静态关联;某级缺失会导致join断链,且跨类型审批字段不统一、状态语义模糊,强行join维护困难;union all拼接各步骤再按时间排序才能准确还原流转顺序。

为什么直接用 JOIN 追踪多级审批状态容易出错?
因为审批流本质是链式依赖关系,不是静态的多表关联。用 LEFT JOIN 把 5 张审批表硬连在一起,一旦某一级缺失(比如跳过二级审批),后续所有 JOIN 都会失败,导致整条链路“断掉”,查不到前面已通过的节点。
更糟的是,不同审批类型(如采购 vs. 人事)可能走不同路径,字段名、状态码也不统一,强行 JOIN 会让 SQL 变成维护噩梦。
- 状态字段命名不一致:
status、approval_status、step_result - 时间字段语义模糊:
updated_at可能是创建、提交或审核时间 - 缺失中间节点时,
INNER JOIN直接丢整行,LEFT JOIN又让结果集膨胀且难判空
用 UNION ALL 拼接各审批步骤再排序才是可行解
把每级审批看作独立事件,用 UNION ALL 合并,再按业务单号 + 时间排序,就能还原真实流转顺序。关键不是“连起来”,而是“排出来”。
示例:追踪单号 'PO-2024-001' 的审批链路
SELECT 'level_1' AS step, approver_id, status, updated_at FROM po_approval_l1 WHERE po_no = 'PO-2024-001' UNION ALL SELECT 'level_2', approver_id, status, updated_at FROM po_approval_l2 WHERE po_no = 'PO-2024-001' UNION ALL SELECT 'level_3', approver_id, status, updated_at FROM po_approval_l3 WHERE po_no = 'PO-2024-001' ORDER BY updated_at;
- 每层表只查自己字段,不依赖其他层是否存在
-
UNION ALL比UNION快,因不查重——审批记录天然唯一 - 必须显式写
ORDER BY updated_at,数据库不保证UNION ALL的自然顺序
如何处理跨系统审批数据源?
当部分审批在外部系统(如钉钉、飞书)完成,只同步状态到本地库,就不能靠主键关联了。得用业务单号 + 时间窗口对齐。
- 外部系统通常只提供
order_id和event_time,没有内部approval_id - 用
WHERE event_time BETWEEN '2024-06-01' AND '2024-06-05'限定范围,避免全表扫描 - 如果时间精度到秒但系统有延迟,加 ±30 秒容差:
ABS(EXTRACT(EPOCH FROM (local_time - external_event_time))) - 别用
LIKE '%PO-2024-001%'做关联——索引失效,查得慢还不准
状态链路里最容易被忽略的“空状态”陷阱
审批流里最危险的不是“拒绝”,而是“未启动”或“超时自动通过”这类隐性状态。它们不会写入审批表,但业务逻辑必须感知。
- 查不到某级记录 ≠ 该级没发生,可能是配置跳过,也可能是系统漏写
- 需配合流程定义表:
SELECT step_name, is_skippable FROM approval_flow_config WHERE flow_type = 'purchase' - 最终展示时,用程序补全缺失步骤(如前端渲染“二级审批:已跳过”),而不是让 SQL 硬补
NULL - 数据库里存
status = 'skipped'比留空更可靠,避免歧义
链路追踪真正难的不是写 SQL,而是厘清“哪些状态必须落库”“哪些靠配置推导”“哪些由下游系统异步回调补全”。SQL 只负责把已落地的事实串起来,别让它承担流程引擎的职责。











