我需要递归 TSQL 查询方面的帮助

I need assistance with recursive TSQL Query

提问人:user2335565 提问时间:11/15/2023 最后编辑:T Nuser2335565 更新时间:11/17/2023 访问量:137

问:

我需要帮助来穿越这条路。 请看下表:

编号 下一个 ID
1 5
2
3 6
4 7
5 8
6 9
7
8
9 10
10

Id 是唯一的。NextID 存储下一个 ID 值。当 NextID 值为 Null 时,路径为 Ends。

在此表中,可能的路径为:

a. 1 --> 5 --> 8 
b. 2
c. 3 -->6 -->9 -->10
d. 4-->7
e. 8

我写了一个查询,但没有根据需要工作。

CREATE TABLE Data (
    ID VARCHAR(10),
    NextID VARCHAR(10)
)
    
INSERT Data VALUES ('1','5'), ('2','Null'),
    ('3','6'), ('4','7'), ('5','8'), ('6','9'),
    ('7','Null'), ('8','Null'), ('9','10'),
    ('10','Null');
      
;WITH RecursivePaths AS (
    SELECT ID, NextID, CAST(ID AS VARCHAR(MAX)) AS Path
    FROM Data
    WHERE ID = 3  -- Starting point, adjust as needed

    UNION ALL
    
    SELECT t.ID, t.NextID, r.Path + ' --> ' + CAST(t.ID AS VARCHAR(MAX))
    FROM Data t
    JOIN RecursivePaths r ON t.ID = r.NextID
    WHERE t.NextID IS NOT NULL
)
SELECT Path
FROM RecursivePaths

在这里,我必须提供不需要的起始ID。

sql sql-server t-sql

评论

