【问题标题】:Trigger AFTER INSERT results can't insert NULL in log table触发器 AFTER INSERT 结果无法在日志表中插入 NULL
【发布时间】:2017-12-12 18:03:05
【问题描述】:

我试图在插入名为 PersonalInfo 的表后编写一个触发器。 GenderID 和 id 是表 Gender 和 RegisterationForm 的外键。

DBLog 是我用来记录触发器操作的表。

这是我写的:

Create Trigger [dbo].[InsertPersonalInfoTrigger] ON [dbo].[PersonalInfo]
after Insert
as
begin
declare @Name nvarchar(50)
declare @FamilyName nvarchar(50)
declare @FatherName nvarchar(50)
declare @BirthDate nchar(10)
declare @GenderID int
declare @NationalId nvarchar(50)
declare @id int


select @Name=Max(@Name),@FamilyName=Max(@FamilyName),@FatherName=Max(@FatherName),@BirthDate=Max(@BirthDate)
,@GenderID=Max(@GenderID),@NationalId=Max(@NationalId),@id=Max(@id)
    from  Inserted 
if(@Name='')
begin
    RAISERROR ('Enter the Name', 
           16, 
           1   
           ); 
    RollBack   
    return;
end
declare @LogID int
set @LogID= (select isnull(max(LogID),0) from DBLog) +1

declare @ActionDes varchar(2000);
set @ActionDes = 'Insert Into Gender INNER JOIN
                  PersonalInfo ON Gender.GenderID = PersonalInfo.GenderID INNER JOIN
                  RegisterationForm ON PersonalInfo.id = RegisterationForm.id(Name,FamilyName,FatherName,BirthDate,GenderID,NationalId,id) 
                  Values("' + @Name + '","' + @FamilyName + '","' + @FatherName + '","' + @BirthDate + '",'+cast(@GenderID as varchar(20))+ ',
                  "' + @NationalId +'",'+cast(@id as varchar(20))+ ')';
declare @dt datetime;
set @dt=getdate();

declare @usr varchar(50);
select @usr =current_user  

insert into DBLog(LogID,ActionDes,ActionTime,ActionUser) 
    values (@LogID,@ActionDes,@dt,@usr)
end
Go

当我尝试将数据插入到我的 PersonalInfo 表时执行触发器后,我收到以下错误:

没有更新任何行

第5行的数据没有提交

错误来源:.Net SqlClient 数据提供程序。

错误消息:无法将值 NULL 插入“ActionDes”列, 表'.dbo.DBLog';列不允许空值。INSERT 失败。

警告:空值被聚合或其他 SET 操作消除。

语句已终止。

确实,我不允许 NULL 进入我的 ActionDes 列,但我在这里插入数据。 NULL 来自哪里?有什么问题?

