在至少一个应用程序中,我需要在关系数据库中保留旧版本的记录。当应该更新某些内容时,将添加新副本,并将旧行标记为不是最新的。当应删除某些内容时,应将其标记为不是当前或已删除。
有一个简单的用例:新版本的记录只能在当前时间添加,每个版本取代一行。这可用于在保存新数据时归档先前的记录。为此,我将为每个表添加以下列:
VersionTime datetime -- Time when this versions becomes effective
IsCurrent bool -- Indicates whether this version is the most current (and not deleted)
如果您只需要知道最新版本的记录是什么,并且仅分别枚举单个记录的先前版本,那么这很好。与第二种变体相比,时间点查询更加痛苦。
更通用的变体是:可以随时为任何指定的有效时间范围添加记录版本。所以我可以声明一个实体的某些设置在2013年底之前有效,并且其另一个版本在2014年有效,而另一个版本将从2015年起生效。这可用于存档旧数据(如上所述),并提前计划在将来的某个时间使用不同的数据(并将此信息保存为存档)。为此,我将为每个表添加以下列:
ValidFrom datetime -- Time when this version becomes valid (inclusive)
ValidTo datetime -- Time when this version becomes invalid (exclusive)
第二种方法基本上也可以代表第一种方法,但更难以了解最新版本的版本 - 因为您还可以为将来添加版本。此外,ValidFrom / ValidTo设计能够声明重叠范围,根据定义,具有最高ValidFrom的行将适用于该情况。
现在,我想知道如何实施有效的解决方案来管理和查询此类数据。通常,您可以使用任何类型的WHERE,GROUP BY和JOIN编写任何SQL查询来获取所需的记录。但是,在应用版本控制的情况下,您需要考虑每个记录的正确版本。因此,不必加入来自另一个表的记录的每个版本,而是必须添加适当的条件以仅选择在给定时间有效的版本。
一个例子:
SELECT a, b, c
FROM t1
必须更改为:
SELECT a, b, c
FROM t1
WHERE t1.ValidFrom <= :time AND t1.ValidTo > :time
ORDER BY t1.ValidFrom
LIMIT 1
使用表连接更复杂:
SELECT a, b, c
FROM t1
LEFT JOIN t2 ON (t2.a = t1.a)
必须更改为:
SELECT a, b, c
FROM t1
LEFT JOIN t2 ON (t2.a = t1.a)
WHERE t1.ValidFrom <= :time AND t1.ValidTo > :time
AND t2.ValidFrom <= :time AND t2.ValidTo > :time
这仍然无法处理选择正确版本的重叠时间跨度。我可以添加一些清理方法来平衡重叠的版本时间范围,但我不知道它会有多高效。
我正在寻求创建一个类(在我的例子中使用C#),它提供了读取和编写此类版本化记录的方法。写作相对容易,因为查询很简单,易于通过事务进行控制。但是查询需要构建一个接受SQL SELECT查询的每个片段的API,并智能地构建SQL查询以从中执行。该查询方法应该只接受一个额外的参数,该参数指定从中获取数据的时间。根据每个实体的有效范围,将选择不同的版本。
这些基本上是关于版本化数据并提供API来管理它的不完整的想法。你有没有做过这样的事情,想告诉我你的想法?你有其他想法运作良好吗?您能否就如何实施此API向我提出任何建议?虽然我理论上知道如何做,但我认为这是很多工作,我无法估计它的效果如何。
答案 0 :(得分:2)
如果您需要将旧数据作为业务逻辑的一部分,那么:
如果旧数据只是更改的跟踪日志,则:
答案 1 :(得分:1)
我在Oracle产品中使用SQL(数据库11g)。我们有庞大的项目,版本控制是其中不可或缺的一部分。你提到的两种方法都很有用
如果您的数据库支持触发并且您可以使用PL / SQL,则可以通过少量努力来分离旧数据。您可以创建before update
和before delete
触发器,然后将所有旧数据存储在特殊历史表中(包含更改日期和类型 - 删除或更新)
假设:您要进行版本控制的所有表格都必须包含主键。
伪代码:
CREATE TRIGGER TRIGGER_ON_VERSIONED_TABLE
BEFORE UPDATE
ON VERSIONED_TABLE
BEGIN
INSERT INTO VERSIONED_TABLE_HISTORY_PART VALUES (:OLD.COLUMN_A, USER, TIMESTAMP);
END
如果您想要有关一个主键的所有历史数据,您可以从“生产”表和历史表中选择数据,只选择您想要的键并按时间戳排序(对于活动记录,将是时间戳SYSTIMESTAMP)。如果您想查看哪个状态是哪个记录,您可以选择您的日期高于历史记录(或生产表)中的日期的第一行。
For before update trigger look here.
如果您有现有解决方案
(因此,您的原始数据库模型不包含版本控制部件)
并且你想创建版本化的表,或者你不能使用PL / SQL,使用你的方法2.我们的工作项目(在Oracle数据库上)也使用这种方法。假设我们有文件表(在现实生活中你有一个版本标识符,它将是这个表的主键,但这只是为了显示原则)
CREATE TABLE DOC(
DOC_NAME VARCHAR(10)
, DOC_NOTE VARCHAR(10)
, VALID_FROM TIMESTAMP
, VALID_TO TIMESTAMP
, CONSTRAINT DOC_PK PRIMARY KEY(DOCUMENT_NAME, VALID_FROM)
);
INSERT INTO doc VALUES ('A', 'FIRST VER', systimestamp, date'2999-12-31');
INSERT INTO doc VALUES ('B', 'FIRST VER', systimestamp, date'2999-12-31');
你不需要这样的地方:
WHERE VALID_FROM <= :time AND VALID_TO > :time
ORDER BY VALID_FROM LIMIT 1
因为在版本化表中,只有一个版本的记录随时有效。所以,你只需要这个:
SELECT * FROM DOC
WHERE SYSTIMESTAMP BETWEEN VALID_FROM AND VALID_TO;
这总是只返回一行,您可以使用任何其他日期代替SYSTIMESTAMP。 但是你无法直接更新记录,首先,你必须更新结束时间戳(但这对你来说不是问题,正如我所见)。所以,如果我更新XK-04,我会这样做:
UPDATE doc SET VALID_TO = systimestamp
WHERE DOC_NAME='A' AND SYSTIMESTAMP BETWEEN VALID_FROM AND VALID_TO;
INSERT INTO doc VALUES ('A', 'SECOND VER', systimestamp, date'2999-12-31');
你可以再次使用与上面相同的选择。
SELECT * FROM DOC WHERE :CUSTOM_DATE BETWEEN VALID_FROM AND VALID_TO;
最佳做法是为版本化表创建ACTIVE和HISTORICAL视图。
在基表中,您拥有所有数据,并且只要您想要实际记录,就必须编写BETWEEN VALID_FROM AND VALID_TO
。更好的方法是创建视图:
CREATE VIEW DOC_ACTIVE
AS SELECT * FROM DOC WHERE SYSTIMESTAMP BETWEEN VALID_FROM AND VALID_TO;
或者,如果您需要旧数据:
CREATE VIEW DOC_INACTIVE
AS SELECT * FROM DOC WHERE NOT SYSTIMESTAMP BETWEEN VALID_FROM AND VALID_TO;
现在,而不是原来的SQL:
SELECT a, b, c FROM t1
您不需要使用复杂的结构,只需将表更改为“活动”视图(如DOC_ACTIVE):
SELECT a, b, c FROM t1_VIEW
请查看此答案:Versioning in SQL Tables - how to handle it?
我不知道你是否看到了有效记录和有效“对象”之间的区别。在我们的工作项目中,我们没有任何有效的重叠范围..例如,所述带有文档的表,来自文档名称和版本号的主键复合...我们有文档A(此文档有效期为2010年 - 2050年)它有2个版本。
Document A, version 1 (2010-2020), record valid 2014-9999: VALID (NEW)
Document A, version 2 (2021-2050), record valid 2014-9999: VALID (NEW)
版本1 是2010年至2020年的文档有效(对象版本,不是记录版本)某些州的文档P.此记录自2014-9999起生效。
版本2 文档有效期为2021年至2050年(对象版本,非记录版本)此记录在2014-9999之间再次有效。文档处于Q状态。
假设它是2016年。您在两个版本的文档中都发现了文书错误。您为两个文档版本创建实际年份(2016年)的新记录版本。完成所有更改后,您将拥有此文档版本:
Document A, version 1 (2010-2020), record valid 2014-2015: INVALID (UPDATED)
Document A, version 2 (2021-2050), record valid 2014-2015: INVALID (UPDATED)
Document A, version 1 (2010-2020), record valid 2016-9999: VALID NOW (NEW)
Document A, version 2 (2021-2050), record valid 2016-9999: VALID NOW (NEW)
在此之后,在2018年,有人创建新版本的文档,仅对2021-2030年有效。 (该文档将来有效,但他的版本今天有效)现在您必须更新VALID版本2并创建版本3.实际状态:
Document A, version 1 (2010-2020), record valid 2014-2015: INVALID (NO CHANGE)
Document A, version 2 (2021-2050), record valid 2014-2015: INVALID (NO CHANGE)
Document A, version 1 (2010-2020), record valid 2016-9999: VALID NOW (NO CHANGE)
Document A, version 2 (2021-2050), record valid 2016-2018: INVALID (UPDATED)
Document A, version 2 (2031-2050), record valid 2018-9999: VALID NOW (NEW)
Document A, version 3 (2021-2030), record valid 2018-9999: VALID NOW (NEW)
我们工作项目中的所有这些操作都是PL / SQL代码 在2018年,如果您选择有效记录的文档,您将获得3行:A1 A2 A3 如果您选择在2015年有效的版本,则只能获得A1(无效)A2(无效)。
因此,您有完整的历史记录,即使该文档有3个有效版本,在同一点有效(记录有效性)。对象有效性是分开的。这是一个非常好的方法,必须满足您的所有要求。
您也可以在VIEWS中轻松使用BETWEEN,对于具有NULL(指示最小值或最大值)的列,如下所示:
CREATE VIEW DOC_ACTIVE AS
SELECT * FROM DOC
WHERE SYSTIMESTAMP BETWEEN NVL(VALID_FROM, SYSTIMESTAMP)
AND NVL(VALID_TO, SYSTIMESTAMP);
答案 2 :(得分:1)
我知道这是一篇过时的文章,但是我不仅想提供解决方案,而且还想与您交流我的想法,并讨论针对此重要版本控制问题的最有效解决方案。
我的想法是
创建一个包含5个主要版本控制字段的表
更新记录时
更新该字段以将( ValidTo )设置为现在日期时间,并将( IsCurrent )设置为false
通过增加( Serial )字段并保持更新记录的相同字段( ID )( ValidFrom )为NOW,( ValidTo )为null,IsCurrent为false。
删除记录时
有效期将设置为现在时间 IsCurrent 设置为false
通过这种方式,您将不会遇到联接问题,因为具有字段ID的联接表将显示所有记录历史记录。
如果您对父表有FK,则可能要删除FK字段的值。
答案 3 :(得分:0)
我曾使用跟踪记录版本,但从不使用重叠范围。但是,我有相似标准选择记录的经验。这是一个应该做你想做的查询。
select *
from t1
where VersionId = (select top 1 VersionId
from t1 as MostRecentlyValid
where MostRecentlyValid.ValidFrom <= @AsOfDate
and (MostRecentlyValid.ValidTo >= @AsOfDate
or MostRecentlyValid.ValidTo is null)
and t1.Id = MostRecentlyValid.Id
order by MostRecentlyValid.ValidFrom desc)
这假设ValidTo也可以为null,表示没有结束日期。如果ValidTo不能为null,则可以删除or条件。这也假设记录在ValidTo日期结束时有效。如果记录在ValidTo日期更改的日期开始时变为旧>&gt; =只是&gt;。
这适用于我尝试过的少量和测试数据,但我相当确定它适用于所有情况。
至于效率,我不是SQL专家,所以我真的不知道这是否是最有效的解决方案。
要加入另一张桌子,你可以做类似的事情
select *
from (select *
from t1
where VersionId = (select top 1 VersionId
from t1 as MostRecentlyValid
where MostRecentlyValid.ValidFrom <= '2014/2/11'
and (MostRecentlyValid.ValidTo >= '2014/2/1'
or MostRecentlyValid.ValidTo is null)
and t1.Id = MostRecentlyValid.Id
order by MostRecentlyValid.ValidFrom desc ) ) as SelectedRecords
inner join t2
on SelectedRecords.Id = t2.Id