20

我知道COLUMNS_UPDATED,我需要一些快速的捷径(如果有人做过,我已经在做一个了,但是如果有人能节省我的时间,我会很高兴的)

我基本上需要一个只包含更新列值的 XML,我需要这个用于复制目的。

SELECT * FROM inserted 给了我每一列,但我只需要更新的。

类似于以下...

CREATE TRIGGER DBCustomers_Insert
    ON DBCustomers
    AFTER UPDATE
AS
BEGIN
    DECLARE @sql as NVARCHAR(1024);
    SET @sql = 'SELECT ';


    I NEED HELP FOR FOLLOWING LINE ...., I can manually write every column, but I need 
    an automated routin which can work regardless of column specification
    for each column, if its modified append $sql = ',' + columnname...

    SET @sql = $sql + ' FROM inserted FOR XML RAW';

    DECLARE @x as XML;
    SET @x = CAST(EXEC(@sql) AS XML);


    .. use @x

END
4

8 回答 8

24

我有另一个完全不同的解决方案,它根本不使用 COLUMNS_UPDATED,也不依赖于在运行时构建动态 SQL。(您可能希望在设计时使用动态 SQL,但那是另一回事。)

基本上你从插入和删除的表开始,对它们中的每一个进行反透视,这样你就只剩下每个唯一的键、字段值和字段名称列。然后你加入两者并过滤任何改变的东西。

这是一个完整的工作示例,包括一些测试调用以显示记录的内容。

-- -------------------- Setup tables and some initial data --------------------
CREATE TABLE dbo.Sample_Table (ContactID int, Forename varchar(100), Surname varchar(100), Extn varchar(16), Email varchar(100), Age int );
INSERT INTO Sample_Table VALUES (1,'Bob','Smith','2295','bs@example.com',24);
INSERT INTO Sample_Table VALUES (2,'Alice','Brown','2255','ab@example.com',32);
INSERT INTO Sample_Table VALUES (3,'Reg','Jones','2280','rj@example.com',19);
INSERT INTO Sample_Table VALUES (4,'Mary','Doe','2216','md@example.com',28);
INSERT INTO Sample_Table VALUES (5,'Peter','Nash','2214','pn@example.com',25);

CREATE TABLE dbo.Sample_Table_Changes (ContactID int, FieldName sysname, FieldValueWas sql_variant, FieldValueIs sql_variant, modified datetime default (GETDATE()));

GO

-- -------------------- Create trigger --------------------
CREATE TRIGGER TriggerName ON dbo.Sample_Table FOR DELETE, INSERT, UPDATE AS
BEGIN
    SET NOCOUNT ON;
    --Unpivot deleted
    WITH deleted_unpvt AS (
        SELECT ContactID, FieldName, FieldValue
        FROM 
           (SELECT ContactID
                , cast(Forename as sql_variant) Forename
                , cast(Surname as sql_variant) Surname
                , cast(Extn as sql_variant) Extn
                , cast(Email as sql_variant) Email
                , cast(Age as sql_variant) Age
           FROM deleted) p
        UNPIVOT
           (FieldValue FOR FieldName IN 
              (Forename, Surname, Extn, Email, Age)
        ) AS deleted_unpvt
    ),
    --Unpivot inserted
    inserted_unpvt AS (
        SELECT ContactID, FieldName, FieldValue
        FROM 
           (SELECT ContactID
                , cast(Forename as sql_variant) Forename
                , cast(Surname as sql_variant) Surname
                , cast(Extn as sql_variant) Extn
                , cast(Email as sql_variant) Email
                , cast(Age as sql_variant) Age
           FROM inserted) p
        UNPIVOT
           (FieldValue FOR FieldName IN 
              (Forename, Surname, Extn, Email, Age)
        ) AS inserted_unpvt
    )

    --Join them together and show what's changed
    INSERT INTO Sample_Table_Changes (ContactID, FieldName, FieldValueWas, FieldValueIs)
    SELECT Coalesce (D.ContactID, I.ContactID) ContactID
        , Coalesce (D.FieldName, I.FieldName) FieldName
        , D.FieldValue as FieldValueWas
        , I.FieldValue AS FieldValueIs 
    FROM 
        deleted_unpvt d

            FULL OUTER JOIN 
        inserted_unpvt i
            on      D.ContactID = I.ContactID 
                AND D.FieldName = I.FieldName
    WHERE
         D.FieldValue <> I.FieldValue --Changes
        OR (D.FieldValue IS NOT NULL AND I.FieldValue IS NULL) -- Deletions
        OR (D.FieldValue IS NULL AND I.FieldValue IS NOT NULL) -- Insertions
