子查询无法实现工作流路径追踪,因其仅返回静态快照,不能建模带时序和依赖关系的状态迁移序列;真正可行的是with recursive递归查询,它通过锚点与递归两部分逐层推进、累积trace并约束时序,才能准确还原“提交→审批中→已驳回→重提→通过”等完整流转路径。

子查询无法实现工作流路径追踪,别硬套
直接用子查询做工作流状态机的多跳路径追踪,90% 会失败——它只能查快照、不能建链条。子查询执行一次就结束,而工作流路径(如“提交→审批中→已驳回→重提→通过”)本质是带时序和依赖关系的状态迁移序列,必须靠递归或应用层迭代来建模。
常见错误写法:SELECT * FROM workflow_steps WHERE doc_id IN (SELECT doc_id FROM workflow_steps WHERE status = 'approved' AND step = 2)。这只能捞出“第二步已通过”的文档,但完全不知道它是否经历过步骤1、有没有被驳回过、第三步是否已触发。没有上下文延续性,就没有路径。
- 子查询返回静态集合,不随当前行状态变化而重算
- 无法表达“从起点出发,逐层推进”的语义
- 空值、跳步、并发写入都会导致结果断裂或错位
真正可用的路径追踪:WITH RECURSIVE 是唯一合理选择
WITH RECURSIVE 是目前主流数据库(PostgreSQL、MySQL 8.0+、SQL Server)中唯一能自然建模状态迁移链的语法。它把路径当栈来维护,每层递归都基于上一层结果生成下一层候选,天然支持时序约束和终止控制。
以追踪一个审批单完整流转为例(表 workflow_log(doc_id, step, status, created_at, prev_step)):
WITH RECURSIVE path AS (
-- 锚点:从初始提交开始
SELECT doc_id, step, status, created_at, 1 AS depth, CAST(step AS VARCHAR(100)) AS trace
FROM workflow_log
WHERE step = 1 AND status = 'submitted'
<p>UNION ALL</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/leiku/1027" title="格式化SQL语句的PHP库"><img
src="" alt="格式化SQL语句的PHP库" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/leiku/1027" title="格式化SQL语句的PHP库" class="overflowclass">格式化SQL语句的PHP库</a>
<p class="overflowclass">格式化SQL语句的PHP库</p>
</div>
<a rel="nofollow" href="/xiazai/leiku/1027" title="格式化SQL语句的PHP库" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><p>-- 递归:找下一个合法步骤(按时间顺序 + 状态依赖)
SELECT w.doc_id, w.step, w.status, w.created_at, p.depth + 1,
p.trace || '→' || w.step
FROM workflow_log w
INNER JOIN path p ON w.doc_id = p.doc_id
AND w.step = p.step + 1
AND w.created_at > p.created_at
WHERE w.status IN ('pending', 'approved', 'rejected')
AND p.depth </p>
-
depth和trace必须显式定义并传递,否则递归中无法累积路径信息 -
w.created_at > p.created_at是关键时序约束,防止乱序日志污染路径 p.depth 不是可选优化,是生产环境强制要求,避免某条异常链拖垮整个查询
WHERE 中的关联子查询只适合单步状态校验
如果你只需要判断“当前是否卡在第二步”,而不是还原整条路径,那用关联子查询反而更轻量、更可控。
例如查所有“已通过第一步、但第二步尚未开始”的文档:
SELECT d.doc_id, d.title FROM documents d WHERE EXISTS ( SELECT 1 FROM workflow_log w1 WHERE w1.doc_id = d.doc_id AND w1.step = 1 AND w1.status = 'approved' ) AND NOT EXISTS ( SELECT 1 FROM workflow_log w2 WHERE w2.doc_id = d.doc_id AND w2.step = 2 );
- 用
EXISTS而非IN,避免NULL导致逻辑失效 - 每个子查询职责单一:一个确认前序完成,一个确认本步未启动
- 这种写法可加索引优化:
workflow_log(doc_id, step, status)复合索引必须覆盖三者
旧版 MySQL(
MySQL 5.7 及更早版本不支持 WITH RECURSIVE,此时硬写多层嵌套子查询(如 WHERE step = 2 AND doc_id IN (SELECT doc_id FROM ... WHERE step = 1))只会让 SQL 变成意大利面条,且最多撑到 3 层就报错或性能归零。
可行方案是分步查 + 应用层拼接:
- 先查所有
step = 1 AND status = 'approved'的doc_id - 再用这批 ID 批量查
step = 2记录,筛出缺失的 - 对有记录的,再查
step = 3,依此类推 - 用程序控制最大深度(比如 7)、超时(比如 2s)、断路逻辑(某步无数据则终止该链)
这不是退化,而是务实——数据库不是万能胶,复杂状态机本就不该全压给 SQL 去推演。










