什么是:SET @Variable1 = (Select field1 From INSERTED) 是什么意思?

Mar*_*ary 2 trigger sql-server-2008-r2

我试图了解我的一个表 (DocumentDistribution) 上的现有触发器,它似乎是 DocumentInfo 和 DocumentSource 表之间的桥接表。我不明白这一行 (SET....FROM INSERTED) 是如何从中获取值的?这就是所谓的动态 SQL,它根据用户的选择从前端应用程序中获取该数据库的值吗?

我在尝试测试时遇到错误,因为子查询返回多行而语法期望单行,有什么建议吗?我正在使用 SQL Server 2008R2。

Alter TRIGGER [dbo].[DocDist_Dup_Check] 
ON [dbo].[DocumentDistribution]
AFTER UPDATE, INSERT 

AS
BEGIN
SET NOCOUNT ON;
SET ANSI_WARNINGS OFF;
DECLARE @docid int,
        @sourceid int,
        @errstr varchar(255);

SET @errstr = 'The distribution you have attempted to create already exists in the database.' + CHAR(13) + 
              'Duplicate distributions are not allowed for any document source except eBinder.';
SET @docid = (SELECT DocumentDistDocID FROM INSERTED);
SET @sourceid = (SELECT DocumentDistSourceID FROM INSERTED);

IF  (SELECT COUNT(*) FROM DocumentDistribution WHERE DocumentDistDocID = @docid AND DocumentDistSourceID = @sourceid) > 1

    AND
    (@sourceid NOT IN(SELECT DocumentSourceID FROM DocumentSource WHERE DocumentSourceName = 'eBinder' OR DocumentSourceName = 'eBinder - West Division' OR DocumentSourceName = 'Consolidated e-Binder' OR DocumentSourceName = 'MA Consolidated eBinder'))
BEGIN
    ROLLBACK TRANSACTION
END

ELSE
BEGIN
    RETURN
END
Run Code Online (Sandbox Code Playgroud)

结尾

Aar*_*and 7

问题是触发器最初是用非常简单的逻辑和最少的测试编写的——它假设更新或插入一次只能影响一行。

您要问的那一行是尝试为可能包含多行的表中的变量分配一个值(SQL Server 中的触发器是按操作触发,而不是按行触发)。当有不止一行可供选择时,不同形式的变量赋值的行为会有所不同,例如:

DECLARE @foo TABLE(id INT);
INSERT @foo VALUES(1),(2);

DECLARE @id INT;
-- this will succeed, picking a single, may-as-well-be arbitrary row:
SELECT @id = id FROM @foo;
PRINT @id;

-- this will fail:
--SET @id = (SELECT id FROM @foo); 
--PRINT @id;
Run Code Online (Sandbox Code Playgroud)

如果您运行第二个版本,您将收到一条您可能已经从触发器中看到的错误消息:

消息 512,级别 16,状态 1
子查询返回了 1 个以上的值。当子查询跟随 =、!=、<、<=、>、>= 或当子查询用作表达式时,这是不允许的。

并非所有都丢失了 - 您的触发器可以重新编写以处理受影响的多行 - 我将假设如果您插入两行并且其中只有一行失败,您希望整个事务回滚,即使另一排完全没问题。(此外,您的初始代码会填充@errstr但从未使用过,因此我暂时将其排除在外。)

ALTER TRIGGER [dbo].[DocDist_Dup_Check] 
ON [dbo].[DocumentDistribution]
AFTER UPDATE, INSERT 
AS
BEGIN
  SET NOCOUNT ON;
  SET ANSI_WARNINGS OFF;

  IF EXISTS
  (
    SELECT DocumentDistDocID, DocumentDistSourceID
    FROM dbo.DocumentDistribution AS dd
    WHERE EXISTS 
    (
      SELECT 1 FROM inserted AS i 
      WHERE i.DocumentDistDocID = dd.DocumentDistDocID
      AND i.DocumentDistSourceID = dd.DocumentDistSourceID
    )
    GROUP BY DocumentDistDocID, DocumentDistSourceID
    HAVING COUNT(*) > 1
  )
  AND EXISTS
  (
    SELECT 1 FROM inserted 
    WHERE DocumentDistSourceID NOT IN 
    (
      SELECT DocumentSourceID FROM dbo.DocumentSource 
      WHERE DocumentSourceName IN 
      ('eBinder','eBinder - West Division',
       'Consolidated e-Binder','MA Consolidated eBinder')
    )
  )
  BEGIN
    ROLLBACK TRANSACTION;
  END
END
Run Code Online (Sandbox Code Playgroud)

我的逻辑是正确的;请在恢复的备份或开发系统上进行测试。

您可能会考虑将其实现为INSTEAD OF触发器 - 这允许您根据业务逻辑插入或不插入(或仅插入不违反约束的行),而不是总是插入然后有时回滚。

如果此触发器的主要目的是防止重复,您还可以考虑实施唯一约束(或者可能是过滤的唯一索引或索引视图,如果只需要对子集强制执行重复项)。