递归 CTE 通过多个级别更新父记录

Posted

技术标签:

【中文标题】递归 CTE 通过多个级别更新父记录【英文标题】:Recursive CTE to update parent records through multiple levels 【发布时间】:2015-10-29 00:03:18 【问题描述】:

CTE 是否会在下一次递归中使用作为 CTE 一部分更新的数据?我正在尝试这种 CTE,因为在 WHILE 循环内类似的 UPDATE 逻辑的性能表现不佳,我希望使用 CTE 会更加基于集合并且性能更好。

我在使用递归 CTE 更新表时遇到问题,直到父行都被正确标记。

SQL Fiddle

SQL fiddle 显示了表格和基本 CTE。在添加任何 AND/OR 或级别逻辑之前,我似乎无法让 CTE 爬上层次结构并将父母标记为“满足”。

这是示例表:

|逻辑ID |父逻辑 |深度 |类型 |说明 |遇见 | |--------- |------------- |-------- |------ |---------- --------------------------- |----- | | 1 |空 |空 |空 |条件全部满足 | 0 | | 2 | 1 | 1 |和 |天空或海洋是蓝色的| 0 | | 3 | 2 | 2 |或 |天空是蓝色的| 0 | | 4 | 2 | 2 |或 |海洋是蓝色的| 1 | | 5 | 1 | 1 |和 |草是绿还是路是黑 | 0 | | 6 | 5 | 2 |或 |草是绿的 | 1 | | 7 | 5 | 2 |或 |路是黑的 | 0 | | 8 | 1 | 1 |和 |鸟、虫子或以下 4 个 | 0 | | 9 | 8 | 2 |或 |有鸟| 0 | | 10 | 8 | 2 |或 |有错误 | 0 | | 11 | 8 | 2 |或 |以下所有 4 个 | 0 | | 12 | 11 | 3 |和 |有狗| 1 | | 13 | 11 | 3 |和 |有猫| 1 | | 14 | 11 | 3 |和 |有人 | 1 | | 15 | 11 | 3 |和 |有椅子| 1 | 删除表 MyLogic 创建表 MyLogic ( 逻辑ID int ,父逻辑整数 ,深度整数 ,类型 varchar(4) ,说明 varchar(35) ,满足整数 ); 插入 MyLogic (LogicID、ParentLogic、深度、类型、描述、Met) 价值观 (1, NULL, NULL, NULL, '条件全部满足', 0), ( 2, 1, 1, 'AND', '天空或海洋是蓝色的', 0 ), ( 3, 2, 2, 'OR', '天空是蓝色的', 0 ), ( 4, 2, 2, 'OR', '海洋是蓝色的', 1 ), ( 5, 1, 1, 'AND', '草是绿还是路是黑', 0 ), ( 6, 5, 2, 'OR', '草是绿色的', 1 ), ( 7, 5, 2, 'OR', '这条路是黑色的', 0 ), ( 8, 1, 1, 'AND', '鸟类、虫子或下面的 4', 0 ), ( 9, 8, 2, 'OR', '有鸟', 0 ), ( 10, 8, 2, 'OR', '有错误', 0 ), ( 11, 8, 2, 'OR', '以下所有 4', 0 ), ( 12, 11, 3, 'AND', '有狗', 1 ), ( 13, 11, 3, 'AND', '有猫', 1 ), ( 14, 11, 3, 'AND', '有人', 1 ), ( 15, 11, 3, 'AND', '有椅子', 1 )

这只是一组更复杂的逻辑的示例。基本上这个想法是我需要每组孩子使用表中的逻辑“汇总”到父母。深度是可变的,但可能是 7 深。

因此,LogicID 12、13、14、15 被 AND 在一起,然后将 11 标记为 Met。然后将评估 9,10,11,如果有 (OR),则将 8 标记为满足。以此类推,直到***父 LogicID 1 满足或不满足。

这可以通过 CTE 完成吗?如果可以,有人可以帮我完成吗?

编辑:: 感谢您的帮助-这里要求的是更新声明。

