触发器:无法在条件内引用插入的 table 列值

Trigger: Inserted table column value cannot be referenced inside condition

我有以下触发器:

    GO
ALTER TRIGGER [dbo].[FooTrigger]
ON [dbo].[Foo]
FOR INSERT
AS 
DECLARE @Url nvarchar(MAX)
SELECT @Url  = ins.Url from INSERTED ins;

IF EXISTS (SELECT *  
           FROM [dbo].[Foo]  p   
           where p.Url =  @Url)  
BEGIN  
RAISERROR ( 'error %s' , 16, 1, @Url)
ROLLBACK TRANSACTION;  
RETURN   
END;
PRINT 'Executed Trigger Insert.'
GO

如果我像

这样硬编码条件,它可以正常工作
IF EXISTS(SELECT * FROM [dbo].[Foo] foo WHERE foo.Url = 'test.com')

但是原来那个不行。它抛出消息:

在条件中使用 @Url 引用插入的 table 数据似乎有错误。但是 RAISERROR ( @Url , 16, 1); 正确地打印了值,所以我认为在这种情况下,引用是不被允许的。有什么解决方法吗?

首先,如果您要确保 url 是唯一的,则使用唯一约束:

alter table foo add constraint unq_foo_url unique (url);

其次,永远不要假设 inserted 只有一行。此代码:

SELECT @Url  = ins.Url from INSERTED ins;

是一个等待发生的错误。

编辑:

如果您的 table 已经违反了唯一约束,那么您的问题就很麻烦了。你可以解决这个问题。您的代码的问题是 FOR INSERT 触发器实际上是 AFTER INSERT 触发器——新数据已经可见。

但是,你可以处理这个。这是一种方法:

CREATE TRIGGER [dbo].[FooInsertTrigger]
ON [dbo].[Foo]
AFTER INSERT
AS
BEGIN
    IF (SELECT TOP (1) COUNT(*)
        FROM inserted i JOIN
             [dbo].[Foo] f
             on i.url = f.url 
        GROUP BY i.url
        ORDER BY COUNT(*) DESC
       ) > 1
    BEGIN  
        RAISERROR('Duplicate URL found' , 16, 1);
        ROLLBACK TRANSACTION;  
        RETURN   
    END;
    PRINT 'Executed Trigger Insert.'
END;

以下情况下最大值count(*)大于1:

  • inserted.url 匹配 foo 中的 1 行。这是正常情况。
  • inserted.url 匹配 foo 中的多行。这是抓到的。

这也将捕获 inserted 中存在倍数的情况。

通过使代码对于多次插入是安全的,更难提取有问题的 URL。但是错误处理正确。

Here 是一个 db<>fiddle.