【问题标题】:SQL Server cursor replacement (Update from another question)SQL Server 游标替换(来自另一个问题的更新)
【发布时间】:2019-11-07 13:11:24
【问题描述】:

这是我在这里提出的一个后续问题: SQL Server better way to iterate through millions of rows。我在那个帖子中被告知要提交一个新问题,因为我真正想要的与我提出的方式不同。

我最初在我的 OP 中并没有明确说明此过程将定期运行。所以这不会运行一次,它将每周运行一次,并且该过程需要能够检查计数器表和临时表。

我正在处理 SAP Timesheet 数据,因此有数百万行。我要做的是从 SAP 表中选择数据并将其插入 MS SQL Server 上的表中。

所以我想插入原始记录(如果它不存在的话),那么如果对原始记录进行更新,它的形式是带有refcounter 的新 SAP 记录,我想找到我表中的原始记录并更新它,保留原始计数器值。

所以我用游标成功完成了这项工作(我知道不是最好的),但是有数百万条记录,我想知道是否有更快的方法,因为我在游标运行的第 4 天。有没有比我下面的更好的方法:

BEGIN
    CREATE TABLE CATSDB 
        (
            [COUNTER] nvarchar(12),
            REFCOUNTER nvarchar(12),
            PERNR nvarchar(8),
            WORKDATE nvarchar(8),
            CATSHOURS decimal(7, 3),
            APDAT nvarchar(8),
            LAETM nvarchar(6),
            CATS_STATUS nvarchar(2),
            APPR_STATUS nvarchar(2)
        )   

    INSERT INTO CATSDB
            (
                [COUNTER],REFCOUNTER,PERNR,WORKDATE,CATSHOURS,APDAT,LAETM,CATS_STATUS,APPR_STATUS
            )
        VALUES
            ('000421692670',NULL,'00000071','20190114','6.00','20190204','174541','30','30'),
            ('000421692671',NULL,'00000071','20190114','3.00','20190204','174541','30','30'),
            ('000421692672',NULL,'00000071','20190115','6.00','00000000','000000','60','20'),
            ('000421692673',NULL,'00000071','20190115','3.00','00000000','000000','60','20'),
            ('000421692712','000421692672','00000071','20190115','0.00','20190115','111007','30','30'),
            ('000421692713','000421692673','00000071','20190115','0.00','20190115','111007','30','30'),
            ('000429718015',NULL,'00000072','20190313','7.00','00000000','000000','60','20'),
            ('000429718016',NULL,'00000072','20190313','1.50','20190315','164659','30','30'),
            ('000429718017',NULL,'00000072','20190313','1.00','20190315','164659','30','30'),
            ('000430154143',NULL,'00000072','20190313','2.00','00000000','000000','60','20'),
            ('000430154142','000429718015','00000072','20190313','5.00','00000000','000000','60','20'),
            ('000430154928','000430154142','00000072','20190313','4.50','20190315','164659','30','30'),
            ('000430154929','000430154143','00000072','20190313','2.50','20190315','164659','30','30'),
            ('000429774620',NULL,'00000152','20190314','1.00','00000000','000000','60','20'),
            ('000429774619',NULL,'00000152','20190314','1.00','00000000','000000','60','20'),
            ('000429802106','000429774620','00000152','20190314','2.00','00000000','000000','60','20'),
            ('000429802105','000429774619','00000152','20190314','3.00','00000000','000000','60','20'),
            ('000429840242','000429802106','00000152','20190314','4.00','20190315','143857','30','30'),
            ('000429840241','000429802105','00000152','20190314','5.00','20190315','143857','30','30')

    CREATE TABLE [TBL_COUNTER]
        (
            [COUNTER] [varchar](12) NOT NULL,
            [REFCOUNTER] [varchar](12) NULL
        )   

    CREATE TABLE TEMP
        (
            [COUNTER] [nvarchar](12) NOT NULL,
            [REFCOUNTER] [nvarchar](12) NULL,
            [PERNR] [nvarchar](8) NULL,
            [WORKDATE] [nvarchar](8) NULL,
            [CATSHOURS] [decimal](7, 3) NULL,
            [APDAT] [nvarchar](8) NULL,
            [LAETM] [nvarchar](6) NULL,
            [CATS_STATUS] [nvarchar](2) NULL,
            [APPR_STATUS] [nvarchar](2) NULL
        )       