【问题讨论】:

  • 这里有很多问题。它说“第 5 行”这一事实表明您正在插入多行,但是您将触发器编码为好像 inserted 表只有一行?现在,我建议在日志表字段中允许 NULL 以帮助您进行调试。此外,建议您单独存储每个字段,而不是组成一个伪 sql (使用 " 分隔字符串意味着它永远无法执行) 来保存所有值。你不实际上知道这就是 INSERT 语句的样子,你在猜测。不要猜测,存储你知道的内容。
  • @MatBailie 我在创建触发器之前插入了 4 行,这就是为什么它说第 5 行。你能给我举个例子来说明如何分别存储每个字段吗?

标签: sql-server database sql-server-2008 triggers


【解决方案1】:

不要存储组成“假装”插入语句的字符串。插入表格的方法有很多种,您猜测使用的格式,这是个坏主意。

您还假设只插入了一行。这是一个有严重缺陷的假设,重新编码以从inserted 表中读取多行。

在日志记录中允许NULLs。如果name 字段不应该是[personalInfo] 表中的NULL,则将字段设为NOT NULL,不要使用触发器来强制执行检查约束。

不要从日志表中读取 id 然后递增值,将 id 列设为 IDENTITY 列并让数据库自行管理。您有一个竞争条件,您可以读取该值,然后其他一些进程插入一行,然后您尝试插入您的行并产生冲突。这是一个非常糟糕的做法。

CREATE [dbo].[InsertPersonalInfoTrigger] ON [dbo].[PersonalInfo]
  AFTER INSERT
AS
BEGIN

    INSERT INTO
      DBLog (
        ActionDes,
        ActionTime,
        ActionUser
      )
    SELECT
      ISNULL('"' + CAST(inserted.ID         AS NVARCHAR(128) + '"', 'NULL') + ' AS id, ' +
      ISNULL('"' + CAST(inserted.Name       AS NVARCHAR(128) + '"', 'NULL') + ' AS name, ' +
      ISNULL('"' + CAST(inserted.FamilyName AS NVARCHAR(128) + '"', 'NULL') + ' AS FamilyName, ' +
      ISNULL('"' + CAST(inserted.FatherName AS NVARCHAR(128) + '"', 'NULL') + ' AS FatherName, ' +
      ISNULL('"' + CAST(inserted.BirthDate  AS NVARCHAR(128) + '"', 'NULL') + ' AS BirthDate, ' +
      ISNULL('"' + CAST(inserted.GenderID   AS NVARCHAR(128) + '"', 'NULL') + ' AS GenderID, ' +
      ISNULL('"' + CAST(inserted.NationalId AS NVARCHAR(128) + '"', 'NULL') + ' AS NationalID',
      GETDATE(),
      CURRENT_USER
    FROM
      inserted
      -- There could be many rows in this table, don't pretend or assume there will only be one
END

GO

更好的是,您从[PersonalInfo] 记录的每个字段都有单独的日志字段。即使这意味着拥有一个通用的 DBLog 表和一个单独的 DBLogPersonalInfo 表。

CREATE [dbo].[InsertPersonalInfoTrigger] ON [dbo].[PersonalInfo]
  AFTER INSERT
AS
BEGIN

    INSERT INTO
        DBLog (
            ActionDes,
            ActionTime,
            ActionUser
        )
    VALUES (
      'INSERT INTO [dbo].[PersonalInfo]',
      GETDATE(),
      CURRENT_USER
    );

    INSERT INTO
        DBLogPersonalInfo (
            DBLogID,
            id,
            Name,
            FamilyName,
            FatherName,
            BirthDate,
            GenderID,
            NationalID
        )
    SELECT
      SCOPE_IDENTITY(),  -- The latest value from the IDENTITY column of `DBlog` (as created by THIS process).
      inserted.ID,
      inserted.Name,
      inserted.FamilyName,
      inserted.FatherName,
      inserted.BirthDate,
      inserted.GenderID,
      inserted.NationalID
    FROM
      inserted

    -- No need for `CAST` or `ISNULL`.
    -- Now you can even join DBLogPersonalInfo to PersonalInfo and compare changes (programatically)
    -- Simpler, more reliable, and more flexible...

END

GO

【讨论】:

    【解决方案2】:

    如果我正确理解您的触发器,那么对于插入表中的每一行,您正在设置 @variable = Max(@variable)。由于您没有为变量赋值,因此所有变量值仍然为空。

    连接规则要求与空值的连接等于空值,因此如果任何参数值为空,您的 @ActionDes 变量最终会为空值,这就是当您尝试插入时抛出错误的原因到 DBLog 表中。

    【讨论】:

    • 很好@Wes H。我在“Max(@variable)”上阅读,改为将其阅读为 MAX()。我赞成你的回应! :)
    • 是的,应该是select @Name=Max(Name),@FamilyName=Max(FamilyName)... 工作,但@MatBailie 也有很好的建议。它不是针对插入表中的每一行,而是在插入表中的所有行中聚合。所以单行插入还可以,多行插入就一团糟。
    • 但是我对另一个没有任何外键的表使用了相同的触发器,它工作正常并且 DBLog 表数据是正确的。
    • @FreeSoul - 此外,您不能插入连接表。插入只能针对单个表。但是,您可以从带有连接的查询中获取值。
    • @FreeSoul @name = MAX(@name)@name = MAX(name) 之间有很大的不同。当您实际上花时间在字段名称前加上表名时,更容易发现这样的错字。 @name = MAX(inserted.@name) 在您看来应该是错误的,并且还会抛出错误。
    【解决方案3】:

    有几点:

    1. 这是一篇关于“Warning: Null value is eliminated by an aggregate or other SET operation.”的好文章
    2. 您的触发器是在插入之后写入的,而不是在更新之后写入的。
    3. 最后,您假设您的所有值 (MAX()) FROM INSERTED 都将为非空值。这可能不是真的。尝试在每个变量周围使用 ISNULL(, N'') 或 COALESCE(,N'') 聚合 @actionDes,我认为您将摆脱“错误消息:无法将值 NULL 插入到列 'ActionDes' ,表 '.dbo.DBLog';列不允许空值。INSERT 失败。"

    我不确定您为什么要在 INSERTED 上执行聚合,但我所描述的内容我相信会解决您提到的问题。

    【讨论】:

      猜你喜欢
      • 2023-02-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多