我正在维护一些在表上有触发器以增加列的代码。然后,该列由第三方应用程序 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
答案 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