【问题标题】:Avoiding cursors to update many records using a trigger避免游标使用触发器更新许多记录
【发布时间】:2012-03-09 20:26:25
【问题描述】:

我有一张包含超过 100 万条记录的表。最初,我的表是空的,但我使用BULK INSERT 将这些记录添加到数据库中。我有一个AFTER INSERT 触发器,用于更新此表中的initialValue 字段。 initialValue 的值是对另一个表 (my_data_db) 中所有记录求和的特定变量的计算。我使用v1v2 等列的值作为表my_data_db 中的列名。

我知道这是不好的做法,但我目前知道如何计算每一行的唯一方法是使用游标。显然,拥有一百万条记录,这真的非常非常慢。

下面是我有触发器的表的示例:

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
...

有没有一种方法可以在不使用光标的情况下完成此操作?

【问题讨论】:

    标签: sql-server sql-server-2008 triggers cursor


    【解决方案1】:

    是的。您可以在 BULK INSERT 之后使用单个 UPDATE - FROM 语句来做到这一点:

    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
    

    【讨论】:

    • 谢谢。在my_data_db 中,我实际上没有名为“combo”的列。我有一个名为“M170_2”的列(参见上面的示例),它是来自test3 中列的值(例如“v1”),我用它来确定要从my_data_db 中的哪些列进行计算。如果我的 2 个表完全不相关,有什么办法可以做到这一点?
    • 触发器内的光标非常非常糟糕。锁定、回滚、死锁,整个世界的恶意行为都很容易发生。不要害怕以超级创意摆脱它们。
    • @tptcat:哦!我现在明白了。我想知道你为什么要在哪里构造一个动态 sql 语句...我做了一些修改来修复它,它现在可以工作了吗?
    • @Diego:如果我不清楚,对不起。我实际上抓住了那部分并修复了它,但问题是我实际上在my_data_db 中没有名为“combo”的列,所以WHERE d.combo = t3.combo 无效。这有意义吗?
    • @Diego:另外 - 它仅在 my_data_db 中的所有三列都不为空时计算。例如,如果 M170_4 为空,我需要它仅计算 M170_2 和 M170_3。我尝试过的一切仍然只考虑所有 3 列都不为空的情况。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-20
    • 1970-01-01
    • 2013-10-31
    相关资源
    最近更新 更多