8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

递归 CTE 以及如何克服循环引用错误

user915 2月前

114 0

我有一个用于查找原始位置的脚本。它以这样的表格开始:IDPreviousIDLocation 2NULL1235 321236 431239 8111237 981234 1091235 11101237 ...

我有一个用于查找原始位置的脚本。它以如下表格开始:

ID 以前的ID 地点
2 无效的 1235
3 2 1236
4 3 1239
8 11 1237
9 8 1234
10 9 1235
11 10 1237

我想找到选定数量的 ID 值的原始位置

ID
2
4
8
10
11

我寻找的结果是:

等级 ID 原始ID 地点 原始位置
0 2 2 1235 1235
2 4 2 1239 1235
0 8 8 1237 1237
2 10 8 1235 1237
3 11 8 1237 1237

ID =2 和 =4 的输出很好。但是,对于 8,10,11,这会导致循环引用并且代码中断。

问题:有没有办法解决这个问题以避免发生错误:

语句终止。语句完成前已用尽最大递归次数 100

并产生所需的输出? 我知道 8 是根,因为它是该链中最小的数字,即使该链中存在循环引用。

这是我目前为 ID 2 和 4 生成输出的脚本。如果删除值 8,10,11,它会起作用

DECLARE @IDs TABLE (
  ID INTEGER
  ,PreviousID INTEGER
  ,Location INTEGER
)

INSERT INTO @IDs
SELECT           2,null,1235
UNION ALL SELECT 3,2,1236
UNION ALL SELECT 4,3,1239
UNION ALL SELECT 8,11,1237
UNION ALL SELECT 9,8,1234
UNION ALL SELECT 10,9,1235
UNION ALL SELECT 11,10,1237

Select * from @IDs


DECLARE @ORDERID Table (OrderID nvarchar (100))
Insert into @ORDERID values
('2')
,('4')
--,('8')
--,('10')
--,('11')

;WITH q AS (
    SELECT 0 lvl,  ID, PreviousID,PreviousID LastId
        ,Location,Location as OriginalLocation
    FROM    @IDs
    where ID in (select OrderID from @ORDERID) 
    UNION ALL 
    SELECT lvl+1, q.ID,u.PreviousId,q.PreviousId LastId
       ,q.Location,u.Location
    FROM    q
            INNER JOIN @IDs u ON u.ID = q.PreviousID
            --and q.ID <> u.PreviousID and q.PreviousID <> u.ID
)
select lvl, ID, coalesce(LastId,Id) OriginalId,Location,OriginalLocation 
from q
where PreviousId is null
order by id;

非常感谢您的任何提示或建议。

