如何将历史记录存储在SQL Server的历史记录表中

时间:2012-08-09 19:47:46

标签: sql sql-server

我有2个表,Table-ATable-A-History

  • Table-A包含当前数据行。
  • Table-A-History包含历史数据

我希望在Table-A中包含最新的数据行,并在Table-A-History中包含历史行。

我可以想到两种方法来实现这个目标:

  1. 每当有新数据行可用时,将当前行从Table-A移至Table-A-History并使用最新数据更新Table-A行(通过insert into selectselect into table

  2. 每当有新数据行可用时,请更新Table-A的行并在Table-A-History中插入新行。

  3. 关于性能方法1或2更好?是否有更好的不同方法来实现这一目标?

9 个答案:

答案 0 :(得分:46)

基本上,您希望跟踪/审核对表的更改,同时保持主表的大小。

有几种方法可以解决这个问题。每种方式的缺点和优点将在下面讨论。

1 - 使用触发器审核表格。

如果您希望审核该表(插入,更新,删除),请查看我如何重新编写不需要的事务 - SQL Saturday slide deck w / code - http://craftydba.com/?page_id=880。如果您选择,填充审计表的触发器可以保存来自多个表的信息,因为数据保存为XML。因此,您可以根据需要通过解析XML来取消删除操作。它跟踪谁和改变了什么。

您可以选择将审计表放在自己的文件组中。

Description:
    Table Triggers For (Insert, Update, Delete)
    Active table has current records.
    Audit (history) table for non-active records.

Pros:
    Active table has smaller # of records.
    Index in active table is small.
    Change is quickly reported in audit table.
    Tells you what change was made (ins, del, upd)

Cons:
    Have to join two tables to do historical reporting.
    Does not track schema changes.

2 - 有效约会记录

如果您永远不会从审计表中清除数据,为什么不将该行标记为已删除但仍保留该行?许多系统如人们使用有效约会来显示记录是否不再有效。在BI世界中,这被称为类型2维表(缓慢变化的维度)。请参阅数据仓库研究所文章。 http://www.bidw.org/datawarehousing/scd-type-2/每条记录都有一个开始和结束日期。

所有有效记录的结束日期均为空。

Description:
    Table Triggers For (Insert, Update, Delete)
    Main table has both active and historical records.

Pros:
    Historical reporting is easy.
    Change is quickly shown in main table.

Cons:
    Main table has a large # of records.
    Index of main table is large.
    Both active & history records in same filegroup.
    Does not tell you what change was made (ins, del, upd)
    Does not track schema changes.

3 - 更改数据捕获(企业功能)。

Micorsoft SQL Server 2008引入了更改数据捕获功能。虽然这跟踪了事后使用LOG阅读器跟踪数据变化(CDC), 它缺乏诸如谁和改变之类的东西。 MSDN详细信息 - http://technet.microsoft.com/en-us/library/bb522489(v=sql.105).aspx

此解决方案取决于正在运行的CDC作业。 sql代理的任何问题都会导致数据出现延迟。

请参阅更改数据捕获表。 http://technet.microsoft.com/en-us/library/bb500353(v=sql.105).aspx

Description:
    Enable change data capture

Pros:
    Do not need to add triggers or tables to capture data.
    Tells you what change was made (ins, del, upd) the _$operation field in 
    <user_defined_table_CT>
    Tracks schema changes.    

Cons:
    Only available in enterprise version.
    Since it reads the log after the fact, time delay in data showing up.
    The CDC tables do not track who or what made the change.
    Disabling CDC removes the tables (not nice)!
    Need to decode and use the _$update_mask to figure out what columns changed.

4 - 更改跟踪功能(所有版本)。

Micorsoft SQL Server 2008引入了更改跟踪功能。与CDC不同,它带有所有版本;但是,它带有一堆TSQL函数,你必须调用它们来弄清楚发生了什么。

它的设计目的是通过应用程序与SQL服务器同步一个数据源。 TechNet上有一个完整的同步框架。

http://msdn.microsoft.com/en-us/library/bb933874.aspx http://msdn.microsoft.com/en-us/library/bb933994.aspx http://technet.microsoft.com/en-us/library/bb934145(v=sql.105).aspx

与CDC不同,您可以指定在清除之前数据库中的最后更改时间。此外,插入和删除不记录数据。更新仅记录更改的字段。

由于您正在将SQL Server源同步到另一个目标,因此可以正常工作。 除非你写一份定期的工作来计算变化,否则对审计不利。

您仍然需要在某处存储该信息。

Description:
    Enable change tracking

Cons:
    Not a good auditing solution

前三个解决方案适用于您的审核。我喜欢第一个解决方案,因为我在我的环境中广泛使用它。

此致

约翰

演示文稿中的代码段(自动数据库)

-- 
-- 7 - Auditing data changes (table for DML trigger)
-- 


-- Delete existing table
IF OBJECT_ID('[AUDIT].[LOG_TABLE_CHANGES]') IS NOT NULL 
  DROP TABLE [AUDIT].[LOG_TABLE_CHANGES]
GO


-- Add the table
CREATE TABLE [AUDIT].[LOG_TABLE_CHANGES]
(
  [CHG_ID] [numeric](18, 0) IDENTITY(1,1) NOT NULL,
  [CHG_DATE] [datetime] NOT NULL,
  [CHG_TYPE] [varchar](20) NOT NULL,
  [CHG_BY] [nvarchar](256) NOT NULL,
  [APP_NAME] [nvarchar](128) NOT NULL,
  [HOST_NAME] [nvarchar](128) NOT NULL,
  [SCHEMA_NAME] [sysname] NOT NULL,
  [OBJECT_NAME] [sysname] NOT NULL,
  [XML_RECSET] [xml] NULL,
 CONSTRAINT [PK_LTC_CHG_ID] PRIMARY KEY CLUSTERED ([CHG_ID] ASC)
) ON [PRIMARY]
GO

-- Add defaults for key information
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_CHG_DATE] DEFAULT (getdate()) FOR [CHG_DATE];
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_CHG_TYPE] DEFAULT ('') FOR [CHG_TYPE];
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_CHG_BY] DEFAULT (coalesce(suser_sname(),'?')) FOR [CHG_BY];
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_APP_NAME] DEFAULT (coalesce(app_name(),'?')) FOR [APP_NAME];
ALTER TABLE [AUDIT].[LOG_TABLE_CHANGES] ADD CONSTRAINT [DF_LTC_HOST_NAME] DEFAULT (coalesce(host_name(),'?')) FOR [HOST_NAME];
GO



