【问题标题】:How can I prevent a record inserted by an SQL trigger attempting to set the identity column如何防止 SQL 触发器插入的记录尝试设置标识列
【发布时间】:2019-04-26 12:27:52
【问题描述】:

我正在尝试创建一个“历史”表,每次更新源表中的一行时都会更新该表。

这是我用来创建历史记录表的 (SQL Server) 代码:

DROP TABLE eventGroup_History 

SELECT 
    CAST(NULL AS UNIQUEIDENTIFIER) AS NewId, 
    CAST(NULL AS varchar(255)) AS DoneBy, 
    CAST(NULL AS varchar(255)) AS Operation, 
    CAST(NULL AS datetime) AS DoneAt, 
    * 
INTO 
    eventGroup_History 
FROM 
    eventGroup 
WHERE 
    1 = 0
GO

ALTER TABLE eventGroup_History 
ALTER COLUMN NewId UNIQUEIDENTIFIER NOT NULL
go

ALTER TABLE eventGroup_History 
ADD PRIMARY KEY (NewId)
GO

ALTER TABLE eventGroup_History 
ADD CONSTRAINT DF_eventGroup_History_NewId DEFAULT NewSequentialId() FOR NewId
GO

触发器是这样创建的:

drop trigger eventGroup_LogUpdate
go

create trigger eventGroup_LogUpdate 
on dbo.eventGroup
for update
as
  declare @Now as DateTime = GetDate()
  set nocount on

  insert into eventGroup_History
      select @Now, SUser_SName(), 'update-deleted', *
      from deleted

  insert into eventGroup_History
      select SUser_SName(), 'update-inserted', @Now, *
      from inserted
go

exec sp_settriggerorder @triggername = 'eventGroup_LogUpdate', @order = 'last', @stmttype = 'update'

但是当我在 SQL Server Management Studio 中更新一行时,我收到一条消息:

第 2 行中的数据未提交。
错误来源:.Net SqlClient 数据提供程序。
错误信息:从字符串转换为唯一标识符时转换失败。

我认为触发器正在尝试将 SUserSName() 作为行的第一列插入,但那是 PK NewId:

表中没有其他唯一标识符列。

如果我从 SQL Management Studio 的编辑网格中添加行,则无需指定 NewId 值即可添加行。

那么,为什么 SQL Server 触发器会尝试使用INSERT INTO 子句中的第一项填充NewId,而不是跳过它以让正常的IDENTITY 操作提供值?

(以及如何阻止这种情况发生以使触发器起作用?)

【问题讨论】:

    标签: sql sql-server triggers uniqueidentifier database-trigger


    【解决方案1】:

    因为自动跳过仅适用于 IDENTITY 列 - 设置有 NewSequentialId() 约束的 GUID 列在许多方面与 IDENTITY 类似,但不是这一点。

    您可以通过明确指定 INSERT 的列来实现您要查找的内容。

    【讨论】:

    • 所以 uniqueidentifier 列不能是 IDENTITY 列?哇……这就解释了。我将其更改为 int。
    【解决方案2】:

    如果您要在 NewId 列上使用默认值,则需要在 INSERT 语句中明确列出列名。默认情况下,SQL Server 将按照它们在SELECT 中列出的顺序插入列,除非您为其提供足够的信息来执行其他操作。以一种或另一种方式明确列出列是一种最佳做法,以避免出现这种意料之外的结果。

    所以你的陈述最终会是这样的:

    INSERT INTO eventGroup_History
      (
        DoneBy,
        Operation,
        DoneAt,
        <All the other columns that are masked by the *>
      )
    SELECT....
    

    【讨论】:

    • 我正在尝试为数据库中的许多表自动生成触发器,这就是为什么我没有指定列名而是使用“*” - 我不想去通过动态确定所有列名的复杂性。感谢您的回答和对我的问题的编辑 - 它大大提高了可读性。
    • 对于自动化部分,您可以动态构建触发器并从系统表中提取列名。这需要时间投入,但可能比所有打字都少。
    猜你喜欢
    • 2013-10-12
    • 1970-01-01
    • 1970-01-01
    • 2017-04-26
    • 2011-03-08
    • 2013-10-05
    • 2020-08-29
    • 2011-05-14
    • 1970-01-01
    相关资源
    最近更新 更多