gpt4 book ai didi

sql - 实现字段更改跟踪的模式

转载 作者:太空狗 更新时间:2023-10-30 01:49:03 24 4
gpt4 key购买 nike

对于我最近的一个项目,我必须实现字段更改跟踪。因此,只要用户更改字段的值,更改就会被记录下来,以便对更改进行全面审核。

在数据库中,我将其实现为具有以下字段的单个表 FieldChanges:

  • 表名
  • 字段名
  • 记录编号
  • 变更日期
  • 改变者
  • 整数值
  • 文本值
  • 日期时间值
  • bool 值

保存对象变化的sproc为每个字段判断它是否已经被改变,如果有则插入一条记录到FieldChanges中:如果被改变的字段的类型是int,它记录它在FieldChanges 表中的 IntValue 字段等

这意味着对于具有任何 id 值的任何表中的任何字段,我可以查询 FieldChanges 表以获取更改列表。

这工作得很好,但有点笨拙。其他任何实现过类似功能的人能否提出更好的方法,以及为什么他们认为这种方法更好?

我真的很感兴趣 - 谢谢。

大卫

最佳答案

触发器。

我们编写了一个 GUI(内部称为 Red Matrix Reloaded)以允许轻松创建/管理审计日志记录触发器。

这里是一些使用的东西的 DDL:


审计日志表

