我要求在调用UPDATE时保留对特定表所做更改的历史记录,但只关注特定列。
所以,我创建了一个历史表:
CREATE TABLE [dbo].[SourceTable_History](
[SourceTable_HistoryID] [int] IDENTITY(1,1) NOT NULL,
[SourceTableID] [int] NOT NULL,
[EventDate] [date] NOT NULL,
[EventUser] [date] NOT NULL,
[ChangedColumn] VARCHAR(50) NOT NULL,
[PreviousValue] VARCHAR(100) NULL,
[NewValue] VARCHAR(100) NULL
CONSTRAINT pk_SourceTable_History PRIMARY KEY ([SourceTable_HistoryID]),
CONSTRAINT fk_SourceTable_HistoryID_History_Source FOREIGN KEY ([SourceTableID]) REFERENCES SourceTable (SourceTableId)
)
Abd我的计划是在SourceTable上创建一个Update触发器。业务只关心某些列的更改,因此,在psudo代码中,我计划做类似的事情
If source.Start <> new.start
Insert into history (PrimaryKey, EventDate, EventUser, ColumnName, OldValue, NewValue)
(PK, GETDATYE(), updateuser, "StartDate", old.value, new.value)
每列我们想要历史就会有一个像这样的块。
我们不允许使用CDC,因此我们必须推出自己的CDC,这是我目前的计划。
这看起来是否合适?
我们需要监控7个表,每个表的列数为2到5列。
我只需要弄清楚如何获取触发器以首先comapr特定列的前后值,然后写一个新行。
我认为这很简单:
CREATE TRIGGER tr_PersonInCareSupportNeeds_History
ON PersonInCareSupportNeeds
FOR UPDATE
AS
BEGIN
IF(inserted.StartDate <> deleted.StartDate)
BEGIN
INSERT INTO [dbo].[PersonInCareSupportNeeds_History]
([PersonInCareSupportNeedsID], [EventDate], [EventUser], [ChangedColumn], [PreviousValue], [NewValue])
VALUES
(inserted.[PersonInCareSupportNeedsID], GETDATE(), [LastUpdateUser], 'StartDate', deleted.[StartDate], deleted.[StartDate])
END
END
答案 0 :(得分:2)
我们有基于触发器的审计系统,我们基本上通过分析如何生成审计触发器的第三方工具ApexSQL Audit创建触发器并管理存储并基于此开发我们自己的系统来创建它。
我认为您的解决方案通常没问题,但您需要考虑稍微修改存储并计划扩展。
如果业务决定跟踪所有表中的所有列,该怎么办?如果他们决定跟踪插入和删除怎么办?您的解决方案能否适应这种情况?
存储:使用两个表来保存数据。一个表用于保存有关事务的所有信息(何时,谁,应用程序名称,表名称,模式名称,受影响的行等...)。另一个表用于保存实际数据(值和主键之前和之后等)。
触发器:我们最终得到了一个插入,更新和删除触发器的模板以及非常简单的C#app,我们输入表和列,以便应用程序输出DDL。这为我们节省了很多时间。
答案 1 :(得分:1)
根据您的要求,我认为历史记录表应该反映您要捕获的表格,以及额外的审计详细信息(谁,何时,为什么)。
这可以更容易地使用相同的现有逻辑(sql,数据类,屏幕等)来查看历史数据。
使用您的设计获取数据是否可以,但以可用格式提取数据有多容易?
答案 2 :(得分:1)
嗯,我认为你的想法并不是那么糟糕。实际上,我在生产中有类似的系统。我不会给你我完整的代码(保存不同的历史记录),但我可以给你一些指导。
主要想法是将数据从关系模型转换为Entity-Attribute-Value model。此外,我们希望我们的触发器尽可能通用,这意味着 - 不要显式写入列名。这可以通过不同的方式完成,但我在SQL Server中最常用的是使用FOR XML然后从xml中选择:
declare @Data xml
select @Data = (select * from Test for xml raw('Data'))
select
T.C.value('../@ID', 'bigint') as ID,
T.C.value('local-name(.)', 'nvarchar(128)') as Name,
T.C.value('.', 'nvarchar(max)') as Value
from @Data.nodes('Data/@*') as T(C)
要获得两个表的不同行,您可以使用EXCEPT:
select * from Test1 except select * from Test2
union all
select * from Test2 except select * from Test1
最后,你的触发器可能是这样的:
create trigger utr_Test_History on Test
after update
as
begin
declare @Data_Inserted xml, @Data_Deleted xml
select @Data_Inserted =
(
select *
from (select * from inserted except select * from deleted) as a
for xml raw('Data')
)
select @Data_Deleted =
(
select *
from (select * from deleted except select * from inserted) as a
for xml raw('Data')
)
;with CTE_Inserted as (
select
T.C.value('../@ID', 'bigint') as ID,
T.C.value('local-name(.)', 'nvarchar(128)') as Name,
T.C.value('.', 'nvarchar(max)') as Value
from @Data_Inserted.nodes('Data/@*') as T(C)
), CTE_Deleted as (
select
T.C.value('../@ID', 'bigint') as ID,
T.C.value('local-name(.)', 'nvarchar(128)') as Name,
T.C.value('.', 'nvarchar(max)') as Value
from @Data_Deleted.nodes('Data/@*') as T(C)
)
insert into History (Table_Name, Record_ID, Event_Date, Event_User, Column_Name, Value_Old, Value_New)
select 'Test', isnull(I.ID, D.ID), getdate(), system_user, isnull(D.Name, I.Name), D.Value, I.Value
from CTE_Inserted as I
full outer join CTE_Deleted as D on D.ID = I.ID and D.Name = I.Name
where
not
(
I.Value is null and D.Value is null or
I.Value is not null and D.Value is not null and I.Value = D.Value
)
end