SQL Server“AFTER INSERT”触发器不会看到刚刚插入的行

考虑这个触发器:

ALTER TRIGGER myTrigger ON someTable AFTER INSERT AS BEGIN DELETE FROM someTable WHERE ISNUMERIC(someField) = 1 END 

我有一个表,someTable,我试图阻止人们插入不良logging。 为了这个问题的目的,一个不好的logging有一个全是数字的字段“someField”。

当然,正确的做法不是触发器,但我不控制源代码…只是SQL数据库。 所以我不能真的阻止坏行的插入,但我可以马上删除它,这足以满足我的需求。

触发器工作,有一个问题…当它触发,它似乎永远不会删除刚刚插入的坏logging…它删除任何旧的坏logging,但它不会删除刚插入的坏logging。 所以经常有一个错误的logging被移除,直到其他人出现,并且执行另一个INSERT。

这是我理解触发器的问题吗? 在触发器运行时新插入的行还没有被提交?

触发器不能修改已更改的数据( InsertedDeleted ),否则当更改再次调用触发器时,可能会获得无限recursion。 一个选项是触发器回滚事务。

编辑:原因是SQL的标准是插入和删除的行不能被触发器修改。 其根本原因是修改可能导致无限recursion。 在一般情况下,这种评估可能涉及多个相互recursion级联的触发器。 让系统智能地决定是否允许这样的更新在计算上是棘手的,基本上是暂停问题的变化。

接受的解决scheme是不允许触发器改变数据的变化,虽然它可以回滚事务。

 create table Foo ( FooID int ,SomeField varchar (10) ) go create trigger FooInsert on Foo after insert as begin delete inserted where isnumeric (SomeField) = 1 end go Msg 286, Level 16, State 1, Procedure FooInsert, Line 5 The logical tables INSERTED and DELETED cannot be updated. 

像这样的东西会回滚交易。

 create table Foo ( FooID int ,SomeField varchar (10) ) go create trigger FooInsert on Foo for insert as if exists ( select 1 from inserted where isnumeric (SomeField) = 1) begin rollback transaction end go insert Foo values (1, '1') Msg 3609, Level 16, State 1, Line 1 The transaction ended in the trigger. The batch has been aborted. 

你可以改变逻辑。 插入之后,不要删除无效行INSTEAD OF只需在确认行有效的情况下写入INSTEAD OF触发器。

 CREATE TRIGGER mytrigger ON sometable INSTEAD OF INSERT AS BEGIN DECLARE @isnum TINYINT; SELECT @isnum = ISNUMERIC(somefield) FROM inserted; IF (@isnum = 1) INSERT INTO sometable SELECT * FROM inserted; ELSE RAISERROR('somefield must be numeric', 16, 1) WITH SETERROR; END 

如果你的应用程序不想处理错误(正如Joel所说的那样),那么就不要RAISERROR 。 只要使触发器静默不做一个无效的插入。

我在SQL Server Express 2005上运行它,它工作。 请注意,如果插入到定义了触发器的同一个表中,则INSTEAD OF触发器不会导致recursion。

这里是我的修改版本的比尔的代码:

 CREATE TRIGGER mytrigger ON sometable INSTEAD OF INSERT AS BEGIN INSERT INTO sometable SELECT * FROM inserted WHERE ISNUMERIC(somefield) = 1 FROM inserted; INSERT INTO sometableRejects SELECT * FROM inserted WHERE ISNUMERIC(somefield) = 0 FROM inserted; END 

这让插入总是成功的,任何虚假logging都会被抛到你的sometableRejects中,以后你可以处理它们。 让你的拒绝表使用nvarchar字段是非常重要的 – 不是int,tinyints等 – 因为如果它们被拒绝,那是因为数据不是你所期望的。

这也解决了多logging插入问题,这将导致比尔的触发失败。 如果你同时插入10条logging(比如你做了select-insert-into),其中只有一条是假的,那么Bill的触发器就会把所有的logging标记为坏。 这处理任何数量的好logging和坏logging。

我在一个数据仓库项目中使用了这个技巧,插入应用程序不知道业务逻辑是否是好的,我们在触发器中做了业务逻辑。 真正令人讨厌的performance,但如果你不能让插入失败,它确实工作。

我认为你可以使用CHECK约束 – 这正是它的发明。

 ALTER TABLE someTable ADD CONSTRAINT someField_check CHECK (ISNUMERIC(someField) = 1) ; 

