如何摆脱游标?

How to get rid of cursors?

我写了一个 SQL 使用游标的查询,我意识到它在服务器上占用了太多资源而且速度也很慢。

所以,这是 table:

Source

我需要计算 Status = Assigned 和 Status = In Progress 之间的时间差,其中 Assigned_group 或 prev_assigned_group 类似于“%Hotline%”。

我也有一个序列,所以我 select 区分事件 ID 并按 Incident_Sequence 升序排列行,因为我必须从最小的序列号开始。

到目前为止一切顺利。

我从第一行开始,然后跳到下一行,直到我发现状态为 "In Progress"。找到状态后,我计算第一个分配的行与状态在其中的行之间的差异发现有进展。

所以最大的问题是:我可以在没有游标的情况下执行此操作吗?如果是,那怎么办?

SET NOCOUNT ON
DECLARE @day DATE

SET @day = '20160606'

CREATE TABLE #result(
[Assigned_Group] NVARCHAR(100),
[ProgressTime] INTEGER,
[Tickets] INTEGER,
[Avarage] FLOAT
)
INSERT INTO #result(Assigned_Group,ProgressTime,Tickets,Avarage)

SELECT DISTINCT Assigned_Group,0,0,0.0

       FROM [grs_dwh].[smt].[Aht]

   WHERE (Assigned_Group like 'CI-Hotline%' OR Prev_assigned_Group like 'CI-Hotline%')
      and CONVERT(DATE,Last_Modified_Date, 104) = @day

-- raw
SELECT [Incident_Sequence]
      ,[Incident_Id]
      ,[Assigned_Group]
      ,[Prev_assigned_Group] 
      ,[Status]
      ,[Last_Modified_Date]    
      ,[Service]

         INTO #rawData

  FROM [grs_dwh].[smt].[Aht]

WHERE (Assigned_Group like 'CI-Hotline%' OR Prev_assigned_Group like 'CI-Hotline%')
  and CONVERT(DATE,Last_Modified_Date, 104) = @day

  ORDER BY Incident_Sequence asc

  --CREATE TABLE #orderList(


  --)
  SELECT DISTINCT[Incident_id] INTO #incidentList FROM #rawData

  DECLARE cur0 CURSOR FOR SELECT incident_Id FROM #incidentList
  DECLARE @currentIncident NVARCHAR(15)

  OPEN cur0 

  FETCH next from cur0 INTO @currentIncident

  WHILE @@FETCH_STATUS = 0

       BEGIN
 -- PRINT @currentIncident
       SELECT * INTO #tmpTable FROM #rawData WHERE Incident_Id = @currentIncident ORDER BY Incident_Sequence

       DECLARE cur1 CURSOR FOR SELECT * FROM #tmpTable ORDER BY Incident_Sequence ASC

       DECLARE @incident_Sequence INTEGER
    DECLARE @incident_Id NVARCHAR(50)
    DECLARE @assigned_Group NVARCHAR(100)
       DECLARE @previous_Assiggned NVARCHAR(100)
    DECLARE @status NVARCHAR(50)
    DECLARE @last_Modified_Date DATETIME
    DECLARE @service NVARCHAR(50)

       DECLARE @progressFound BIT
       DECLARE @startProgressDate DATETIME
       DECLARE @ticketProgressTime INTEGER
       DECLARE @resultGroup NVARCHAR(100)
       SET @progressFound = 0

       OPEN cur1
       FETCH next from cur1

    INTO @incident_Sequence, @incident_Id, @assigned_Group, @previous_Assiggned, @status, @last_Modified_Date, @service

       WHILE @@FETCH_STATUS = 0

             BEGIN

                    IF @progressFound = 0 AND @status <> 'In Progress'
                    BEGIN

                    FETCH next from cur1 INTO @incident_Sequence, @incident_Id, @assigned_Group, @previous_Assiggned, @status, @last_Modified_Date, @service
                    CONTINUE
                    END

                    IF @progressFound = 0
                    BEGIN
                    SET @startProgressDate = @last_Modified_Date
                    SET @resultGroup = @assigned_Group
                    SET @progressFound = 1
                    FETCH next from cur1 INTO @incident_Sequence, @incident_Id, @assigned_Group, @previous_Assiggned, @status, @last_Modified_Date, @service
                    CONTINUE
                    END
                    ELSE
                    BEGIN
                           SET @ticketProgressTime = DATEDIFF(SECOND,  @startProgressDate,  @last_Modified_Date)


                           UPDATE #result SET ProgressTime = ProgressTime + @ticketProgressTime, Tickets = Tickets+1 WHERE Assigned_Group = @resultGroup
                           SET @ticketProgressTime = 0
                           SET @progressFound = 0
                    END
             FETCH next from cur1
             INTO @incident_Sequence, @incident_Id, @assigned_Group, @previous_Assiggned, @status, @last_Modified_Date, @service
             END
             CLOSE cur1
             DEALLOCATE cur1
             --IF @incident_Id = 'INC000010047798'
             --SELECT * FROM #tmpTable ORDER BY Incident_Sequence ASC
             DROP TABLE #tmpTable
             FETCH next from cur0 INTO @currentIncident
       END
  CLOSE cur0
  DEALLOCATE cur0
  SET NUMERIC_ROUNDABORT OFF
  UPDATE #result SET Avarage = CAST(ProgressTime AS float) / CASE WHEN Tickets = 0 THEN 1 ELSE CAST(Tickets AS float) END
   SELECT * FROM #result
   ORDER BY 1 asc
   DROP TABLE #result
   DROP TABLE #rawData
   DROP TABLE #incidentList