END
GO
-- -------------------- Try some changes --------------------
UPDATE Sample_Table SET age = age+1;
UPDATE Sample_Table SET Extn = '5'+Extn where Extn Like '221_';

DELETE FROM Sample_Table WHERE ContactID = 3;

INSERT INTO Sample_Table VALUES (6,'Stephen','Turner','2299','st@example.com',25);

UPDATE Sample_Table SET ContactID = 7 where ContactID = 4; --this will be shown as a delete and an insert
-- -------------------- See the results --------------------
SELECT *, SQL_VARIANT_PROPERTY(FieldValueWas, 'BaseType') FieldBaseType, SQL_VARIANT_PROPERTY(FieldValueWas, 'MaxLength') FieldMaxLength from Sample_Table_Changes;

-- -------------------- Cleanup --------------------
DROP TABLE dbo.Sample_Table; DROP TABLE dbo.Sample_Table_Changes;

所以不要搞乱 bigint 位域和arth 溢出问题。如果您在设计时知道要比较的列,那么您不需要任何动态 SQL。

不利的一面是,输出格式不同,所有字段值都转换为 sql_variant,第一个可以通过再次旋转输出来修复,第二个可以通过根据您的知识重新转换为所需类型来修复表的设计,但这两者都需要一些复杂的动态sql。这两个在您的 XML 输出中可能都不是问题。这个问题的作用类似于将输出恢复为相同的格式。

编辑:查看下面的评论,如果您有一个可以更改的自然主键,那么您仍然可以使用此方法。您只需要使用 NEWID() 函数添加默认填充有 GUID 的列。然后使用此列代替主键。

您可能希望为该字段添加索引,但由于触发器中的已删除表和已插入表在内存中,因此它可能不会被使用并且可能会对性能产生负面影响。

于 2011-11-05T13:29:02.743 回答
20

在触发器内部,您可以COLUMNS_UPDATED()像这样使用以获得更新的值

-- Get the table id of the trigger
--
DECLARE @idTable      INT

SELECT  @idTable = T.id 
FROM    sysobjects P JOIN sysobjects T ON P.parent_obj = T.id 
WHERE   P.id = @@procid

-- Get COLUMNS_UPDATED if update
--
DECLARE @Columns_Updated VARCHAR(50)

SELECT  @Columns_Updated = ISNULL(@Columns_Updated + ', ', '') + name 
FROM    syscolumns 
WHERE   id = @idTable   
AND     CONVERT(VARBINARY,REVERSE(COLUMNS_UPDATED())) & POWER(CONVERT(BIGINT, 2), colorder - 1) > 0

但是当你有一个超过 62 列的表时,这段代码会失败.. Arth.Overflow ......

这是处理超过 62 列的最终版本,但仅给出更新列的数量。很容易与“syscolumns”链接以获取名称

DECLARE @Columns_Updated VARCHAR(100)
SET     @Columns_Updated = ''   

DECLARE @maxByteCU INT
DECLARE @curByteCU INT
SELECT  @maxByteCU = DATALENGTH(COLUMNS_UPDATED()), 
        @curByteCU = 1

