Замена курсора SQL Server (обновление из другого вопроса) - PullRequest
1 голос
/ 07 ноября 2019

Это своего рода дополнительный вопрос, который я задал здесь: SQL Server - лучший способ перебирать миллионы строк . В этой ветке мне сказали отправить новый вопрос, потому что то, что я действительно искал, отличалось от того, как я его задавал.

Изначально в моем ОП не было ясно, что эта процедура будет выполняться периодически. Таким образом, это не будет выполнено один раз, это будет выполняться еженедельно, и процедура должна иметь возможность проверять таблицу счетчиков и временную таблицу.

Я работаю с данными расписания SAP, поэтому есть миллионыстрок. Я пытаюсь выбрать данные из таблицы SAP и вставить их в таблицу на MS SQL Server.

Итак, я хочу вставить исходную запись (если она еще не существует), а затем, если произойдет обновление исходной записи, которая имеет форму новой записи SAP с refcounter, я хочунайти исходную запись в моей таблице и обновить ее, сохранив исходное значение счетчика.

Итак, я успешно сделал это с помощью курсора (я знаю, что не самый лучший), но с миллионами записей мне интересно, есть ли более быстрый способ, потому что я на 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 часов.

1 Ответ

1 голос
/ 07 ноября 2019

Ваш код довольно длинный и сложный, и я не уверен, что полностью понимаю, что он делает. Тем не менее, я могу показать вам способ думать об этом более «реляционной базой данных».

Ваш курсор перебирает миллионы записей, и для каждой записи выполняется довольно простойif и, где это возвращает true, он что-то делает.

В базе данных вы можете заменить 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 для всего набора данных.

Вы можете использовать аналогичный подход со следующим утверждением - применяется тот же принцип.

ЕСЛИ НЕ СУЩЕСТВУЕТ (ВЫБРАТЬ * ИЗ ТЕМПЕРАТУРЫ ГДЕ [СЧЕТЧИК] = @COUNTER) НАЧАЛО - Если заполнено поле REFCOUNTER, получить исходное значение СЧЕТЧИКА, а затем обновить эту строку новыми значениями. В противном случае вставьте новую запись

   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.

...