您可以通过WHILE 重写Cursor(但并非在所有情况下都推荐)。为了加快光标速度,您可以将光标定义为 FAST_FORWARD 而不是摆脱光标。

DECLARE cur1 CURSOR FAST_FORWARD

阅读更多有关通过 WHILE HERE

重写 Cursor 的信息

您可以使用 ROW_NUMBER 函数来跟踪序列并进行自连接以计算这些值:

;WITH   Data    AS
(       -- Sample data (http://i.stack.imgur.com/TfzL7.png)
        SELECT  Id, Incident_Id Incident, Incident_Sequence Sequence,
                Prev_Assigned_Group Previous, Assigned_Group GroupName,
                Status, CAST(Last_Modified_Date AS DATETIME) Modified,
                ROW_NUMBER() OVER (PARTITION BY Incident_Id ORDER BY Id) RowNumber  -- Start over the count for every Incident_Id
        FROM    (   VALUES
                    (164293, 05, 'INC000010047798', 'Eastern Europe1'   , 'CI-Hotline North America', 'Assigned'   , '2016-06-04 12:28:46'),
                    (171241, 07, 'INC000010047798', 'CI-Hotline'        , 'Eastern Europe1'         , 'Assigned'   , '2016-06-06 06:42:16'),
                    (171919, 09, 'INC000010047798', 'CI-Hotline'        , 'Eastern Europe1'         , 'In Progress', '2016-06-06 06:46:19'),
                    (172138, 10, 'INC000010047798', 'CI-Hotline Romania', 'CI-Hotline'              , 'Assigned'   , '2016-06-06 06:46:35'),
                    (172483, 12, 'INC000010047798', 'CI-Hotline Romania', 'CI-Hotline'              , 'In Progress', '2016-06-06 07:11:53'),
                    (173003, 15, 'INC000010047798', 'Austria Adria3'    , 'CI-Hotline Romania'      , 'Assigned'   , '2016-06-06 07:15:36'),
                    (208011, 17, 'INC000010047798', 'Austria Adria3'    , 'CI-Hotline Romania'      , 'Resolved'   , '2016-06-10 12:14:05')
                )   AS X(Id, Incident_Sequence, Incident_Id, Assigned_Group, Prev_Assigned_Group, Status, Last_Modified_Date)
        WHERE   Assigned_Group LIKE '%HOTLINE%' OR 
                Prev_Assigned_Group LIKE '%HOTLINE%'
)
SELECT  Assigned.Incident,
        Assigned.Status + ' » ' + InProgress.Status,
        DATEDIFF(second, InProgress.Modified, Assigned.Modified) / 60.0 / 60.0 Hours
        --,Assigned.*, InProgress.*
FROM    Data Assigned
JOIN    Data InProgress
    ON  Assigned.Incident  = InProgress.Incident AND
        Assigned.RowNumber = InProgress.RowNumber + 1

下次请以文本格式发送您的示例数据;-)