WHILE @curByteCU <= @maxByteCU BEGIN
    DECLARE @cByte INT
    SET     @cByte = SUBSTRING(COLUMNS_UPDATED(), @curByteCU, 1)

    DECLARE @curBit INT
    DECLARE @maxBit INT
    SELECT  @curBit = 1, 
            @maxBit = 8
    WHILE @curBit <= @maxBit BEGIN
        IF CONVERT(BIT, @cByte & POWER(2,@curBit - 1)) <> 0 
            SET @Columns_Updated = @Columns_Updated + '[' + CONVERT(VARCHAR, 8 * (@curByteCU - 1) + @curBit) + ']'
        SET @curBit = @curBit + 1
    END
    SET @curByteCU = @curByteCU + 1
END
于 2009-12-17T14:49:16.483 回答
5

我已经将其作为简单的“单线”完成了。无需使用、枢轴、循环、许多变量等,这使它看起来像过程编程。SQL应该用于处理数据集:-),解决方案是:

DECLARE @sql as NVARCHAR(1024);

select @sql = coalesce(@sql + ',' + quotename(column_name), quotename(column_name))
from INFORMATION_SCHEMA.COLUMNS
where substring(columns_updated(), columnproperty(object_id(table_schema + '.' + table_name, 'U'), column_name, 'columnId') / 8 + 1, 1) & power(2, -1 + columnproperty(object_id(table_schema + '.' + table_name, 'U'), column_name, 'columnId') % 8 ) > 0
    and table_name = 'DBCustomers'
    -- and column_name in ('c1', 'c2') -- limit to specific columns
    -- and column_name not in ('c3', 'c4') -- or exclude specific columns

SET @sql = 'SELECT ' + @sql + ' FROM inserted FOR XML RAW';

DECLARE @x as XML;
SET @x = CAST(EXEC(@sql) AS XML);

它使用COLUMNS_UPDATED,处理超过八列 - 它可以处理任意数量的列。

它注意正确的列顺序,应该使用COLUMNPROPERTY获得。

它基于视图COLUMNS,因此它可能仅包含或排除特定列。

于 2013-03-19T11:55:57.613 回答
2

下面的代码适用于超过 64 列,并且只记录更新的列。按照评论中的说明进行操作,一切都会好起来的。

/*******************************************************************************************
 *         Add the below table to your database to track data changes using the trigger    *
 *         below. Remember to change the variables in the trigger to match the table that  *
 *         will be firing the trigger                                                      *
 *******************************************************************************************/
SET ANSI_NULLS ON;
GO

SET QUOTED_IDENTIFIER ON;
GO

CREATE TABLE [dbo].[AuditDataChanges]
(
  [RecordId] [INT] IDENTITY(1, 1)
                   NOT NULL ,
  [TableName] [VARCHAR](50) NOT NULL ,
  [RecordPK] [VARCHAR](50) NOT NULL ,
  [ColumnName] [VARCHAR](50) NOT NULL ,
  [OldValue] [VARCHAR](50) NULL ,
  [NewValue] [VARCHAR](50) NULL ,
  [ChangeDate] [DATETIME2](7) NOT NULL ,
  [UpdatedBy] [VARCHAR](50) NOT NULL ,
  CONSTRAINT [PK_AuditDataChanges] PRIMARY KEY CLUSTERED
    ( [RecordId] ASC )
    WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
           IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
           ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY]
)
ON  [PRIMARY];

GO

ALTER TABLE [dbo].[AuditDataChanges] ADD  CONSTRAINT [DF_AuditDataChanges_ChangeDate]  DEFAULT (GETDATE()) FOR [ChangeDate];
GO



/************************************************************************************************
 * Add the below trigger to any table you want to audit data changes on. Changes will be saved  *
 * in the AuditChangesTable.                                                                    *
 ************************************************************************************************/


ALTER TRIGGER trg_Survey_Identify_Updated_Columns ON Survey --Change to match your table name
   FOR INSERT, UPDATE
AS
SET NOCOUNT ON;

