基于PostgreSQL的DAG递归遍历:任务依赖存储与查询问询
问题解答
1. 这种存储方式是否合理?
是否合理取决于Task和Subtask的业务属性差异:
- 合理场景:如果Task和Subtask的元数据结构差异显著(比如Task包含项目归属、全局优先级,Subtask仅含执行人、预估工时这类细分属性),分两张表存储能避免字段冗余,符合数据库设计的范式要求。此时只要
dependencies表能明确区分依赖双方的节点类型(比如新增source_node_type、target_node_type字段,取值为'TASK'或'SUBTASK'),就能正确关联不同表的节点,这种存储方案是可行的。 - 不合理场景:如果Task和Subtask的元数据高度相似,仅存在层级/归属关系的区别,分表会增加后续维护成本(比如新增元数字段时需要同时修改两张表),更推荐使用单表+类型字段的方案:用一张
TaskNode表,加node_type字段区分TASK和SUBTASK,再用parent_node_id关联归属关系,依赖关系仍存于dependencies表,这样结构更简洁,维护更方便。
2. PostgreSQL中展开Task A依赖的Task B,获取完整子任务树的查询语句
假设你的表结构如下(已调整dependencies表支持跨类型依赖):
TaskTable:task_id(主键),nameSubTaskTable:subtask_id(主键),task_id(关联TaskTable.task_id),namedependencies:dep_id(主键),source_type(取值'TASK'/'SUBTASK'),source_id,target_type,target_id
可以用PostgreSQL的递归CTE来遍历整个依赖树,同时避免循环:
WITH RECURSIVE task_subtask_tree AS ( -- 初始步骤:找到Task A直接依赖的所有Task(这里以Task A的task_id='A'为例) SELECT 'TASK' AS node_type, t.task_id AS node_id, t.name AS node_name, NULL::VARCHAR AS parent_node_type, NULL::VARCHAR AS parent_node_id, ARRAY[t.task_id] AS path FROM TaskTable t JOIN dependencies d ON d.target_type = 'TASK' AND d.target_id = t.task_id AND d.source_type = 'TASK' AND d.source_id = 'A' -- 替换为你的Task A的task_id UNION ALL -- 递归分支1:获取当前Task节点的直接子任务 SELECT 'SUBTASK' AS node_type, st.subtask_id AS node_id, st.name AS node_name, 'TASK' AS parent_node_type, st.task_id AS parent_node_id, tst.path || st.subtask_id FROM task_subtask_tree tst JOIN SubTaskTable st ON tst.node_type = 'TASK' AND st.task_id = tst.node_id WHERE NOT st.subtask_id = ANY(tst.path) -- 防止意外循环 UNION ALL -- 递归分支2:获取当前节点(Task/Subtask)依赖的其他节点 SELECT d.target_type AS node_type, d.target_id AS node_id, CASE WHEN d.target_type = 'TASK' THEN t.name ELSE st.name END AS node_name, tst.node_type AS parent_node_type, tst.node_id AS parent_node_id, tst.path || d.target_id FROM task_subtask_tree tst JOIN dependencies d ON d.source_type = tst.node_type AND d.source_id = tst.node_id LEFT JOIN TaskTable t ON d.target_type = 'TASK' AND t.task_id = d.target_id LEFT JOIN SubTaskTable st ON d.target_type = 'SUBTASK' AND st.subtask_id = d.target_id WHERE NOT d.target_id = ANY(tst.path) -- 避免DAG中的循环依赖 ) -- 输出完整的子任务树,可根据需求调整返回字段 SELECT node_type, node_id, node_name, parent_node_type, parent_node_id FROM task_subtask_tree;
语句说明:
- 递归CTE的初始部分先定位Task A依赖的目标Task(比如Task B);
- 第一个递归分支负责把Task对应的所有直接Subtask加入树中;
- 第二个递归分支处理节点间的依赖关系,遍历所有可达的依赖节点;
- 用
path数组记录遍历路径,确保不会重复处理同一节点,避免循环。
内容的提问来源于stack exchange,提问作者Noufal Ibrahim
相关产品推荐
相关产品推荐

