【问题标题】:SQL Server Trigger on Specified Columns指定列上的 SQL Server 触发器
【发布时间】:2021-08-18 19:50:30
【问题描述】:

嘿,

我正在尝试更新SQL Server 2014 表中的一些列,并在trigger 上编写了一些代码,然后使用以下代码过程将新值插入到新表中:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Atrin Noori
-- =============================================
ALTER TRIGGER [dbo].[UsersOnUPDATE] 
   ON  [dbo].[Users]
   After UPDATE
AS 
BEGIN
    SET NOCOUNT ON;
    --
    DECLARE @user_key int
    SELECT @user_key = i.User_key FROM inserted i;
    --  
    IF UPDATE (User_fullname) 
    BEGIN
        DECLARE @newfullname nvarchar(MAX);
        DECLARE @oldfullname nvarchar(MAX);
        --
        SELECT @oldfullname = i.User_fullname FROM deleted i;
        SELECT @newfullname = i.User_fullname FROM inserted i;
        --
        INSERT INTO UsersLogs (UL_user_key_r, UL_date, UL_oldname, UL_newname, UL_IsDeleted)
        VALUES (@user_key, GETDATE(), @oldfullname, @newfullname, 0)
    END
    --
    ELSE IF UPDATE (User_password) 
    BEGIN
        DECLARE @newpassword nvarchar(10);
        DECLARE @oldpassword nvarchar(10);      
        -- 
        SELECT @oldpassword = i.User_password FROM deleted i;
        SELECT @newpassword = i.User_password FROM inserted i;
        --
        INSERT INTO UsersLogs (UL_user_key_r,UL_date,UL_oldpass,UL_newpass,UL_IsDeleted)
        VALUES (@user_key, GETDATE(), @oldpassword, @newpassword, 0)
    END 
    --
    ELSE IF UPDATE (User_username) 
    BEGIN
        DECLARE @newusername nvarchar(10);
        DECLARE @oldusername nvarchar(10);
        --
        SELECT @oldusername = i.User_username FROM deleted i;
        SELECT @newusername = i.User_username FROM inserted i;
        --
        INSERT INTO UsersLogs (UL_user_key_r, UL_date, UL_oldusername, UL_newusername, UL_IsDeleted)
        VALUES (@user_key, GETDATE(), @oldusername, @newusername, 0)
    END
    --
    ELSE IF UPDATE (User_position_id_r) 
    BEGIN
        DECLARE @newposid tinyint;
        DECLARE @oldposid tinyint;
        --
        SELECT @oldposid = i.User_position_id_r FROM deleted i;
        SELECT @newposid = i.User_position_id_r FROM inserted i;
        --
        INSERT INTO UsersLogs (UL_user_key_r, UL_date, UL_oldPosid, UL_newPosid, UL_IsDeleted)
        VALUES (@user_key, GETDATE(), @oldposid, @newposid, 0)
    END
END

当我手动更改每列中的值并插入到名为UsersLogs 的新表中时,此触发器工作正常。 但是,当我使用我正在开发的 c# 应用程序来更新值时,它不能正常工作...

考虑:

我正在尝试通过我的应用程序更改用户的密码 并使用Stored Procedure

Stored Procedure 工作正常并更新值,UserOnUpdate 的触发器将oldoldnew 值(不是旧密码和新密码)插入到新表(UsersLgos) 并将其他表设置为null(可以为null)。

我的意思是只有这部分代码会运行:

IF UPDATE (User_fullname) 
    BEGIN
        DECLARE @newfullname nvarchar(MAX);
        DECLARE @oldfullname nvarchar(MAX);
        --
        SELECT @oldfullname = i.User_fullname FROM deleted i;
        SELECT @newfullname = i.User_fullname FROM inserted i;
        --
        INSERT INTO UsersLogs (UL_user_key_r, UL_date, UL_oldname, UL_newname, UL_IsDeleted)
        VALUES (@user_key, GETDATE(), @oldfullname, @newfullname, 0)
    END

根据我刚才所说的...我更改了密码并设置了一个条件来检查更新的列。

但问题是为什么触发器无法意识到通过应用程序更新了哪一列?

注意:

我再说一遍:手动更改和更新可以正常工作

【问题讨论】:

  • 一般deletedinserted 包含一组 记录,而不仅仅是一个记录。因此,您将SELECTs 从它们转换为标量变量通常没有意义。您首先需要先修复这个根本缺陷。
  • 但在手动更新时效果很好

标签: c# sql sql-server sql-server-2014


【解决方案1】:

update() 触发函数,如果列更新,即使具有相同的值也返回true。 所以看起来当您更新密码时,您可能正在更新 User_fullname 列和其他列(当然与以前的值相同)。所以 UPDATE (User_fullname) 返回 true 。

但是,您编写代码的方式也是如此,触发器适用于仅更新一列并且按照代码中的顺序更新的情况,例如,如果 UPDATE (User_fullname) 为 true ,则您的代码不会检查其他条件,因为ELSE IF。您可能想删除 else 并检查每一列,或者完全改变您的策略以在触发器内记录数据。