DECLARE @sql VARCHAR(5000) ,
    @sqlInserted NVARCHAR(500) ,
    @sqlDeleted NVARCHAR(500) ,
    @NewValue NVARCHAR(100) ,
    @OldValue NVARCHAR(100) ,
    @UpdatedBy VARCHAR(50) ,
    @ParmDefinitionD NVARCHAR(500) ,
    @ParmDefinitionI NVARCHAR(500) ,
    @TABLE_NAME VARCHAR(100) ,
    @COLUMN_NAME VARCHAR(100) ,
    @modifiedColumnsList NVARCHAR(4000) ,
    @ColumnListItem NVARCHAR(500) ,
    @Pos INT ,
    @RecordPk VARCHAR(50) ,
    @RecordPkName VARCHAR(50);

SELECT  *
INTO    #deleted
FROM    deleted;
SELECT  *
INTO    #Inserted
FROM    inserted;

SET @TABLE_NAME = 'Survey'; ---Change to your table name
SELECT  @UpdatedBy = UpdatedBy --Change to your column name for the user update field
FROM    inserted;
SELECT  @RecordPk = SurveyId --Change to the table primary key field
FROM    inserted;   
SET @RecordPkName = 'SurveyId';
SET @modifiedColumnsList = STUFF(( SELECT   ',' + name
                                   FROM     sys.columns
                                   WHERE    object_id = OBJECT_ID(@TABLE_NAME)
                                            AND SUBSTRING(COLUMNS_UPDATED(),
                                                          ( ( column_id
                                                          - 1 ) / 8 + 1 ),
                                                          1) & ( POWER(2,
                                                          ( ( column_id
                                                          - 1 ) % 8 + 1 )
                                                          - 1) ) = POWER(2,
                                                          ( column_id - 1 )
                                                          % 8)
                                 FOR
                                   XML PATH('')
                                 ), 1, 1, '');


WHILE LEN(@modifiedColumnsList) > 0
    BEGIN
        SET @Pos = CHARINDEX(',', @modifiedColumnsList);
        IF @Pos = 0
            BEGIN
                SET @ColumnListItem = @modifiedColumnsList;
            END;
        ELSE
            BEGIN
                SET @ColumnListItem = SUBSTRING(@modifiedColumnsList, 1,
                                                @Pos - 1);
            END;    

        SET @COLUMN_NAME = @ColumnListItem;
        SET @ParmDefinitionD = N'@OldValueOut NVARCHAR(100) OUTPUT';
        SET @ParmDefinitionI = N'@NewValueOut NVARCHAR(100) OUTPUT';
        SET @sqlDeleted = N'SELECT @OldValueOut=' + @COLUMN_NAME
            + ' FROM #deleted where ' + @RecordPkName + '='
            + CONVERT(VARCHAR(50), @RecordPk);
        SET @sqlInserted = N'SELECT @NewValueOut=' + @COLUMN_NAME
            + ' FROM #Inserted where ' + @RecordPkName + '='
            + CONVERT(VARCHAR(50), @RecordPk);
        EXECUTE sp_executesql @sqlDeleted, @ParmDefinitionD,
            @OldValueOut = @OldValue OUTPUT;
        EXECUTE sp_executesql @sqlInserted, @ParmDefinitionI,
            @NewValueOut = @NewValue OUTPUT;
        IF ( LTRIM(RTRIM(@NewValue)) != LTRIM(RTRIM(@OldValue)) )
            BEGIN   
                SET @sql = 'INSERT INTO [dbo].[AuditDataChanges]
                                               ([TableName]
                                               ,[RecordPK]
                                               ,[ColumnName]
                                               ,[OldValue]
                                               ,[NewValue]
                                               ,[UpdatedBy])
                                         VALUES
                                               (' + QUOTENAME(@TABLE_NAME, '''') + '
                                               ,' + QUOTENAME(@RecordPk, '''') + '
                                               ,' + QUOTENAME(@COLUMN_NAME, '''') + '
                                               ,' + QUOTENAME(@OldValue, '''') + '
                                               ,' + QUOTENAME(@NewValue, '''') + '
                                               ,' + QUOTENAME(@UpdatedBy, '''') + ')';


                EXEC (@sql);
            END;     
        SET @COLUMN_NAME = '';
        SET @NewValue = '';
        SET @OldValue = '';
        IF @Pos = 0
            BEGIN
                SET @modifiedColumnsList = '';
            END;
        ELSE
            BEGIN
           -- start substring at the character after the first comma
                SET @modifiedColumnsList = SUBSTRING(@modifiedColumnsList,
                                                     @Pos + 1,
                                                     LEN(@modifiedColumnsList)
                                                     - @Pos);
            END;
    END;