END

BEGIN
    DECLARE     @COUNTER nvarchar(12),  
                @REFCOUNTER nvarchar(12),   
                @PERNR nvarchar(8), 
                @WORKDATE nvarchar(8),  
                @CATSHOURS decimal(7, 3),
                @APDAT nvarchar(8),
                @LAETM nvarchar(6),
                @CATS_STATUS nvarchar(2),
                @APPR_STATUS nvarchar(2)

    DECLARE @orig_counter nvarchar(12)
END

--The below cursor will include a where statement where it selects data
--where the workdate or apdat fall within a data range.  Since I am
--running this weekly, the to and from dates will be the last work week.
BEGIN
    DECLARE curs CURSOR FOR
        SELECT 
                [COUNTER],
                REFCOUNTER,
                PERNR,
                WORKDATE,
                CATSHOURS,
                APDAT,
                LAETM,
                CATS_STATUS,
                APPR_STATUS
        FROM 
                CATSDB
END

BEGIN
    OPEN curs
END

BEGIN
    FETCH NEXT FROM curs INTO
        @COUNTER,
        @REFCOUNTER,
        @PERNR,
        @WORKDATE,
        @CATSHOURS,
        @APDAT,
        @LAETM,
        @CATS_STATUS,
        @APPR_STATUS
END

BEGIN
    WHILE @@FETCH_STATUS = 0
        BEGIN
            BEGIN
                IF NOT EXISTS (SELECT * FROM TBL_COUNTER WHERE [COUNTER] = @COUNTER)
                    BEGIN
                        INSERT INTO TBL_COUNTER
                                ([COUNTER]
                                ,REFCOUNTER)
                            VALUES
                                (@COUNTER
                                ,@REFCOUNTER)
                    END
            END
            BEGIN
                IF NOT EXISTS (SELECT * FROM TEMP WHERE [COUNTER] = @COUNTER)
                    BEGIN
                            --If REFCOUNTER is populated, get the original COUNTER value, then update that row with the new values. Otherwise insert new record
                            IF @REFCOUNTER <> '' AND @REFCOUNTER IS NOT NULL
                                BEGIN
                                    BEGIN
                                        WITH n([COUNTER], REFCOUNTER) AS 
                                            (
                                                SELECT 
                                                        cnt.[COUNTER], 
                                                        cnt.REFCOUNTER 
                                                FROM 
                                                        TBL_COUNTER cnt
                                                WHERE 
                                                        cnt.[COUNTER] = @REFCOUNTER
                                            UNION ALL
                                                SELECT 
                                                        nplus1.[COUNTER], 
                                                        nplus1.REFCOUNTER 
                                                FROM 
                                                        TBL_COUNTER as nplus1, 
                                                        n
                                                WHERE 
                                                        n.[COUNTER] = nplus1.REFCOUNTER
                                            )
                                        SELECT @orig_counter = [COUNTER] FROM n WHERE REFCOUNTER = '' OR REFCOUNTER IS NULL
                                    END
                                    BEGIN
                                        UPDATE TEMP
                                           SET 
                                               [REFCOUNTER] = @REFCOUNTER
                                              ,[PERNR] = @PERNR 
                                              ,[WORKDATE] = @WORKDATE                                               
                                              ,[CATSHOURS] = @CATSHOURS                                                                                    
                                              ,[APDAT] = @APDAT                                        
                                              ,[LAETM] = @LAETM
                                              ,[CATS_STATUS] = @CATS_STATUS
                                              ,[APPR_STATUS] = @APPR_STATUS                                        
                                            WHERE [COUNTER] = @orig_counter
                                    END
                                END
                            ELSE
                                BEGIN
                                    INSERT INTO TEMP
                                               ([COUNTER]
                                               ,[REFCOUNTER]                                               
                                               ,[PERNR]                                               
                                               ,[WORKDATE]                                               
                                               ,[CATSHOURS]                                             
                                               ,[APDAT]                                              
                                               ,[LAETM]
                                               ,[CATS_STATUS]                                               
                                               ,[APPR_STATUS])                                              
                                         VALUES
                                               (@COUNTER
                                               ,@REFCOUNTER                                              
                                               ,@PERNR                                               
                                               ,@WORKDATE                                             
                                               ,@CATSHOURS                                               
                                               ,@APDAT                                               
                                               ,@LAETM                                               
                                               ,@CATS_STATUS                                               
                                               ,@APPR_STATUS)                                               
                                END
                    END

            FETCH NEXT FROM curs INTO
                @COUNTER,
                @REFCOUNTER,
                @PERNR,
                @WORKDATE,
                @CATSHOURS,
                @APDAT,
                @LAETM,
                @CATS_STATUS,
                @APPR_STATUS
        END
    END
