【问题标题】:SQL Server: Inserting into table with INSTEAD OF INSERT triggersSQL Server:使用 INSTEAD OF INSERT 触发器插入表
【发布时间】:2017-11-26 08:52:31
【问题描述】:

Microsoft Documentation 声明“关于何时引用具有 INSTEAD OF 触发器的表的 INSERT 或 UPDATE 语句必须为列提供值的规则与表没有 INSTEAD OF 触发器时的规则相同”。

但是:

CREATE TABLE tbTriggerTest (id INT PRIMARY KEY);
GO

CREATE TRIGGER dbo.tgTriggerTest
ON dbo.tbTriggerTest
INSTEAD OF INSERT
BEGIN
    SELECT * FROM INSERTED
END
GO

INSERT tbTriggerTest DEFAULT VALUES
GO

即使在 INSERT 语句级别我将空值插入到主键中,也不会引发错误。实际上 INSERTED.id 在这种情况下为 NULL。那么文档不正确还是仅适用于触发器内部?

编辑:响应建议的答案,说明调用者必须为非空列提供一些值,即使它为空,以下不会引发错误但可以说不提供任何值对于列ij

CREATE TABLE tbTriggerTest2
(
    id INT,
    ij INT,
    CONSTRAINT PK PRIMARY KEY (id, ij)
)
GO

CREATE TRIGGER dbo.tgTriggerTest2
   ON  dbo.tbTriggerTest2
   INSTEAD OF INSERT
AS 
BEGIN
    SELECT * FROM INSERTED;
END
GO

INSERT tbTriggerTest2 (id) VALUES (0)
GO

【问题讨论】:

  • 您的触发器只是选择...,它试图从触发器返回结果集,但已弃用。它实际上并没有插入任何东西。它应该改为“插入到 tblTriggerTest SELECT * FROM 插入”。
  • 我知道@pmbAustin,这只是表明当触发器放在桌面上时规则确实会改变。

标签: sql-server tsql triggers sql-server-2016


【解决方案1】:

嗯,你引用的docs 说:

关于何时执行 INSERT 或 UPDATE 语句的以下规则 使用 INSTEAD OF 触发器引用表必须为 列与表没有 INSTEAD OF 相同 触发器:

  • INSERT 语句必须为所有没有 DEFAULT 约束的 NOT NULL 列提供值。

您在问题中的INSERT 声明

INSERT tbTriggerTest DEFAULT VALUES

为所有列提供值。它没有省略任何列。是的,它提供 NULL 值,但文档没有说 INSERT 语句应该提供 non-null 值。它说INSERT 语句应该提到所有非空列。这就是解析器检查的内容 - 列列表。

当违反NOT NULL 约束时,实际的INSERT 操作将在稍后失败。在您的示例中,实际的 INSERT 从未发生过,因此您看不到任何错误。


这就像你在桌子上没有任何触发器并试图运行一样

INSERT tbTriggerTest VALUES (NULL)

服务器将解析语句,看到VALUES 子句与表具有相同的列列表并尝试执行INSERT。它将因违反约束而失败。

无法将值 NULL 插入到列“id”、表中 'AdventureWorks2014.dbo.tbTriggerTest';列不允许空值。 插入失败。声明已终止。

你可以尝试得到一个估计的执行计划,你就会得到一个计划。引擎生成了一个计划并开始执行它。


如果你尝试运行

INSERT tbTriggerTest VALUES (NULL, NULL)

如果不尝试执行,语句将失败。

列名或提供的值数与表不匹配 定义。

如果您试图获得一个估计的执行计划,您将得不到一个。引擎无法生成计划,因为列列表不匹配。

第二个例子

在第二个示例中,idij 列都可以为空。因此,同样,无论有没有触发器,引擎都会接受只有一个值的INSERT 语句。第二个值将被假定为NULL

如果VALUES 子句的列数少于表中的列数,则引擎会假定缺少的列为NULL(或者更准确地说,默认值)。所以,在你的例子中,一个表有两列,语句

INSERT tbTriggerTest2 (id) VALUES (0)

等同于:

INSERT tbTriggerTest2 (id, ij) VALUES (0, NULL)

第二列可以为空,所以它有一个默认的NULL 值。您可以为列指定不同的默认值,如果您不将该列包含在列列表中,INSERT 也将按预期工作。

如果将表的定义更改为:

CREATE TABLE tbTriggerTest2
(
    id INT NOT NULL,
    ij INT NOT NULL,
    CONSTRAINT PK PRIMARY KEY (id, ij)
)

那么如果没有触发器,下面的语句就会失败:

INSERT tbTriggerTest2 (id) VALUES (0)

无法将值 NULL 插入列“ij”,表 'dbo.tbTriggerTest2';列不允许空值。插入失败。这 语句已终止。

使用触发器,整个语句将成功运行,因为触发器禁止尝试将具有 NULL 值的行插入不接受 NULL 的列中。


如果将表的定义更改为:

CREATE TABLE tbTriggerTest3
(
    id INT NOT NULL,
    ij INT NOT NULL DEFAULT 10
)

然后

INSERT tbTriggerTest3 (id) VALUES (0)

与以下内容相同:

INSERT tbTriggerTest3 (id, ij) VALUES (0, 10)

不管有没有触发器,它都会成功。

另一方面,

INSERT tbTriggerTest3 (id) VALUES (NULL)

等同于:

INSERT tbTriggerTest3 (id, ij) VALUES (NULL, 10)

没有触发器就会失败

无法将值 NULL 插入到列“id”、表中 'dbo.tbTriggerTest3';列不允许空值。插入失败。

,因为它会尝试将NULL 插入到不接受NULL 的id

使用禁止插入行的触发器会成功。

【讨论】:

  • @ThomasMcLeod,我为第二个例子添加了解释
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多