36

我希望设计一个数据库来跟踪每组更改,以便将来可以参考它们。例如:

Database A 

+==========+========+==========+
|   ID     |  Name  | Property |

     1        Kyle      30

如果我将行的“属性”字段更改为 50,它应该将该行更新为:

1    Kyle    50

但是应该保存该行的属性在某个时间点为 30 的事实。然后如果该行再次更新为 70:

1    Kyle    70

应该保留该行的属性为 50 和 70 的两个事实,以便通过一些查询我可以检索:

1    Kyle    30
1    Kyle    50

它应该认识到这些只是在不同时间点的“相同条目”。

编辑:这段历史需要在某个时间点呈现给用户,所以理想情况下,应该了解哪些行属于同一个“修订集群”

设计该数据库的最佳方法是什么?

4

4 回答 4

30

一种方法是为MyTableNameHistory数据库中的每个表创建一个,并使其架构与 table 的架构相同MyTableName,除了 History 表的主键有一个名为effectiveUtcDateTime 的附加列。例如,如果您有一个名为 的表Employee

Create Table Employee
{
  employeeId integer Primary Key Not Null,
  firstName varChar(20) null,
  lastName varChar(30) Not null,
  HireDate smallDateTime null,
  DepartmentId integer null
}

那么历史表将是

Create Table EmployeeHistory
{
  employeeId integer Not Null,
  effectiveUtc DateTime Not Null,
  firstName varChar(20) null,
  lastName varChar(30) Not null,
  HireDate smallDateTime null,
  DepartmentId integer null,
  Primary Key (employeeId , effectiveUtc)
}

然后,您可以在 Employee 表上放置一个触发器,这样每次您在 Employee 表中插入、更新或删除任何内容时,都会将一条新记录插入到 EmployeeHistory 表中,其中所有常规字段的值完全相同,并且当前EffectiveUtc 列中的 UTC 日期时间。

然后要查找过去任何时间点的值,您只需从历史记录表中选择其有效Utc 值是您想要的值的 asOf 日期时间之前的最高值的记录。

 Select * from EmployeeHistory h
 Where EmployeeId = @EmployeeId
   And effectiveUtc =
    (Select Max(effectiveUtc)
     From EmployeeHistory 
     Where EmployeeId = h.EmployeeId
        And effcetiveUtc < @AsOfUtcDate) 
于 2013-06-12T21:35:59.717 回答
12

为了补充查尔斯的答案,我将使用实体-属性-值模型,而不是为数据库中的每个其他表创建不同的历史表。

基本上,您会像这样创建一个 History表:

Create Table History
{
  tableId varChar(64) Not Null,
  recordId varChar(64) Not Null,
  changedAttribute varChar(64) Not Null,
  newValue varChar(64) Not Null,
  effectiveUtc DateTime Not Null,
  Primary Key (tableId , recordId , changedAttribute, effectiveUtc)
}

然后,您将在History任何时候在您的一个表中创建修改数据时创建一条记录。

按照您的示例,当您将“Kyle”添加到Employee表中时,您将创建两条记录(每个非 id 属性一个),然后在每次属性更改时创建一条新记录:

History 
+==========+==========+==================+==========+==============+
| tableId  | recordId | changedAttribute | newValue | effectiveUtc |
| Employee | 1        | Name             | Kyle     | N            |
| Employee | 1        | Property         | 30       | N            |
| Employee | 1        | Property         | 50       | N+1          |
| Employee | 1        | Property         | 70       | N+2          |

或者,正如此评论中建议的a_horse_with_no_name,如果您不想为每个字段更改存储新记录,则可以将分组更改(例如在同一更新中更改为 'Kyle' 和30)存储为单个记录. 在这种情况下,您需要以 JSON 或其他一些 blob 格式表示更改的集合。这会将and字段合并为一个 ( )。例如:HistoryNamePropertychangedAttributenewValuechangedValues

History 
+==========+==========+================================+==============+
| tableId  | recordId | changedValues                  | effectiveUtc |
| Employee | 1        | { Name: 'Kyle', Property: 30 } | N            |

这可能比为数据库中的每个其他表创建历史表更困难,但它有多个好处:

  • 向数据库中的表添加新字段不需要将相同的字段添加到另一个表
  • 使用较少的表
  • 随着时间的推移,更容易将更新关联到不同的表

这种设计的一个架构优势是您将应用程序的关注点与您的历史/审计功能解耦。这种设计与使用独立于应用程序数据库的关系甚至 NoSQL 数据库的微服务一样有效。

于 2017-03-03T22:55:13.900 回答
5

最好的方法取决于你在做什么。您想更深入地研究缓慢变化的维度:

https://en.wikipedia.org/wiki/Slowly_changeing_dimension

在 Postgres 9.2 中也不要错过 tsrange 类型。它允许合并start_dateend_date单个列中,并使用 GIST(或 GIN)索引以及排除约束来索引这些内容,以避免重叠日期范围。


编辑:

应该了解哪些行属于同一个“修订集群”

在这种情况下,您希望表格中的日期范围以某种方式或另一种方式出现,而不是修订号或实时标志,否则您最终会在整个地方复制相关数据。

另外,请考虑将审计表与实时数据区分开来,而不是将所有内容存储在同一个表中。它更难实现和管理,但它可以更有效地查询实时数据。


也请参阅此相关帖子:临时数据库设计,有一个转折(实时与草稿行)

于 2013-06-12T21:23:40.437 回答
1

记录所有更改的方法之一是创建所谓的audit triggers. 这样的触发器可以将它们所在的表的任何更改记录到单独的日志表中(可以查询该表以查看更改的历史记录)。

实施细则here

于 2013-06-12T21:30:52.817 回答