--
--  8 - Make DML trigger to capture changes
--


-- Delete existing trigger
IF OBJECT_ID('[ACTIVE].[TRG_FLUID_DATA]') IS NOT NULL 
  DROP TRIGGER [ACTIVE].[TRG_FLUID_DATA]
GO

-- Add trigger to log all changes
CREATE TRIGGER [ACTIVE].[TRG_FLUID_DATA] ON [ACTIVE].[CARS_BY_COUNTRY]
  FOR INSERT, UPDATE, DELETE AS
BEGIN

  -- Detect inserts
  IF EXISTS (select * from inserted) AND NOT EXISTS (select * from deleted)
  BEGIN
    INSERT [AUDIT].[LOG_TABLE_CHANGES] ([CHG_TYPE], [SCHEMA_NAME], [OBJECT_NAME], [XML_RECSET])
    SELECT 'INSERT', '[ACTIVE]', '[CARS_BY_COUNTRY]', (SELECT * FROM inserted as Record for xml auto, elements , root('RecordSet'), type)
    RETURN;
  END

  -- Detect deletes
  IF EXISTS (select * from deleted) AND NOT EXISTS (select * from inserted)
  BEGIN
    INSERT [AUDIT].[LOG_TABLE_CHANGES] ([CHG_TYPE], [SCHEMA_NAME], [OBJECT_NAME], [XML_RECSET])
    SELECT 'DELETE', '[ACTIVE]', '[CARS_BY_COUNTRY]', (SELECT * FROM deleted as Record for xml auto, elements , root('RecordSet'), type)
    RETURN;
  END

  -- Update inserts
  IF EXISTS (select * from inserted) AND EXISTS (select * from deleted)
  BEGIN
    INSERT [AUDIT].[LOG_TABLE_CHANGES] ([CHG_TYPE], [SCHEMA_NAME], [OBJECT_NAME], [XML_RECSET])
    SELECT 'UPDATE', '[ACTIVE]', '[CARS_BY_COUNTRY]', (SELECT * FROM deleted as Record for xml auto, elements , root('RecordSet'), type)
    RETURN;
  END

END;
GO



--
--  9 - Test DML trigger by updating, deleting and inserting data
--

-- Execute an update
UPDATE [ACTIVE].[CARS_BY_COUNTRY]
SET COUNTRY_NAME = 'Czech Republic'
WHERE COUNTRY_ID = 8
GO

-- Remove all data
DELETE FROM [ACTIVE].[CARS_BY_COUNTRY];
GO

-- Execute the load
EXECUTE [ACTIVE].[USP_LOAD_CARS_BY_COUNTRY];
GO 

-- Show the audit trail
SELECT * FROM [AUDIT].[LOG_TABLE_CHANGES]
GO

-- Disable the trigger
ALTER TABLE [ACTIVE].[CARS_BY_COUNTRY] DISABLE TRIGGER [TRG_FLUID_DATA];

**外观&amp;审计表的感觉**

enter image description here

答案 1 :(得分:20)

记录更改是我通常使用基表上的触发器来记录日志表中的更改。日志表有其他列来记录数据库用户,操作和日期/时间。

