2009-04-12 172 views
126

我需要在表A上编写一个Insert,Update触发器,它将删除表B中的所有行,其中一列(称为Desc)具有值,如插入/更新的值表A的列(比如Col1)。我将如何去写它,以便我可以处理更新和插入案例。我将如何确定是否为更新或插入执行触发器。插入更新触发器如何确定是否插入或更新

回答

136

如果是MS SQL服务器...

触发器具有特殊的INSERTEDDELETED表来跟踪“之前”和“之后”的数据。因此,您可以使用类似IF EXISTS (SELECT * FROM DELETED)的内容来检测更新。更新时只有DELETED中的行,但总是有INSERTED中的行。

寻找 “插入” 在CREATE TRIGGER

编辑,2011年11月23日

评论后,这个答案只对INSERTEDUPDATED触发器。
显然,DELETE触发器不能有“在INSERTED总是排”正如我上面

10

说了很多搜索我找不到它处理的所有(3)三个条件的单个SQL服务器触发的确切实例后触发器操作INSERT,UPDATE和DELETE。我终于找到了一段文字,谈到了发生DELETE或UPDATE时,常见的DELETED表将包含这两个动作的记录。根据这些信息,我创建了一个小型的Action例程,它确定了触发器被激活的原因。当在INSERT与UPDATE触发器上同时存在通用配置和特定操作时,有时需要此类接口。在这些情况下,为UPDATE创建单独的触发器,INSERT将成为维护问题。 (即两个触发器正确更新为必要的常见数据算法修复?)

为此,我想给出以下多触发事件代码片段来处理INSERT,UPDATE,DELETE在一个触发器中的Microsoft SQL Server。

CREATE TRIGGER [dbo].[INSUPDDEL_MyDataTable] 
ON [dbo].[MyDataTable] FOR INSERT, UPDATE, DELETE 
AS 

-- SET NOCOUNT ON added to prevent extra result sets from 
-- interfering with caller queries SELECT statements. 
-- If an update/insert/delete occurs on the main table, the number of records affected 
-- should only be based on that table and not what records the triggers may/may not 
-- select. 
SET NOCOUNT ON; 

-- 
-- Variables Needed for this Trigger 
-- 
DECLARE @PACKLIST_ID varchar(15) 
DECLARE @LINE_NO smallint 
DECLARE @SHIPPED_QTY decimal(14,4) 
DECLARE @CUST_ORDER_ID varchar(15) 
-- 
-- Determine if this is an INSERT,UPDATE, or DELETE Action 
-- 
DECLARE @Action as char(1) 
DECLARE @Count as int 
SET @Action = 'I' -- Set Action to 'I'nsert by default. 
SELECT @Count = COUNT(*) FROM DELETED 
if @Count > 0 
    BEGIN 
     SET @Action = 'D' -- Set Action to 'D'eleted. 
     SELECT @Count = COUNT(*) FROM INSERTED 
     IF @Count > 0 
      SET @Action = 'U' -- Set Action to 'U'pdated. 
    END 

if @Action = 'D' 
    -- This is a DELETE Record Action 
    -- 
    BEGIN 
     SELECT @PACKLIST_ID =[PACKLIST_ID] 
        ,@LINE_NO = [LINE_NO] 
     FROM DELETED 

     DELETE [dbo].[MyDataTable] 
     WHERE [PACKLIST_ID][email protected]_ID AND [LINE_NO][email protected]_NO 
    END 
