【问题标题】:Logging update operation in triggers在触发器中记录更新操作
【发布时间】:2012-07-31 09:45:22
【问题描述】:

我有一个 UPDATE 触发器,它会生成如下所示的 INSERTED 和 DELETED 表:

插入

Id  Name    Surname
1   Stack   Overflow
2   Luigi   Saggese

已删除

Id  Name    Surname
1   Stacks  Overflow
2   Luigi   Sag

我想将此更新记录到日志表中。我的 Log 表(对所有表都是全局的)是这样的(然后我必须处理我的 INSERTED 和 DELETED 表):

Id_Table    Table_Key_Value   Id_Value   Old_Value  New_Value
12345               1          4556645    Stack      Stacks
12345               1           544589   Overflow   Overflows
12345               2           544589   Saggese       Sag

Id_Table 是我执行了 UPDATE 语句的表的系统 object_id,Table_Key_Value is 是 UPDATEd 列的主键的值,Id_Value 是我映射到每个表中的每个列的自定义 ID。仅当列被 UPDATE 更改时,才会记录列的数据。

我想到了两种方法:

  1. 对表执行一次SELECT,每列一次:

    INSERT INTO LOG (Id_Table, Table_Key_Value, Id_Value,Old_Value, New_Value)
       SELECT 12345, Id, 4556645, D.Name, I.Name
       FROM INSERTED I 
       INNER JOIN DELETED D ON I.ID = D.ID
       WHERE D.Name <> I.Name
    
       union
    
       SELECT 12345, Id, 544589, D.Surname, I.Surname
       FROM INSERTED I 
       INNER JOIN DELETED D ON I.ID = D.ID
       WHERE D.Surname <> I.Surname
    
  2. 对 UDF 执行单次选择:

    SELECT CustomFunction(12345,Id, I.Name, D.Name, I.Surname, D.Surname) 
    FROM INSERTED I  
    INNER JOIN DELETED D ON I.ID = D.ID
    
    **CustomFunction** (_Id_Table,_Table_Key_Value, _Old_Value_Name, _New_Value_Name, _Old_Value_Surname, _New_Value_Surname)
    
    INSERT INTO LOG(Id_Table, Table_Key_Value, Id_Value,Old_Value, New_Value)
    VALUES(_Id_Table,_Table_Key_Value, 4556645, _Old_Value_Name, _New_Value_Name)
    
    INSERT INTO LOG(Id_Table, Table_Key_Value, Id_Value,Old_Value, New_Value)
    VALUES(_Id_Table,_Table_Key_Value, 544589, _Old_Value_Surname, _New_Value_Surname)
    

还有其他方法可以做到这一点吗?什么是最有效和可维护的方式?

【问题讨论】:

  • 您正在使用任何特定的 DBMS? SQL Server、Oracle、MySQL、PostGreSql、SQLite?

标签: sql triggers sql-server-2012


【解决方案1】:

在回答之前,让我先说一下,我认为最好不要将所有表记录到单个表中。如果您的数据库增长,您最终可能会在 Log 表上出现严重的争用。另外,您的所有数据都必须更改为 varchar 或 sql_variant 才能放在同一列中,从而迫使它占用更多空间。我还认为,将每个更新的列记录到单独的行(跳过未更新的列)会让您非常难以查询。您是否知道如何将所有这些数据汇总在一起,以实际获得每行更改、何时以及由谁更改的综合且合理的视图?在我看来,每个表都有一个日志表会容易得多。这样您就不会遇到在尝试使其正常工作时遇到的问题。

另外,您知道 SQL Server 2008 Change Data Capture 吗?如果您使用的是 SQL Server 的 Enterprise 或 Developer 版本,请改用它!

除了这个问题之外,您还可以使用逻辑 UNPIVOT(执行您自己的版本)来做您想做的事情。您不能真正使用 Native SQL 2005 UNPIVOT,因为您有两个目标列,而不是一个。这是 SQL Server 2005 及更高版本使用 CROSS APPLY 执行 UNPIVOT 的示例:

INSERT INTO dbo.LOG (Id_Table, Table_Key_Value, Id_Value, Old_Value, New_Value)
SELECT 12345, I.Id, X.Id_Value, X.Old_Value, X.New_Value
FROM
   INSERTED I 
   INNER JOIN DELETED D ON I.ID = D.ID
   CROSS APPLY (
      SELECT 4556645, D.Name, I.Name
      UNION ALL SELECT 544589, D.Surname, I.Surname
    ) X (Id_Value, Old_Value, New_Value)
WHERE
   X.Old_Value <> X.New_Value

对于 SQL 2000 或其他 DBMS 来说,这是一种更通用的方法(理论上应该在 Oracle、MySQL 等中工作——对于 Oracle,将 FROM DUAL 添加到派生表中的每个 SELECT 中):

INSERT INTO dbo.LOG (Id_Table, Table_Key_Value, Id_Value, Old_Value, New_Value)
SELECT *
FROM (
   SELECT
      12345,
      I.Id,
      X.Id_Value,
      CASE X.Id_Value
         WHEN 4556645 THEN D.Name
         WHEN 544589 THEN D.Surname
      END Old_Value,
      CASE X.Id_Value
         WHEN 4556645 THEN I.Name
         WHEN 544589 THEN I.Surname
      END New_Value   
   FROM
      INSERTED I 
      INNER JOIN DELETED D ON I.ID = D.ID
      CROSS JOIN (
         SELECT 4556645
         UNION ALL SELECT 544589
      ) X (Id_Value)
) Y
WHERE
   Y.Old_Value <> Y.New_Value

SQL Server 2005 及更高版本确实具有本机 UNPIVOT 命令,但总的来说,即使 UNPIVOT 可以工作,我也喜欢使用 CROSS APPLY 代替,因为可以更灵活地执行我想要的操作。具体来说,本机 UNPIVOT 命令在此处不可用,因为 UNPIVOT 只能针对单个目标列,但您需要两个(Old_Value、New_Value)。将两列连接成一个值(然后再分开)不好;之后为 PIVOT 创建一个无意义的行相关器值并不好,我想不出另一种方法来做到这一点,这不是这两者的变化。 CROSS APPLY 解决方案确实是最适合您匹配您所描述的确切日志表结构的解决方案。

与我在此处的查询相比,您的方法 #1 的性能不会那么好(以大约 {the number of columns}:1 更差的性能比率)。您的方法 #2 是一个好主意,但仍然不是最理想的,因为调用 UDF 的开销很大,而且您必须遍历每一行(不寒而栗)。

【讨论】:

  • 对于您的第一个问题,我使用了一个表格来映射所有列和表格以及我自己的 ID。这样,如果我要更改数据库,我就没有问题(娱乐表或其他......)。对于你的第二个问题,我有单个表的日志和这个唯一的表日志供我内部使用(它不是太多......我对此没有任何看法,只是一个分析它的 Windows 服务)。那么,如何在 MS SQL Server 2012 上使用 UNPIVOT 命令?这个命令执行?
  • 我在这里向您展示的方法将为 UNPIVOT 提供同等或更好的性能。另外,UNPIVOT 仅适用于一个目标列,因此您必须在之后进行 PIVOT(您只需要 partial 取消数据透视。我没有时间给您至少 17 小时的 PIVOT 版本.
猜你喜欢
  • 2021-06-30
  • 2017-07-22
  • 1970-01-01
  • 1970-01-01
  • 2021-10-16
  • 1970-01-01
  • 2012-01-08
  • 1970-01-01
  • 2013-06-11
相关资源
最近更新 更多