create trigger Table-A_LogDelete on dbo.Table-A
  for delete
as
  declare @Now as DateTime = GetDate()
  set nocount on
  insert into Table-A-History
    select SUser_SName(), 'delete-deleted', @Now, *
      from deleted
go
exec sp_settriggerorder @triggername = 'Table-A_LogDelete', @order = 'last', @stmttype = 'delete'
go
create trigger Table-A_LogInsert on dbo.Table-A
  for insert
as
  declare @Now as DateTime = GetDate()
  set nocount on
  insert into Table-A-History
    select SUser_SName(), 'insert-inserted', @Now, *
      from inserted
go
exec sp_settriggerorder @triggername = 'Table-A_LogInsert', @order = 'last', @stmttype = 'insert'
go
create trigger Table-A_LogUpdate on dbo.Table-A
  for update
as
  declare @Now as DateTime = GetDate()
  set nocount on
  insert into Table-A-History
    select SUser_SName(), 'update-deleted', @Now, *
      from deleted
  insert into Table-A-History
    select SUser_SName(), 'update-inserted', @Now, *
      from inserted
go
exec sp_settriggerorder @triggername = 'Table-A_LogUpdate', @order = 'last', @stmttype = 'update'

记录触发器应始终设置为最后触发。否则,后续触发器可能会回滚原始事务,但日志表已经更新。这是一个令人困惑的事态。

答案 2 :(得分:15)

最新版本的SQL Server(2016+和Azure)具有时态表,可提供所需的确切功能,作为一流功能。 https://docs.microsoft.com/en-us/sql/relational-databases/tables/temporal-tables

微软的某些人可能会阅读此页面。 :)

答案 3 :(得分:4)

方法3如何:Table-ATable-A-History进行观看。插入Table-A-History并让适当的过滤逻辑生成Table-A。这样你就只能插入一张桌子了。

答案 4 :(得分:3)

即使它消耗更多空间,让历史表包含最新记录也可以节省编写报告和查看更改发生时间和时间的痛苦。在我看来值得考虑的事情。

就性能而言,我希望它们是相同的。但是,您当然不希望从非hist表中删除记录(选项1的“移动”),因为您在两个表之间使用参照完整性,对吗?

答案 5 :(得分:3)

选项1没问题。 但你也有方法4:)

  1. 在表格中插入新记录,

  2. 使用mysql调度程序将旧记录移动到常规基础上的归档表。您可以在最小负载时安排数据归档,例如在夜间。

答案 6 :(得分:2)

我更喜欢方法1 另外,我也会在历史表中保持目前的记录 这取决于需要。

答案 7 :(得分:0)

现有数据库执行此查询。

EXECUTE GenerateAudit 'Audit_DB_Name'  , 'dbo', 'Audit_Table_Name' , 0, 1, 1,'',''

创建新的审核数据库,该数据库必须在现有数据库中存储数据的插入,更新和删除历史记录。

CREATE PROC [dbo].[GenerateAudit] @SchemanameAudit SYSNAME = '' --for other database
    ,@Schemaname SYSNAME = 'dbo'
    ,@Tablename SYSNAME
    ,@GenerateScriptOnly BIT = 1
    ,@ForceDropAuditTable BIT = 0
    ,@IgnoreExistingColumnMismatch BIT = 0
    ,@DontAuditforUsers NVARCHAR(4000) = ''
    ,@DontAuditforColumns NVARCHAR(4000) = ''
AS
SET NOCOUNT ON

/*   
Parameters   
@Schemaname            - SchemaName to which the table belongs to. Default value 'dbo'.   
@Tablename            - TableName for which the procs needs to be generated.   
@GenerateScriptOnly - When passed 1 , this will generate the scripts alone..   
                      When passed 0 , this will create the audit tables and triggers in the current database.   
                      Default value is 1   
@ForceDropAuditTable - When passed 1 , will drop the audit table and recreate 
                       When passed 0 , will generate the alter scripts 
                       Default value is 0 
@IgnoreExistingColumnMismatch - When passed 1 , will not stop with the error on the mismatch of existing column and will create the trigger. 
                                When passed 0 , will stop with the error on the mismatch of existing column. 
                                Default value is 0 
@DontAuditforUsers - Pass the UserName as comma seperated for whom the audit is not required.
                     Default value is '' which will do audit for all the users.

@DontAuditforColumns - Pass the ColumnNames as comma seperated for which the audit is not required.
      Default value is '' which will do audit for all the users.
*/
DECLARE @SQL VARCHAR(MAX)
DECLARE @SQLTrigger VARCHAR(MAX)
DECLARE @ErrMsg VARCHAR(MAX)
DECLARE @AuditTableName SYSNAME
DECLARE @QuotedSchemaName SYSNAME
DECLARE @QuotedSchemaNameAudit SYSNAME --for other database
DECLARE @QuotedTableName SYSNAME
DECLARE @QuotedAuditTableName SYSNAME
DECLARE @InsertTriggerName SYSNAME
DECLARE @UpdateTriggerName SYSNAME
DECLARE @DeleteTriggerName SYSNAME
DECLARE @QuotedInsertTriggerName SYSNAME
DECLARE @QuotedUpdateTriggerName SYSNAME
DECLARE @QuotedDeleteTriggerName SYSNAME
DECLARE @DontAuditforUsersTmp NVARCHAR(4000)

