Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/78.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在SQL Server的历史记录表中存储历史记录_Sql_Sql Server - Fatal编程技术网

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

如何在SQL Server的历史记录表中存储历史记录,sql,sql-server,Sql,Sql Server,我有两个表,Table-A和Table-A-History 表A包含当前数据行 表A-History包含历史数据 我希望在Table-A和Table-A-History中包含历史行的数据的最新行 我可以想出两种方法来实现这一点: 每当新数据行可用时,将当前行从表a移动到表a-历史,并使用最新数据更新表a行(通过插入选择或选择进入表) 或 每当有新的数据行可用时,更新表a的行,并将新行插入表a-历史 在性能方面,方法1或2更好吗?有没有更好的方法来实现这一点?方法3:将表格a与表格a-历史

我有两个表,
Table-A
Table-A-History

  • 表A
    包含当前数据行
  • 表A-History
    包含历史数据
我希望在
Table-A
Table-A-History
中包含历史行的数据的最新行

我可以想出两种方法来实现这一点:

  • 每当新数据行可用时,将当前行从
    表a
    移动到
    表a-历史
    ,并使用最新数据更新
    表a
    行(通过
    插入选择
    选择进入表

  • 每当有新的数据行可用时,更新
    表a
    的行,并将新行插入
    表a-历史


  • 在性能方面,方法1或2更好吗?有没有更好的方法来实现这一点?

    方法3:将
    表格a
    表格a-历史
    进行对比。插入
    表A-History
    ,并让适当的过滤逻辑生成
    表A
    。这样,您只需插入到一个表中。

    即使它会占用更多的空间,但让历史记录表也包含最新的记录也可以省去编写报告和查看更改发生的时间和方式的麻烦。在我看来,这是一件值得思考的事情


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

    我更喜欢方法1
    此外,我还将在历史记录表中维护当前记录
    这取决于需要。

    选项1可以。 但你也有方法4:)

  • 将新记录插入到表中

  • 使用mysql调度程序定期将旧记录移动到存档表。您可以在最小负载时安排数据归档,例如在夜间


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

    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'
    

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

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

    有几种方法可以解决这个问题。下面讨论每种方法的利弊

    1-使用触发器审核表。

    如果您希望审核表(插入、更新、删除),请查看我的“如何防止不需要的事务”-带代码的SQL星期六幻灯片组-。填充审核表的触发器可以保存来自多个表的信息(如果选择),因为数据保存为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-记录的有效日期

    如果您永远不会从审核表中清除数据,为什么不将该行标记为已删除,但将其永久保留?许多系统如people soft使用有效的约会来显示记录是否不再活跃。在BI世界中,这称为类型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引入了更改数据捕获功能。虽然这会在事后使用日志读取器跟踪数据更改(CDC), 它缺少诸如是谁以及是什么造成了这种变化之类的东西。MSDN详细信息-

    此解决方案取决于运行的CDC作业。sql代理的任何问题都将导致数据显示延迟

    请参阅更改数据捕获表。

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

    诚恳

    约翰

    演示文稿中的代码片段(Autos数据库)

    **审计表的外观**


    最新版本的SQL server(2016+和Azure)具有时态表,作为一级功能提供了所需的确切功能。


    微软可能有人读过此页。:)

    您可以简单地创建过程或作业来解决此问题,如下所示:

     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.Column Name]是表的常用列。

    我想我应该将表分开,因为表A将保存约10K条经常使用的记录。历史记录表将变得巨大,使用量将大大减少。与表A相比,有5-10%的时间。就性能而言,如果我将表A和表A-History结合起来,让数据库经常搜索10K条记录,而不是一个巨大的表,这不是更好吗?可能,可能不是,取决于插入与选择的比率。您还可以使表A成为索引视图()。这可能会彻底解决您的搜索问题。您是否考虑过使用
    Table-A
    上的触发器为您创建
    Table-A-History
    行?确保它们被设置为最后一次开火()。不,我没有。我将研究触发器。谢谢。好的,我会更新表A中的记录。表A-History表使用代理键+外键链接到表AOPP,对不起。但想法是一样的。如果你不想在白天放松表演,就在晚上做;-)我认为问题不仅在于插入,还在于als
    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];
    
     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]
     )