使用CTE在SQL Server中生成类似MS Project的任务清单(含层级计算与权重汇总)
CTE在SQL Server中生成类似MS Project的任务清单(含层级计算与权重汇总)
嘿,我完全懂你想要的效果——把SQL Server里的树形任务表,转换成像MS Project那样带层级缩进、自动汇总父任务权重的清单对吧?这刚好可以用递归CTE来实现,我给你一步步拆解解决方案。
核心思路
我们需要完成两个关键目标:
- 递归遍历任务树,计算每个任务的层级,同时生成路径保证排序符合树形结构
- 从叶子节点往上汇总权重,给未设置权重的父任务自动计算子任务的权重总和
完整SQL代码
WITH TaskHierarchy AS ( -- 锚点成员:获取所有根任务(无父节点的任务) SELECT task_id, task_name, task_weight, task_parent, 1 AS task_level, -- 根节点层级设为1 CAST(task_id AS VARCHAR(MAX)) AS task_path -- 记录任务路径,用于后续排序和权重汇总 FROM task WHERE task_parent IS NULL UNION ALL -- 递归成员:遍历所有子任务,继承父任务的层级和路径 SELECT t.task_id, t.task_name, t.task_weight, t.task_parent, th.task_level + 1 AS task_level, th.task_path + '>' + CAST(t.task_id AS VARCHAR(MAX)) AS task_path FROM task t INNER JOIN TaskHierarchy th ON t.task_parent = th.task_id ), TaskWeightSummary AS ( -- 初始化权重:有自身权重的用自身值,没有的先设为0 SELECT task_id, task_name, task_parent, task_level, task_path, COALESCE(task_weight, 0) AS base_weight, COALESCE(task_weight, 0) AS total_weight FROM TaskHierarchy UNION ALL -- 递归汇总权重:父任务总权重 = 自身基础权重 + 子任务的总权重 SELECT th.task_id, th.task_name, th.task_parent, th.task_level, th.task_path, COALESCE(th.task_weight, 0) AS base_weight, th.base_weight + tws.total_weight AS total_weight FROM TaskHierarchy th INNER JOIN TaskWeightSummary tws ON th.task_id = tws.task_parent ) -- 最终输出:格式化层级显示,取最终的权重汇总结果 SELECT DISTINCT task_id, task_name, task_level, -- 用缩进模拟MS Project的层级显示效果 REPLICATE(' ', task_level - 1) + task_name AS formatted_task_name, -- 规则:如果任务有自身设置的权重则显示原值,否则显示子任务汇总的权重 CASE WHEN base_weight <> 0 THEN base_weight ELSE total_weight END AS task_weight FROM TaskWeightSummary ORDER BY task_path;
代码详解
TaskHierarchy CTE:
- 锚点成员先抓出所有根任务(
task_parent IS NULL),给它们设置层级为1,路径初始化为自身ID - 递归成员通过关联父任务的ID,遍历所有子任务,层级在父任务基础上加1,路径追加子任务ID,完整记录每个任务的树形位置
- 锚点成员先抓出所有根任务(
TaskWeightSummary CTE:
- 第一步先给每个任务初始化权重:如果任务自己有
task_weight就用这个值,没有的话先设为0 - 然后反向递归,把父任务的权重和子任务的总权重相加,实现从叶子到根的权重汇总
- 第一步先给每个任务初始化权重:如果任务自己有
最终查询:
- 用
DISTINCT去重(因为权重汇总的递归过程会生成多条同一任务的记录) - 用
REPLICATE(' ', task_level - 1)给任务名称添加缩进,完全模拟MS Project的层级视觉效果 - 排序时用
task_path保证任务按树形结构的顺序排列,根任务在前,子任务紧跟父任务
- 用
示例结果(对应你的测试数据)
| task_id | task_name | task_level | formatted_task_name | task_weight |
|---|---|---|---|---|
| 1 | t1 | 1 | t1 | 0.10 |
| 2 | t2 | 2 | t2 | 0.07 |
| 4 | t4 | 3 | t4 | 0.04 |
| 5 | t5 | 3 | t5 | 0.03 |
| 3 | t3 | 2 | t3 | 0.03 |
| 6 | t6 | 1 | t6 | 0.01 |
| 7 | t7 | 2 | t7 | 0.01 |
自定义调整说明
如果你的需求是父任务的自身权重优先,不汇总子任务,只需要修改最终查询的CASE语句:
CASE WHEN task_weight IS NOT NULL THEN task_weight ELSE total_weight END AS task_weight
备注:内容来源于stack exchange,提问作者Mohammad Hasan Bakhtiarifar




