考虑这个触发器:

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

我有一张桌子,有桌子,我正试图阻止人们插入不良记录。出于这个问题的目的,坏记录有一个字段“someField”。这都是数字。

当然,正确的方法是使用触发器,但我不控制源代码......只是SQL数据库。所以我无法阻止插入坏行,但我可以立即将其删除,这对我的需求来说已经足够了。

触发器有效,有一个问题......当它触发时,它似乎永远不会删除刚刚插入的坏记录......它会删除任何旧的坏记录,但它不会删除刚刚插入的坏记录。所以通常会有一个不好的记录,直到其他人出现并执行另一次INSERT时才会被删除。

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

有帮助吗?

解决方案

触发器无法修改已更改的数据( Inserted Deleted ),否则您可以获得无限递归,因为更改会再次调用触发器。一种选择是触发器回滚事务。

编辑:这样做的原因是SQL的标准是触发器无法修改插入和删除的行。根本原因是修改可能导致无限递归。在一般情况下,此评估可能涉及相互递归级联中的多个触发器。让系统智能地决定是否允许这样的更新在计算上是难以处理的,本质上是停止问题的变体。

可接受的解决方案是不允许触发器更改更改的数据,尽管它可以回滚事务。

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 触发器以仅插入 ,而不是删除无效行。

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 触发器不会导致递归。

这是Bill的代码的修改版本:

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

这使得插入总是成功,并且任何伪造的记录都会被抛入sometableRejects,您可以在以后处理它们。重要的是让你的拒绝表使用nvarchar字段来处理所有事情 - 而不是整数,小写等 - 因为如果它们被拒绝,那是因为数据不是你所期望的那样。

这也解决了多记录插入问题,这将导致Bill的触发器失败。如果你同时插入10个记录(就像你进行select-insert-into)而其中只有一个是伪造的,那么Bill的触发器会将所有记录标记为坏。这可以处理任意数量的好记录和坏记录。

我在数据仓库项目中使用了这个技巧,其中插入应用程序不知道业务逻辑是否有用,而我们在触发器中执行了业务逻辑。真的很讨厌性能,但如果你不能让插入失败,它确实有效。

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

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

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

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

更新:从触发器中删除适用于MSSql 7和MSSql 2008。

我不是关系大师,也不是SQL标准。然而 - 与接受的答案相反 - MSSQL与r 递归和嵌套触发器评估。我不知道其他RDBMS。

相关选项包括'递归触发器'和'嵌套触发器'。嵌套触发器限制为32级,默认为1.默认情况下,递归触发器处于关闭状态,并且没有谈论限制 - 但坦率地说,我从未打开它们,所以我不知道不可避免的会发生什么堆栈溢出。我怀疑MSSQL会杀死你的spid(或者有一个递归限制)。

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

删除刚刚插入的行的快速测试也有效:

 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

这样至少可以为您提供查看新数据的方法。

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

我找到了这个参考:

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."

我还没有对它进行过测试,但从逻辑上看它看起来应该是dp你所追求的...而不是删除插入的数据,完全阻止插入,因此不需要你必须撤消插入。它应该表现更好,因此最终应该更容易处理更高的负载。

编辑:当然, 的潜力是,如果插入发生在其他有效的事务中,那么wole事务可以回滚,因此您需要考虑该场景并确定如果插入无效数据行将构成完全无效的交易......

INSERT是否可能有效,但是之后的单独UPDATE是无效的但不会触发触发器?

上面列出的技术很好地描述了您的选择。但是用户看到了什么?我无法想象你和负责软件的人之间的这种基本冲突如何不会与用户混淆和对抗。

我会竭尽所能找到摆脱僵局的其他方法 - 因为其他人很容易看到你所做的任何改变都会使问题升级。

编辑:

我会得到我的第一个“取消删除”并承认在这个问题首次出现时发布上述内容。当我看到它来自JOEL SPOLSKY时,我当然感到很沮丧。但看起来它落在了附近。不需要投票,但我会将其记录在案。

除了业务规则范围之外的细粒度完整性约束之外,IME,触发器很少是正确的答案。

MS-SQL具有防止递归触发器触发的设置。这是通过sp_configure存储的进程确认的,您可以在其中打开或关闭递归或嵌套触发器。

在这种情况下,如果您关闭递归触发器以通过主键链接插入表中的记录,并且对记录进行更改,则是可能的。

在问题的具体情况中,它并不是真正的问题,因为结果是删除记录,这不会重写此特定触发器,但通常可能是一种有效的方法。我们以这种方式实现了乐观并发。

可以这种方式使用的触发器代码是:

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

你的“触发器”正在做一些“触发”的事情。不应该这样做。您可以简单地运行Sql Server Agent

DELETE FROM someTable
WHERE ISNUMERIC(someField) = 1
每隔1秒钟左右。当你在它的时候,如何编写一个漂亮的小SP来阻止编程人员将错误插入到你的表中。 SP的一个好处是参数是类型安全的。

我偶然发现了这个问题,寻找插入声明和事件过程中事件顺序的详细信息。触发。我最终编写了一些简短的测试来确认SQL 2016(EXPRESS)的行为 - 并且认为分享是合适的,因为它可能有助于其他人搜索类似的信息。

根据我的测试,可以从“插入”数据中选择数据。 table并使用它来更新插入的数据本身。而且,我感兴趣的是,插入的数据对于其他查询是不可见的,直到触发完成,此时最终结果是可见的(至少最好,因为我可以测试)。我没有对递归触发器等进行测试(我希望嵌套的触发器能够完全看到表中插入的数据,但这只是猜测)。

例如 - 假设我们有表格“表格”。具有整数字段“字段”的字段和主键字段“pk”和插入触发器中的以下代码:

select @value=field,@pk=pk from inserted
update table set field=@value+1 where pk=@pk
waitfor delay '00:00:15'

我们为“field”插入值为1的行,然后该行将以值2结束。此外 - 如果我在SSMS中打开另一个窗口并尝试:    从表中选择*,其中pk = @pk

其中@pk是我最初插入的主键,查询将为空,直到15秒到期,然后显示更新的值(字段= 2)。

我对触发器执行时其他查询可见的数据感兴趣(显然没有新数据)。我测试了添加的删除:

select @value=field,@pk=pk from inserted
update table set field=@value+1 where pk=@pk
delete from table where pk=@pk
waitfor delay '00:00:15'

同样,插入需要15秒才能执行。在不同会话中执行的查询显示没有新数据 - 在执行insert + trigger期间或之后(尽管我希望即使没有插入数据也会增加任何标识)。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top