DROP TABLE #Inserted;
DROP TABLE #deleted;

GO
于 2017-05-15T21:05:10.597 回答
2

我将接受的答案转换为以逗号分隔的列名列表(根据作者的建议)。输出 - “Columns_Updated”为 'Column1,Column2,Column5'

-- get names of updated columns
DECLARE @idTable      INT
declare @ColumnName nvarchar(300)
declare @ColId int

SELECT  @idTable = T.id 
FROM    sysobjects P JOIN sysobjects T ON P.parent_obj = T.id 
WHERE   P.id = @@procid

DECLARE @changedProperties nvarchar(max) = ''

DECLARE @Columns_Updated VARCHAR(2000) = ''

DECLARE @maxByteCU INT
DECLARE @curByteCU INT
SELECT  @maxByteCU = DATALENGTH(COLUMNS_UPDATED()), 
        @curByteCU = 1

WHILE @curByteCU <= @maxByteCU BEGIN
    DECLARE @cByte INT
    SET     @cByte = SUBSTRING(COLUMNS_UPDATED(), @curByteCU, 1)

    DECLARE @curBit INT
    DECLARE @maxBit INT
    SELECT  @curBit = 1, 
            @maxBit = 8
    WHILE @curBit <= @maxBit BEGIN
        IF CONVERT(BIT, @cByte & POWER(2, @curBit - 1)) <> 0 BEGIN
            SET @ColId = cast( CONVERT(VARCHAR, 8 * (@curByteCU - 1) + @curBit) as int)

            select @ColumnName = [Name]
            FROM syscolumns 
            WHERE id = @idTable and colid = @ColId

            SET @Columns_Updated = @Columns_Updated + ',' + @ColumnName
        END
        SET @curBit = @curBit + 1
    END
    SET @curByteCU = @curByteCU + 1
END
于 2019-08-04T18:01:17.017 回答
1

在我看来,无需硬编码列名即可完成此操作的唯一方法是将已删除表的内容删除到临时表,然后基于表定义构建查询以比较临时表的内容和实际表,并根据它们是否匹配返回一个分隔的列列表。诚然,以下是详尽的。

Declare @sql nvarchar(4000)
DECLARE @ParmDefinition nvarchar(500)
Declare @OutString varchar(8000)
Declare @tbl sysname

Set @OutString = ''
Set @tbl = 'SomeTable' --The table we are interested in
--Store the contents of deleted in temp table
Select * into #tempDelete from deleted 
--Build sql string based on definition 
--of table 
--to retrieve the column name
--or empty string
--based on comparison between
--target table and temp table
set @sql = ''
Select @sql = @sql + 'Case when IsNull(i.[' + Column_Name + 
'],0) = IsNull(d.[' + Column_name + '],0) then '''' 
 else ' + quotename(Column_Name, char(39)) + ' + '',''' + ' end +'
from information_schema.columns 
where table_name = @tbl
--Define output parameter
set @ParmDefinition = '@OutString varchar(8000) OUTPUT'
--Format sql
set @sql = 'Select @OutString = ' 
+ Substring(@sql,1 , len(@sql) -1) + 
' From SomeTable i  ' --Will need to be updated for target schema
+ ' inner join #tempDelete d on
i.PK = d.PK ' --Will need to be updated for target schema
--Execute sql and retrieve desired column list in output parameter
exec sp_executesql @sql, @ParmDefinition, @OutString OUT
drop table  #tempDelete
--strip trailing column if a non-zero length string 
--was returned
if Len(@Outstring) > 0 
    Set @OutString = Substring(@OutString, 1, Len(@Outstring) -1)