编辑: 要计算不同于 'In Progress' 的任何状态与下一个 'In Progress' 状态(或最后一个可用状态)之间的时间差,使用此代码:

;WITH   Data    AS
(       -- Sample data (http://i.stack.imgur.com/TfzL7.png)
        SELECT  Id, Incident_Id Incident, Incident_Sequence Sequence,
                Prev_Assigned_Group Previous, Assigned_Group GroupName,
                Status, CAST(Last_Modified_Date AS DATETIME) Modified,
                ROW_NUMBER() OVER
                (   PARTITION BY Incident_Id
                    ORDER BY Incident_Sequence
                )   RowNumber       -- Start over the count for every Incident_Id
        FROM    [aht_data_one_day]
        WHERE   -- Incident_Id IN ('INC000010164572') AND
                (Assigned_Group LIKE '%HOTLINE%' OR Prev_Assigned_Group LIKE '%HOTLINE%')
)
SELECT      Assigned.Id, Assigned.Incident,
            CAST(Assigned.Sequence AS VARCHAR(5)) + ' » ' + 
            CAST(InProgress.Sequence AS VARCHAR(5)) Transition,
            DATEDIFF(second, Assigned.Modified, InProgress.Modified) TotalSeconds
            --, Assigned.*, InProgress.*
FROM        Data Assigned
JOIN        Data InProgress
        ON  Assigned.Status NOT IN ('In Progress') AND
            InProgress.Id = ISNULL(
                (   -- Try to locate the next 'In Progress' status
                    SELECT  MIN(Id)
                    FROM    Data
                    WHERE   Status IN ('In Progress') AND
                            Data.Incident = Assigned.Incident AND
                            Data.RowNumber > Assigned.RowNumber -- That's the trick
                ),
                (   -- If not found, get the latest status
                    SELECT  MAX(Id)
                    FROM    Data
                    WHERE   Data.Incident = Assigned.Incident
                ))
ORDER BY    Assigned.Incident, Assigned.Id

编辑: 在您的数据更新中,我保留了以前的代码并稍作改动。下面是一个大事件的逻辑验证和查询返回的数据:

Id          Incident        Transition    TotalSeconds
----------- --------------- ------------- ------------
172090      INC000010164572 10 » 13       1877
172939      INC000010164572 15 » 25       6578
173241      INC000010164572 17 » 25       4045
173597      INC000010164572 20 » 25       3616
173949      INC000010164572 23 » 25       1125
174298      INC000010164572 27 » 34       981
174468      INC000010164572 30 » 34       287
174647      INC000010164572 33 » 34       100
174773      INC000010164572 36 » 36       0

编辑: 最后一次尝试

SELECT      InProgress.Id, InProgress.Incident,
            CAST(InProgress.Sequence AS VARCHAR(5)) + ' » ' + 
            CAST(NextStatus.Sequence AS VARCHAR(5)) Transition,
            DATEDIFF(second, InProgress.Modified, NextStatus.Modified) TotalSeconds
            -- , InProgress.*, NextStatus.*
FROM        Data InProgress
JOIN        Data NextStatus
        ON  InProgress.Status IN ('In Progress') AND
            InProgress.Incident = NextStatus.Incident AND
            NextStatus.Id = ISNULL(
                (   -- Try to locate the next status different than 'In Progress'
                    SELECT  MIN(Id)
                    FROM    Data
                    WHERE   Status NOT IN ('In Progress') AND
                            Data.Incident = InProgress.Incident AND
                            Data.RowNumber > InProgress.RowNumber -- That's the trick
                ),
                (   -- If not found, get the latest status
                    SELECT  MAX(Id)
                    FROM    Data
                    WHERE   Data.Incident = InProgress.Incident
                ))
ORDER BY    InProgress.Incident, InProgress.Id

输出:

Id          Incident        Transition    TotalSeconds
----------- --------------- ------------- ------------
172564      INC000010164572 13 » 15       236
174123      INC000010164572 25 » 27       688
174689      INC000010164572 34 » 36       77

祝你好运。