ygo*_*goe 5 sql database database-design version
至少在一个应用程序中,我需要在关系数据库中保留旧版本的记录。当应该更新某些内容时,将添加一个新副本,并将旧行标记为非当前行。当应删除某些内容时,应将其标记为非当前或已删除。
有一个简单的用例:记录的新版本只能在当前时间添加,每个版本取代一行。这可用于在保存新数据时存档以前的记录。为此,我会将以下列添加到每个表中:
VersionTime datetime -- Time when this versions becomes effective
IsCurrent bool -- Indicates whether this version is the most current (and not deleted)
Run Code Online (Sandbox Code Playgroud)
如果您只需要知道记录的最新版本是什么,并且只需要单独枚举单个记录的先前版本,那么这很好。时间点查询比第二种变体更痛苦。
一个更通用的变体是这样的:可以在任何指定的有效时间范围内随时添加记录的版本。所以我可以声明一个实体的某些设置在 2013 年底之前有效,它的另一个版本在 2014 年有效,另一个版本将从 2015 年开始有效。这可用于存档旧数据(如上),并提前计划在未来某个时间使用不同的数据(并将此信息保存为存档)。为此,我会将以下列添加到每个表中:
ValidFrom datetime -- Time when this version becomes valid (inclusive)
ValidTo datetime -- Time when this version becomes invalid (exclusive)
Run Code Online (Sandbox Code Playgroud)
第二种方法基本上也可以代表第一种方法,但很难知道哪个版本是最新的 - 因为您还可以为将来添加版本。此外,ValidFrom/ValidTo 设计能够声明重叠范围,并且根据定义,具有最高 ValidFrom 的行应适用于这种情况。
现在我想知道如何实施有效的解决方案来管理和查询此类数据。通常,您可以使用任何类型的 WHERE、GROUP BY 和 JOIN 编写任何 SQL 查询来获取您想要的记录。但是应用版本控制后,您需要考虑每条记录的正确版本。因此,不是连接来自另一个表的记录的每个版本,而是必须添加适当的条件以仅选择在给定时间有效的版本。
一个例子:
SELECT a, b, c
FROM t1
Run Code Online (Sandbox Code Playgroud)
必须改为:
SELECT a, b, c
FROM t1
WHERE t1.ValidFrom <= :time AND t1.ValidTo > :time
ORDER BY t1.ValidFrom
LIMIT 1
Run Code Online (Sandbox Code Playgroud)
更复杂的表连接:
SELECT a, b, c
FROM t1
LEFT JOIN t2 ON (t2.a = t1.a)
Run Code Online (Sandbox Code Playgroud)
必须改为:
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
Run Code Online (Sandbox Code Playgroud)
这仍然不能处理选择重叠时间跨度的正确版本。我可以添加一些清理方法来拉平重叠的版本时间范围,但我不知道这会有多高效。
我正在寻求创建一个类(在我的例子中是 C#),它提供了读取和写入此类版本化记录的方法。编写相对容易,因为查询简单且易于通过事务控制。但是查询需要构建一个 API,该 API 接受 SQL SELECT 查询的每个片段,并智能地构建 SQL 查询以从中执行。这个查询方法应该只接受一个额外的参数,指定从中获取数据的时间。根据每个实体的有效范围,每个实体将选择不同的版本。
这些基本上是我对版本化数据和提供 API 来管理它的不完整想法。你有没有做过这样的事情,想告诉我你的想法?你还有其他有效的想法吗?你能给我提供关于如何实现这个 API 的任何建议吗?虽然我理论上知道怎么做,但我认为这需要做很多工作,我无法估计它的效果如何。
我正在 Oracle 产品(数据库 11g)中使用 SQL。我们有一个庞大的项目,版本控制是其中的重要组成部分。您提到的两种方法都很有用。
如果您的数据库支持触发器并且可以使用 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
Run Code Online (Sandbox Code Playgroud)
如果你想要关于一个主键的所有历史数据,你可以从“生产”表中选择数据,历史表只选择你想要的键并按时间戳排序(对于活动记录将是时间戳SYSTIMESTAMP)。如果您想查看哪条记录处于哪个状态,您可以选择您的日期高于历史(或生产表)中的日期的第一行。
如果您有现有的解决方案
(因此,您的原始数据库模型不包含版本控制部分)
并且您想要创建版本化表,或者您不能使用 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');
Run Code Online (Sandbox Code Playgroud)
你不需要这样的地方:
WHERE VALID_FROM <= :time AND VALID_TO > :time
ORDER BY VALID_FROM LIMIT 1
Run Code Online (Sandbox Code Playgroud)
因为在版本化表中,只有一个版本的记录在任何时间都有效。所以,你只需要这个:
SELECT * FROM DOC
WHERE SYSTIMESTAMP BETWEEN VALID_FROM AND VALID_TO;
Run Code Online (Sandbox Code Playgroud)
这总是只返回一行,您可以使用任何其他日期代替 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');
Run Code Online (Sandbox Code Playgroud)
您可以再次使用与上面相同的选择。
SELECT * FROM DOC WHERE :CUSTOM_DATE BETWEEN VALID_FROM AND VALID_TO;
Run Code Online (Sandbox Code Playgroud)
最佳实践是为版本化表创建活动视图和历史视图。在基表中,您拥有所有数据,并且任何时候您想要实际记录时都必须写入BETWEEN VALID_FROM AND VALID_TO。更好的方法是创建视图:
CREATE VIEW DOC_ACTIVE
AS SELECT * FROM DOC WHERE SYSTIMESTAMP BETWEEN VALID_FROM AND VALID_TO;
Run Code Online (Sandbox Code Playgroud)
或者,如果您需要旧数据:
CREATE VIEW DOC_INACTIVE
AS SELECT * FROM DOC WHERE NOT SYSTIMESTAMP BETWEEN VALID_FROM AND VALID_TO;
Run Code Online (Sandbox Code Playgroud)
现在,代替原来的 SQL:
SELECT a, b, c FROM t1
Run Code Online (Sandbox Code Playgroud)
您不需要使用复杂的结构,只需将表更改为“活动”视图(如 DOC_ACTIVE):
SELECT a, b, c FROM t1_VIEW
Run Code Online (Sandbox Code Playgroud)
请也看看这个答案: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)
Run Code Online (Sandbox Code Playgroud)
版本 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)
Run Code Online (Sandbox Code Playgroud)
此后,在 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)
Run Code Online (Sandbox Code Playgroud)
我们工作项目中的所有这些操作都是由 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);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
6222 次 |
| 最近记录: |