SQL Server用于处理多行插入的触发器

时间:2010-02-01 18:01:27

标签: sql-server tsql

我正在维护一些在表上有触发器以增加列的代码。然后,该列由第三方应用程序 A 使用。假设该表名为 test ,其中包含两列 num1 num2 。触发器在 test 中的 num1 的每个插入上运行。以下是触发器:

USE [db1]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[TEST_MYTRIG] ON [dbo].[test]
FOR INSERT AS
begin
SET NOCOUNT ON
DECLARE @PROC_NEWNUM1 VARCHAR (10)
DECLARE @NEWNUM2 numeric(20)

SELECT @PROC_NEWNUM1 = num1 FROM INSERTED
select @NEWNUM2 = MAX(num2) from TEST
if @NEWNUM2 is null
Begin
set  @NEWNUM2  = 0
end
set @NEWNUM2 = @NEWNUM2 + 1
UPDATE TEST SET num2 = @NEWNUM2 WHERE num1 = @PROC_NEWNUM1
SET NOCOUNT OFF
End

这在简单的基于行的插入中工作正常,但还有另一个第三方应用程序 B (叹气),有时会在此表上执行多次插入,但不完全相同:

INSERT INTO [db1].[dbo].[test]
           ([num1])

   Select db1.dbo.test.num1 from [db1].[dbo].[test]
GO

这会导致触发器表现不正常......

现在我无法访问应用 A B 的来源,只能控制数据库和触发器。是否可以使用触发器执行任何操作,以便在多次插入的情况下对 num2 执行的更新是正确的?

解决方案:

以下是基于affan代码的解决方案:

 DECLARE @PROC_NEWNUM1 VARCHAR (10)
 DECLARE @NEWNUM2 numeric(20)
 DECLARE my_Cursor CURSOR FAST_FORWARD FOR SELECT num1 FROM INSERTED;

 OPEN my_Cursor 
 FETCH NEXT FROM my_Cursor into @PROC_NEWNUM1

 WHILE @@FETCH_STATUS = 0 
 BEGIN 

 select @NEWNUM2 = MAX(num2) from TEST
 if @NEWNUM2 is null
 Begin
    set  @NEWNUM2  = 0
 End
 set @NEWNUM2 = @NEWNUM2 + 1
 UPDATE TEST SET num2 = @NEWNUM2  WHERE num1 = @PROC_NEWNUM1
 FETCH NEXT FROM my_Cursor into @PROC_NEWNUM1  
 END

CLOSE my_Cursor
DEALLOCATE my_Cursor

点击此处查看基于集合的方法: SQL Server - Rewrite trigger to avoid cursor based approach

5 个答案:

答案 0 :(得分:5)

你只需要在INSERTED上打开一个游标并为@ PROC_NEWNUM1重复它并将你剩下的代码循环。 e.g

 DECLARE @PROC_NEWNUM1 VARCHAR (10)
 DECLARE @NEWNUM2 numeric(20)
 DECLARE my_Cursor CURSOR FOR SELECT num1 FROM INSERTED; 
 OPEN my_Cursor; 

 FETCH NEXT FROM @PROC_NEWNUM1; 


 WHILE @@FETCH_STATUS = 0 
 BEGIN FETCH NEXT FROM my_Cursor 
 select @NEWNUM2 = MAX(num2) from TEST
 if @NEWNUM2 is null
 Begin
  set  @NEWNUM2  = 0
 end
 set @NEWNUM2 = @NEWNUM2 + 1
 UPDATE TEST SET num2 = @NEWNUM2 WHERE num1 = @PROC_NEWNUM1

 END; 

CLOSE my_Cursor; DEALLOCATE my_Cursor;

答案 1 :(得分:3)

在触发器中查看inserted伪表,因为在这些操作期间它将包含多行。无论如何,您应始终在触发器中处理多行。

有关详细信息,请参阅此处:

How to test for multiple row actions in a SQL Server trigger?

答案 2 :(得分:2)

需要重写触发器以处理多行插入。永远不要使用变量编写类似的触发器。所有触发器都必须考虑到有一天有人会进行多行插入/更新/删除。

你不应该在触发器中以这种方式递增列,如果你需要递增的列号,为什么你不使用标识列?

答案 3 :(得分:0)

INSERT INTO dbo.media_queue (table_name, table_id, media_id, tunnel, sub_tunnel, event)
        SELECT
            'media_info'
            ,i.id
            ,i.session_id
            ,i.tunnel
            ,i.sub_tunnel
            ,NULL
        FROM INSERTED i;
END

答案 4 :(得分:0)

正如已经指出的,游标可能有问题,最好在触发表与插入和删除表之间使用连接。

这是一个如何做到这一点的例子:

ALTER TRIGGER [dbo].[TR_assign_uuid_to_some_varchar_column]
ON [dbo].[myTable]
AFTER INSERT, UPDATE
AS
BEGIN
/********************************************
 APPROACH
  * we only care about update and insert in this case
  * for every row in the "inserted" table, assign a new uuid for blanks
*********************************************/

       update t 
              set uuid_as_varchar = lower(newid()) 
       from myTable t 

        -- inserted table is populated for row updates and new row inserts
       inner join inserted i on i.myPrimaryKey = t.myPrimarykey

        -- deleted table is populated for row updates and row deletes
       left join deleted d on d.myPrimaryKey = i.myPrimaryKey

        -- only update the triggered table for rows applicable to the trigger and
        -- the condition of currently having a blank or null stored for the id
       where 
           coalesce(i.uuid_as_varchar,'') = '' 

           -- you can also check the row being replaced as use that as part of the conditions, e.g. 
           or ( coalesce(i.uuid_as_varchar,'') <> coalesce(d.uuid_as_varchar,'') );
       
END