2

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

已插入

Id  Name    Surname
1   Stack   Overflow
2   Luigi   Saggese

已删除

Id  Name    Surname
1   Stacks  Overflow
2   Luigi   Sag

我想将此更新捕获到日志表中。我的日志表(对所有表都是全局的)是这样的(然后我必须处理我的 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 isUPDATEd 列的主键的值,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)
    

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

4

1 回答 1

3

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

另外,您知道 SQL Server 2008变更数据捕获吗?如果您使用的是 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 and up do have the native UNPIVOT command, though in general, even when UNPIVOT will work, I like using CROSS APPLY instead because there is more flexibility to do what I want. Specifically, the native UNPIVOT command isn't workable here because UNPIVOT can only target a single destination column, but you need two (Old_Value, New_Value). Concatenating the two columns into a single value (and separating later) is not good; creating a meaningless row correlator value to PIVOT with afterward is not good, and I can't think of another way to do it that's not a variation on those two. The CROSS APPLY solution is truly going to be the best for you to match the exact log table structure you've described.

Compared to my queries here, your method #1 will not perform as well (in a ratio of about {the number of columns}:1 worse performance). Your method #2 is a good idea but still suboptimal because calling a UDF has a large overhead, plus then you have to loop over each row (shudder).

于 2012-08-03T05:41:53.270 回答