Else 
    BEGIN 
      -- 
      -- Table INSERTED is common to both the INSERT, UPDATE trigger 
      -- 
      SELECT @PACKLIST_ID =[PACKLIST_ID] 
       ,@LINE_NO = [LINE_NO] 
       ,@SHIPPED_QTY =[SHIPPED_QTY] 
       ,@CUST_ORDER_ID = [CUST_ORDER_ID] 
      FROM INSERTED 

     if @Action = 'I' 
      -- This is an Insert Record Action 
      -- 
      BEGIN 
       INSERT INTO [MyChildTable] 
        (([PACKLIST_ID] 
        ,[LINE_NO] 
        ,[STATUS] 
       VALUES 
        (@PACKLIST_ID 
        ,@LINE_NO 
        ,'New Record' 
        ) 
      END 
     else 
      -- This is an Update Record Action 
      -- 
      BEGIN 
       UPDATE [MyChildTable] 
        SET [PACKLIST_ID] = @PACKLIST_ID 
          ,[LINE_NO] = @LINE_NO 
          ,[STATUS]='Update Record' 
       WHERE [PACKLIST_ID][email protected]_ID AND [LINE_NO][email protected]_NO 
      END 
    END 
99
CREATE TRIGGER dbo.TableName_IUD 
ON dbo.TableName 
AFTER INSERT, UPDATE, DELETE 
AS 
BEGIN 
    SET NOCOUNT ON; 

    -- 
    -- Check if this is an INSERT, UPDATE or DELETE Action. 
    -- 
    DECLARE @action as char(1); 

    SET @action = 'I'; -- Set Action to Insert by default. 
    IF EXISTS(SELECT * FROM DELETED) 
    BEGIN 
     SET @action = 
      CASE 
       WHEN EXISTS(SELECT * FROM INSERTED) THEN 'U' -- Set Action to Updated. 
       ELSE 'D' -- Set Action to Deleted.  
      END 
    END 
    ELSE 
     IF NOT EXISTS(SELECT * FROM INSERTED) RETURN; -- Nothing updated or inserted. 

    ... 

    END 
+0

+1这比更有效`COUNT` – 2011-09-15 12:14:31

+0

更有效的方法是“从插入选择1”... – ganders 2014-03-28 14:33:07

+0

我喜欢写SELECT 1 FROM INSERTED,因为我认为它更清楚地表明意图,但如果这样我会被MSSQL程序员失望在这方面有所作为... – 2014-04-30 11:12:30

2

这可能是一个更快的方法:

DECLARE @action char(1) 

IF COLUMNS_UPDATED() > 0 -- insert or update 
BEGIN 
    IF EXISTS (SELECT * FROM DELETED) -- update 
     SET @action = 'U' 
    ELSE 
     SET @action = 'I' 
    END 
ELSE -- delete 
    SET @action = 'D' 
+3

这种方式对于具有大量列的表无效,因为columns_updated()返回的是一个巨大的varbinary。因此,“> 0”失败,因为0默认为一个内部存储的数字,比从columns_updated()返回的值小得多() – Graham 2012-04-05 16:07:43

3

与所提供两种解决方案的一个潜在的问题是,这取决于它们是如何写的,更新查询可以更新零个记录和一个插入查询可能会插入零个记录。在这些情况下,插入和删除记录集将为空。在很多情况下,如果Inserted和Deleted记录集都是空的,你可能只想退出触发器而不做任何事情。

2

我发现了一个小错误,否则格雷厄姆凉爽的解决方案:

应该 IF COLUMNS_UPDATED()<> 0 - 插入或更新
代替> 0 可能是因为最高位被解释作为SIGNED整数符号位...(?)。 所以总:

DECLARE @action CHAR(8) 
IF COLUMNS_UPDATED() <> 0 -- delete or update? 
BEGIN  
    IF EXISTS (SELECT * FROM deleted) -- updated cols + old rows means action=update  
    SET @action = 'UPDATE'  
    ELSE 
    SET @action = 'INSERT' -- updated columns and nothing deleted means action=insert 
END 
ELSE -- delete  
BEGIN 
    SET @action = 'DELETE' 
END 
62

很多的这些建议如果运行删除任何一个delete语句不考虑。
假设您尝试删除ID等于表中不存在的某个值的位置。
您的触发器仍然被调用,但是在已删除或已插入的表中没有任何内容。

使用这是安全的:

--Determine if this is an INSERT,UPDATE, or DELETE Action or a "failed delete". 
DECLARE @Action as char(1); 
    SET @Action = (CASE WHEN EXISTS(SELECT * FROM INSERTED) 
         AND EXISTS(SELECT * FROM DELETED) 
         THEN 'U' -- Set Action to Updated. 
         WHEN EXISTS(SELECT * FROM INSERTED) 
         THEN 'I' -- Set Action to Insert. 
         WHEN EXISTS(SELECT * FROM DELETED) 
         THEN 'D' -- Set Action to Deleted. 
         ELSE NULL -- Skip. It may have been a "failed delete". 
        END) 

特别感谢@KenDog和@Net_Prog了他们的答案。
我从他们的脚本构建了这个。

+1

这是奖励,处理不存在的删除。干得好! – 2017-04-05 18:58:08

+2

我们也可能有一个不影响行(甚至是INSERT)的UPDATE。 – 2017-08-31 10:06:57

0

在第一种情形下我假设你的表有IDENTITY列

CREATE TRIGGER [dbo].[insupddel_yourTable] ON [yourTable] 
FOR INSERT, UPDATE, DELETE 
AS 
IF @@ROWCOUNT = 0 return 
SET NOCOUNT ON; 
DECLARE @action nvarchar(10) 
SELECT @action = CASE WHEN COUNT(i.Id) > COUNT(d.Id) THEN 'inserted' 
         WHEN COUNT(i.Id) < COUNT(d.Id) THEN 'deleted' ELSE 'updated' END 
FROM inserted i FULL JOIN deleted d ON i.Id = d.Id 

在第二种情况下不需要使用IDENTITTY列

CREATE TRIGGER [dbo].[insupddel_yourTable] ON [yourTable] 
FOR INSERT, UPDATE, DELETE 
AS 
IF @@ROWCOUNT = 0 return 
SET NOCOUNT ON; 
DECLARE @action nvarchar(10), 
     @insCount int = (SELECT COUNT(*) FROM inserted), 
     @delCount int = (SELECT COUNT(*) FROM deleted) 
SELECT @action = CASE WHEN @insCount > @delCount THEN 'inserted' 
         WHEN @insCount < @delCount THEN 'deleted' ELSE 'updated' END 
0

快速解决方案的MySQL

通过方式:我使用MySQL PDO。

(1)在自动增量表刚刚从增量列每个脚本运行一次先拿到的最高值(我的列名= ID):

$select = " 
    SELECT MAX(id) AS maxid 
    FROM [tablename] 
    LIMIT 1 
"; 

(2)运行MySQL查询你normaly会,并把结果为整数,如:

$iMaxId = (int) $result[0]->maxid; 

(3)经过 “INSERT INTO ... ON DUPLICATE KEY UPDATE” 查询得到最后插入的ID您喜欢的方式,如:

$iLastInsertId = (int) $db->lastInsertId(); 

(4)比较和反应:如果lastInsertId高于表中的最高值,那可能是INSERT,对吧?反之亦然。

if ($iLastInsertId > $iMaxObjektId) { 
    // IT'S AN INSERT 
} 
else { 
    // IT'S AN UPDATE 
} 

我知道这很快,也许很脏。这是一个旧帖子。但是,嘿,我长期以来一直在寻找解决方案,也许有人发现我的方式有点有用。祝一切顺利!

1

这是否把戏对我来说:

declare @action_type int; 
select @action_type = case 
         when i.id is not null and d.id is  null then 1 -- insert 
         when i.id is not null and d.id is not null then 2 -- update 
         when i.id is  null and d.id is not null then 3 -- delete 
        end 
    from  inserted i 
    full join deleted d on d.id = i.id 

由于并非所有列在同一时间进行更新,你可以检查特定列正在通过一些更新这样的:

IF UPDATE([column_name]) 
0

只是简单的方式

CREATE TRIGGER [dbo].[WO_EXECUTION_TRIU_RECORD] ON [dbo].[WO_EXECUTION] 
WITH EXECUTE AS CALLER 
FOR INSERT, UPDATE 
AS 
BEGIN 

    select @vars = [column] from inserted 
    IF UPDATE([column]) BEGIN 
    -- do update action base on @vars 
    END ELSE BEGIN 
    -- do insert action base on @vars 
    END 

END 
0
DECLARE @INSERTEDCOUNT INT, 
     @DELETEDCOUNT INT 

SELECT @INSERTEDCOUNT = COUNT([YourColumnName]) FROM inserted 

SELECT @DELETEDCOUNT = COUNT([YourColumnName]) FROM deleted 

如果更新用

@INSERTEDCOUNT = 1 
@DELETEDCOUNT = 1 

如果插入

@INSERTEDCOUNT = 1 
@DELETEDCOUNT = 0 
1
declare @insCount int 
declare @delCount int 
declare @action char(1) 

select @insCount = count(*) from INSERTED 
select @delCount = count(*) from DELETED 

    if(@insCount > 0 or @delCount > 0)--if something was actually affected, otherwise do nothing 
    Begin 
     if(@insCount = @delCount) 
      set @action = 'U'--is update 
     else if(@insCount > 0) 
      set @action = 'I' --is insert 
     else 
      set @action = 'D' --is delete 

     --do stuff here 
    End 
15

我使用下面,还正确地检测删除删除任何声明:

CREATE TRIGGER dbo.TR_TableName_TriggerName 
    ON dbo.TableName 
    AFTER INSERT, UPDATE, DELETE 
AS 
BEGIN 
    SET NOCOUNT ON; 

    IF NOT EXISTS(SELECT * FROM INSERTED) 
     -- DELETE 
     PRINT 'DELETE'; 
    ELSE 
    BEGIN 
     IF NOT EXISTS(SELECT * FROM DELETED) 
      -- INSERT 
      PRINT 'INSERT'; 
     ELSE 
      -- UPDATE 
      PRINT 'UPDATE'; 
    END 
END; 
0

我我很长一段时间都在使用这些exists (select * from inserted/deleted)查询,但它仍然不够清空CRUD操作(当inserteddeleted表中没有记录时)。所以经过研究这个题目有点我已经找到更精确的解决方案:

declare 
    @columns_count int = ?? -- number of columns in the table, 
    @columns_updated_count int = 0 

-- this is kind of long way to get number of actually updated columns 
-- from columns_updated() mask, it's better to create helper table 
-- or at least function in the real system 
with cte_columns as (
    select @columns_count as n 
    union all 
    select n - 1 from cte_columns where n > 1 
), cte_bitmasks as (
    select 
     n, 
     (n - 1)/8 + 1 as byte_number, 
     power(2, (n - 1) % 8) as bit_mask 
    from cte_columns 
) 
select 
    @columns_updated_count = count(*) 
from cte_bitmasks as c 
where 
    convert(varbinary(1), substring(@columns_updated_mask, c.byte_number, 1)) & c.bit_mask > 0 

-- actual check 
if exists (select * from inserted) 
    if exists (select * from deleted) 
     select @operation = 'U' 
    else 
     select @operation = 'I' 
else if exists (select * from deleted) 
    select @operation = 'D' 
else if @columns_updated_count = @columns_count 
    select @operation = 'I' 
else if @columns_updated_count > 0 
    select @operation = 'U' 
else 
    select @operation = 'D' 

它也可以使用columns_updated() & power(2, column_id - 1) > 0,看看是否列被更新,但它不是大数量的列表安全。我用了一些复杂的计算方法(请参阅下面的帮助文章)。此外,这种方法仍然会错误地将某些更新分类为插入(如果表中的每一列都受到更新的影响),并且可能会对只插入缺省值作为删除的插入进行分类,但这些插入是罕见的操作(在我的系统中是最初的)。 除此之外,目前我不知道如何改进此解决方案。

1

我喜欢的是解决方案, “计算机科学优雅。”我的解决方案在每次点击[插入]和[删除]伪类以获得它们的状态并将结果放入一个位映射变量中。然后,通过有效的二进制评估(除了不太可能的INSERT或DELETE组合),可以在整个触发器中轻松测试INSERT,UPDATE和DELETE的各种可能组合。

它确实假设如果没有行被修改(这应该满足绝大多数情况),它与DML语句的内容无关。所以尽管它不像罗马派克的解决方案那样完整,但它更有效率。使用这种方法,我们可以在每个表格中添加一个“FOR INSERT,UPDATE,DELETE”触发器,从而使我们能够:A)完全控制操作顺序,以及b)每个适用于多行为操作的代码实现。 (显然,每个实施模式都有其优缺点,您需要分别评估您的系统是否真正有效。)

