在审核表中记录SQL server中的更改
下表:在审核表中记录SQL server中的更改,sql,sql-server,Sql,Sql Server,下表: CREATE TABLE GUESTS ( GUEST_ID int IDENTITY(1,1) PRIMARY KEY, GUEST_NAME VARCHAR(50), GUEST_SURNAME VARCHAR(50), ADRESS VARCHAR(100), CITY VARCHAR(50), CITY_CODE VARCHAR(10), COUNTRY VARCHAR(50)
CREATE TABLE GUESTS (
GUEST_ID int IDENTITY(1,1) PRIMARY KEY,
GUEST_NAME VARCHAR(50),
GUEST_SURNAME VARCHAR(50),
ADRESS VARCHAR(100),
CITY VARCHAR(50),
CITY_CODE VARCHAR(10),
COUNTRY VARCHAR(50),
STATUS VARCHAR(20),
COMMENT nvarchar(max);
对于日志记录:
CREATE TABLE AUDIT_GUESTS (
ID int IDENTITY(1,1) PRIMARY KEY,
GUEST_ID int,
OLD_GUEST_NAME VARCHAR(50),
NEW_GUEST_NAME VARCHAR(50),
OLD_GUEST_SURNAME VARCHAR(50),
NEW_GUEST_SURNAME VARCHAR(50),
OLD_ADRESS VARCHAR(100),
NEW_ADRESS VARCHAR(100),
OLD_CITY VARCHAR(50),
NEW_CITY VARCHAR(50),
OLD_CITY_CODE VARCHAR(10),
NEW_CITY_CODE VARCHAR(10),
OLD_COUNTRY VARCHAR(50),
NEW_COUNTRY VARCHAR(50),
OLD_STATUS VARCHAR(20),
NEW_STATUS VARCHAR(20),
OLD_COMMENT nvarchar(max),
NEW_COMMENT nvarchar(max),
AUDIT_ACTION varchar(100),
AUDIT_TIMESTAMP datetime);
我想在GUESTS表上创建一个触发器,以记录AUDIT\u GUESTS表中的所有更改。在SQL Server 2014 Express中如何实现这一点
我试过:
create TRIGGER trgAfterUpdate ON [dbo].[GUESTS]
FOR UPDATE
AS
declare @GUEST_ID int;
declare @GUEST_NAME varchar(50);
declare @GUEST_SURNAME VARCHAR(50);
declare @ADRESS VARCHAR(100);
declare @CITY VARCHAR(50);
declare @CITY_CODE VARCHAR(10);
declare @COUNTRY VARCHAR(50);
declare @STATUS VARCHAR(20);
declare @COMMENT nvarchar(max);
declare @AUDIT_ACTION varchar(100);
declare @AUDIT_TIMESTAMP datetime;
select @GUEST_ID=i.GUEST_ID from inserted i;
select @GUEST_NAME=i.GUEST_NAME from inserted i;
select @GUEST_SURNAME=i.GUEST_SURNAME from inserted i;
select @ADRESS=i.ADRESS from inserted i;
select @CITY=i.CITY from inserted i;
select @CITY_CODE=i.CITY_CODE from inserted i;
select @COUNTRY=i.COUNTRY from inserted i;
select @STATUS=i.STATUS from inserted i;
select @COMMENT=i.COMMENT from inserted i;
if update(GUEST_NAME)
set @audit_action='Updated Record -- After Update Trigger.';
if update(GUEST_SURNAME)
set @audit_action='Updated Record -- After Update Trigger.';
if update(ADRESS)
set @audit_action='Updated Record -- After Update Trigger.';
if update(CITY)
set @audit_action='Updated Record -- After Update Trigger.';
if update(CITY_CODE)
set @audit_action='Updated Record -- After Update Trigger.';
if update(COUNTRY)
set @audit_action='Updated Record -- After Update Trigger.';
if update(STATUS)
set @audit_action='Updated Record -- After Update Trigger.';
if update(COMMENT)
set @audit_action='Updated Record -- After Update Trigger.';
insert into AUDIT_GUESTS
(GUEST_ID,GUEST_NAME,GUEST_SURNAME,ADRESS,CITY,CITY_CODE,COUNTRY,STATUS,COMMENT,audit_action,AUDIT_TIMESTAMP)
values(@GUEST_ID,@GUEST_NAME,@GUEST_SURNAME,@ADRESS,@CITY,@CITY_CODE,@COUNTRY,@STATUS,@COMMENT,@audit_action,getdate());
GO
工作有点好,但我想看到旧的新的价值观
在SQLite中,我有:
CREATE TRIGGER [LOG_UPDATE]
AFTER UPDATE OF [GUEST_NAME], [GUEST_SURNAME], [ADRESS], [CITY], [CITY_CODE], [COUNTRY], [STATUS], [COMMENT]
ON [GUESTS]
BEGIN
INSERT INTO GUESTS_LOG
( GUEST_ID,
NAME_OLD,NAME_NEW,
SURNAME_OLD,SURNAME_NEW,
ADRESS_OLD,ADRESS_NEW,
CITY_OLD,CITY_NEW,
CITY_CODE_OLD,CITY_CODE_NEW,
COUNTRY_OLD,COUNTRY_NEW,
STATUS_OLD,STATUS_NEW,
COMMENT_OLD,COMMENT_NEW,sqlAction,DATE_TIME)
VALUES
(OLD.GUEST_ID,
OLD.GUEST_NAME,NEW.GUEST_NAME,
OLD.GUEST_SURNAME,NEW.GUEST_SURNAME,
OLD.ADRESS,NEW.ADRESS,
OLD.CITY,NEW.CITY,
OLD.CITY_CODE,NEW.CITY_CODE,
OLD.COUNTRY,NEW.COUNTRY,
OLD.STATUS,NEW.STATUS,
OLD.COMMENT,NEW.COMMENT,'record changed',datetime('now','localtime'));
END
而且效果不错。只是不知道如何将其传递给SQL server。刚刚开始学习。在Simple-talk.com上查看。它指导您创建一个通用触发器,该触发器将记录所有更新列的OLDVALUE和NEWVALUE。该代码非常通用,您可以将其应用于任何要审核的表,也可以应用于任何CRUD操作,即插入、更新和删除。唯一的要求是要审核的表应该有一个主键,大多数设计良好的表都应该有主键
这是与来宾表相关的代码
创建审计表。
在来宾表上创建更新触发器,如下所示。
嘿,很简单,看到了吗 @OLD_GUEST_NAME=d.已删除d中的GUEST_NAME 此变量将存储旧的已删除值,然后您可以将其插入所需的位置 比如说-
Create trigger testupdate on test for update, delete
as
declare @tableid varchar(50);
declare @testid varchar(50);
declare @newdata varchar(50);
declare @olddata varchar(50);
select @tableid = count(*)+1 from audit_test
select @testid=d.tableid from inserted d;
select @olddata = d.data from deleted d;
select @newdata = i.data from inserted i;
insert into audit_test (tableid, testid, olddata, newdata) values (@tableid, @testid, @olddata, @newdata)
go
我知道这很古老,但也许这会帮助其他人 不要记录新值。您现有的表GUESTS具有新的值。您将有双重数据输入,而且您的数据库大小将以这种方式增长得太快 在本例中,我对其进行了清理并最小化,但以下是注销更改所需的表:
CREATE TABLE GUESTS (
GuestID INT IDENTITY(1,1) PRIMARY KEY,
GuestName VARCHAR(50),
ModifiedBy INT,
ModifiedOn DATETIME
)
CREATE TABLE GUESTS_LOG (
GuestLogID INT IDENTITY(1,1) PRIMARY KEY,
GuestID INT,
GuestName VARCHAR(50),
ModifiedBy INT,
ModifiedOn DATETIME
)
当GUESTS表ex:Guest name中的值发生更改时,只需使用触发器将整行数据按原样注销到日志/审核表中。来宾表具有当前数据,日志/审核表具有旧数据
然后使用select语句从两个表中获取数据:
SELECT 0 AS 'GuestLogID', GuestID, GuestName, ModifiedBy, ModifiedOn FROM [GUESTS] WHERE GuestID = 1
UNION
SELECT GuestLogID, GuestID, GuestName, ModifiedBy, ModifiedOn FROM [GUESTS_LOG] WHERE GuestID = 1
ORDER BY ModifiedOn ASC
从最早到最新,您的数据将显示表的外观,第一行是创建的数据&最后一行是当前数据。你可以确切地看到什么改变了,谁改变了它,以及他们什么时候改变了它
或者,我曾经有一个函数在Classic ASP中循环记录集,只显示网页上更改的值。这是一个很好的审计跟踪,用户可以看到随着时间的推移发生了什么变化。这是带有两个错误修复的代码。Royi Namir在对这个问题的公认答案的评论中提到了第一个bug修复。有关此错误的说明,请参见。第二个是由@Fandango68发现的,它用多个单词的名称修复了列
ALTER TRIGGER [dbo].[TR_person_AUDIT]
ON [dbo].[person]
FOR UPDATE
AS
DECLARE @bit INT,
@field INT,
@maxfield INT,
@char INT,
@fieldname VARCHAR(128),
@TableName VARCHAR(128),
@PKCols VARCHAR(1000),
@sql VARCHAR(2000),
@UpdateDate VARCHAR(21),
@UserName VARCHAR(128),
@Type CHAR(1),
@PKSelect VARCHAR(1000)
--You will need to change @TableName to match the table to be audited.
-- Here we made GUESTS for your example.
SELECT @TableName = 'PERSON'
SELECT @UserName = SYSTEM_USER,
@UpdateDate = CONVERT(NVARCHAR(30), GETDATE(), 126)
-- Action
IF EXISTS (
SELECT *
FROM INSERTED
)
IF EXISTS (
SELECT *
FROM DELETED
)
SELECT @Type = 'U'
ELSE
SELECT @Type = 'I'
ELSE
SELECT @Type = 'D'
-- get list of columns
SELECT * INTO #ins
FROM INSERTED
SELECT * INTO #del
FROM DELETED
-- Get primary key columns for full outer join
SELECT @PKCols = COALESCE(@PKCols + ' and', ' on')
+ ' i.[' + c.COLUMN_NAME + '] = d.[' + c.COLUMN_NAME + ']'
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE pk.TABLE_NAME = @TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
-- Get primary key select for insert
SELECT @PKSelect = COALESCE(@PKSelect + '+', '')
+ '''<[' + COLUMN_NAME
+ ']=''+convert(varchar(100),
coalesce(i.[' + COLUMN_NAME + '],d.[' + COLUMN_NAME + ']))+''>'''
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE pk.TABLE_NAME = @TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
IF @PKCols IS NULL
BEGIN
RAISERROR('no PK on table %s', 16, -1, @TableName)
RETURN
END
SELECT @field = 0,
-- @maxfield = MAX(COLUMN_NAME)
@maxfield = -- FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @TableName
MAX(
COLUMNPROPERTY(
OBJECT_ID(TABLE_SCHEMA + '.' + @TableName),
COLUMN_NAME,
'ColumnID'
)
)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @TableName
WHILE @field < @maxfield
BEGIN
SELECT @field = MIN(
COLUMNPROPERTY(
OBJECT_ID(TABLE_SCHEMA + '.' + @TableName),
COLUMN_NAME,
'ColumnID'
)
)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @TableName
AND COLUMNPROPERTY(
OBJECT_ID(TABLE_SCHEMA + '.' + @TableName),
COLUMN_NAME,
'ColumnID'
) > @field
SELECT @bit = (@field - 1)% 8 + 1
SELECT @bit = POWER(2, @bit - 1)
SELECT @char = ((@field - 1) / 8) + 1
IF SUBSTRING(COLUMNS_UPDATED(), @char, 1) & @bit > 0
OR @Type IN ('I', 'D')
BEGIN
SELECT @fieldname = COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @TableName
AND COLUMNPROPERTY(
OBJECT_ID(TABLE_SCHEMA + '.' + @TableName),
COLUMN_NAME,
'ColumnID'
) = @field
SELECT @sql =
'
insert into Audit ( Type,
TableName,
PK,
FieldName,
OldValue,
NewValue,
UpdateDate,
UserName)
select ''' + @Type + ''','''
+ @TableName + ''',' + @PKSelect
+ ',''' + @fieldname + ''''
+ ',convert(varchar(1000),d.' + @fieldname + ')'
+ ',convert(varchar(1000),i.' + @fieldname + ')'
+ ',''' + @UpdateDate + ''''
+ ',''' + @UserName + ''''
+ ' from #ins i full outer join #del d'
+ @PKCols
+ ' where i.' + @fieldname + ' <> d.' + @fieldname
+ ' or (i.' + @fieldname + ' is null and d.'
+ @fieldname
+ ' is not null)'
+ ' or (i.' + @fieldname + ' is not null and d.'
+ @fieldname
+ ' is null)'
EXEC (@sql)
END
END
这需要各种组合,这些组合往往太长。好的方法是拥有新旧价值观。这就是我问的原因。如何记录新旧值?好的,您正在寻找一种方法来记录更新到日志表中的任何字段。如果更新了3个字段,您希望在审计中记录3个单独的行,每个字段在该审计行中用新旧值更改1个?真正的答案是Shiva从pop-铆接中得到的信息。您应该更改代码。参见@Shiva noob问题:为什么触发器仅用于更新事件?它有效吗?如果我删除或插入一条记录是否有效?我们也在寻找一个删除触发器,以防人们删除数据,这是他们不应该做的。感谢您触发器中的第一个也是最基本的缺陷是假设插入和删除只包含一行—事实并非如此!如果UPDATE语句一次影响10行,则触发器将触发一次,并且插入和删除了10行-您需要使用基于集合的方法,而不仅仅是从这些伪表中选择您认为唯一的值@用户763539一个字段?它注销您正在编辑的表中的整个当前行。那么if将适用于您更新的所有字段。您能解释一下您的答案吗,关于bug是什么以及您是如何修复的?没有解释的拍打代码很糟糕。@Fandango68谢谢你的请求,不需要评判性的语言。在上一个答案的其中一条评论中提到了这个bug,我将在我的答案中清楚地提供这个链接。顺便说一句,我并没有修复它,我只是想通过提供与bug代码相近的修复代码来提供帮助。我很抱歉。这在我看来太频繁了。还有另一个bug。列名称在单词之间有空格的表将不起作用。所有列名必须是单个单词。修复很容易。在逻辑中放置[和]字符。@Fandango68你是对的,我当然有自己的烦恼。不管怎样,你的观点很好。不清楚我在发布什么。我希望现在好多了。你打算让我在我发布的代码中包含你的补丁吗?是的,为什么不?将这些字符放在引用列名称的位置周围。那么我认为你的答案应该是正式的最终答案!:P
SELECT 0 AS 'GuestLogID', GuestID, GuestName, ModifiedBy, ModifiedOn FROM [GUESTS] WHERE GuestID = 1
UNION
SELECT GuestLogID, GuestID, GuestName, ModifiedBy, ModifiedOn FROM [GUESTS_LOG] WHERE GuestID = 1
ORDER BY ModifiedOn ASC
ALTER TRIGGER [dbo].[TR_person_AUDIT]
ON [dbo].[person]
FOR UPDATE
AS
DECLARE @bit INT,
@field INT,
@maxfield INT,
@char INT,
@fieldname VARCHAR(128),
@TableName VARCHAR(128),
@PKCols VARCHAR(1000),
@sql VARCHAR(2000),
@UpdateDate VARCHAR(21),
@UserName VARCHAR(128),
@Type CHAR(1),
@PKSelect VARCHAR(1000)
--You will need to change @TableName to match the table to be audited.
-- Here we made GUESTS for your example.
SELECT @TableName = 'PERSON'
SELECT @UserName = SYSTEM_USER,
@UpdateDate = CONVERT(NVARCHAR(30), GETDATE(), 126)
-- Action
IF EXISTS (
SELECT *
FROM INSERTED
)
IF EXISTS (
SELECT *
FROM DELETED
)
SELECT @Type = 'U'
ELSE
SELECT @Type = 'I'
ELSE
SELECT @Type = 'D'
-- get list of columns
SELECT * INTO #ins
FROM INSERTED
SELECT * INTO #del
FROM DELETED
-- Get primary key columns for full outer join
SELECT @PKCols = COALESCE(@PKCols + ' and', ' on')
+ ' i.[' + c.COLUMN_NAME + '] = d.[' + c.COLUMN_NAME + ']'
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE pk.TABLE_NAME = @TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
-- Get primary key select for insert
SELECT @PKSelect = COALESCE(@PKSelect + '+', '')
+ '''<[' + COLUMN_NAME
+ ']=''+convert(varchar(100),
coalesce(i.[' + COLUMN_NAME + '],d.[' + COLUMN_NAME + ']))+''>'''
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE pk.TABLE_NAME = @TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
IF @PKCols IS NULL
BEGIN
RAISERROR('no PK on table %s', 16, -1, @TableName)
RETURN
END
SELECT @field = 0,
-- @maxfield = MAX(COLUMN_NAME)
@maxfield = -- FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @TableName
MAX(
COLUMNPROPERTY(
OBJECT_ID(TABLE_SCHEMA + '.' + @TableName),
COLUMN_NAME,
'ColumnID'
)
)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @TableName
WHILE @field < @maxfield
BEGIN
SELECT @field = MIN(
COLUMNPROPERTY(
OBJECT_ID(TABLE_SCHEMA + '.' + @TableName),
COLUMN_NAME,
'ColumnID'
)
)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @TableName
AND COLUMNPROPERTY(
OBJECT_ID(TABLE_SCHEMA + '.' + @TableName),
COLUMN_NAME,
'ColumnID'
) > @field
SELECT @bit = (@field - 1)% 8 + 1
SELECT @bit = POWER(2, @bit - 1)
SELECT @char = ((@field - 1) / 8) + 1
IF SUBSTRING(COLUMNS_UPDATED(), @char, 1) & @bit > 0
OR @Type IN ('I', 'D')
BEGIN
SELECT @fieldname = COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @TableName
AND COLUMNPROPERTY(
OBJECT_ID(TABLE_SCHEMA + '.' + @TableName),
COLUMN_NAME,
'ColumnID'
) = @field
SELECT @sql =
'
insert into Audit ( Type,
TableName,
PK,
FieldName,
OldValue,
NewValue,
UpdateDate,
UserName)
select ''' + @Type + ''','''
+ @TableName + ''',' + @PKSelect
+ ',''' + @fieldname + ''''
+ ',convert(varchar(1000),d.' + @fieldname + ')'
+ ',convert(varchar(1000),i.' + @fieldname + ')'
+ ',''' + @UpdateDate + ''''
+ ',''' + @UserName + ''''
+ ' from #ins i full outer join #del d'
+ @PKCols
+ ' where i.' + @fieldname + ' <> d.' + @fieldname
+ ' or (i.' + @fieldname + ' is null and d.'
+ @fieldname
+ ' is not null)'
+ ' or (i.' + @fieldname + ' is not null and d.'
+ @fieldname
+ ' is null)'
EXEC (@sql)
END
END