【问题标题】:Trigger for update multi line更新多行触发器
【发布时间】:2010-09-17 15:29:04
【问题描述】:

这个触发器有问题:

ALTER TRIGGER [dbo].[trg_B_U_Login]
   ON [dbo].[Login]
   FOR UPDATE
AS
BEGIN
    IF @@ROWCOUNT = 0
        RETURN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    DECLARE @Campos nvarchar(500);
    DECLARE @Old_DataRemocao nvarchar(20);
    DECLARE @New_DataRemocao nvarchar(20);
    DECLARE @Old_IDCriador nvarchar(10);
    DECLARE @New_IDCriador nvarchar(10);
    SELECT @Campos='';

    IF(Exists(SELECT * FROM deleted WHERE DataRemocao is NULL))
    BEGIN
        SELECT @Old_DataRemocao='NULL';
    END
    ELSE
    BEGIN
        SELECT @Old_DataRemocao=(SELECT * DataRemocao FROM deleted);
    END

    IF(Exists(SELECT * FROM inserted WHERE DataRemocao is NULL))
    BEGIN
        SELECT @New_DataRemocao=NULL;

    END
    ELSE
    BEGIN
        SELECT @New_DataRemocao=(SELECT * DataRemocao FROM inserted);
    END

    IF(@Old_DataRemocao<>@New_DataRemocao)
    BEGIN
        SELECT @Campos=@Campos+'DataRemocao={'+@Old_DataRemocao+' -> ' + @New_DataRemocao +'}; ';
    END

    IF(EXISTS(SELECT * FROM deleted as del, inserted AS ins WHERE ins.Login<>del.Login))
    BEGIN
        SELECT @Campos=@Campos+'Login={'+ del.Login +' -> '+ ins.Login+'}; ' FROM deleted as del, inserted as ins
    END

......

    IF(EXISTS(SELECT * FROM deleted as del, inserted AS ins WHERE ins.DataCriacao<>del.DataCriacao))
    BEGIN
        SELECT @Campos=@Campos+'DataCriacao={'+ del.DataCriacao +' -> '+ ins.DataCriacao+'}; ' FROM deleted as del, inserted as ins
    END

    IF(EXISTS(SELECT * FROM deleted as del, inserted AS ins WHERE ins.Nome<>del.Nome))
    BEGIN
        SELECT @Campos=@Campos+'Nome={'+ del.Nome +' -> '+ ins.Nome+'}; ' FROM deleted as del, inserted as ins
    END

    IF(@Campos<>'')
    BEGIN
        INSERT Login_Hs (IDUtilizador, Tabela, Metodo, Chave, Campos, Data)
            SELECT '1', 'Login', 'UPDATE', 'IDLogin='+Convert(nvarchar,ID),
                @Campos, CONVERT(DATETIME, GETDATE(), 105)
            FROM inserted
    END
END

保存在更新的每一行中所做的更改并由该更新执行

UPDATE Login
SET DataRemocao = CONVERT(datetime,GETDATE(),105)
WHERE IDPerfil = 25 AND DataRemocao IS NULL;

更新不止一行

而 sql server 2008 出现此错误:

Msg 512, Level 16, State 1, Procedure trg_B_U_Login, Line 83
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
The statement has been terminated.

这仅在我更新一行时有效。 我该怎么做才能解决这个问题?

