4

当调用 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)
)

我的计划是在 SourceTable 上创建一个更新触发器。业务只关心对某些列的更改,所以,在伪代码中,我打算做类似的事情

If source.Start <> new.start
Insert into history (PrimaryKey, EventDate, EventUser, ColumnName, OldValue, NewValue)
(PK, GETDATYE(), updateuser, "StartDate", old.value, new.value)

我们想要历史记录的每一列都会有一个类似的块。

我们不允许使用 CDC,所以我们必须自己动手,这是我目前的计划。

这似乎是一个合适的计划?

我们需要监控 7 个表,每个表的列数在 2 到 5 列之间。

我只需要弄清楚如何让触发器首先对特定列的前后值进行比较,然后写入新行。

我认为这很简单:

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
4

3 回答 3

2

我们有基于触发器的审计系统,我们基本上通过分析生成审计触发器的第三方工具 ApexSQL Audit 如何创建触发器和管理存储并在此基础上开发我们自己的系统来创建它。

我认为您的解决方案通常没问题,但您需要考虑稍微修改存储并计划扩展。

如果企业决定跟踪所有表中的所有列怎么办?如果他们决定同时跟踪插入和删除怎么办?您的解决方案能够适应这种情况吗?

存储:使用两个表来保存您的数据。一张表,用于保存有关事务的所有信息(何时、谁、应用程序名称、表名、模式名称、受影响的行等……)。还有另一个表来保存实际数据(之前和之后的值、主键等)。

触发器:我们最终得到了一个用于插入、更新和删除触发器的模板以及非常简单的 C# 应用程序,我们在其中输入表和列,以便应用程序输出 DDL。这为我们节省了很多时间。

于 2013-07-26T09:50:27.920 回答
1

根据您的要求,我认为历史表应该反映您要捕获的表,以及额外的审计细节(谁、何时、为什么)。

这可以更容易地使用相同的现有逻辑(sql、数据类、屏幕等)来查看历史数据。

在您的设计中获取数据是可以的,但是以可用的格式提取数据有多容易?

于 2013-07-26T04:07:39.793 回答
1

好吧,我认为您的想法还不错。实际上,我在生产中也有类似的系统。我不会给你我的完整代码(带有异步历史保存),但我可以给你一些指导。

主要思想是将您的数据从关系模型转换为实体-属性-值模型。此外,我们希望我们的触发器尽可能通用,这意味着 - 不要显式编写列名。这可以通过不同的方式完成,但我在 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 提琴示例

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

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

SQL 提琴示例

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

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 提琴示例

于 2013-07-26T05:00:52.453 回答