SQL表中的版本控制 - 如何处理它?

cor*_*iKa 35 sql versioning orm

这是一个虚构的场景,其中包含一些填充数据.出于税收目的,我的虚构公司必须保留历史数据的记录.出于这个原因,我在表格中添加了一个版本列.

TABLE EMPLOYEE: (with personal commentary)

|ID | VERSION | NAME       | Position | PAY |
+---+---------+------------+----------+-----+
| 1 |    1    | John Doe   | Owner    | 100 | Started company
| 1 |    2    | John Doe   | Owner    |  80 | Pay cut to hire a coder
| 2 |    1    | Mark May   | Coder    |  20 | Hire said coder
| 2 |    2    | Mark May   | Coder    |  30 | Productive coder gets raise
| 3 |    1    | Jane Field | Admn Asst|  15 | Need office staff
| 2 |    3    | Mark May   | Coder    |  35 | Productive coder gets raise
| 1 |    3    | John Doe   | Owner    | 120 | Sales = profit for owner!
| 3 |    2    | Jane Field | Admn Asst|  20 | Raise for office staff
| 4 |    1    | Cody Munn  | Coder    |  20 | Hire another coder
| 4 |    2    | Cody Munn  | Coder    |  25 | Give that coder raise
| 3 |    3    | Jane Munn  | Admn Asst|  20 | Jane marries Cody <3
| 2 |    4    | Mark May   | Dev Lead |  40 | Promote mark to Dev Lead
| 4 |    3    | Cody Munn  | Coder    |  30 | Give Cody a raise
| 2 |    5    | Mark May   | Retired  |   0 | Mark retires
| 5 |    1    | Joey Trib  | Dev Lead |  40 | Bring outside help for Dev Lead
| 6 |    1    | Hire Meplz | Coder    |  10 | Hire a cheap coder
| 3 |    4    | Jane Munn  | Retired  |   0 | Jane quits
| 7 |    1    | Work Fofre | Admn Asst|  10 | Hire Janes replacement
| 8 |    1    | Fran Hesky | Coder    |  10 | Hire another coder
| 9 |    1    | Deby Olav  | Coder    |  25 | Hire another coder
| 4 |    4    | Cody Munn  | VP Ops   |  80 | Promote Cody
| 9 |    2    | Deby Olav  | VP Ops   |  80 | Cody fails at VP Ops, promote Deby
| 4 |    5    | Cody Munn  | Retired  |   0 | Cody retires in shame
| 5 |    2    | Joey Trib  | Dev Lead |  50 | Give Joey a raise
+---+---------+------------+----------+-----+
Run Code Online (Sandbox Code Playgroud)

现在,如果我想做一些像"获取当前程序员列表"这样的事情,我不能这么做,SELECT * FROM EMPLOYEE WHERE Position = 'Coder'因为这会返回大量的历史数据......这很糟糕.

我正在寻找处理这种情况的好主意.我看到了一些跳出来的选项,但是我肯定有人会说"哇,这是一个新手的错误,发光......试试这个大小:"这就是这个地方的全部,对吧?:-)

创意编号1:使用当前版本保留版本表

TABLE EMPLOYEE_VERSION:

|ID |VERSION|
+---+-------+
| 1 |   3   |
| 2 |   5   |
| 3 |   4   |
| 4 |   6   |
| 5 |   2   |
| 6 |   1   |
| 7 |   1   |
| 8 |   1   |
| 9 |   2   |     
+---+-------+
Run Code Online (Sandbox Code Playgroud)

虽然我不确定如何使用单个查询来做到这一点,但我确信它可以完成,我敢打赌,我可以通过相当少的努力来解决这个问题.

当然,每次插入EMPLOYEE表时,我都必须更新此表,以增加给定ID的版本(或者在创建新ID时插入版本表).

这的开销似乎是不可取的.

想法2:保留存档表和主表.在更新主表之前,将我要覆盖的行插入到归档表中,并像往常一样使用主表,就好像我不关心版本控制一样.