END

BEGIN
    CLOSE curs
    DEALLOCATE curs
END

我缩短了它并为大家创建了表格,以便大家能够看到正在发生的事情。预期结果是

+--------------+--------------+----------+----------+-----------+----------+--------+-------------+-------------+
|   COUNTER    |  REFCOUNTER  |  PERNR   | WORKDATE | CATSHOURS |  APDAT   | LAETM  | CATS_STATUS | APPR_STATUS |
+--------------+--------------+----------+----------+-----------+----------+--------+-------------+-------------+
| 000421692670 | NULL         | 00000071 | 20190114 |      6.00 | 20190204 | 174541 |          30 |          30 |
| 000421692671 | NULL         | 00000071 | 20190114 |      3.00 | 20190204 | 174541 |          30 |          30 |
| 000421692672 | 000421692672 | 00000071 | 20190115 |      0.00 | 20190115 | 111007 |          30 |          30 |
| 000421692673 | 000421692673 | 00000071 | 20190115 |      0.00 | 20190115 | 111007 |          30 |          30 |
| 000429718015 | 000430154142 | 00000072 | 20190313 |      4.50 | 20190315 | 164659 |          30 |          30 |
| 000429718016 | NULL         | 00000072 | 20190313 |      1.50 | 20190315 | 164659 |          30 |          30 |
| 000429718017 | NULL         | 00000072 | 20190313 |       1.0 | 20190315 | 164659 |          30 |          30 |
| 000430154143 | 000430154143 | 00000072 | 20190313 |      2.50 | 20190315 | 164659 |          30 |          30 |
| 000429774620 | 000429774620 | 00000152 | 20190314 |      2.00 | 00000000 | 000000 |          60 |          20 |
| 000429774619 | 000429802105 | 00000152 | 20190314 |      5.00 | 20190315 | 143857 |          30 |          30 |
+--------------+--------------+----------+----------+-----------+----------+--------+-------------+-------------+

我将从原始源中提取数据以获取新记录和上次运行时更改的记录。所以我不会每周都有完整的链条。需要有一种方法可以在没有完整数据集的情况下返回原始计数器值,这就是我有计数器表的原因。我很抱歉没有更清楚。

我希望我想要做的事情是明确的。正如我所说,我让它与光标一起工作,但它太慢了。处理一周的数据工作至少需要 24 小时。

【问题讨论】:

  • 您是否尝试过查看递归 ctes?这似乎是您在这里所需要的。不过,我很难破译这里的逻辑。我的头随着所有临时交换的东西而游动。你能解释一下你试图完成的逻辑吗?
  • @SeanLange 我知道这有点令人困惑。因此,我每周都会从 HANA 数据库中选择数据并将其合并到 CATSDB 表中。然后我想在 CATSDB 表中获取新数据并更新另一个表(在示例中 temp 是一个糟糕的选择) 假设 temp 表是数据的最终表。这是否有助于澄清它。

标签: sql sql-server sap database-cursor


【解决方案1】:

您的代码相当长且复杂,我不确定我是否完全理解它的作用。但是,我可以向您展示如何以更“关系数据库”的方式来思考这个问题。

您的游标遍历数百万条记录,并为每条记录执行一个相当简单的if 语句,如果结果为真,它就会执行一些操作。

在数据库中,您可以将游标中单行的if 替换为针对整个数据库的where。只要您的 where 子句命中索引,它应该非常快。

比如你的第一步是make sure that the current @counter is present in the table COUNTER:

IF NOT EXISTS (SELECT * FROM TBL_COUNTER WHERE [COUNTER] = @COUNTER)
BEGIN
   INSERT INTO TBL_COUNTER
   ([COUNTER]
   ,REFCOUNTER)
   VALUES
   (@COUNTER
   ,@REFCOUNTER)