请注意,由于没有磁盘访问(https://social.msdn.microsoft.com/Forums/en-US/01744422-23fe-42f6-9ab0-a255cdf2904a),“exists(select * from«inserted/deleted»)”语句非常有效。

use tempdb 
; 
create table dbo.TrigAction (asdf int) 
; 
GO 
create trigger dbo.TrigActionTrig 
on dbo.TrigAction 
for INSERT, UPDATE, DELETE 
as 
declare @Action tinyint 
; 
-- Create bit map in @Action using bitwise OR "|" 
set @Action = (-- 1: INSERT, 2: DELETE, 3: UPDATE, 0: No Rows Modified 
    (select case when exists (select * from inserted) then 1 else 0 end) 
| (select case when exists (select * from deleted) then 2 else 0 end)) 
; 
-- 21 <- Binary bit values 
-- 00 -> No Rows Modified 
-- 01 -> INSERT -- INSERT and UPDATE have the 1 bit set 
-- 11 -> UPDATE < 
-- 10 -> DELETE -- DELETE and UPDATE have the 2 bit set 

raiserror(N'@Action = %d', 10, 1, @Action) with nowait 
; 
if (@Action = 0) raiserror(N'No Data Modified.', 10, 1) with nowait 
; 
-- do things for INSERT only 
if (@Action = 1) raiserror(N'Only for INSERT.', 10, 1) with nowait 
; 
-- do things for UPDATE only 
if (@Action = 3) raiserror(N'Only for UPDATE.', 10, 1) with nowait 
; 
-- do things for DELETE only 
if (@Action = 2) raiserror(N'Only for DELETE.', 10, 1) with nowait 
; 
-- do things for INSERT or UPDATE 
if (@Action & 1 = 1) raiserror(N'For INSERT or UPDATE.', 10, 1) with nowait 
; 
-- do things for UPDATE or DELETE 
if (@Action & 2 = 2) raiserror(N'For UPDATE or DELETE.', 10, 1) with nowait 
; 
-- do things for INSERT or DELETE (unlikely) 
if (@Action in (1,2)) raiserror(N'For INSERT or DELETE.', 10, 1) with nowait 
-- if already "return" on @Action = 0, then use @Action < 3 for INSERT or DELETE 
; 
GO 

set nocount on; 

raiserror(N' 
INSERT 0...', 10, 1) with nowait; 
insert dbo.TrigAction (asdf) select top 0 object_id from sys.objects; 

raiserror(N' 
INSERT 3...', 10, 1) with nowait; 
insert dbo.TrigAction (asdf) select top 3 object_id from sys.objects; 

raiserror(N' 
UPDATE 0...', 10, 1) with nowait; 
update t set asdf = asdf /1 from dbo.TrigAction t where asdf <> asdf; 

raiserror(N' 
UPDATE 3...', 10, 1) with nowait; 
update t set asdf = asdf /1 from dbo.TrigAction t; 

raiserror(N' 
DELETE 0...', 10, 1) with nowait; 
delete t from dbo.TrigAction t where asdf < 0; 

raiserror(N' 
DELETE 3...', 10, 1) with nowait; 
delete t from dbo.TrigAction t; 
GO 

drop table dbo.TrigAction 
; 
GO 
7

相信嵌套IFS有点混乱和:

平优于嵌套[Python中的禅]

;)

DROP TRIGGER IF EXISTS AFTER_MYTABLE 

GO 

CREATE TRIGGER dbo.AFTER_MYTABLE ON dbo.MYTABLE AFTER INSERT, UPDATE, DELETE 

AS BEGIN 

    --- FILL THE BEGIN/END SECTION FOR YOUR NEEDS. 

    SET NOCOUNT ON; 

    IF EXISTS(SELECT * FROM INSERTED) AND EXISTS(SELECT * FROM DELETED) 
     BEGIN PRINT 'UPDATE' END 
    ELSE IF EXISTS(SELECT * FROM INSERTED) AND NOT EXISTS(SELECT * FROM DELETED) 
     BEGIN PRINT 'INSERT' END 
    ELSE IF EXISTS(SELECT * FROM DELETED) AND NOT EXISTS(SELECT * FROM INSERTED) 
     BEGIN PRINT 'DELETED' END 
    ELSE BEGIN PRINT 'NOTHING CHANGED'; RETURN; END -- NOTHING 

END 
4

试试这个..

ALTER TRIGGER ImportacionesGS ON dbo.Compra 
    AFTER INSERT, UPDATE, DELETE 
AS 
BEGIN 
    -- idCompra is PK 
    DECLARE @vIdCompra_Ins INT,@vIdCompra_Del INT 
    SELECT @vIdCompra_Ins=Inserted.idCompra FROM Inserted 
    SELECT @vIdCompra_Del=Deleted.idCompra FROM Deleted 
    IF (@vIdCompra_Ins IS NOT NULL AND @vIdCompra_Del IS NULL) 
    Begin 
    -- Todo Insert 
    End 
    IF (@vIdCompra_Ins IS NOT NULL AND @vIdCompra_Del IS NOT NULL) 
    Begin 
    -- Todo Update 
    End 
    IF (@vIdCompra_Ins IS NULL AND @vIdCompra_Del IS NOT NULL) 
    Begin 
    -- Todo Delete 
    End 
END 
4
Declare @Type varchar(50)=''; 
IF EXISTS (SELECT * FROM inserted) and EXISTS (SELECT * FROM deleted) 
BEGIN 
    SELECT @Type = 'UPDATE' 
END 
ELSE IF EXISTS(SELECT * FROM inserted) 
BEGIN 
    SELECT @Type = 'INSERT' 
END 
ElSE IF EXISTS(SELECT * FROM deleted) 
BEGIN 
    SELECT @Type = 'DELETE' 
END 
0

,同时我也很喜欢发表@Alex答案,我提供这个变化来@以上

格雷厄姆的解决这个专门使用在插入的记录存在与更新的表,而不是使用COLUMNS_UPDATED为第一次测试。 它还提供了偏执的程序员救济知道最后的情况下,一直被认为是...

declare @action varchar(4) 
    IF EXISTS (SELECT * FROM INSERTED) 
    BEGIN 
     IF EXISTS (SELECT * FROM DELETED) 
      SET @action = 'U' -- update 
     ELSE 
      SET @action = 'I' --insert 
     END 
    ELSE IF EXISTS (SELECT * FROM DELETED) 
     SET @action = 'D' -- delete 
    else 
     set @action = 'noop' --no records affected 
--print @action 

你会得到NOOP与像声明如下:

update tbl1 set col1='cat' where 1=2