插入更新触发器如何确定是否插入或更新
我需要在表A上编写一个Insert,Update Trigger,它将从表B中删除表B中所有行,其中一列(称为Desc)具有类似于表A的列(比如Col1)中插入/更新的值。 我将如何去写它,以便我可以处理更新和插入案例。 我将如何确定触发器是否执行更新或插入。
如果是MS SQL Server …
触发器有特殊的INSERTED
和DELETED
表来跟踪“之前”和“之后”的数据。 所以你可以使用像IF EXISTS (SELECT * FROM DELETED)
来检测更新。 在更新时只有DELETED
行,但在INSERTED
中总是有行。
在CREATE TRIGGER中查找“插入”
编辑,2011年11月23日
评论之后,这个答案只适用于INSERTED
和UPDATED
触发器。
显然,如上所述,DELETE触发器不能“在INSERTED
总是行”
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
如果您运行删除语句不删除任何内容,其中许多build议不会考虑在内。
假设您尝试删除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的答案。
我从他们的脚本build立这个。
我正在使用以下,它也正确地检测删除语句,删除任何东西:
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;
经过大量的search,我找不到一个SQL Server触发器的确切例子,它可以处理触发器动作INSERT,UPDATE和DELETE的所有(3)三个条件。 我终于find了一段文字,谈到DELETE或UPDATE发生时,常见的DELETED表将包含这两个动作的logging。 基于这些信息,我创build了一个小的Action例程,它决定了触发器被激活的原因。 当在INSERT和UPDATE触发器上同时存在通用configuration和特定操作时,有时需要这种types的接口。 在这些情况下,为UPDATE创build单独的触发器,INSERT将成为维护问题。 (即两个触发器正确更新为必要的常见数据algorithm修复?)
为此,我想给出以下多触发事件代码片段,用于在Microsoft SQL Server的一个触发器中处理INSERT,UPDATE,DELETE。
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]=@PACKLIST_ID AND [LINE_NO]=@LINE_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]=@PACKLIST_ID AND [LINE_NO]=@LINE_NO END END
我相信嵌套如果有点混乱,并且:
平面比嵌套更好[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
尝试这个..
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
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
提供的两个解决scheme的潜在问题是,根据它们的写法,更新查询可能会更新零个logging,插入查询可能会插入零个logging。 在这些情况下,插入和删除logging集将是空的。 在许多情况下,如果“插入”和“已删除”logging集都为空,则可能只是想不做任何事情而退出触发器。
这可能是一个更快的方法:
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'
我喜欢“计算机科学优雅”的解决scheme。 我的解决scheme在这里每次触发[插入]和[删除]伪指令来获得它们的状态,并把结果放在一个位映射variables中。 然后,可以通过有效的二进制评估(除了不太可能的INSERT或DELETE组合)在INSERT,UPDATE和DELETE的各个可能组合之间轻松地对整个触发器进行testing。
它确实假定,如果没有行被修改(这应该满足绝大多数情况),DML语句是什么并不重要。 所以虽然没有罗马派克的解决scheme那么完整,但效率更高。
通过这种方法,我们有可能为每个表创build一个“FOR INSERT,UPDATE,DELETE”触发器,给我们A)完全控制动作顺序和b)每个多行为适用动作的一个代码实现。 (很明显,每个实现模型都有其优缺点,您需要分别评估您的系统是否真正有效。
请注意,“exists(select * from«inserted / deleted»)”语句是非常有效的,因为没有磁盘访问( https://social.msdn.microsoft.com/Forums/en-US/01744422-23fe-42f6 -9ab0-a255cdf2904a )。
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
我在Grahams中发现了一个小错误,否则很酷的解决scheme:
它应该是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
快速解决MySQL
顺便说一下:我正在使用MySQL PDO。
(1)在自动增量表中,每个脚本首先运行一次,从递增的列中获取最高值(我的列名= id):
$select = " SELECT MAX(id) AS maxid FROM [tablename] LIMIT 1 ";
(2)像你一样运行MySQL查询,并将结果转换为整数,例如:
$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 }
我知道这很快,也许很脏。 这是一个旧的职位。 但是,嘿,我正在寻找一个很长时间的解决scheme,也许有人觉得我的方式有点有用。 祝一切顺利!
这对我来说是个窍门:
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])
只是简单的方法
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
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
在第一种情况下,我认为你的表有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
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
我已经使用这些exists (select * from inserted/deleted)
查询很长一段时间,但它仍然是不够的空CRUD操作(当inserted
和deleted
表中没有logging)。 所以在研究了这个话题之后,我发现了更精确的解决scheme:
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
来查看列是否更新,但对于列数很大的表是不安全的。 我用了一些复杂的计算方法(请参阅下面的帮助文章)。
此外,这种方法仍然会错误地将某些更新分类为插入(如果表中的每一列都受到更新的影响),并且可能会对只插入默认值的插入进行分类,但这些操作是罕见操作之王在我的系统租赁他们)。 除此之外,目前我不知道如何改进这个解决scheme。
- 对于Piotr Rodak的审计触发器,COLUMNS_UPDATED()
- Adam Machanic处理非常大的位掩码