SELECT @AuditTableName = @Tablename + '_Audit'

SELECT @QuotedSchemaNameAudit = QUOTENAME(@SchemanameAudit) --for other database

SELECT @QuotedSchemaName = QUOTENAME(@Schemaname)

SELECT @QuotedTableName = QUOTENAME(@Tablename)

SELECT @QuotedAuditTableName = QUOTENAME(@AuditTableName)

SELECT @InsertTriggerName = @Tablename + '_Insert'

SELECT @UpdateTriggerName = @Tablename + '_Update'

SELECT @DeleteTriggerName = @Tablename + '_Delete'

SELECT @QuotedInsertTriggerName = QUOTENAME(@InsertTriggerName)

SELECT @QuotedUpdateTriggerName = QUOTENAME(@UpdateTriggerName)

SELECT @QuotedDeleteTriggerName = QUOTENAME(@DeleteTriggerName)

IF LTRIM(RTRIM(@DontAuditforUsers)) <> ''
BEGIN
    IF RIGHT(@DontAuditforUsers, 1) = ','
    BEGIN
        SELECT @DontAuditforUsersTmp = LEFT(@DontAuditforUsers, LEN(@DontAuditforUsers) - 1)
    END
    ELSE
    BEGIN
        SELECT @DontAuditforUsersTmp = @DontAuditforUsers
    END

    SELECT @DontAuditforUsersTmp = REPLACE(@DontAuditforUsersTmp, ',', ''',''')
END

SELECT @DontAuditforColumns = ',' + UPPER(@DontAuditforColumns) + ','

IF NOT EXISTS (
        SELECT 1
        FROM sys.objects
        WHERE Name = @TableName
            AND Schema_id = Schema_id(@Schemaname)
            AND Type = 'U'
        )
BEGIN
    SELECT @ErrMsg = @QuotedSchemaNameAudit + '.' + @QuotedSchemaName + '.' + @QuotedTableName + ' Table Not Found '

    RAISERROR (
            @ErrMsg
            ,16
            ,1
            )

    RETURN
END

----------------------------------------------------------------------------------------------------------------------   
-- Audit Create OR Alter table    
----------------------------------------------------------------------------------------------------------------------   
DECLARE @ColList VARCHAR(MAX)
DECLARE @InsertColList VARCHAR(MAX)
DECLARE @UpdateCheck VARCHAR(MAX)
DECLARE @NewAddedCols TABLE (
    ColumnName SYSNAME
    ,DataType SYSNAME
    ,CharLength INT
    ,Collation SYSNAME NULL
    ,ChangeType VARCHAR(20) NULL
    ,MainTableColumnName SYSNAME NULL
    ,MainTableDataType SYSNAME NULL
    ,MainTableCharLength INT NULL
    ,MainTableCollation SYSNAME NULL
    ,AuditTableColumnName SYSNAME NULL
    ,AuditTableDataType SYSNAME NULL
    ,AuditTableCharLength INT NULL
    ,AuditTableCollation SYSNAME NULL
    )

SELECT @ColList = ''

SELECT @UpdateCheck = ' '

SELECT @SQL = ''

SELECT @InsertColList = ''

