这本质上是一个间隙和岛屿问题,但它是非典型的。我确实将示例缩减到最低限度。我需要识别超过特定阈值的间隙,并且重复不会成为问题,尽管此示例删除了它们。
在任何情况下,使用 ROW_NUMBER() 的常见解决方案都没有帮助,因为即使是 1 的间隙也无法处理,并且间隙值是“现实生活”中的参数。
下面的代码实际上可以正确运行。而且速度超级快!但如果你看看它,你就会明白为什么人们对依赖它相当害羞。该方法首次发布于 9 年前,http ://www.sqlservercentral.com/articles/T-SQL/68467/,我已经阅读了全部 32 页的评论。除了说“这不是有记录的行为”之外,没有人成功地找出其中的漏洞。我在 2005 年到 2019 年的每个版本上都尝试过,效果很好。
问题是,除了使用游标或 while 循环逐一查看数百万行之外,这需要多长时间,因为我在 30 分钟后取消了。- 是否有一种“受支持”的方法可以在合理的时间内获得相同的结果?即使慢 100 倍也会在 10 分钟内完成 4M 行,但我找不到接近这个目标的方法!
CREATE TABLE #t (CreateDate date not null
,TufpID int not null
,Cnt int not null
,FuzzyGroup int null);
ALTER TABLE #t ADD CONSTRAINT PK_temp PRIMARY KEY CLUSTERED (CreateDate,TufpID);
-- Takes 40 seconds to write 4.4M rows from a source of 70M rows.
INSERT INTO #T
SELECT X.CreateDate
,X.TufpID
,Cnt = COUNT(*)
,FuzzyGroup = null
FROM SessionState SS
CROSS APPLY(VALUES (CAST(SS.CreateDate as date),SS.TestUser_Form_Part_id)) X(CreateDate,TufpID)
GROUP BY X.CreateDate
,X.TufpID
ORDER BY x.CreateDate,x.TufpID;
-- Takes 6 seconds to update 4.4M rows. They WILL update in clustered index order!
-- (Provided all the rules are followed - see the link above)
DECLARE @FuzzFactor int = 38
DECLARE @Prior int = -@FuzzFactor; -- Insure 1st row has it's own group
DECLARE @Group int;
DECLARE @CDate date;
UPDATE #T
SET @Group = FuzzyGroup = CASE WHEN t.TufpID - @PRIOR < @FuzzFactor AND t.CreateDate = @CDate
THEN @Group ELSE t.TufpID END
,@CDate = CASE WHEN @CDate = t.CreateDate THEN @CDate ELSE t.CreateDate END
,@Prior = CASE WHEN @Prior = t.TufpID-1 THEN @Prior + 1 ELSE t.TufpID END
FROM #t t WITH (TABLOCKX) OPTION(MAXDOP 1);
Run Code Online (Sandbox Code Playgroud)
执行上述操作后,FuzzyGroup 列包含组中 TufpID 的最低值。IOW 第一行(按聚集索引顺序)包含它自己的 TufpID 列的值。此后,每行都会获得相同的值,直到日期更改或超出间隙大小(在本例中为 38)。在这些情况下,当前的 TufpID 会成为放入 FuzzyGroup 中的值,直到检测到另一个更改。因此,6 秒后,我可以运行按 FuzzyGroup 分组的查询并分析岛屿。
在实践中,我也在同一次传递中进行了一些运行计数和总计,因此需要 8 秒而不是 6 秒,但如果需要的话,我可以很容易地使用窗口函数完成这些操作,所以我将它们保留下来。
这是最小的表,我最终需要处理 100M 行。因此 10 分钟的 4.4M 可能还不够好,但它是一个起点。
这应该相当有效,并避免依赖无证行为
WITH T1
AS (SELECT *,
PrevTufpID = LAG(TufpID)
OVER (PARTITION BY CreateDate
ORDER BY TufpID)
FROM #T),
T2
AS (SELECT *,
_FuzzyGroup = MAX(CASE
WHEN PrevTufpID IS NULL
OR TufpID - PrevTufpID >= @FuzzFactor
THEN TufpID
END)
OVER (PARTITION BY CreateDate
ORDER BY TufpID ROWS UNBOUNDED PRECEDING)
FROM T1)
UPDATE T2
SET FuzzyGroup = _FuzzyGroup
Run Code Online (Sandbox Code Playgroud)
执行计划对聚集索引进行单次有序扫描,然后行值流经一些窗口函数运算符并进入更新。