我以前的答案(也可能有点矫枉过正):

我认为正确的方法是使用INSTEAD OF触发器来防止插入错误的数据(而不是事后删除)

更新:从触发器删除工作在MSSql 7和MSSql 2008。

我不是关系型专家,也不是SQL标准专家。 然而,与接受的答案相反,MSSQL在处理recursion和嵌套触发器评估方面都很好。 我不知道其他关系型数据库。

相关选项是“recursion触发器”和“嵌套触发器” 。 嵌套的触发器被限制在32个级别,默认为1.recursion触发器默认是closures的,没有谈论的限制 – 但坦率地说,我从来没有把它们打开,所以我不知道会发生什么堆栈溢出。 我怀疑MSSQL会杀死你的spid(或有一个recursion的限制)。

当然,这只是表明接受的答案有错误的原因 ,并不是不正确的。 但是,在INSTEAD OF触发器之前,我记得编写ON INSERT触发器,它将快速更新刚插入的行。 这一切工作正常,和预期的一样。

删除刚插入的行的快速testing也可以工作:

  CREATE TABLE Test ( Id int IDENTITY(1,1), Column1 varchar(10) ) GO CREATE TRIGGER trTest ON Test FOR INSERT AS SET NOCOUNT ON DELETE FROM Test WHERE Column1 = 'ABCDEF' GO INSERT INTO Test (Column1) VALUES ('ABCDEF') --SCOPE_IDENTITY() should be the same, but doesn't exist in SQL 7 PRINT @@IDENTITY --Will print 1. Run it again, and it'll print 2, 3, etc. GO SELECT * FROM Test --No rows GO 

你还有别的事情在这里。

从CREATE TRIGGER文档:

删除插入是逻辑(概念)表。 它们在结构上与定义触发器的表(即用户动作尝试的表)相似,并保存用户操作可能更改的行的旧值或新值。 例如,要检索已删除表中的所有值,请使用: SELECT * FROM deleted

这样至less可以让你看到新的数据。

我无法看到文档中的任何内容,指定在查询普通表时不会看到插入的数据…

我发现这个参考:

 create trigger myTrigger on SomeTable for insert as if (select count(*) from SomeTable, inserted where IsNumeric(SomeField) = 1) <> 0 /* Cancel the insert and print a message.*/ begin rollback transaction print "You can't do that!" end /* Otherwise, allow it. */ else print "Added successfully." 

我没有testing过,但从逻辑上看,它应该像你应该在…之后…而不是删除插入的数据,完全阻止插入,因此不需要你必须撤消插入。 它应该performance更好,因此应该更轻松地处理更高的负载。

编辑:当然,如果插入发生在另一个有效的事务中,那么这个事务可能会被回滚,所以你需要考虑这个事件并确定插入一个无效的数据行是否构成一个完全无效的交易…

是否有可能INSERT是有效的,但是一个单独的更新后,这是无效的,但不会触发触发器?

上面列出的技术很好地描述了你的select。 但是用户看到了什么? 我无法想象你和谁负责这个软件的这种基本冲突怎么也不会和用户混淆和对立起来。

我会尽我所能从僵局中find一些其他方法 – 因为其他人可以很容易地看到你所做的任何改变,从而使问题升级。

编辑:

当我第一次出现这个问题的时候,我会得到我的第一个“反删除”,并承认发布上述内容。 当我看到那是来自JOEL SPOLSKY的时候,我当然会变得很冷淡。 但它看起来像降落在附近。 不需要投票,但我会把它logging下来。

IME,除了商业规则领域以外的细粒度完整性约束,触发器很less是正确的答案。

MS-SQL有一个设置来防止recursion触发器触发。 这通过sp_configure存储的过程进行确认,您可以在其中开启或closuresrecursion或嵌套触发器。

在这种情况下,如果您closuresrecursion触发器以通过主键链接插入的表中的logging,并对logging进行更改,将是可能的。

在这个问题的具体情况下,这不是一个真正的问题,因为结果是删除logging,这不会反映这个特定的触发器,但总的来说,这可能是一个有效的方法。 我们这样实现了乐观并发。

你的触发器的代码可以这样使用:

 ALTER TRIGGER myTrigger ON someTable AFTER INSERT AS BEGIN DELETE FROM someTable INNER JOIN inserted on inserted.primarykey = someTable.primarykey WHERE ISNUMERIC(inserted.someField) = 1 END