SELECT @ColList = @ColList + CASE SC.is_identity
        WHEN 1
            THEN 'CONVERT(' + ST.name + ',' + QUOTENAME(SC.name) + ') as ' + QUOTENAME(SC.name)
        ELSE QUOTENAME(SC.name)
        END + ','
    ,@InsertColList = @InsertColList + QUOTENAME(SC.name) + ','
    ,@UpdateCheck = @UpdateCheck + CASE 
        WHEN CHARINDEX(',' + UPPER(SC.NAME) + ',', @DontAuditforColumns) = 0
            THEN 'CASE WHEN UPDATE(' + QUOTENAME(SC.name) + ') THEN ''' + QUOTENAME(SC.name) + '-'' ELSE '''' END + ' + CHAR(10)
        ELSE ''
        END
FROM SYS.COLUMNS SC
JOIN SYS.OBJECTS SO ON SC.object_id = SO.object_id
JOIN SYS.schemas SCH ON SCH.schema_id = SO.schema_id
JOIN SYS.types ST ON ST.user_type_id = SC.user_type_id
    AND ST.system_type_id = SC.system_type_id
WHERE SCH.Name = @Schemaname
    AND SO.name = @Tablename
    AND UPPER(ST.name) <> UPPER('timestamp')

SELECT @ColList = SUBSTRING(@ColList, 1, LEN(@ColList) - 1)

SELECT @UpdateCheck = SUBSTRING(@UpdateCheck, 1, LEN(@UpdateCheck) - 3)

SELECT @InsertColList = SUBSTRING(@InsertColList, 1, LEN(@InsertColList) - 1)

SELECT @InsertColList = @InsertColList + ',AuditDataState,AuditDMLAction,AuditUser,AuditDateTime,UpdateColumns'

IF EXISTS (
        SELECT 1
        FROM sys.objects
        WHERE Name = @AuditTableName
            AND Schema_id = Schema_id(@Schemaname)
            AND Type = 'U'
        )
    AND @ForceDropAuditTable = 0
BEGIN
    ----------------------------------------------------------------------------------------------------------------------   
    -- Get the comparision metadata for Main and Audit Tables 
    ----------------------------------------------------------------------------------------------------------------------   
    INSERT INTO @NewAddedCols (
        ColumnName
        ,DataType
        ,CharLength
        ,Collation
        ,ChangeType
        ,MainTableColumnName
        ,MainTableDataType
        ,MainTableCharLength
        ,MainTableCollation
        ,AuditTableColumnName
        ,AuditTableDataType
        ,AuditTableCharLength
        ,AuditTableCollation
        )
    SELECT ISNULL(MainTable.ColumnName, AuditTable.ColumnName)
        ,ISNULL(MainTable.DataType, AuditTable.DataType)
        ,ISNULL(MainTable.CharLength, AuditTable.CharLength)
        ,ISNULL(MainTable.Collation, AuditTable.Collation)
        ,CASE 
            WHEN MainTable.ColumnName IS NULL
                THEN 'Deleted'
            WHEN AuditTable.ColumnName IS NULL
                THEN 'Added'
            ELSE NULL
            END
        ,MainTable.ColumnName
        ,MainTable.DataType
        ,MainTable.CharLength
        ,MainTable.Collation
        ,AuditTable.ColumnName
        ,AuditTable.DataType
        ,AuditTable.CharLength
        ,AuditTable.Collation
    FROM (
        SELECT SC.Name AS ColumnName
            ,ST.Name AS DataType
            ,SC.is_identity AS isIdentity
            ,SC.Max_length AS CharLength
            ,SC.Collation_Name AS Collation
        FROM SYS.COLUMNS SC
        JOIN SYS.OBJECTS SO ON SC.object_id = SO.object_id
        JOIN SYS.schemas SCH ON SCH.schema_id = SO.schema_id
        JOIN SYS.types ST ON ST.user_type_id = SC.user_type_id
            AND ST.system_type_id = SC.system_type_id
        WHERE SCH.Name = @Schemaname
            AND SO.name = @Tablename
            AND UPPER(ST.name) <> UPPER('timestamp')
        ) MainTable
    FULL OUTER JOIN (
        SELECT SC.Name AS ColumnName
            ,ST.Name AS DataType
            ,SC.is_identity AS isIdentity
            ,SC.Max_length AS CharLength
            ,SC.Collation_Name AS Collation
        FROM SYS.COLUMNS SC
        JOIN SYS.OBJECTS SO ON SC.object_id = SO.object_id
        JOIN SYS.schemas SCH ON SCH.schema_id = SO.schema_id
        JOIN SYS.types ST ON ST.user_type_id = SC.user_type_id
            AND ST.system_type_id = SC.system_type_id
        WHERE SCH.Name = @Schemaname
            AND SO.name = @AuditTableName
            AND UPPER(ST.name) <> UPPER('timestamp')
            AND SC.Name NOT IN (
                'AuditDataState'
                ,'AuditDMLAction'
                ,'AuditUser'
                ,'AuditDateTime'
                ,'UpdateColumns'
                )
        ) AuditTable ON MainTable.ColumnName = AuditTable.ColumnName

    ----------------------------------------------------------------------------------------------------------------------   
    -- Find data type changes between table 
    ----------------------------------------------------------------------------------------------------------------------   
    IF EXISTS (
            SELECT *
            FROM @NewAddedCols NC
            WHERE NC.MainTableColumnName = NC.AuditTableColumnName
                AND (
                    NC.MainTableDataType <> NC.AuditTableDataType
                    OR NC.MainTableCharLength > NC.AuditTableCharLength
                    OR NC.MainTableCollation <> NC.AuditTableCollation
                    )
            )
    BEGIN
        SELECT CONVERT(VARCHAR(50), CASE 
                    WHEN NC.MainTableDataType <> NC.AuditTableDataType
                        THEN 'DataType Mismatch'
                    WHEN NC.MainTableCharLength > NC.AuditTableCharLength
                        THEN 'Length in maintable is greater than Audit Table'
                    WHEN NC.MainTableCollation <> NC.AuditTableCollation
                        THEN 'Collation Difference'
                    END) AS Mismatch
            ,NC.MainTableColumnName
            ,NC.MainTableDataType
            ,NC.MainTableCharLength
            ,NC.MainTableCollation
            ,NC.AuditTableColumnName
            ,NC.AuditTableDataType
            ,NC.AuditTableCharLength
            ,NC.AuditTableCollation
        FROM @NewAddedCols NC
        WHERE NC.MainTableColumnName = NC.AuditTableColumnName
            AND (
                NC.MainTableDataType <> NC.AuditTableDataType
                OR NC.MainTableCharLength > NC.AuditTableCharLength
                OR NC.MainTableCollation <> NC.AuditTableCollation
                )

        RAISERROR (
                'There are differences in Datatype or Lesser Length or Collation difference between the Main table and Audit Table. Please refer the output'
                ,16
                ,1
                )

        IF @IgnoreExistingColumnMismatch = 0
        BEGIN
            RETURN
        END
    END

    ----------------------------------------------------------------------------------------------------------------------   
    -- Find the new and deleted columns  
    ----------------------------------------------------------------------------------------------------------------------   
    IF EXISTS (
            SELECT *
            FROM @NewAddedCols
            WHERE ChangeType IS NOT NULL
            )
    BEGIN
        SELECT @SQL = @SQL + 'ALTER TABLE ' + @QuotedSchemaNameAudit + '.' + @QuotedSchemaName + '.' + @QuotedAuditTableName + CASE 
                WHEN NC.ChangeType = 'Added'
                    THEN ' ADD ' + QUOTENAME(NC.ColumnName) + ' ' + NC.DataType + ' ' + CASE 
                            WHEN NC.DataType IN (
                                    'char'
                                    ,'varchar'
                                    ,'nchar'
                                    ,'nvarchar'
                                    )
                                AND NC.CharLength = - 1
                                THEN '(max) COLLATE ' + NC.Collation + ' NULL '
                            WHEN NC.DataType IN (
                                    'char'
                                    ,'varchar'
                                    )
                                THEN '(' + CONVERT(VARCHAR(5), NC.CharLength) + ') COLLATE ' + NC.Collation + ' NULL '
                            WHEN NC.DataType IN (
                                    'nchar'
                                    ,'nvarchar'
                                    )
                                THEN '(' + CONVERT(VARCHAR(5), NC.CharLength / 2) + ') COLLATE ' + NC.Collation + ' NULL '
                            ELSE ''
                            END
                WHEN NC.ChangeType = 'Deleted'
                    THEN ' DROP COLUMN ' + QUOTENAME(NC.ColumnName)
                END + CHAR(10)
        FROM @NewAddedCols NC
        WHERE NC.ChangeType IS NOT NULL
    END
END
ELSE
BEGIN
    SELECT @SQL = '  IF EXISTS (SELECT 1    
                                          FROM sys.objects    
                                         WHERE Name=''' + @AuditTableName + '''   
                                           AND Schema_id=Schema_id(''' + @Schemaname + ''')   
                                           AND Type = ''U'')   
                            DROP TABLE ' + @QuotedSchemaNameAudit + '.' + @QuotedSchemaName + '.' + @QuotedAuditTableName + '

                    SELECT ' + @ColList + '   
                        ,AuditDataState=CONVERT(VARCHAR(10),'''')    
                        ,AuditDMLAction=CONVERT(VARCHAR(10),'''')     
                        ,AuditUser =CONVERT(SYSNAME,'''')   
                        ,AuditDateTime=CONVERT(DATETIME,''01-JAN-1900'')   
                        ,UpdateColumns = CONVERT(VARCHAR(MAX),'''')  
                        Into ' + @QuotedSchemaNameAudit + '.' + @QuotedSchemaName + '.' + @QuotedAuditTableName + '   
                    FROM ' + @QuotedSchemaName + '.' + @QuotedTableName + '   
                    WHERE 1=2 
                    ALTER TABLE ' + @QuotedSchemaNameAudit + '.' + @QuotedSchemaName + 
        '.' + @QuotedAuditTableName + ' ADD AuditId INT IDENTITY(1,1)
                    '
        --imran tag added the above alter table auto identity         
END

IF @GenerateScriptOnly = 1
BEGIN
    PRINT REPLICATE('-', 200)
    PRINT '--Create \ Alter Script Audit table for ' + @QuotedSchemaName + '.' + @QuotedTableName
    PRINT REPLICATE('-', 200)
    PRINT @SQL

    IF LTRIM(RTRIM(@SQL)) <> ''
    BEGIN
        PRINT 'GO'
    END
    ELSE
    BEGIN
        PRINT '-- No changes in table structure'
    END
END
ELSE
BEGIN
    IF RTRIM(LTRIM(@SQL)) = ''
    BEGIN
        PRINT 'No Table Changes Found'
    END
    ELSE
    BEGIN
        PRINT 'Creating \ Altered Audit table for ' + @QuotedSchemaName + '.' + @QuotedTableName

        EXEC (@SQL)

        PRINT 'Audit table ' + @QuotedSchemaNameAudit + '.' + @QuotedSchemaName + '.' + @QuotedAuditTableName + ' Created \ Altered succesfully'
    END
END

----------------------------------------------------------------------------------------------------------------------   
-- Create Insert Trigger   
----------------------------------------------------------------------------------------------------------------------   
SELECT @SQL = '   
IF EXISTS (SELECT 1    
             FROM sys.objects    
            WHERE Name=''' + @Tablename + '_Insert' + '''   
              AND Schema_id=Schema_id(''' + @Schemaname + ''')   
              AND Type = ''TR'')   
DROP TRIGGER ' + @QuotedSchemaName + '.' + @QuotedInsertTriggerName

SELECT @SQLTrigger = '   
CREATE TRIGGER ' + @QuotedSchemaName + '.' + @QuotedInsertTriggerName + '
ON ' + @QuotedSchemaName + '.' + @QuotedTableName + '   
FOR INSERT   
AS   
'

IF LTRIM(RTRIM(@DontAuditforUsersTmp)) <> ''
BEGIN
    SELECT @SQLTrigger = @SQLTrigger + CHAR(10) + ' IF SUSER_NAME() NOT IN (''' + @DontAuditforUsersTmp + ''')'

    SELECT @SQLTrigger = @SQLTrigger + CHAR(10) + ' BEGIN'
END

SELECT @SQLTrigger = @SQLTrigger + CHAR(10) + ' INSERT INTO ' + @QuotedSchemaNameAudit + '.' + @QuotedSchemaName + '.' + @QuotedAuditTableName + CHAR(10) + '(' + @InsertColList + ')' + CHAR(10) + 'SELECT ' + @ColList + ',''New'',''Insert'',SUSER_SNAME(),getdate(),''''  FROM INSERTED '

IF LTRIM(RTRIM(@DontAuditforUsersTmp)) <> ''
BEGIN
    SELECT @SQLTrigger = @SQLTrigger + CHAR(10) + ' END'
END

IF @GenerateScriptOnly = 1
BEGIN
    PRINT REPLICATE('-', 200)
    PRINT '--Create Script Insert Trigger for ' + @QuotedSchemaName + '.' + @QuotedTablename
    PRINT REPLICATE('-', 200)
    PRINT @SQL
    PRINT 'GO'
    PRINT @SQLTrigger
    PRINT 'GO'
END
ELSE
BEGIN
    PRINT 'Creating Insert Trigger ' + @QuotedInsertTriggerName + '  for ' + @QuotedSchemaName + '.' + @QuotedTablename

    EXEC (@SQL)

    EXEC (@SQLTrigger)

    PRINT 'Trigger ' + @QuotedSchemaName + '.' + @QuotedInsertTriggerName + ' Created succesfully'
END

----------------------------------------------------------------------------------------------------------------------   
-- Create Delete Trigger   
----------------------------------------------------------------------------------------------------------------------   
SELECT @SQL = '   

IF EXISTS (SELECT 1    
             FROM sys.objects    
            WHERE Name=''' + @Tablename + '_Delete' + '''   
              AND Schema_id=Schema_id(''' + @Schemaname + ''')   
              AND Type = ''TR'')   
DROP TRIGGER ' + @QuotedSchemaName + '.' + + @QuotedDeleteTriggerName + '   
'

SELECT @SQLTrigger = '   
CREATE TRIGGER ' + @QuotedSchemaName + '.' + @QuotedDeleteTriggerName + '   
ON ' + @QuotedSchemaName + '.' + @QuotedTableName + '   
FOR DELETE   
AS   '

IF LTRIM(RTRIM(@DontAuditforUsersTmp)) <> ''
BEGIN
    SELECT @SQLTrigger = @SQLTrigger + CHAR(10) + ' IF SUSER_NAME() NOT IN (''' + @DontAuditforUsersTmp + ''')'

    SELECT @SQLTrigger = @SQLTrigger + CHAR(10) + ' BEGIN'
END

SELECT @SQLTrigger = @SQLTrigger + CHAR(10) + '  INSERT INTO ' + @QuotedSchemaNameAudit + '.' + @QuotedSchemaName + '.' + @QuotedAuditTableName + CHAR(10) + '(' + @InsertColList + ')' + CHAR(10) + 'SELECT ' + @ColList + ',''Old'',''Delete'',SUSER_SNAME(),getdate(),''''  FROM DELETED'

IF LTRIM(RTRIM(@DontAuditforUsersTmp)) <> ''
BEGIN
    SELECT @SQLTrigger = @SQLTrigger + CHAR(10) + ' END'
END

IF @GenerateScriptOnly = 1
BEGIN
    PRINT REPLICATE('-', 200)
    PRINT '--Create Script Delete Trigger for ' + @QuotedSchemaName + '.' + @QuotedTableName
    PRINT REPLICATE('-', 200)
    PRINT @SQL
    PRINT 'GO'
    PRINT @SQLTrigger
    PRINT 'GO'
END
ELSE
BEGIN
    PRINT 'Creating Delete Trigger ' + @QuotedDeleteTriggerName + '  for ' + @QuotedSchemaName + '.' + @QuotedTableName

    EXEC (@SQL)

    EXEC (@SQLTrigger)

    PRINT 'Trigger ' + @QuotedSchemaName + '.' + @QuotedDeleteTriggerName + ' Created succesfully'
END

----------------------------------------------------------------------------------------------------------------------   
-- Create Update Trigger   
----------------------------------------------------------------------------------------------------------------------   
SELECT @SQL = '   

IF EXISTS (SELECT 1    
             FROM sys.objects    
            WHERE Name=''' + @Tablename + '_Update' + '''   
              AND Schema_id=Schema_id(''' + @Schemaname + ''')   
              AND Type = ''TR'')   
DROP TRIGGER ' + @QuotedSchemaName + '.' + @QuotedUpdateTriggerName + '   
'

SELECT @SQLTrigger = '   
CREATE TRIGGER ' + @QuotedSchemaName + '.' + @QuotedUpdateTriggerName + '     
ON ' + @QuotedSchemaName + '.' + @QuotedTableName + '   
FOR UPDATE   
AS '

IF LTRIM(RTRIM(@DontAuditforUsersTmp)) <> ''
BEGIN
    SELECT @SQLTrigger = @SQLTrigger + CHAR(10) + ' IF SUSER_NAME() NOT IN (''' + @DontAuditforUsersTmp + ''')'

    SELECT @SQLTrigger = @SQLTrigger + CHAR(10) + ' BEGIN'
END

SELECT @SQLTrigger = @SQLTrigger + CHAR(10) + '  

    DECLARE @UpdatedCols varchar(max)

   SELECT @UpdatedCols = ' + @UpdateCheck + '

   IF LTRIM(RTRIM(@UpdatedCols)) <> ''''
   BEGIN
          INSERT INTO ' + @QuotedSchemaNameAudit + '.' + @QuotedSchemaName + '.' + @QuotedAuditTableName + CHAR(10) + '(' + @InsertColList + ')' + CHAR(10) + 'SELECT ' + @ColList + ',''New'',''Update'',SUSER_SNAME(),getdate(),@UpdatedCols  FROM INSERTED    

          INSERT INTO ' + @QuotedSchemaNameAudit + '.' + @QuotedSchemaName + '.' + @QuotedAuditTableName + CHAR(10) + '(' + @InsertColList + ')' + CHAR(10) + 'SELECT ' + @ColList + ',''Old'',''Update'',SUSER_SNAME(),getdate(),@UpdatedCols  FROM DELETED 
   END'

IF LTRIM(RTRIM(@DontAuditforUsersTmp)) <> ''
BEGIN
    SELECT @SQLTrigger = @SQLTrigger + CHAR(10) + ' END'
END

IF @GenerateScriptOnly = 1
BEGIN
    PRINT REPLICATE('-', 200)
    PRINT '--Create Script Update Trigger for ' + @QuotedSchemaName + '.' + @QuotedTableName
    PRINT REPLICATE('-', 200)
    PRINT @SQL
    PRINT 'GO'
    PRINT @SQLTrigger
    PRINT 'GO'
END
ELSE
BEGIN
    PRINT 'Creating Delete Trigger ' + @QuotedUpdateTriggerName + '  for ' + @QuotedSchemaName + '.' + @QuotedTableName

    EXEC (@SQL)

    EXEC (@SQLTrigger)

    PRINT 'Trigger ' + @QuotedSchemaName + '.' + @QuotedUpdateTriggerName + '  Created succesfully'
END

SET NOCOUNT OFF

答案 8 :(得分:0)

您可以简单地创建过程或工作来克服此问题,如下所示:

 create procedure [dbo].[sp_LoadNewData]
 AS
INSERT INTO [dbo].[Table-A-History]
 (
 [1.Column Name], [2.Column Name], [3.Column Name], [4.Column Name]
 )    
 SELECT [1.Column Name], [2.Column Name], [3.Column Name], [4.Column Name]
 FROM dbo.[Table-A] S

 WHERE NOT EXISTS
 (
 SELECT  * FROM [dbo].[Table-A-History] D WHERE D.[1.Column Name] =S.[1.Column Name]
 )

注意:[1.列名]是表的公用列。