Sql server SQL Server是处理多行插入的触发器

Sql server SQL Server是处理多行插入的触发器,sql-server,tsql,Sql Server,Tsql,我正在维护一些代码,这些代码在表上有一个触发器来增加一列。该列随后由第三方应用程序a使用。假设该表名为test,包含两列num1和num2。触发器在测试中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 DECLA

我正在维护一些代码,这些代码在表上有一个触发器来增加一列。该列随后由第三方应用程序a使用。假设该表名为test,包含两列num1和num2。触发器在测试中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(sigh)有时会在此表上执行多个插入,类似于这样,但不完全是这样:

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
在此处查看基于集合的方法:

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

有关更多信息,请参见此处:


需要重写触发器以处理多行插入。永远不要使用变量编写这样的触发器。所有触发器都必须考虑到有一天有人会做多行插入/更新/删除。< /P>
您也不应该在触发器中以这种方式递增列,如果您需要递增的列号,为什么不使用标识列?

您只需在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;

正如前面所指出的,游标可能会有问题,最好在触发的表与插入和删除的表之间使用联接

下面是一个如何做到这一点的示例:

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
ALTER TRIGGER[dbo]。[TR\u将\u uuid\u分配给\u某个\u varchar\u列]
在[dbo].[myTable]上
插入后,更新
作为
开始
/********************************************
方法
*在这种情况下,我们只关心更新和插入
*对于“插入”表中的每一行,为空白指定一个新的uuid
*********************************************/
更新t
将uuid设置为\u varchar=lower(newid())
来自MyT表
--为行更新和新行插入填充插入表
在i.myPrimaryKey=t.myPrimaryKey上插入的内部联接i
--为行更新和行删除填充已删除表
d.myPrimaryKey=i.myPrimaryKey上的左联接已删除d
--仅更新适用于触发器的行的触发表,并且
--当前为id存储空白或null的条件
哪里
合并(i.uuid_as_varchar,'')=
--您还可以检查要替换的行,并将其作为条件的一部分使用,例如。
或(合并(i.uuid_as_varchar,)合并(d.uuid_as_varchar,);
结束

我也不喜欢列递增的方式,我甚至在Oracle中更改了触发器以使用序列。至于身份,我的第一个想法是使用ALTALTABLE把它变成一个身份,但不幸的是,这是不可能的,其他的方式需要比我足够勇敢去冒险的更多的改变。虽然上面并不完全正确,而且有一些错误,但它确实帮助了我,给了我一个工作的基础。在我的问题中,我已经添加了上述内容的更新版本。很抱歉,光标不是最好的方法。@Cocowalla您可以在插入的表和基础数据表之间执行
JOIN
。要获取更新的递增值,请使用
Row_Number()
(SQL 2005及以上版本)或插入到具有标识列的临时表中,或插入到具有一百万行左右的数字表中(仅向数据库添加13兆字节)。事实上,比所有这一切更好的是用已经给出递增值的标识列替换该列!在触发器中放置光标是一个非常糟糕的主意;游标是出了名的慢和内存消耗,触发器中的任何东西都应该尽可能的瘦和快。我强烈建议使用另一种方法(根据我个人对此类构造的痛苦经历),如果@NEWNUM2为null,则不能用
替换
检查合并(MAX(num2),0)