帖子版权声明 1、本帖标题:递归 CTE 以及如何克服循环引用错误
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由user915在本站《t-sql》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 您基本上已经完成了...您只需要进行两项更改:

    1. 添加额外的连接条件, q.ID > u.Id 当到达最小的 Id 时停止。
    2. 计算要显示哪些结果以包括那些有回避的结果。我发现添加特定计算来确定根比使 where 子句逻辑复杂化更清楚。
    WITH q AS (
        SELECT 0 lvl
            , ID
            , PreviousID
            , PreviousID LastId
            , Location
            , Location AS OriginalLocation
            , CONVERT(bit, CASE WHEN PreviousID IS NULL OR PreviousId > Id THEN 1 ELSE 0 END) IsRoot
        FROM @IDs
        WHERE ID in (select OrderID from @ORDERID) 
      
        UNION ALL 
      
        SELECT lvl + 1
            , q.ID
            , u.PreviousId
            , q.PreviousId LastId
            , q.Location
            , u.Location
            , CONVERT(bit, CASE WHEN u.PreviousID IS NULL OR u.PreviousId > u.Id THEN 1 ELSE 0 END) IsRoot
        FROM q
        INNER JOIN @IDs u ON u.ID = q.PreviousID
            -- Ensure we stop at the lowest Id
            AND q.ID > u.Id
    )
    SELECT lvl, ID, coalesce(LastId,Id) OriginalId, Location, OriginalLocation
    FROM q
    WHERE IsRoot = 1
    ORDER BY id;
    

    数据库Fiddle

  • 其实没什么 :) 我只是习惯转换为位,以便在客户端代码中它以布尔值而不是整数形式出现。但在这种情况下,您完全可以将其取出。

  • 建议的调整:(1)在 IsRoot 计算(两个位置)中将 OR PreviousId > Id 更改为 OR PreviousId => Id,以处理像 12 -> 12* 这样的自循环情况。(2)如果应用了 #1,那么 CTE 终止条件 AND q.ID > u.Id 就可以替换为 WHERE q.IsRoot = 0。这还可以处理像 23 -> 22 -> 21 -> 22* 这样的情况,其中循环完全低于原始 ID。

  • @TN,你能保留你建议的其他解决方案吗?几天前它还在那里,但我想你现在把它删除了。我的实时数据遇到了问题,递归错误仍然达到 100 最大值。我想使用你建议的解决方案来排除可能出错的地方

  • @T340B 只需对你的 cte 设置一个递归限制并删除过滤器 - 这就是调试它的方法,因为你将看到每个计算。

  • 正如下面的评论中所述,这不会产生 OP 想要的“最小数字”结果。我暂时将其发布,等待进一步讨论和可能的改进。其他发布的答案可能是最简单的答案。

    我将假设一种一般情况,其中顺序 ID 无关紧要,并且 PreviousID 值的序列可能会任意上下跳跃。我还将允许序列可能线性进展然后陷入可能包含或不包含原始 ID 值的循环的情况。

    检测循环的一种方法是构建已访问 ID 值列表,并根据已访问 ID 列表检查每个新 ID。由于 SQL Server 没有数组或类似的多值类型,我们可以回退到以字符串形式存储的以逗号分隔的已访问 ID 列表。该列表将使用原始 ID 值初始化,其他 ID 将附加在 CTE 的递归部分中。 适当分隔的 CHARINDEX() 测试可以检查我们是否已到达先前访问的 ID。

    就像是:

    WITH q AS (
        SELECT
            0 lvl, ID, PreviousID, PreviousID LastId
            ,Location, Location as OriginalLocation
            ,CAST(CONCAT(',', ID, ',') AS VARCHAR(MAX)) as VisitedIDs
            ,CAST(0 AS BIGINT) AS LoopDetected
        FROM    IDs
        where ID in (select OrderID from ORDERID) 
        UNION ALL 
        SELECT lvl+1, q.ID,u.PreviousId,q.PreviousId LastId
           ,q.Location,u.Location
           ,CONCAT(VisitedIDs, u.ID, ',')
           ,CHARINDEX(CONCAT(',', u.ID, ','), VisitedIDs) AS LoopDetected
        FROM    q
        INNER JOIN IDs u ON u.ID = q.PreviousID
        WHERE q.LoopDetected = 0
    )
    select lvl, ID, coalesce(LastId,Id) OriginalId,Location,OriginalLocation
        , q.LoopDetected, q.VisitedIDs
        , CASE WHEN q.LoopDetected > 0 THEN 'Loop detected' ELSE '' END AS ErrorCheck
    from q
    where (PreviousId is null OR q.LoopDetected > 0)
    order by id, lvl;
    

    请注意, CHARINDEX() 测试用逗号括住 ID 值,以确保例如 ID = 5 仅匹配 ...,5,... 并且不匹配 ...,15,... ...,51,... VisitedIDs 还有额外的逗号来支持此测试。

    样本结果(附带一些额外的测试数据):

    等级 ID 原始ID 地点 原始位置 检测到环路 访问 ID 错误检查
    0 2 2 1235 1235 0 ,2,
    2 4 2 1239 1235 0 ,4,3,2,
    4 8 8 1237 1237 1 ,8,11,10,9,8, 检测到环路
    4 10 10 1235 1235 1 ,10,9,8,11,10, 检测到环路
    4 11 11 1237 1237 1 ,11,10,9,8,11, 检测到环路
    8 12 10 9999 1235 11 ,12,5,13,​​6,10,9,8,11,10, 检测到环路
    3 23 22 9999 9999 0 ,23,21,24,22,

    如果您想避免创建列表,您可以跟踪一个样本先前 ID(最初是起始 ID),并根据该跟踪 ID 检查每个步骤。问题是可能会出现不包括原始 ID 的循环。为了处理这种情况,您可以以增加的间隔(2 的幂)重置跟踪 ID,这样当进入循环时,跟踪 ID 最终 最终 循环

    WITH q AS (
        SELECT
            0 lvl, ID, PreviousID, PreviousID LastId
            ,Location, Location as OriginalLocation
            ,ID as LoopCheckID
        FROM    IDs
        where ID in (select OrderID from ORDERID) 
        UNION ALL 
        SELECT lvl+1, q.ID,u.PreviousId,q.PreviousId LastId
           ,q.Location,u.Location
           ,CASE WHEN lvl & (lvl - 1) = 0 THEN u.ID ELSE q.LoopCheckID END
        FROM    q
        INNER JOIN IDs u ON u.ID = q.PreviousID
        WHERE q.PreviousID <> q.LoopCheckID
    )
    select lvl, ID, coalesce(LastId,Id) OriginalId,Location,OriginalLocation
        , q.PreviousID, q.LoopCheckID
        , CASE WHEN q.PreviousID = q.LoopCheckID THEN 'Loop detected' ELSE '' END AS ErrorCheck
    from q
    where (PreviousId is null OR q.PreviousID = q.LoopCheckID)
    order by id, lvl;
    

    lvl & (lvl - 1) = 0 是一个棘手的位操作检查,当 lvl 是 2 的幂(1、2、4、8、16……)时为真。

    示例结果:

    等级 ID 原始ID 地点 原始位置 以前的ID 回路检查编号 错误检查
    0 2 2 1235 1235 p12 2
    2 4 2 1239 1235 p13 2
    8 8 8 1237 1237 11 11 检测到环路
    8 10 10 1235 1235 9 9 检测到环路
    8 11 11 1237 1237 10 10 检测到环路
    8 12 10 9999 1235 9 9 检测到环路
    3 23 22 9999 9999 p14 22

    请注意,在上述几种情况下,循环检测比早期基于列表的解决方案晚了几个步骤。

    请参阅 this db<>fiddle 了解上述两种技术的演示。

    另一种可能的方法是提前检查整个 IDs 表以确定哪些 ID 可以从具有 的行中(反向)到达 PreviousID = null 。一旦建立了该列表,任何不在该列表中的 ID 都将被视为循环的一部分。(我还没有编写这种方法。)

  • 但是,这并没有产生 OP 期望的结果...他们指定循环应该在访问的最低 ID 处结束,而不是在它回到自身时结束。

  • DTYK 2月前 0 只看Ta
    引用 9

    @DaleK - 说得好。我没听懂。可能需要在过程中跟踪 MIN(ID),并在最终选择中重新加入该行。这样可以处理像 23 -> 21 -> 22 -> 24 -> 20 -> 26 -> 25 这样的奇怪情况,在这些情况下,我们直到到达末尾才知道最小值。

  • 虽然我钦佩您对通用解决方案的承诺,但我认为对于这种情况和许多其他情况来说,这有点矫枉过正。SQL 最佳实践更侧重于解决特定问题,而不是像在其他编码语言中那样构建通用查询。而且我很确定我之前已经看到过对此问题的一般解决方案的回答,所以在这种情况下这将是重复的。

  • @TN,即使你的解决方案没有产生预期的结果,但它很有趣。你的建议对我来说很有价值。不过我还需要一些时间来消化它。谢谢。

  • 引用 12

    CHARINDEX(CONCAT(',', u.ID), VistiedIDs) AS LoopDetected 不应该是 CHARINDEX(CONCAT(',', u.ID, ','), VistiedIDs) AS LoopDetected 吗?否则 ',5' 可以匹配 ',42,55,9,'。Vistied 是什么意思?

  • SQL Server:假设我有一张包含 Column1 和 Column2 的表。为什么会出现这样的情况:SELECT Column1, * FROM MyTable ORDER BY Column1; 会抛出错误:列名“Column1”不明确但会出现这样的情况:SELECT

    SQL Server:假设我有一张表, Column1 其中 Column2 .

    为什么会这样:

    SELECT Column1, * 
    FROM MyTable 
    ORDER BY Column1;
    

    抛出错误:

    列名‘Column1’不明确

    但是这个:

    SELECT Column1, * 
    FROM MyTable 
    ORDER BY MyTable.Column1;
    

    不会引发错误?

    尝试运行两个命令

  • 我正在创建一个存储过程,用于从 SSRS 报告服务器导出 pdf 报告。该报告需要一个名为“ExportId”的参数。该过程运行成功,但它确实...

    我正在创建一个存储过程,用于从 SSRS 报告服务器导出 PDF 报告。该报告需要一个名为“ExportId”的参数。该过程运行成功,但未创建 PDF。我在这里做错了什么?

    
    Alter PROCEDURE rsp_GenerateAndDeliverReports
        @ReportName NVARCHAR(100),
        @Parameter1Name NVARCHAR(100),
        @Parameter1Value NVARCHAR(100),
        @OutputFile NVARCHAR(100)
    AS
    BEGIN
        -- Variables
        DECLARE @executionID AS INT
        DECLARE @reportBytes AS VARBINARY(MAX)
        DECLARE @zipFilePath AS NVARCHAR(100)
        DECLARE @reportService AS INT -- Change the data type to INT
    
        -- Create an instance of the SSRS Execution Service
        EXEC sp_OACreate 'SSRS.ReportExecutionService', @reportService OUT
    
        -- Initialize the service
        EXEC sp_OASetProperty @reportService, 'Url', 'http://localhost/ReportServer/ReportExecution2005.asmx'
    
        -- Load Report 1
        EXEC sp_OAMethod @reportService, 'LoadReport', NULL, @ReportName, NULL, @executionID OUT
    
        -- Set Report 1 Parameters
        EXEC sp_OAMethod @reportService, 'SetExecutionParameters', NULL, @Parameter1Name, @Parameter1Value
    
        -- Render Report 1
        EXEC sp_OAMethod @reportService, 'Render', @reportBytes OUTPUT, 'PDF', NULL, NULL, NULL, NULL, NULL
    
        -- Close and release the service
        EXEC sp_OADestroy @reportService
    
    
    
    
    
        -- Save Report 1 as PDF
        DECLARE @sql NVARCHAR(MAX)
        SET @sql = 'DECLARE @file AS VARBINARY(MAX) 
                    SET @file = ' + CONVERT(NVARCHAR(MAX), @reportBytes, 1) + '
                    EXEC sp_writebytesfile @file, ''' + @OutputFile + ''
        EXEC sp_executesql @sql
    
    END
    
    
    EXEC rsp_GenerateAndDeliverReports @ReportName='/All Reports/Subreports/07_sub', @Parameter1Name ='ExportId', @Parameter1Value='2', @OutputFile='C:\Users\Public\Public Downloads\Report.pdf'
    
    
    
  • 我有一张包含 XML 列的表。我没有该列的数据字典,也没有 XML 结构的定义。大约有 150 万条记录。从对小样本的目视检查来看,

    我有一张包含 XML 列的表。我没有该列的数据字典,也没有 XML 结构的定义。大约有 150 万条记录。从对小样本的目视检查来看,XML 的结构似乎相当相似。我希望能够运行一些 SQL,以便为我提供所有记录的 XML 结构超集。

    该表称为 Remittance ,该列称为 RemittInstr .

    例如,如果我有两个测试数据记录,其 RemittInstr 列中的 XML 值为:

    第 1 行:

    <ri>
        <Msg Type="MT103">
            <AccountNo>12345678</AccountNo>
            <Description code="ORCSR">Ordering Customer<Description>
        </Msg>
    </ri>
    

    第 2 行:

    <ri>
        <Msg Type="MT202">
            <BICFI>ABCD1234</BICFI>
            <Description code="FI">Financial Institution<Description>
        </Msg>
    </ri>
    

    如何编写返回以下行的查询:

    /ri
    /ri/Msg
    /ri/Msg/@Type
    /ri/Msg/AccountNo
    /ri/Msg/BICFI
    /ri/Msg/Description
    /ri/Msg/Description/@code
    

    这样我就能全面了解所有行的 XML 结构了吗?

    编辑:这与链接的问题完全不同,因为链接的问题只处理单个 XML 值。问题是关于在整个表中的行中查找 XML 的结构。解决此问题的解决方案完全不同。@Charlieface 在下面的评论中提供了一个指向良好解决方案的链接。

  • SELECT Column1, * FROM MyTable ORDER BY Column1;
    

    有效地

    SELECT Column1, Column1, Column2 FROM MyTable ORDER BY Column1;
    

    因此引擎会感到困惑,不知道 Column1 使用哪个进行排序。

    Query and result

    另一方面,通过编写如下查询

    SELECT Column1, * FROM MyTable ORDER BY MyTable.Column1;
    

    它将按 进行排序 Column1 ,但它知道您指的是 Column1 特定表中的特定名称。想象一下,您要连接两个表,并且它们都有带 Column1 名称的列。引擎无法确定 Column1 您指的是哪个表中的哪个。在您的例子中,很明显它们是相同的,但在其他用例中并非如此,因此出现错误。

    如果您不喜欢使用表名,则可以对列使用不同的名称。

    SELECT Column1 as c1, * FROM MyTable ORDER BY c1;
    

    Query and result

  • T-SQL 是一种 SQL 方言,被 Sybase、SQL Server 和 Azure Synapse 等多种产品使用。您在这里使用的 (R)DBMS 产品是什么?

  • 不要这么做。这种逻辑到底属于数据库吗?它之所以很难,是因为你不应该这么做。自己编写一个小服务或计划任务。你将拥有更多的控制权,更容易调试,可扩展,最重要的是不会搞砸你的数据库。

  • 听起来生成这个可能需要一段时间,因为你需要递归地解析每一行中的每个节点,你确定这本字典会帮助你,而不只是产生很多噪音吗

  • 改用 SQL 代理作业 和 PowerShell 脚本。实际上,它更简单、更强大。从 TSQL 将报告请求写入表,然后调用 sp_start_job 或安排计划启动代理作业以呈现所有待处理的报告导出请求。

    如果您想了解如何使用 sp_oaxxx 过程,请参见 此处 .

    而且使用 SSRS SOAP API 进行 报告导出 大材小用 URL 访问 ,只需向 URL 发送一个 HTTP GET 即可获得呈现的报告。

返回
作者最近主题: