【问题标题】:How to re write while loop using cte如何使用 cte 重写 while 循环
【发布时间】:2018-11-15 23:31:02
【问题描述】:

我有两张桌子,一张是事件,另一张是剧集。 剧集有开始日期和结束日期,事件只有一个日期。 剧集和事件都有六种类型之一。

目前,我正在使用一些模糊逻辑在 Events 表上运行更新脚本,以将其 ID 字段设置为匹配的 Episode。它通过检查情节开始和结束之间的事件日期来做到这一点,两者都具有相同的类型,以及其他一些链接,如相同的用户等。

由于事件可以位于情节之外,或者具有不同的类型,我所做的是循环浏览一系列扩展日期范围(StartDate-1、-2 等),并循环浏览每个类型以寻找匹配项.

我一直在读到 while 循环效率不高,所以想知道是否有办法将此嵌套循环重写为 CTE 函数。

我使用的是 SQL Server 2012。 事件列表只是一个临时表,其中包含所有可能的类型以及循环顺序。

我目前的循环是:

WHILE @CurrBefore <= @Before and @CurrentAfter <= @After
BEGIN
  SET @Row = 0
  WHILE @Row <= @MaxRow
  BEGIN
    UPDATE P
    SET P.ID = E.ID
    FROM Event P
    OUTER APPLY (SELECT TOP 1 E.Id, E.Type
             FROM Episode E
             WHERE E.User = P.User AND
                   E.Type = CASE WHEN @Row=0 THEN P.Event ELSE (SELECT Event FROM #EventList WHERE RN = @Row) END AND
                   P.Date BETWEEN E.StartDate-@CurrentBefore AND E.EndDate+@CurrentAfter
             ORDER BY P.Date) E
             WHERE P.ID = 0

    INCREMENT @ROW CODE 
    END

INCREMENT @BEFORE/AFTER CODE
END

样本数据:

IF OBJECT_ID('tempdb..#EventList') IS NOT NULL
BEGIN
    DROP TABLE  #EventList
    CREATE TABLE #EventList(Event  Varchar(50), RN  INT);
    INSERT INTO  #EventList SELECT 'A', 1
    INSERT INTO  #EventList SELECT 'B', 2
    INSERT INTO  #EventList SELECT 'C', 3
    INSERT INTO  #EventList SELECT 'D', 4
    INSERT INTO  #EventList SELECT 'E', 5
    INSERT INTO  #EventList SELECT 'F', 6
END

   CREATE TABLE dbo.Episode ([ID] INT, [Start] DateTime, [End] DateTime, [Type] varchar(1), [User] INT)
    INSERT INTO [dbo].Episode    ([ID], [Start], [End], [Type],[User])
    VALUES
        (1, '2018-07-01 10:00', '2018-07-02 14:00', 'A',10),
        (2, '2018-07-05 6:00', '2018-07-06 13:00', 'A',11),
        (3, '2018-07-03 9:00', '2018-07-04 8:00', 'B',10),
        (4, '2018-07-02 15:00', '2018-07-03 7:00', 'B',12),
        (5, '2018-07-01 1:00', '2018-07-02 8:00', 'C',13),
        (6, '2018-07-01 6:00', '2018-07-01 8:00', 'D',11)

CREATE TABLE dbo.Event ([ID] INT, [Date] DateTime, [Type] varchar(1), [User] INT)
INSERT INTO [dbo].Event    ([ID], [Date], [Type],[User])
VALUES
    (0, '2018-07-01 12:00', 'A',10),
    (0, '2018-07-05 15:00', 'A',11),
    (0, '2018-07-03 13:00', 'C',10),
    (0, '2018-07-10 9:00', 'B',12),
    (0, '2018-07-01 5:00', 'C',10),
    (0, '2018-07-01 10:00', 'D',11)

预期结果,事件现在看起来像这样:

1   2018-07-01 12:00:00.000 A   10
2   2018-07-05 15:00:00.000 A   11
3   2018-07-03 13:00:00.000 C   10
0   2018-07-10 09:00:00.000 B   12
1   2018-07-01 05:00:00.000 C   10
6   2018-07-01 10:00:00.000 D   11

【问题讨论】:

  • 请编辑问题并包含一些样本数据(一打或两行)以及您期望达到的最终结果。这个例子帮助大家理解所需的逻辑。
  • 我想你也需要在这里解释一下“逻辑”;为什么第 5 个事件 (0, '2018-07-01 5:00', 'C',10) 的 Episode.Id 为 1(仅用户匹配)而不是 5(日期/类型匹配)?另外你的while循环有UPDATE P——但P不匹配任何表/别名,你有CASE...THEN P.Event ELSE但只有你的临时表有一个名为Event的列——但如果临时表是 P 则 UPDATE 应该失败,因为临时表中没有 ID 列。
  • 由于 p.User = e.User 的要求而匹配。时间和类型是灵活的。哎呀,在复制代码时一定错过了几个引用。这是我的脚本的一个更简单的版本,只有基本要求。我会修正参考资料。
  • 基本上,代码所做的是将事件与剧集相匹配。该事件必须针对同一用户。事件有时可以在剧集中,也可以在剧集的任何一边。类型通常匹配,但有时可能不同。
  • 我希望有一天,用于示例数据的 ddl+dml 会被如此广泛地使用,以至于我觉得没有必要为此投票赞成问题。但这不是这一天。

标签: sql-server tsql


【解决方案1】:

我不知道,如果我完全理解逻辑,但这可能有助于让你运行:

USE master;
GO
CREATE DATABASE TestDB
GO
USE TestDB;
GO

CREATE TABLE dbo.Episode ([ID] INT, [Start] DateTime, [End] DateTime, [Type] varchar(1), [User] INT)
    INSERT INTO [dbo].Episode    ([ID], [Start], [End], [Type],[User])
    VALUES
        (1, '2018-07-01 10:00', '2018-07-02 14:00', 'A',10),
        (2, '2018-07-05 6:00', '2018-07-06 13:00', 'A',11),
        (3, '2018-07-03 9:00', '2018-07-04 8:00', 'B',10),
        (4, '2018-07-02 15:00', '2018-07-03 7:00', 'B',12),
        (5, '2018-07-01 1:00', '2018-07-02 8:00', 'C',13),
        (6, '2018-07-01 6:00', '2018-07-01 8:00', 'D',11)

CREATE TABLE dbo.[Event] ([ID] INT, [Date] DateTime, [Type] varchar(1), [User] INT)
INSERT INTO [dbo].[Event]    ([ID], [Date], [Type],[User])
VALUES
    (0, '2018-07-01 12:00', 'A',10),
    (0, '2018-07-05 15:00', 'A',11),
    (0, '2018-07-03 13:00', 'C',10),
    (0, '2018-07-10 9:00', 'B',12),
    (0, '2018-07-01 5:00', 'C',10),
    (0, '2018-07-01 10:00', 'D',11)
GO

CREATE TABLE #EventList(Event  Varchar(50), RN  INT);
INSERT INTO #EventList VALUES ('A', 1),('B', 2),('C', 3),('D', 4),('E', 5),('F', 6);

WITH mathingEpisodes AS
(
    SELECT ev.ID AS evID
          ,ev.[Date] AS evDate
          ,ev.[Type] AS evType
          ,ev.[User] AS evUser
          ,e1.RN AS evRN
          ,ep.ID AS epID
          ,ep.[Type] AS epType
          ,e2.RN AS epRN
    FROM [Event] ev
    LEFT JOIN Episode ep ON ev.[User]=ep.[User] AND ev.[Date] >= ep.[Start] AND ev.[Date] < ep.[End]
    LEFT JOIN #EventList e1 ON ev.[Type]=e1.[Event]
    LEFT JOIN #EventList e2 ON ep.[Type]=e2.[Event]
)
SELECT COALESCE(epID,Closest.ID) AS FittingEpisodeID
      ,me.evDate
      ,evType
      ,evUser
FROM mathingEpisodes me
OUTER APPLY(SELECT TOP 1 * 
            FROM Episode ep
            CROSS APPLY(SELECT ABS(DATEDIFF(SECOND,me.evDate,ep.[Start])) AS DiffToStart
                              ,ABS(DATEDIFF(SECOND,me.evDate,ep.[End])) AS DiffToEnd) Diffs
            CROSS APPLY(SELECT CASE WHEN DiffToStart<DiffToEnd THEN DiffToStart ELSE DiffToEnd END AS Smaller) Diffs2  
            WHERE ep.[User] = me.evUser
            AND   me.epID IS NULL
            ORDER BY Diffs2.Smaller
            ) Closest
ORDER BY evDate;
GO
USE master;
GO
DROP DATABASE TestDB;
GO
DROP TABLE #EventList
GO

结果

1   2018-01-07 05:00:00.000 C   10
6   2018-01-07 10:00:00.000 D   11
1   2018-01-07 12:00:00.000 A   10
3   2018-03-07 13:00:00.000 C   10
2   2018-05-07 15:00:00.000 A   11
4   2018-10-07 09:00:00.000 B   12

一些解释

在第一个 cte 中,我尝试找到合适的剧集(范围内相同的用户和日期)。
第二个 cte 将在第一个 cte 未成功的所有情况下为同一用户计算 最近 集。

此示例的唯一区别是 userId=12 的事件。我的逻辑会将此绑定到该用户最近的剧集(ID=4),而您的预期输出在此位置显示零。

无论如何,我的解决方案是完全基于集合的,因此比循环更快,并且应该更接近您的需求。尝试适应它...

更新一些想法...

我没有了解您的#EventList...意思是……

【讨论】:

  • 基本上,如果有两个情节可以匹配到一个事件,则 #EventList 充当优先顺序。谢谢你,我会玩,看看我怎么走
  • 您的 Userid=12 大小写可能是匹配的,因为 +- 修改了日期范围。我的测试只看两边最多 2 天
  • @Matt 您可以轻松地将WHERE MaxDiff ... 添加到查询中,以定义匹配剧集的最大距离。在相同距离的情况下,使用ORDER BY 中的#EventList 来强制执行正确的情节也应该很容易。
  • 是的,我已经完成了这项工作,现在需要几秒钟而不是几分钟。我在第一个 cte 中使用了 row_number 来进行排序,然后是设置一些限制并正确获取订单逻辑的简单案例。我还没有探索过你的交叉应用代码在做什么,这是我的下一个任务。感谢您的帮助
猜你喜欢
  • 2018-06-24
  • 2012-10-07
  • 2014-09-03
  • 1970-01-01
  • 1970-01-01
  • 2013-07-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多