在更新触发器中忽略两列

时间:2011-05-23 19:11:19

标签: sql sql-server

我在触发器中使用下面的代码。我在监控的表中每隔60分钟由Windows服务更新两列。如何在写入审计表时忽略这两列。

ALTER trigger [dbo].[tMonitors_ChangeTracking] on [dbo].[tMonitors] for insert, update, delete
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),
@PKFieldSelect varchar(1000),
@PKValueSelect varchar(1000)

select @TableName = 'tMonitors'

-- date and user
select @UserName = system_user ,
@UpdateDate = convert(varchar(8), getdate(), 112) + ' ' + convert(varchar(12), getdate(), 114)

-- 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 fields select for insert
select @PKFieldSelect = coalesce(@PKFieldSelect+'+','') + '''' + 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

select @PKValueSelect = coalesce(@PKValueSelect+'+','') + '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(ORDINAL_POSITION) from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = @TableName
while @field < @maxfield
begin
select @field = min(ORDINAL_POSITION) from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = @TableName and ORDINAL_POSITION > @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 ORDINAL_POSITION = @field
select @sql = 'insert tMonitors_Audit (Type, TableName, PrimaryKeyField, PrimaryKeyValue, FieldName, OldValue, NewValue, UpdateDate, UserName)'
select @sql = @sql + ' select ''' + @Type + ''''
select @sql = @sql + ',''' + @TableName + ''''
select @sql = @sql + ',' + @PKFieldSelect
select @sql = @sql + ',' + @PKValueSelect
select @sql = @sql + ',''' + @fieldname + ''''
select @sql = @sql + ',convert(varchar(1000),d.' + @fieldname + ')'
select @sql = @sql + ',convert(varchar(1000),i.' + @fieldname + ')'
select @sql = @sql + ',''' + @UpdateDate + ''''
select @sql = @sql + ',''' + @UserName + ''''
select @sql = @sql + ' from #ins i full outer join #del d'
select @sql = @sql + @PKCols
select @sql = @sql + ' where i.' + @fieldname + ' <> d.' + @fieldname 
select @sql = @sql + ' or (i.' + @fieldname + ' is null and  d.' + @fieldname + ' is not null)' 
select @sql = @sql + ' or (i.' + @fieldname + ' is not null and  d.' + @fieldname + ' is null)' 
exec (@sql)
end
end

3 个答案:

答案 0 :(得分:6)

这个简单的方法,假设这些列要忽略,只在这个每小时的过程中 更新:

IF NOT (UPDATE(col_to_ignore_1) AND UPDATE(col_to_ignore_2))
BEGIN
    ... 
    EXEC(@sql);
END

否则,您可以基于INFORMATION_SCHEMA.COLUMNS构建一个类似的列表,这样只有在至少有一个其他列更新时才会发生日志记录,最终会像这样:

IF (UPDATE(col_to_audit_1) OR UPDATE(col_to_audit_2) ... )
BEGIN
    ... 
    EXEC(@sql);
END

从您的问题中不清楚您是想要始终忽略这两列,还是仅在此每小时进程运行时。也许另一个想法是使用事务并禁用此更新的触发器...它显然会使触发器变得更加复杂。

答案 1 :(得分:1)

您可以使用“UPDATE(column_name)”语法指定要注意的列,如下所示:

CREATE TRIGGER trigger_name ON tablename
FOR insert, update, delete
AS
SET NOCOUNT ON
IF ( UPDATE(Column1) OR UPDATE(Column2))
BEGIN
  --your sql here
END

official documentation提供了其他选项。

答案 2 :(得分:0)

您可以使用COLUMNS_UPDATED()访问已更改的列的完整列表,并将其与预定义的位模式进行比较。对于包含许多列的表,计算位位置比列出UPDATED(every) OR UPDATED(other) OR UPDATED(column)更容易。

例如,对于表格:

CREATE TABLE Users
(
    UserID INT NOT NULL PRIMARY KEY IDENTITY,
    Name nvarchar(255) NOT NULL,
    EmailAddress nvarchar(255) NOT NULL,
    LastPageViewDate datetime NULL,
    GroupID INT NOT NULL
);

您要忽略LastPageViewDate的更新。您可以通过以下方式确定位掩码:

SELECT name, column_id FROM sys.columns WHERE object_id = OBJECT_ID('dbo.Users')
name                 column_id
-------------------- -----------
UserID               1
Name                 2
EmailAddress         3
LastPageViewDate     4
GroupID              5

然后触发器可以检查little-endian位掩码0x08忽略尾随0

CREATE TRIGGER dbo.TR_IUD_Users_LogEdit
    ON dbo.Users 
    AFTER INSERT, UPDATE
AS
BEGIN
    SET NOCOUNT ON;

    -- Ignore page view logging caused by
    -- UPDATE Users SET LastPageViewDate = GETDATE() WHERE UserID = @UserID
    -- Bit 4 == LastPageViewDate
    IF COLUMNS_UPDATED() = 0x08 RETURN;

    -- For forward compatibility, you can ignore unchanged new columns added
    -- by RTRIM'ing the '0's (required since 0x08 != 0x0800)
    --
    --IF REPLACE(RTRIM(REPLACE(CONVERT(varchar(max), COLUMNS_UPDATED(), 2), '0', ' ')), ' ', '0') IN ('08')
    --  RETURN;

    /* Snipped trigger body */
END
GO