SQL历史记录表和触发器

时间:2013-07-26 04:02:31

标签: sql sql-server

我要求在调用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

3 个答案:

答案 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)

SQL FIDDLE EXAMPLE

要获得两个表的不同行,您可以使用EXCEPT

select * from Test1 except select * from Test2
union all
select * from Test2 except select * from Test1

SQL FIDDLE EXAMPLE

最后,你的触发器可能是这样的:

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

SQL FIDDLE EXAMPLE

相关问题