--return comma delimited list of changed columns. 
Select @OutString 
End
于 2009-08-10T14:11:11.380 回答
0

Rick 提供的示例代码缺少对多行更新的处理。

请让我增强 Rick 的版本如下:

USE [AFC]
GO

/****** Object:  Trigger [dbo].[trg_Survey_Identify_Updated_Columns]    Script Date: 27/7/2018 14:08:49 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

ALTER TRIGGER [dbo].[trg_Survey_Identify_Updated_Columns] ON [dbo].[Sample_Table] --Change to match your table name
FOR INSERT
	,UPDATE
AS
SET NOCOUNT ON;

DECLARE @sql VARCHAR(5000)
	,@sqlInserted NVARCHAR(500)
	,@sqlDeleted NVARCHAR(500)
	,@NewValue NVARCHAR(100)
	,@OldValue NVARCHAR(100)
	,@UpdatedBy VARCHAR(50)
	,@ParmDefinitionD NVARCHAR(500)
	,@ParmDefinitionI NVARCHAR(500)
	,@TABLE_NAME VARCHAR(100)
	,@COLUMN_NAME VARCHAR(100)
	,@modifiedColumnsList NVARCHAR(4000)
	,@ColumnListItem NVARCHAR(500)
	,@Pos INT
	,@RecordPk VARCHAR(50)
	,@RecordPkName VARCHAR(50);

SELECT *
INTO #deleted
FROM deleted;

SELECT *
INTO #Inserted
FROM inserted;

SET @TABLE_NAME = 'Sample_Table';---Change to your table name

DECLARE t_cursor CURSOR
FOR
SELECT ContactID 
FROM inserted

OPEN t_cursor

FETCH NEXT
FROM t_cursor
INTO @RecordPk 

WHILE @@FETCH_STATUS = 0
BEGIN
	--SELECT @UpdatedBy = Surname --Change to your column name for the user update field
	--FROM inserted;
	--SELECT @RecordPk = ContactID --Change to the table primary key field
	--FROM inserted;
	SET @RecordPkName = 'ContactID';
	SET @modifiedColumnsList = STUFF((
				SELECT ',' + name
				FROM sys.columns
				WHERE object_id = OBJECT_ID(@TABLE_NAME)
					AND SUBSTRING(COLUMNS_UPDATED(), ((column_id - 1) / 8 + 1), 1) & (POWER(2, ((column_id - 1) % 8 + 1) - 1)) = POWER(2, (column_id - 1) % 8)
				FOR XML PATH('')
				), 1, 1, '');

	WHILE LEN(@modifiedColumnsList) > 0
	BEGIN
		SET @Pos = CHARINDEX(',', @modifiedColumnsList);

		IF @Pos = 0
		BEGIN
			SET @ColumnListItem = @modifiedColumnsList;
		END;
		ELSE
		BEGIN
			SET @ColumnListItem = SUBSTRING(@modifiedColumnsList, 1, @Pos - 1);
		END;

		SET @COLUMN_NAME = @ColumnListItem;
		SET @ParmDefinitionD = N'@OldValueOut NVARCHAR(100) OUTPUT';
		SET @ParmDefinitionI = N'@NewValueOut NVARCHAR(100) OUTPUT';
		SET @sqlDeleted = N'SELECT @OldValueOut=' + @COLUMN_NAME + ' FROM #deleted where ' + @RecordPkName + '=' + CONVERT(VARCHAR(50), @RecordPk);
		SET @sqlInserted = N'SELECT @NewValueOut=' + @COLUMN_NAME + ' FROM #Inserted where ' + @RecordPkName + '=' + CONVERT(VARCHAR(50), @RecordPk);

		EXECUTE sp_executesql @sqlDeleted
			,@ParmDefinitionD
			,@OldValueOut = @OldValue OUTPUT;

		EXECUTE sp_executesql @sqlInserted
			,@ParmDefinitionI
			,@NewValueOut = @NewValue OUTPUT;

		--PRINT @newvalue
		--PRINT @oldvalue

		IF (LTRIM(RTRIM(@NewValue)) != LTRIM(RTRIM(@OldValue)))
		BEGIN
			SET @sql = 'INSERT INTO [dbo].[AuditDataChanges]
                                               ([TableName]
                                               ,[RecordPK]
                                               ,[ColumnName]
                                               ,[OldValue]
                                               ,[NewValue] )
                                         VALUES
                                               (' + QUOTENAME(@TABLE_NAME, '''') + '
                                               ,' + QUOTENAME(@RecordPk, '''') + '
                                               ,' + QUOTENAME(@COLUMN_NAME, '''') + '
                                               ,' + QUOTENAME(@OldValue, '''') + '
                                               ,' + QUOTENAME(@NewValue, '''') + '
                                               '  + ')';

			EXEC (@sql);
		END;

		SET @COLUMN_NAME = '';
		SET @NewValue = '';
		SET @OldValue = '';

		IF @Pos = 0
		BEGIN
			SET @modifiedColumnsList = '';
		END;
		ELSE
		BEGIN
			-- start substring at the character after the first comma
			SET @modifiedColumnsList = SUBSTRING(@modifiedColumnsList, @Pos + 1, LEN(@modifiedColumnsList) - @Pos);
		END;
	END;

	FETCH NEXT
	FROM t_cursor
	INTO @RecordPk 
END

DROP TABLE #Inserted;

DROP TABLE #deleted;

CLOSE t_cursor;

DEALLOCATE t_cursor;

于 2018-07-27T06:51:24.303 回答
0

这是使用唯一记录和 UpdatedBy 用户跟踪更新列值的完美示例。

IF NOT EXISTS
      (SELECT * FROM sysobjects WHERE id = OBJECT_ID(N'[dbo].[ColumnAuditLogs]') 
               AND OBJECTPROPERTY(id, N'IsUserTable') = 1)
       CREATE TABLE ColumnAuditLogs
               (Type CHAR(1), 
               TableName VARCHAR(128), 
               PK VARCHAR(1000), 
               FieldName VARCHAR(128), 
               OldValue VARCHAR(1000), 
               NewValue VARCHAR(1000), 
               UpdateDate datetime, 
               UserName VARCHAR(128),
               UniqueId uniqueidentifier,
               UpdatedBy int
               )
GO

create TRIGGER TR_ABCTable_AUDIT ON ABCTable 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),
       @UniqueId varchar(100),
       @UpdatedBy VARCHAR(50) 


--You will need to change @TableName to match the table to be audited. 
-- Here we made ABCTable for your example.
SELECT @TableName = 'ABCTable' -- change table name accoring your table name

-- use for table unique records for everytime updation.
set @UniqueId = CONVERT(varchar(100),newID())
-- date and user
SELECT         @UserName = SYSTEM_USER ,
       @UpdateDate = CONVERT (NVARCHAR(30),GETDATE(),126)

 SELECT  @UpdatedBy = ModifiedBy --Change to your column name for the user update field
FROM    inserted;


-- 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+'+','') 
       + '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 ColumnAuditLogs (    Type, 
               TableName, 
               PK, 
               FieldName, 
               OldValue, 
               NewValue, 
               UpdateDate, 
               UserName,
               UniqueId,
               [UpdatedBy])
select ''' + @Type + ''',''' 
       + @TableName + ''',' + @PKSelect
       + ',''' + @fieldname + ''''
       + ',convert(varchar(1000),d.' + @fieldname + ')'
       + ',convert(varchar(1000),i.' + @fieldname + ')'
       + ',''' + @UpdateDate + ''''
       + ',''' + @UserName + ''''
       + ',''' + @UniqueId + ''''
        + ',' + QUOTENAME(@UpdatedBy, '''')
       + ' 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

GO
于 2019-09-28T06:40:10.930 回答