如何使用CTE重新编写While循环

时间:2018-11-15 23:31:03

标签: sql-server tsql

我有两个桌子,一个桌子有活动,另一个桌子有情节。 情节具有开始日期和结束日期,事件具有单个日期。 情节和事件都具有六种类型之一。

当前,我正在使用一些模糊逻辑在“事件”表上运行更新脚本,以将其ID字段设置为匹配的“情节”。为此,它会检查情节开始和结束之间的事件日期,它们都具有相同的Type,以及其他一些链接,例如相同的User等。

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

我一直在阅读,虽然循环效率不是很高,所以想知道是否有办法将此嵌套循环重写为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

1 个答案:

答案 0 :(得分:2)

我不知道,如果我完全掌握了逻辑,但这可能有助于您正常运行:

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的精髓...我将结果绑定到集合中(您可以使用SELECT *而不是显式列列表将其可见。但这大概是-不你是什​​么意思...