【问题讨论】:

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


    【解决方案1】:

    我发现了两个问题。

    1. 您正试图从 (Select * From) 查询写入 varchar 变量。如果有人出现并在您的表格中添加一列,那将会中断。在选择中指定您的列名。

    2. 如果您的代码一次只更新一行,您的触发器应该可以正常工作。如果您更新的不止这些,则 INSERTED 和 DELETED 表变量会有多行。因此错误。如果您在 Oracle 中工作,您只需将 FOR EACH ROW 选项添加到您的触发器中,代码就会在更改的记录中游标。

    在 MSSQL 中,如果您真的需要这样做,您必须在 DELETED 或 INSERTED 中的记录上打开一个游标并逐一处理这些行。这可能真的很慢。如果我是你,我会尝试重构这段代码以使用集合操作。

    类似这样的:

    ALTER TRIGGER [dbo].[trg_B_U_Login]
       ON [dbo].[Login]
       FOR UPDATE
    AS
    BEGIN
            INSERT Login_Hs (IDUtilizador, Tabela, Metodo, Chave, Campos, Data)
                SELECT '1', 'Login', 'UPDATE', 'IDLogin='+Convert(nvarchar,ID),
                'DataRemocao={'+ coalesce(deleted.campos, 'NULL') + ' -> ' 
                   + coalesce(inserted.campos, 'NULL'),
                   CONVERT(DATETIME, GETDATE(), 105)
                FROM inserted
                  Inner Join deleted on inserted.idcol=deleted.idcol
    END;
    

    【讨论】:

      【解决方案2】:

      好吧,您正试图在可能的多值查询中捕获单个值。一个这样的地方如下:

         SELECT @Old_DataRemocao=(SELECT * DataRemocao FROM deleted);
      

      更新: 鉴于您最近的言论,我想补充一点,您需要尝试这样的事情来审核对登录表的更改:

      insert into Login_Hs (columns)
      select i.col1, i.col2, ..., i.colN
      FROM inserted i
      

      【讨论】:

      • 忘了告诉我要将每一行中所做的所有更改保存在另一个表中
      • 那是“基于集合的语句”对吗?我搜索解决方案,我找到了,但我不明白如何使用它。所以不是我的插入使用它吗?
      • 还有一个问题,如果我只想保存更改的数据?
      • @SlimBoy inserted 是正在插入的新数据,deleted 是旧数据,即被新数据替换;确保您可以加入他们并检测差异或其他任何东西
      • 你能给我一个线索吗?我正在尝试这样做,就像这样 IF(EXISTS(SELECT * FROM deleted as del INNER JOIN 插入 AS ins ON ins.ID=del.ID WHERE ins.PassWorddel.PassWord)) BEGIN SELECT @Campos=@ Campos+'密码={'+ del.PassWord +' -> '+ ins.PassWord+'}; ' FROM 删除为 del INNER JOIN 插入为 ins ON ins.ID=del.ID END 但它不起作用....
      【解决方案3】:

      感谢@Bill,

      我做了这个并且工作! ID、Login、PassWord、IDHomePage、IDSkin、IDPerfil、IDCriador、Email、DataCriacao、DataRemocao、Nome是我的表Login的字段,我保存了。

      INSERT INTO Login_Hs(IDUtilizador、Tabela、Metodo、Chave、Campos、Data) 选择“1”,“登录”,“更新”,“ID=”+转换(nvarchar,i.ID), '登录={'+ 转换(nvarchar,coalesce(d.Login,'NULL')) + '->' + 转换(nvarchar,coalesce(i.Login,'NULL')) + '};' + 'PassWord={'+ coalesce(d.PassWord,'NULL') + ' -> ' + coalesce(i.PassWord,'NULL') + '};' + 'IDHomePage={'+ Convert(nvarchar,coalesce(d.IDHomePage,'NULL')) + ' -> ' + Convert(nvarchar,coalesce(i.IDHomePage,'NULL')) + '};' + 'IDPerfil={'+ 转换(nvarchar,coalesce(d.IDPerfil,'NULL')) + ' -> ' + 转换(nvarchar,coalesce(i.IDPerfil,'NULL')) + '};' + 'IDCriador={'+ 转换(nvarchar,coalesce(d.IDCriador,'NULL')) + ' -> ' + 转换(nvarchar,coalesce(i.IDCriador,'NULL')) + '};' + '电子邮件={'+合并(d.Email,'NULL')+'->'+合并(i.Email,'NULL')+'};' + 'DataCriacao={'+ coalesce(Convert(varchar(20),d.DataCriacao,105),'NULL') + ' -> ' + coalesce(Convert(varchar(20),i.DataCriacao,105),'NULL ') + '};' + 'DataRemocao={'+ coalesce(Convert(varchar(20),d.DataRemocao,105),'NULL') + ' -> ' + coalesce(Convert(varchar(20),i.DataRemocao,105),'NULL ') + '};' + 'Nome={'+ coalesce(d.Nome,'NULL') + ' -> ' + coalesce(i.Nome,'NULL') + '};' , 转换(日期时间,获取日期(),105) FROM 插入我 INNER JOIN 已删除 d ON i.ID=d.ID;

      有什么办法可以只保存不同的吗?

      【讨论】:

      • 我相信最好的做法是将旧版本的更改行存储在历史表中,然后他们在报表生成器中对其进行处理,或者创建一个 xslt 转换来完成除草工作数据并检测更改以很好地输出给最终用户。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-05-24
      • 1970-01-01
      • 2021-03-03
      • 1970-01-01
      • 2011-11-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多