然后根据您的评论,摆脱所有 if else 并为所有列使用一个类似这样的插入语句:

INSERT INTO UsersLogs (UL_user_key_r,UL_date,UL_oldpass,UL_newpass,<all columns>)
select (@user_key, GETDATE(), case when deleted.password <> new.password then deleted.password else null end , case when deleted.password <> new.password then inserted.password else null end , ....)
from inserted i
join deleted d on i.userkey = d.userkey

【讨论】:

  • 是的,我怀疑并删除了“ELSE”部分,猜猜发生了什么?它在日志表中插入了 8 行,这很好,因为它可以记录所有更改,但是......我想将我的数据库保持在尽可能少的类型......所以其他人将被设置为 null,但我没有我的表不想要 8 行
  • 我认为错误在于存储过程,因为当我用自己的值替换一个值时,它不会将其视为更新或更改,但是当存储过程用它自己的列更新列时值它认为它是一个更新......所以我改变了代码的一些部分..我删除了else部分并设置了另一个条件来检查old value是否等于new value,如果它不是插入日志,它将 8 行减少到 3 行甚至更少,或者其他取决于更改的列,但如果我找到一种方法将所有更改插入一行,那就太好了:)
  • 好的,我刚看到你的更新版本,让我检查一下我认为它工作正常
  • @Charlieface 是的,但我数据库中的用户每人只有一行,这对我来说很好
  • @AtrinNoori 我无论如何都更新了我的答案以反映这一点,
【解决方案2】:

UPDATE() 函数仅告诉您列是否在UPDATE 语句存在,而不是值是否实际更改。此外,inserteddeleted可能包含多行,也可能不包含任何行

所以,你的触发器应该是这样的:

ALTER TRIGGER [dbo].[UsersOnUPDATE] 
   ON  [dbo].[Users]
   After UPDATE
AS 

SET NOCOUNT ON;

IF NOT (UPDATE(User_fullname) OR UPDATE(User_password) OR UPDATE(User_username) OR UPDATE(User_position_id_r))
    RETURN;  -- this only tells you if the column was present

INSERT INTO UsersLogs
    (UL_user_key_r, UL_date, UL_IsDeleted,
    UL_oldname, UL_newname,
    UL_oldpass, UL_newpass,
    UL_oldusername, UL_newusername,
    UL_oldPosid, UL_newPosid)
SELECT
    i.user_key, GETDATE(), 0,
    d.fullname, i.fullname,
    d.User_password, i.User_password,
    d.User_username, i.User_username,
    d.User_position_id_r, i.User_position_id_r
FROM inserted i
JOIN deleted d ON i.user_key = d.user_key
WHERE EXISTS (    -- this checks for any differences
    SELECT i.fullname, i.User_password, i.User_username, i.User_position_id_r
    EXCEPT        -- this will deal with nulls correctly
    SELECT d.fullname, d.User_password, d.User_username, d.User_position_id_r
);

如果只有一些列被更新,这确实会让其他列具有相同的前后值。但是一些CASE 表达式应该可以解决这个问题。

ALTER TRIGGER [dbo].[UsersOnUPDATE] 
   ON  [dbo].[Users]
   After UPDATE
AS 

SET NOCOUNT ON;

IF NOT (UPDATE(User_fullname) OR UPDATE(User_password) OR UPDATE(User_username) OR UPDATE(User_position_id_r))
    RETURN;  -- this only tells you if the column was present

INSERT INTO UsersLogs
    (UL_user_key_r, UL_date, UL_IsDeleted,
    UL_oldname, UL_newname,
    UL_oldpass, UL_newpass,
    UL_oldusername, UL_newusername,
    UL_oldPosid, UL_newPosid)
SELECT
    i.user_key, GETDATE(), 0,
    CASE WHEN d.fullname <> i.fullname THEN d.fullname END, CASE WHEN d.fullname <> i.fullname THEN i.fullname END
    CASE WHEN d.User_password <> i.User_password THEN d.User_password END, CASE WHEN d.User_password <> i.User_password THEN i.User_password END,
    CASE WHEN d.User_username <> i.User_username THEN d.User_username END, CASE WHEN d.User_username <> i.User_username THEN i.User_username END,
    CASE WHEN d.User_position_id_r <> i.User_position_id_r THEN d.User_position_id_r END, CASE WHEN d.User_position_id_r <> i.User_position_id_r THEN i.User_position_id_r END
FROM inserted i
JOIN deleted d ON i.user_key = d.user_key
WHERE EXISTS (    -- this checks for any differences
    SELECT i.fullname, i.User_password, i.User_username, i.User_position_id_r
    EXCEPT        -- this will deal with nulls correctly
    SELECT d.fullname, d.User_password, d.User_username, d.User_position_id_r
);

如果您希望每个更改的值都有单独的行,您可以使用 CROSS APPLY 取消透视

【讨论】:

  • 这有一个问题,那就是它填充了所有的列并且没有将未更改的列设置为空
  • 好的,给你CASE 解决方案
  • :) tnQ 是的,它工作得很好,所以问题是 UPDATE() 关注当前的专栏......很好
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多