1赞 Patrick Hurst 11/15/2023
请提供一个最小的可重现示例,包括 DDL/DML(例如: 请以可解析的格式(例如表格)演示您的预期输出(您可以在问题中使用 markdown,例如:DECLARE @Table TABLE (Col INT, Col2 DATE, ...); INSERT INTO @Table (Col, Col2, ...) VALUES (1, '2023-11-14', ...), (1, '2023-11-15', ....);|Col|Col2| |:---|:---| |1|2023-11-14| |2|2023-11-14| )
2赞 Yitzhak Khabinsky 11/15/2023
在提出问题时,您需要提供一个最小的可重现示例:(1) DDL 和示例数据填充,即 CREATE 表和 INSERT T-SQL 语句。(2) 需要执行的操作,即逻辑和代码尝试在 T-SQL 中实现它。(3) 期望的输出,基于上面 #1 中的示例数据。(4) 您的 SQL Server 版本 (SELECT @@version;)。
1赞 Thom A 11/15/2023
提问时,请不要上传代码/数据/错误的图片。用图像替换未格式化的文本没有帮助、无用或无改进。
1赞 Zohar Peled 11/15/2023
欢迎来到 stackoverflow。请花点时间阅读如何提问,以及 t-sql 标记信息中有关如何提出好的 t-sql 问题的说明。
3赞 Sean Lange 11/15/2023
你试过什么?我猜你需要研究递归 ctes。

答:

1赞 siggemannen 11/15/2023 #1

这实际上看起来像是伪装的缺口和岛屿。可能的解决方案:

select  datenew 
,   source, destination, Category
,   FIRST_VALUE(Destination) OVER(PARTITION BY category, grp ORDER BY dateNew DESC) AS final_destination -- Take first value ordered by last date per group
from (
    select  *
    ,   sum(flag)over(partition by category order by dateNew) as grp -- 2. Create grouping
    from (
        select  case when lag(destination) over(partition by category order by cast(date as date)) = source then 0 else 1 end as flag -- 1. Watch me for the changes
        ,   cast(date as date) as dateNew
        ,   *
        from
        (
            VALUES  (N'04-14-2020', N'AA', N'AC', N'T', NULL)
            ,   (N'06-02-2020', N'AC', N'AD', N'T', NULL)
            ,   (N'07-22-2020', N'AD', N'BB', N'T', NULL)
            ,   (N'10-22-2020', N'BB', N'DM', N'T', NULL)
            ,   (N'06-07-2021', N'BM', N'CD', N'T', NULL)
            ,   (N'08-09-2021', N'CD', N'QR', N'T', NULL)
            ,   (N'08-07-2021', N'BW', N'BW', N'M', NULL)
            ,   (N'05-11-2020', N'OR', N'BO', N'M', NULL)
        ) t (Date,Source,Destination,Category,final_destination)
        ) x
    ) x
order by category DESC, datenew

前两个步骤是标准的:

  1. 创建一个标志字段,每当与上一个目标相比发生更改时,该字段都会递增。我猜你想按类别分组,如果这是错误的假设,你可以按部分删除分区
  2. 通过创建滚动 SUM 来汇总标志,这将创建一组属于一起的目标
  3. 由于您需要每个组的最后一个目的地,因此我们使用FIRST_VALUE窗口函数来获取所需的值。

输出:

日期新 目的地 类别 final_destination
2020-04-14 机 管 局 交流 T 分米
2020-06-02 交流 广告 T 分米
2020-07-22 广告 BB型 T 分米
2020-10-22 BB型 分米 T 分米
2021-06-07 BM公司 光盘 T 二维码
2021-08-09 光盘 二维码 T 二维码
2020-05-11 M
2021-08-07 BW系列 BW系列 M BW系列

评论

0赞 T N 11/15/2023
我认为这可能不适用于一般情况,因为它假设没有重叠的进展。如果我将数据添加到混合中,则进度会分解进度。请参阅此 db<>fiddle。(这主要是测试数据过于简化和/或OP要求不足的问题。我怀疑需要递归 CTE,正如 Sean 之前建议的那样。, (N'07-01-2020', N'XX', N'YY', N'T', NULL), (N'07-02-2020', N'YY', N'ZZ', N'T', NULL)XX > YY > ZZAA > AC > AD > BB > DM
0赞 T N 11/15/2023 #2

我将假设一个比测试数据所指示的更普遍的情况。

  1. 进展链可以混合在一起。
  2. 多行(具有不同的源)可以具有相同的直接目标。
  3. 链接目标时,将选择当前行之后最早的匹配行。
  4. 源可能会多次出现,但日期不同,进度不同。

要跟踪数据中的所有路径,最好从最终目标行(没有后续行的行)开始,然后递归返回以查找所有匹配的前置任务。递归公用表表达式 (CTE) 是实现此目的的一种方法。

但是,在跟踪链接之前,我们需要确定每一行的逻辑后继者。这也可以通过 CTE 来完成。为方便起见,可以使用另一个 CTE 来临时分配不同的行 ID,以便可以轻松将这些链接表示为单个 NextId 值。

结果将是 CTE 的进展,然后是最终选择。

;WITH CTE_Numbered AS (
    -- Assign arbitrary unique IDs
    SELECT D.*, Id = ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
    FROM Data D
),
CTE_Linked AS (
    -- Identify the successor ID for each row (null if final)
    SELECT N.*, NextId = N2.Id
    FROM CTE_Numbered N
    OUTER APPLY (
        SELECT TOP 1 N2.Id
        FROM CTE_Numbered N2
        WHERE N2.Category = N.Category
        AND N2.Date > N.Date
        AND N2.Source = N.Destination
        ORDER BY N2.Date
    ) N2
),
CTE_Chained AS (
    -- Recursively join the final destinations back to all predicessors
    SELECT L.*, L.Destination AS Final_Destination
    FROM CTE_Linked L
    WHERE L.NextId IS NULL
    UNION ALL
    SELECT L.*, C.Final_Destination AS Final_Destination
    FROM CTE_Chained C
    JOIN CTE_Linked L
        ON L.NextId = C.Id
)
SELECT C.*
FROM CTE_Chained C
ORDER BY C.Category DESC, C.Date

结果(带有一些额外的测试数据):

日期 目的地 类别 同上 下一个 Id Final_Destination
2020-04-14 机 管 局 交流 T 1 2 分米
2020-06-02 交流 广告 T 2 5 分米
2020-07-01 XX公司 YY的 T 3 4 ZZ公司
2020-07-02 YY的 ZZ公司 T 4 ZZ公司
2020-07-22 广告 BB型 T 5 6 分米
2020-10-22 BB型 分米 T 6 分米
2021-06-07 BM公司 光盘 T 7 8 二维码
2021-08-09 光盘 二维码 T 8 二维码
2020-05-11 M 10
2021-08-07 BW系列 BW系列 M 9 BW系列
2022-01-01 加语 GD系列 G 11 14 通用 电气
2022-01-02 国标 气相色谱仪 G 12 13 通用 电气
2022-01-03 气相色谱仪 通用 电气 G 13 通用 电气
2022-01-04 GD系列 通用 电气 G 14 通用 电气
2022-01-05 加语 国标 G 15 16 气相色谱仪
2022-01-06 国标 气相色谱仪 G 16 气相色谱仪

请参阅此 db<>fiddle,其中还包含一些额外的测试数据。

评论

0赞 user2335565 11/16/2023
此查询部分有帮助,但是递归查询在具有 6K 记录的数据库中运行时花费的时间太长(未在 20 分钟内完成)
0赞 T N 11/16/2023
递归可能需要一个索引。这可以通过使用查询结果填充临时表,添加索引,然后将其馈送到查询的其余部分来完成。如果仍然存在性能问题,则可以将查询结果存储到第二个临时表中,并在 上添加索引。#CTE_NumberedCTE_Numbered#CTE_Numbered(Category, Source, Date)CTE_Linked#CTE_Linked#CTE_Linked(NextId)
0赞 T N 11/16/2023
请参阅此 db<>fiddle
0赞 T N 11/16/2023
如果您的原始数据已经具有唯一的 ID 值(例如主键),则可以跳过第一步并使用现有 ID。您仍然需要一个索引 - 按该顺序排列的复合索引。此外,如果源数据的列数比示例中显示的多得多,则应将第一个查询修改为仅选择所需的数据,而不是 .(Category, Source, Date)*