END

此代码针对光标中的每一行执行(数百万次)。 相反,在光标之外,您只需运行一次以下查询:

INSERT INTO TBL_COUNTER
   ([COUNTER]
   ,REFCOUNTER)
SELECT [COUNTER]
       REFCOUNTER
FROM CATSDB
WHERE COUNTER NOT IN 
  (SELECT COUNTER FROM TBL_COUNTER)

这用where 子句对整个数据集逐一检查每一行。

您可以对下一条语句采取类似的方法 - 相同的原则适用。

如果不存在(SELECT * FROM TEMP WHERE [COUNTER] = @COUNTER) 开始 --如果填充了 REFCOUNTER,则获取原始 COUNTER 值,然后使用新值更新该行。否则插入新记录

   IF @REFCOUNTER <> '' AND @REFCOUNTER IS NOT NULL
   ....
   ELSE
   BEGIN
    INSERT INTO TEMP
      ([COUNTER]
       ,[REFCOUNTER]                                               
       ,[PERNR]                                               
       ,[WORKDATE]                                               
       ,[CATSHOURS]                                             
       ,[APDAT]                                              
       ,[LAETM]
       ,[CATS_STATUS]                                               
       ,[APPR_STATUS])                                              
       VALUES
         (@COUNTER
         ,@REFCOUNTER                                              
         ,@PERNR                                               
         ,@WORKDATE                                             
         ,@CATSHOURS                                               
         ,@APDAT                                               
         ,@LAETM                                               
         ,@CATS_STATUS                                               
         ,@APPR_STATUS)                                               
    END

你可以重写为:

INSERT INTO TEMP
          ([COUNTER]
           ,[REFCOUNTER]                                               
           ,[PERNR]                                               
           ,[WORKDATE]                                               
           ,[CATSHOURS]                                             
           ,[APDAT]                                              
           ,[LAETM]
           ,[CATS_STATUS]                                               
           ,[APPR_STATUS])   
    SELECT 
 [COUNTER],
 REFCOUNTER,
 PERNR,
 WORKDATE,
 CATSHOURS,
 APDAT,
 LAETM,
 CATS_STATUS,
 APPR_STATUS
 FROM 
 CATSDB
  --- We want the opposite of IF @REFCOUNTER <> '' AND @REFCOUNTER IS NOT NULL
  WHERE @REFCOUNTER = '' OR @REFCOUNTER IS NULL

这很可能为您提供了足够的速度提升(当然,您必须调整光标以排除这些情况)。

最后是带有 CTE 的子句。很晚了,我可能错过了什么,但评论说:

 --If REFCOUNTER is populated, get the original COUNTER value, then update that row with the new values. Otherwise insert new record

认为您可以通过以下方式实现:

 UPDATE TEMP
 SET 
 [REFCOUNTER] = c.REFCOUNTER
 ,[PERNR] = c.PERNR 
 ,[WORKDATE] = c.WORKDATE                                               
 ,[CATSHOURS] = c.CATSHOURS                                                                                    
 ,[APDAT] = c.APDAT                                        
 ,[LAETM] = c.LAETM
 ,[CATS_STATUS] = c.CATS_STATUS
 ,[APPR_STATUS] = c.APPR_STATUS                                        
 from TEMP t, 
 CATSDB c
 WHERE t.[COUNTER] = C.REFCOUNTER

我想我错过了 CTE 中 UNION 的第二部分正在实现的目标。

【讨论】:

  • 我知道你在做什么。让我玩一下它。谢谢
  • 好的,这部分适用于它,但对于第二部分,它不会起作用,因为要回到原始计数器值。
  • 您能否在代码中插入 cmets 以显示“后退”在哪里?
  • 它就在您插入计数器值的部分下方,您说我应该更改。它是一个以“WITH n([COUNTER], REFCOUNTER) AS”开头的 CTE
  • 所以我在看第二部分而不是光标,使用带有合并语句的 CTE“Walk Back”。唯一的问题是我无法弄清楚如何在 CTE 中更改“cnt.COUNTER = @REFCOUNTER”,因为我不再有光标中的@REFCOUNTER。
猜你喜欢
  • 2019-05-31
  • 1970-01-01
  • 2021-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-17
  • 1970-01-01
相关资源
最近更新 更多