Избегайте курсоров для обновления многих записей с помощью триггера - PullRequest
0 голосов
/ 10 марта 2012

У меня есть таблица с чуть более 1 миллиона записей. Первоначально моя таблица пуста, но я использую BULK INSERT для добавления этих записей в базу данных. У меня есть AFTER INSERT триггер, который я использую для обновления поля initialValue в этой таблице. Значение initialValue является вычислением конкретных переменных в другой таблице (my_data_db), суммируемых по всем записям. В качестве имен столбцов в таблице my_data_db.

я использую значения столбцов v1, v2 и т. Д.

Я знаю, что это плохая практика, но единственный способ, которым я в настоящее время знаю, как сделать это вычисление для каждой отдельной строки, это использовать курсор. Очевидно, что с миллионами записей это действительно очень медленно.

Вот пример таблицы, на которой у меня есть триггер:

TABLE: test3
rowID    v1      v2      v3       combo   initialValue
1        NULL    M170_3  M170_4   C       NULL
2        M170_2  M170_3  M170_4   ABC     NULL
3        M170_2  M170_3  NULL     AB      NULL
...

Мой триггер:

CREATE TRIGGER [dbo].[trig_UPDATE_test3] 
ON [dbo].[test3] 

AFTER INSERT 
AS 
Begin 

DECLARE @sql VARCHAR(MAX) 
DECLARE @v1 VARCHAR(20) 
DECLARE @v2 VARCHAR(20) 
DECLARE @v3 VARCHAR(20) 
DECLARE @combo VARCHAR(30) 
DECLARE mycursor CURSOR FOR     
    SELECT v1, v2, v3, combo 
    FROM Inserted 

    OPEN mycursor 
    FETCH NEXT FROM mycursor INTO @v1, @v2, @v3, @combo 
    WHILE @@FETCH_STATUS = 0 
    BEGIN 
        IF( @v1 IS NOT NULL OR @v2 IS NOT NULL OR @v3 IS NOT NULL) 
        BEGIN 
            SET @sql = 'DECLARE @finalValue DECIMAL(18, 15);' 
            SET @sql = @sql + 'UPDATE test3 Set initialValue = (SELECT CAST(SUM(' 

            IF(@v1 IS NOT NULL) 
            BEGIN 
                SET @sql = @sql + 'CASE ' + @v1 + ' WHEN 1 THEN 1 WHEN 2 THEN .75 WHEN 3 THEN .25 WHEN 4 THEN .1 END * ' 
            END 

            IF(@v2 IS NOT NULL) 
            BEGIN 
                SET @sql = @sql + 'CASE ' + @v2 + ' WHEN 1 THEN 1 WHEN 2 THEN .75 WHEN 3 THEN .25 WHEN 4 THEN .1 END * ' 
            END 

            IF(@v3 IS NOT NULL) 
            BEGIN 
                SET @sql = @sql + 'CASE ' + @v3 + ' WHEN 1 THEN 1 WHEN 2 THEN .75 WHEN 3 THEN .25 WHEN 4 THEN .1 END * ' 
            END 

            SET @sql = @sql + 'RESP_WEIGHT / 4898.947426) AS FLOAT) FROM dbo.my_data_db) WHERE combo = ''' + @combo + ''';' 

            EXECUTE(@sql) 

        END 
        FETCH NEXT FROM mycursor INTO @v1, @v2, @v3, @combo 
    END 
    CLOSE mycursor 
    DEALLOCATE mycursor 
End

После запуска триггера моя таблица test3 будет выглядеть примерно так:

TABLE: test3
rowID    v1      v2      v3       combo   initialValue
1        NULL    M170_3  M170_4   C       0.138529
2        M170_2  M170_3  M170_4   ABC     0.683190
3        M170_2  M170_3  NULL     AB      0.014923
...

Есть ли способ, которым я могу сделать это без использования курсора?

1 Ответ

2 голосов
/ 10 марта 2012

Да.Вы можете сделать это с помощью одного оператора UPDATE - FROM после вашего BULK INSERT:

UPDATE t3 SET initialValue = t.mySum
FROM test3 t3
CROSS APPLY (SELECT SUM(
       CASE t3.v1 WHEN 'M170_2' THEN CASE d.M170_2 
             WHEN 1 THEN 1 
             WHEN 2 THEN .75 
             WHEN 3 THEN .25 
             WHEN 4 THEN .1 
       ELSE 1 END END * 
       CASE t3.v1 WHEN 'M170_3' THEN CASE d.M170_3 
             WHEN 1 THEN 1 
             WHEN 2 THEN .75 
             WHEN 3 THEN .25 
             WHEN 4 THEN .1 
       ELSE 1 END END * 
       CASE t3.v1 WHEN 'M170_4' THEN CASE d.M170_4 
             WHEN 1 THEN 1 
             WHEN 2 THEN .75 
             WHEN 3 THEN .25 
             WHEN 4 THEN .1 
       ELSE 1 END END * 
       d.RESP_WEIGHT / 4898.947426) as mySum 
       FROM my_data_db d WHERE d.combo = t3.combo) t
WHERE t3.v1 IS NOT NULL OR t3.v2 IS NOT NULL OR t3.v3 IS NOT NULL

Чтобы сделать это из своего триггера, вам нужно внести небольшие изменения:

UPDATE t3 SET initialValue = t.mySum
FROM test3 t3
-- Here's the change
INNER JOIN inserted i ON i.RowID = t3.RowID
CROSS APPLY (SELECT SUM(
       CASE t3.v1 WHEN 'M170_2' THEN CASE d.M170_2 
             WHEN 1 THEN 1 
             WHEN 2 THEN .75 
             WHEN 3 THEN .25 
             WHEN 4 THEN .1 
       ELSE 1 END END * 
       CASE t3.v1 WHEN 'M170_3' THEN CASE d.M170_3 
             WHEN 1 THEN 1 
             WHEN 2 THEN .75 
             WHEN 3 THEN .25 
             WHEN 4 THEN .1 
       ELSE 1 END END * 
       CASE t3.v1 WHEN 'M170_4' THEN CASE d.M170_4 
             WHEN 1 THEN 1 
             WHEN 2 THEN .75 
             WHEN 3 THEN .25 
             WHEN 4 THEN .1 
       ELSE 1 END END * 
       d.RESP_WEIGHT / 4898.947426) as mySum 
       FROM my_data_db d WHERE d.combo = t3.combo) t
WHERE t3.v1 IS NOT NULL OR t3.v2 IS NOT NULL OR t3.v3 IS NOT NULL
...