DECLARE @maxdepth AS int = ( SELECT MAX (Depth) FROM MyLogic) DECLARE @counter AS int = 0 WHILE ( @counter < @maxdepth ) BEGIN UPDATE UP SET UP.Met = --SELECT *, CASE WHEN ORIG.Type = 'AND' AND ORIG.Met = 0 AND COUNTS.CountMet = 2 THEN 0 WHEN ORIG.Type = 'AND' AND ORIG.Met = 0 AND COUNTS.CountMet = 1 THEN 0 WHEN ORIG.Type = 'AND' AND ORIG.Met = 1 AND COUNTS.CountMet = 2 THEN 0 WHEN ORIG.Type = 'AND' AND ORIG.Met = 1 AND COUNTS.CountMet = 1 THEN 1 WHEN ORIG.Type = 'OR' AND ORIG.Met = 1 AND COUNTS.CountMet = 1 THEN 1 WHEN ORIG.Type = 'OR' AND ORIG.Met = 0 AND COUNTS.CountMet = 2 THEN 1 WHEN ORIG.Type = 'OR' AND ORIG.Met = 1 AND COUNTS.CountMet = 2 THEN 1 WHEN ORIG.Type = 'OR' AND ORIG.Met = 0 AND COUNTS.CountMet = 1 THEN 0 END FROM MyLogic UP INNER JOIN dbo.MyLogic ORIG ON UP.LogicID = ORIG.ParentLogic INNER JOIN ( SELECT DIST.ParentLogic ,COUNT(DISTINCT DIST.Met) AS CountMet FROM MyLogic DIST GROUP BY DIST.ParentLogic ) COUNTS ON ORIG.ParentLogic = COUNTS.ParentLogic SET @counter = @counter + 1 END

【问题讨论】:

您能提供您当前的 UPDATE/WHILE 解决方案示例吗?我觉得看看能不能优化会更有成果。 您可能会发现此相关答案很有启发性:***.com/a/8529569/116614。我的想法是逻辑超出了基于集合的高效处理的范围。将数据下载到另一种编程语言,处理逻辑,然后将结果写回数据库。 对于 1 个级别,是始终 AND 还是始终 OR?或者是否可以为 11 个孩子混合使用 AND/OR,例如 12、13、14 和 15 和 OR?为什么不使用父类型(12-15 是 OR 而不是 AND) 没有 AND/OR 混合 - 至少在我认为您要问的意义上不是。这将需要带括号的逻辑,在这种情况下,我将创建 AND 组的“假”父级以避免这种情况。它看起来像这样,我将 AND 移动到子集。或:阳光明媚的日子和玛格丽塔酒......和 ​​- 晴天......和 ​​- 玛格丽塔酒或:墨西哥之旅 好的。但对我来说,做 12 AND 13 AND 14 AND 15 = 11 似乎不合逻辑,因为其中 4 个有 AND。我认为在 11 中设置如何计算它的子节点会更合乎逻辑。无论如何我都会调查它。看起来很有趣。 【参考方案1】:

我不相信您能够在这种情况下使用 CTE 实现您的目标。我尝试使用 CTE 的每种方法都取决于能否在 CTE 的递归部分使用 GROUP BY,这是不允许的。

这是一个可以加快更新速度的替代方法。它仍然有点迭代,但在我的快速测试中似乎要快一点,这主要是因为更多的基于集合的方法和更少的连接需要。它应该比当前流程更有效地扩展。

DECLARE @D AS INT = ( SELECT MAX (Depth) FROM MyLogic)
WHILE @D > 0 BEGIN
    UPDATE P SET
        Met = C.Met
    FROM MyLogic P
        INNER JOIN (
            SELECT

                ParentLogic,
                CASE
                    WHEN SUM(CASE WHEN Type = 'OR' THEN Met ELSE 0 END) > 0
                        OR SUM(CASE WHEN Type = 'AND' THEN Met ELSE 0 END) = COUNT(*)
                    THEN 1 ELSE 0 END AS Met
            FROM MyLogic
            WHERE Depth = @D
            GROUP BY
                ParentLogic
        ) C -- Only update parents with children
            ON P.LogicID = C.ParentLogic
    WHERE COALESCE(P.Depth, 0) = @D - 1 -- Get next level up
    SET @D = @D - 1
END
SELECT * FROM MyLogic

【讨论】:

以上是关于递归 CTE 通过多个级别更新父记录的主要内容,如果未能解决你的问题,请参考以下文章

通过 LEAD/LAG 或递归 CTE 更新?

递归 CTE 导致缓慢和索引扫描

sql server使用cte递归查询获取树形的父节点/子节点

如何获得递归 CTE 中生成的最后一条记录?

递归 CTE - row_number() 聚合

通过递归 CTE 分解 BOM