CREATE TABLE [AuditLog] (
[AuditLogID] [int] IDENTITY (1, 1) NOT NULL ,
[ChangeDate] [datetime] NOT NULL CONSTRAINT [DF_AuditLog_ChangeDate] DEFAULT (getdate()),
[RowGUID] [uniqueidentifier] NOT NULL ,
[ChangeType] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
[TableName] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
[FieldName] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
[OldValue] [varchar] (8000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
[NewValue] [varchar] (8000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
[Username] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
[Hostname] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
[AppName] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
[UserGUID] [uniqueidentifier] NULL ,
[TagGUID] [uniqueidentifier] NULL ,
[Tag] [varchar] (8000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
)

记录插入的触发器

CREATE TRIGGER LogInsert_Nodes ON dbo.Nodes
FOR INSERT
AS

/* Load the saved context info UserGUID */
DECLARE @SavedUserGUID uniqueidentifier

SELECT @SavedUserGUID = CAST(context_info as uniqueidentifier)
FROM master.dbo.sysprocesses
WHERE spid = @@SPID

DECLARE @NullGUID uniqueidentifier
SELECT @NullGUID = '{00000000-0000-0000-0000-000000000000}'

IF @SavedUserGUID = @NullGUID
BEGIN
SET @SavedUserGUID = NULL
END

/*We dont' log individual field changes Old/New because the row is new.
So we only have one record - INSERTED*/

INSERT INTO AuditLog(
ChangeDate, RowGUID, ChangeType,
Username, HostName, AppName,
UserGUID,
TableName, FieldName,
TagGUID, Tag,
OldValue, NewValue)

SELECT
getdate(), --ChangeDate
i.NodeGUID, --RowGUID
'INSERTED', --ChangeType
USER_NAME(), HOST_NAME(), APP_NAME(),
@SavedUserGUID, --UserGUID
'Nodes', --TableName
'', --FieldName
i.ParentNodeGUID, --TagGUID
i.Caption, --Tag
null, --OldValue
null --NewValue
FROM Inserted i

触发记录更新

CREATE TRIGGER LogUpdate_Nodes ON dbo.Nodes
FOR UPDATE AS

/* Load the saved context info UserGUID */
DECLARE @SavedUserGUID uniqueidentifier

SELECT @SavedUserGUID = CAST(context_info as uniqueidentifier)
FROM master.dbo.sysprocesses
WHERE spid = @@SPID

DECLARE @NullGUID uniqueidentifier
SELECT @NullGUID = '{00000000-0000-0000-0000-000000000000}'

IF @SavedUserGUID = @NullGUID
BEGIN
SET @SavedUserGUID = NULL
END

/* ParentNodeGUID uniqueidentifier */
IF UPDATE (ParentNodeGUID)
BEGIN
INSERT INTO AuditLog(
ChangeDate, RowGUID, ChangeType,
Username, HostName, AppName,
UserGUID,
TableName, FieldName,
TagGUID, Tag,
OldValue, NewValue)
SELECT
getdate(), --ChangeDate
i.NodeGUID, --RowGUID
'UPDATED', --ChangeType
USER_NAME(), HOST_NAME(), APP_NAME(),
@SavedUserGUID, --UserGUID
'Nodes', --TableName
'ParentNodeGUID', --FieldName
i.ParentNodeGUID, --TagGUID
i.Caption, --Tag
d.ParentNodeGUID, --OldValue
i.ParentNodeGUID --NewValue
FROM Inserted i
INNER JOIN Deleted d
ON i.NodeGUID = d.NodeGUID
WHERE (d.ParentNodeGUID IS NULL AND i.ParentNodeGUID IS NOT NULL)
OR (d.ParentNodeGUID IS NOT NULL AND i.ParentNodeGUID IS NULL)
OR (d.ParentNodeGUID <> i.ParentNodeGUID)
END

/* Caption varchar(255) */
IF UPDATE (Caption)
BEGIN
INSERT INTO AuditLog(
ChangeDate, RowGUID, ChangeType,
Username, HostName, AppName,
UserGUID,
TableName, FieldName,
TagGUID, Tag,
OldValue, NewValue)
SELECT
getdate(), --ChangeDate
i.NodeGUID, --RowGUID
'UPDATED', --ChangeType
USER_NAME(), HOST_NAME(), APP_NAME(),
@SavedUserGUID, --UserGUID
'Nodes', --TableName
'Caption', --FieldName
i.ParentNodeGUID, --TagGUID
i.Caption, --Tag
d.Caption, --OldValue
i.Caption --NewValue
FROM Inserted i
INNER JOIN Deleted d
ON i.NodeGUID = d.NodeGUID
WHERE (d.Caption IS NULL AND i.Caption IS NOT NULL)
OR (d.Caption IS NOT NULL AND i.Caption IS NULL)
OR (d.Caption <> i.Caption)
END

...

/* ImageGUID uniqueidentifier */
IF UPDATE (ImageGUID)
BEGIN
INSERT INTO AuditLog(
ChangeDate, RowGUID, ChangeType,
Username, HostName, AppName,
UserGUID,
TableName, FieldName,
TagGUID, Tag,
OldValue, NewValue)
SELECT
getdate(), --ChangeDate
i.NodeGUID, --RowGUID
'UPDATED', --ChangeType
USER_NAME(), HOST_NAME(), APP_NAME(),
@SavedUserGUID, --UserGUID
'Nodes', --TableName
'ImageGUID', --FieldName
i.ParentNodeGUID, --TagGUID
i.Caption, --Tag
(SELECT Caption FROM Nodes WHERE NodeGUID = d.ImageGUID), --OldValue
(SELECT Caption FROM Nodes WHERE NodeGUID = i.ImageGUID) --New Value
FROM Inserted i
INNER JOIN Deleted d
ON i.NodeGUID = d.NodeGUID
WHERE (d.ImageGUID IS NULL AND i.ImageGUID IS NOT NULL)
OR (d.ImageGUID IS NOT NULL AND i.ImageGUID IS NULL)
OR (d.ImageGUID <> i.ImageGUID)
END

触发日志删除

CREATE TRIGGER LogDelete_Nodes ON dbo.Nodes
FOR DELETE
AS

/* Load the saved context info UserGUID */
DECLARE @SavedUserGUID uniqueidentifier

SELECT @SavedUserGUID = CAST(context_info as uniqueidentifier)
FROM master.dbo.sysprocesses
WHERE spid = @@SPID

DECLARE @NullGUID uniqueidentifier
SELECT @NullGUID = '{00000000-0000-0000-0000-000000000000}'

IF @SavedUserGUID = @NullGUID
BEGIN
SET @SavedUserGUID = NULL
END

/*We dont' log individual field changes Old/New because the row is new.
So we only have one record - DELETED*/

INSERT INTO AuditLog(
ChangeDate, RowGUID, ChangeType,
Username, HostName, AppName,
UserGUID,
TableName, FieldName,
TagGUID, Tag,
OldValue,NewValue)

SELECT
getdate(), --ChangeDate
d.NodeGUID, --RowGUID
'DELETED', --ChangeType
USER_NAME(), HOST_NAME(), APP_NAME(),
@SavedUserGUID, --UserGUID
'Nodes', --TableName
'', --FieldName
d.ParentNodeGUID, --TagGUID
d.Caption, --Tag
null, --OldValue
null --NewValue
FROM Deleted d

为了知道软件中的哪个用户进行了更新,每个连接都通过调用存储过程“将自身登录到 SQL Server”:

CREATE PROCEDURE dbo.SaveContextUserGUID @UserGUID uniqueidentifier AS

/* Saves the given UserGUID as the session's "Context Information" */
IF @UserGUID IS NULL
BEGIN
PRINT 'Emptying CONTEXT_INFO because of null @UserGUID'
DECLARE @BinVar varbinary(128)
SET @BinVar = CAST( REPLICATE( 0x00, 128 ) AS varbinary(128) )
SET CONTEXT_INFO @BinVar
RETURN 0
END

DECLARE @UserGUIDBinary binary(16) --a guid is 16 bytes
SELECT @UserGUIDBinary = CAST(@UserGUID as binary(16))
SET CONTEXT_INFO @UserGUIDBinary


/* To load the guid back
DECLARE @SavedUserGUID uniqueidentifier

SELECT @SavedUserGUID = CAST(context_info as uniqueidentifier)
FROM master.dbo.sysprocesses
WHERE spid = @@SPID

select @SavedUserGUID AS UserGUID
*/

注意事项

  • Stackoverflow 代码格式删除了大部分空白行 - 因此格式很糟糕
  • 我们使用用户表,而不是集成安全性
  • 提供此代码是为了方便起见 - 不允许批评我们的设计选择。纯粹主义者可能会坚持所有日志记录代码都应该在业务层完成——他们可以来这里为我们编写/维护它。
  • 无法使用 SQL Server 中的触发器记录 blob(博客没有“之前”版本——只有现在)。 Text 和 nText 是 blob - 这使得注释要么不可记录,要么使它们成为 varchar(2000) 的。
  • 标签列用作标识行的任意文本(例如,如果客户被删除,标签将在审计日志表中显示“通用汽车北美公司”。
  • TagGUID 用于指向行的“父级”。例如,记录 InvoiceLineItems 指向 InvoiceHeader。这样,任何搜索与特定发票相关的审核日志条目的人都会在审核跟踪中找到按行项目的 TagGUID 删除的“行项目”。
  • 有时“OldValue”和“NewValue”值被写为子选择 - 以获得有意义的字符串。即。”

    旧值:{233d-ad34234..}新值:{883-sdf34...}

在审计追踪中的用处小于:

OldValue: Daimler Chrysler
NewValue: Cerberus Capital Management

最后说明:请随意不要做我们做的事。这对我们来说很棒,但其他人可以不使用它。

关于sql - 实现字段更改跟踪的模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2682782/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com