想法3:找到一个增加某些东西的查询SELECT * FROM EMPLOYEE WHERE Position = 'Coder' and version=MaxVersionForId(EMPLOYEE.ID)...不完全确定我是怎么做到的.这对我来说似乎是最好的主意,但我现在还不确定.

想法编号4:为"当前"创建一列并添加"WHERE current = true AND ..."

它发生在我之前,人们肯定已经做过这件事,碰到这些相同的问题,并有分享的见解,所以我来收集它!:)我已经尝试在这里找到问题的例子,但它们似乎专门针对特定场景.

谢谢!

编辑1:

首先,我感谢所有答案,你们都说了同样的话 - DATE比...更好VERSION NUMBER.我采用的一个原因是VERSION NUMBER简化服务器中的更新过程以防止出现以下情况

人员A在他的会话中加载员工记录3,它具有版本4.人员B在他的会话中加载员工记录3,它具有版本4.人员A进行更改和提交.这是有效的,因为数据库中的最新版本是4.现在是5. Person B进行更改和提交.这失败了,因为最新的版本是5,而他的版本是4.

EFFECTIVE DATE模式将如何解决这个问题?

编辑2:

我想我可以做这样的事情:人A在他的会话中加载员工记录3,它的生效日期是1-1-2010,下午1:00,没有任何消息.B人在他的会话中加载员工记录3,其生效日期为1-1-2010,1:00 pm,没有任何费用.人员A进行更改和提交.旧副本进入存档表(基本上是想法2),考试日期为9/22/2010下午1:00.主表的更新版本的生效日期为2010年9月22日下午1:00.B人进行更改和提交.提交失败,因为生效日期(在数据库和会话中)不匹配.

Not*_*tMe 35

我想你已经走错了路.

通常,对于版本控制或存储历史数据,您可以执行两项(或两项)操作之一.

  1. 您有一个单独的表,模仿原始表+更改日期的日期/时间列.每当更新记录时,您都会在更新之前将现有内容插入到历史记录表中.

  2. 您有一个单独的仓库数据库.在这种情况下,您可以像上面的#1那样对其进行版本修改,或者您只需每隔一段时间对其进行一次快照(每小时,每天,每周......)

将您的版本号保存在与正常版本相同的表中有几个问题.首先,表格大小会像疯了一样增长.这将对正常的生产查询施加持续的压力.

