提问人:user2335565 提问时间:11/15/2023 最后编辑:T Nuser2335565 更新时间:11/17/2023 访问量:137
我需要递归 TSQL 查询方面的帮助
I need assistance with recursive TSQL Query
问:
我需要帮助来穿越这条路。 请看下表:
编号 | 下一个 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。
答:
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
前两个步骤是标准的:
- 创建一个标志字段,每当与上一个目标相比发生更改时,该字段都会递增。我猜你想按类别分组,如果这是错误的假设,你可以按部分删除分区
- 通过创建滚动 SUM 来汇总标志,这将创建一组属于一起的目标
- 由于您需要每个组的最后一个目的地,因此我们使用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 > ZZ
AA > AC > AD > BB > DM
0赞
T N
11/15/2023
#2
我将假设一个比测试数据所指示的更普遍的情况。
- 进展链可以混合在一起。
- 多行(具有不同的源)可以具有相同的直接目标。
- 链接目标时,将选择当前行之后最早的匹配行。
- 源可能会多次出现,但日期不同,进度不同。
要跟踪数据中的所有路径,最好从最终目标行(没有后续行的行)开始,然后递归返回以查找所有匹配的前置任务。递归公用表表达式 (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_Numbered
CTE_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)
*
评论
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|
)