我有一个UPDATE触发器,它生成INSERTED和DELETED表,如下所示:
INSERTED
Id Name Surname
1 Stack Overflow
2 Luigi Saggese
DELETED
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
是表的系统object_id,其中我执行了UPDATE语句,Table_Key_Value is
UPDATEd列的主键值,Id_Value
是我映射到每个列的自定义ID每个表中的列。只有在UPDATE更改了列时才会记录列的数据。
我想到了两种方法:
在表格上执行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
针对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)
还有其他方法吗?什么是最有效和可维护的方式?
答案 0 :(得分:3)
在回答之前,我先说我认为最好将所有表都记录到一个表中。如果您的数据库增长,最终可能会在Log表上发生严重争用。另外,所有数据都必须更改为varchar或sql_variant才能放在同一列中,从而迫使它占用更多空间。我还认为将每个更新的列记录到一个单独的行(跳过未更新的列)将使非常难以查询。您是否知道如何将所有数据汇总在一起,以实际获得每行更改的复合和合理视图,何时以及由谁?在我看来,每个表有一个日志表会更容易。那么你就不会遇到试图让它发挥作用的问题。
另外,您是否了解SQL Server 2008 Change Data Capture?如果您使用的是SQL Server的企业版或开发人员版,请使用它!
除了这个问题,您可以使用逻辑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的执行效果不佳(约为{列数}:1表现更差)。你的方法#2是一个好主意,但仍然不是最理想的,因为调用UDF会产生很大的开销,而且你必须遍历每一行(不寒而栗)。