其次,它会大大增加连接等的查询复杂性,以确保使用每个记录的最新版本.

  • [本文](https://www.simple-talk.com/sql/sql-training/yet-another-sql-strategy-for-versioned-data/)显示了一种解决保留策略的一些问题的方法同一个表中的版本化数据 - 通过添加一个属性来指示该行是否为最后一个版本.它可能非常有用,特别是在您希望在选择当前版本时获得最佳性能的情况下(而不是插入或报告旧数据的性能) (2认同)

Zac*_*tes 30

你在这里所拥有的是一个缓慢变化的维度(SCD).有一些经过验证的方法可以解决它:

http://en.wikipedia.org/wiki/Slowly_changing_dimension

以为我会补充说,因为似乎没有人通过名字来称呼它.

  • 感谢您指向常见的设计模式 (2认同)
  • @Konrad SCD 模式适用于数据仓库和 OLTP 数据库 - 但只需确保您在使用设计模式之前了解应用程序的需求即可。那就是疯狂 (2认同)

Red*_*ter 10

这是我建议的方法,过去对我来说效果很好:

  • 忘记版本号.相反,使用StartDateEndDate
  • 写一个触发器,以确保没有相同的重叠日期范围ID,并且只有一个记录与a NULL EndDate相同ID(这是您当前有效的记录)
  • 把索引放在StartDateEndDate; 这应该会给你合理的表现

这将很容易让您按日期报告:

select *
from MyTable 
where MyReportDate between StartDate and EndDate
Run Code Online (Sandbox Code Playgroud)

或获取当前信息:

select *
from MyTable 
where EndDate is null
Run Code Online (Sandbox Code Playgroud)


Jam*_*ane 10

我为最近的数据库设计的方法是使用如下修订:

  • 将您的实体保存在两个表中:

    1. "employee"存储主键ID以及您不希望进行版本控制的任何数据(如果有).

    2. "employee_revision"存储有关雇员的所有显着数据,其中包含employee表的外键和外键,"RevisionID"表示名为"revision"的表.

  • 创建一个名为"revision"的新表.这可以由数据库中的所有实体使用,而不仅仅是员工.它包含主键的标识列(或自动编号,或者数据库调用的任何内容).它还包含EffectiveFrom和EffectiveTo列.我还在表上有一个文本列 - entity_type - 出于人类可读性的原因,它包含主修订表的名称(在本例中为"employee").修订表不包含外键.EffectiveFrom的默认值是19-Jan-1900,EffectiveTo的默认值是31-Dec-9999.这使我无法简化日期查询.

我确保修订表已在(EffectiveFrom,EffectiveTo,RevisionID)以及(RevisionID,EffectiveFrom,EffectiveTo)上编入索引.

然后我可以使用连接和简单的<>比较来为任何日期选择适当的记录.这也意味着实体之间的关系也是完全版本化的.实际上,我发现使用SQL Server表值函数可以非常简单地查询任何日期.

这是一个示例(假设您不希望对员工姓名进行版本设置,以便在他们更改名称时,此更改在历史上是有效的).

--------
employee
--------
employee_id  |  employee_name
-----------  |  -------------
12351        |  John Smith

-----------------
employee_revision
-----------------
employee_id  |  revision_id  |  department_id  |  position_id  |  pay
-----------  |  -----------  |  -------------  |  -----------  |  ----------
12351        |  657442       |  72             |  23           |  22000.00
12351        |  657512       |  72             |  27           |  22000.00
12351        |  657983       |  72             |  27           |  28000.00

--------
revision
--------
revision_id  |  effective_from  |  effective_to  |  entity_type
-----------  |  --------------  |  ------------  |  -----------
657442       |  01-Jan-1900     |  03-Mar-2007   |  EMPLOYEE
657512       |  04-Mar-2007     |  22-Jun-2009   |  EMPLOYEE
657983       |  23-Jun-2009     |  31-Dec-9999   |  EMPLOYEE
Run Code Online (Sandbox Code Playgroud)

将修订元数据存储在单独的表中的一个优点是,可以轻松地将其一致地应用于所有实体.另一个是它更容易扩展它以包括其他东西,例如分支或场景,而不必修改每个表.我的主要原因是它使您的主要实体表保持清晰和整洁.

(上面的数据和示例都是虚构的 - 我的数据库没有为员工建模).


Moh*_*ari 7

尽管这个问题在 8 年前就有人提出过,但值得一提的是,SQL Server 2016 中有一个专门针对此问题的功能。系统版本化的临时表

SQL Server 2016 及更高版本中的每个表都可以有一个历史表,历史数据将由 SQL Server 本身自动填充。

您只需要向表中添加两个 datetime2 列和一个子句:

CREATE TABLE Employee 
(
    Id int NOT NULL PRIMARY KEY CLUSTERED,
    [Name] varchar(50) NOT NULL,
    Position varchar(50)  NULL,
    Pay money NULL,
    ValidFrom datetime2 GENERATED ALWAYS AS ROW START NOT NULL,
    ValidTo datetime2 GENERATED ALWAYS AS ROW END NOT NULL,
        PERIOD FOR SYSTEM_TIME (ValidFrom,ValidTo)
)  
WITH (SYSTEM_VERSIONING = ON);
Run Code Online (Sandbox Code Playgroud)

系统版本表创建了一个临时表,用于维护数据的历史记录。您可以使用自定义名称WITH (SYSTEM_VERSIONING = ON ( HISTORY_TABLE = dbo.EmployeeHistory ) );

此链接中,您可以找到有关系统版本时态表的更多详细信息。

正如@NotMe 提到的,历史表可以增长得非常快,所以有几种方法可以解决这个问题。看看这里