用于保存更改历史记录的SQL架构模式

时间:2015-10-02 11:24:09

标签: sql database-schema

考虑一个维护人员及其联系信息列表的数据库,包括地址等。

有时,联系信息会发生变化。我不想简单地将单人记录更新为新值,而是希望保留更改的历史记录。

我喜欢保留历史记录,当我查看一个人的记录时,我可以快速确定该人的数据也有较旧的记录。但是,我也希望避免构建非常复杂的SQL查询以仅检索每个人的记录的最新版本(虽然这可能很容易使用单个表,一旦表连接到其他表,它很快就会变得困难表)。

我提出了几种方法,我将在下面添加作为答案,但我想知道是否有更好的方法(虽然我是经验丰富的代码编写者,我' m对数据库设计来说相当新,所以我缺乏经验并且已经陷入了一些死胡同。)

哪个DB?我目前正在使用sqlite,但最终计划转移到基于服务器的数据库引擎,可能是Postgres。但是,我的意思是这个问题以更一般的形式提出,并不是针对任何特定的引擎,尽管在某些引擎中如何解决这个问题的建议也是值得赞赏的。

7 个答案:

答案 0 :(得分:2)

通常,变更历史不必是结构化的,因为历史记录仅用于审计目的,并且实际上不需要能够对历史数据执行查询。因此,通常只需 记录对数据库进行的每个修改 ,您只需要一个带有日期时间字段和一些变量的日志表长度文本字段,您可以将人类可读的消息格式化为谁更改了什么,旧值是什么,以及新值是什么。不需要将任何内容添加到实际数据表中,也不需要为查询添加额外的复杂性。

如果必须在数据库中保留历史信息以便能够对其执行查询,那么 我建议使用视图。 将每个表从“NAME”重命名为“NAME_HISTORY”,然后创建一个名为“NAME”的视图,该视图仅向您显示最新记录。如果修改表的代码必须将表引用为“NAME_HISTORY”而不是“NAME”,这是可以的,因为该代码可能还必须考虑到它不更新表的事实,它正在追加新的历史记录。事实上,使用视图可以防止您在不考虑历史性的情况下意外修改表格,这是一件好事。

答案 1 :(得分:1)

这通常被称为Slowly Changing Dimension,链接的维基百科页面提供了几种方法来使这个工作。

Martin Fowler有一个Temporal Patterns列表,这些列表并不完全是特定于数据库的,但它提供了一个很好的起点。

最后,Microsoft SQL Server提供Change Data Capture and Change Tracking

答案 2 :(得分:1)

我们使用所谓的Verity-Block模式。

enter image description here

verity包含周期性,块包含不可变数据。

对于个人数据,我们的Identity版本具有有效期,IdentificationBlock包含NameLastName,{BirthDate {1}}

块是不可变的,因此每当我们更改某些内容时,应用程序都会确保创建一个新块。

因此,如果您的姓氏在2015年1月1日从Smits更改为约翰逊,那么我们有一个真实身份,从[mindate]到2014年12月31日有效,链接到一个其中Lastname = Smits和Identity的IdentificationBlock从2014年1月1日到[maxdate]有效,链接到其中LastName = Johnson的IdentificationBlock。

所以在数据库中我们有表:

Identification
  ID_Identification [PK]

Identity
  ID_Identity [PK]
  ID_Identification [FK]
  ID_IdentificationBlock [FK]
  ValidFrom
  ValidTo

IdentificationBlock
  ID_IdentificationBlock [PK]
  ID_Identification [FK]
  FirstName
  LastName
  BirthDate

获取当前名称的典型查询是

Select idb.Name, idb.LastName from IdentificationBlock idb
join Identity i on idb.ID_Identification = i.ID_Identification
where getDate() between i.ValidFrom and i.ValidTo

答案 3 :(得分:0)

添加"有效"标记或添加"版本"号。

  • 使用标记需要在每个查询的涉及该表的WHERE子句中添加active=1等条件。

  • 使用版本号需要添加子查询,例如:

    version = (SELECT MAX(version) FROM MyTable t2 WHERE MyTable.id = t2.id)

<强>优点:

保持数据库设计简单。

检测历史记录条目很简单 - 只需从查询中删除额外条件即可。

<强>缺点:

更新数据需要相应地设置活动或版本值。 (虽然这可能是用SQL触发器处理的,但我想。)

使查询变得复杂。虽然这可能不会影响性能,但是手动编写和维护此类查询会变得更加困难,查询会变得越复杂,尤其是在涉及联合查询时。

此表中的外键不能使用rowid来引用某个人,因为对该人的更新会在表中创建一个新条目,从而有效地更改该人的最新数据的rowid。

仅在最新版本的数据中维护sqlite中的FTS(全文搜索)表 稍微困难 ,因为自动更新FTS的触发器需要考虑活动值或版本值,以确保只存储最新数据,同时删除过时数据。

答案 4 :(得分:0)

将旧版本移至单独的&#34;历史记录&#34;表

通过使用SQL触发器,旧数据会自动写入&#34;历史记录&#34;表

<强>优点:

只询问最新数据的查询仍然很简单。

通过使用触发器,更新数据并不需要关心维护历史记录。

仅在最新版本的数据中维护sqlite中的FTS(全文搜索)表 easy ,因为触发器仅附加到&#34 ;电流&#34; (非历史)表,从而避免存储过时的数据。

<强>缺点:

检测历史记录条目需要解析一个单独的表(但这不是一个大问题)。通过在历史表中添加反向链接列作为外键,也可以缓解这种情况。

每个维护历史记录的表都需要一个重复的历史表。使编写模式变得乏味,除非编写程序代码来创建这样的&#34;历史&#34;动态表。

答案 5 :(得分:0)

您可以使用AutoAudit,它会创建一个数据库范围的触发器来历史记录表条目。

不要使用软删除(active=1),这是非常糟糕的设计 相反,请使用触发器正确地记录条目。

与为所有历史记录创建一个表的AutoAudit不同,您还可以为每个表创建一个历史记录表,但这需要更多工作。

请记住,AutoAudit不会保存varbinary或text / varchar(MAX)列。

答案 6 :(得分:0)

我们使用历史整数列。始终插入新行,历史记录为0,该条目的任何先前行的历史记录都会增加1。

根据历史数据的使用频率,将历史记录行存储在单独的表中可能更为明智。如果需要组合数据,可以使用简单的视图,如果您通常只需要当前行,它应该加快速度。