关系型数据库建模:极端历史版本多对多

3
您有两个表 foobar,它们之间存在一个 M:N 的关系。
您想要维护这两个表以及它们之间的关系的历史版本,使得:
  1. 您插入一行到 Foo 表中,然后插入一行到 Bar 表中,最后插入一行到连接了这两个表的 FooBar 表中。您应该能够回溯时间并查看到在 Foo 表中的行曾经是独立的,同理在 Bar 表中的行也是独立的。

  2. 然后您再插入一行到 Bar 表中,并将这第二个 bar 与第一个 foo 连接起来放入到 FooBar 表中。您应该能够回溯时间并查看到第一个 foo 行只被连接到了第一个 bar 行。

  3. 然后您更新 foo 表中的一个属性。您应该能够回溯时间并查看到在先前属性的情况下,Bar 表中的两行都曾经与那个 foo 行连接。

尽管我已经实现了这个功能,但我的解决方案比较繁琐,并且对于单个更新/插入操作会导致大量的 DML 操作。如果添加一个 M:N 的 Baz 表,将会大幅增加 DML 操作次数。是否有更好的标准方法来完成此任务? 以下是我的解决方案:

DDL

Foo
--------------
foo_id            INT            --sequence generated
foo_version_id    INT     UNIQUE --sequence generated
foo_name          VARCHAR
active            INT     CHECK (active in (0,1))
CONSTRAINT  PRIMARY_KEY (foo_id, foo_version_id)


Bar
--------------
bar_id            INT            --sequence generated
bar_version_id    INT     UNIQUE --sequence generated
bar_name          VARCHAR
active            INT     CHECK (active in (0,1))
CONSTRAINT  PRIMARY_KEY (bar_id, bar_version_id)

FooBar
--------------
foo_version_id    FK to foo.foo_version_id
bar_version_id    FK to bar.bar_version_id
CONSTRAINT PRIMARY KEY (foo_version_id, bar_version_id)

DML

以下是三种情况的伪代码。我已经将它们实现为过程。

对于第一种情况,这会导致4个DML操作来将两个独立的foobar链接在一起,不包括前两行:

Insert the first foo row
Insert the first bar row
Update the first foo row and set active to 0. 
Insert a new foo row with the same foo_id, foo_name, but new foo_version_id
Update the first bar row and set active to 0
Insert a new bar row and with the same bar_id, bar_name, but new bar_version_id
Insert a row into foo_bar with the foo_version_id and bar_version_id from the newly created active foo and bar rows.

对于<强>案例#2,这将导致9个DML操作来链接一个新的<代码>bar到与第一个<代码>bar相关联的<代码>foo,不包括第一行:
Insert the second bar row 
Update the active foo and set active to 0
Insert a new foo row with same foo_id, foo_name, but new foo_version_id
Update the first active bar and set active to 0
Insert a new bar row with same bar_id, bar_name, but new bar_version_id
Update the second active bar and set active to 0
Insert a new bar row with same bar_id, bar_name, but new bar_version_id
Insert a row into foo_bar with the foo_version_id and bar_version_id from the foo and first bar.
Insert a row into foo_bar with the foo_version_id and bar_version_id from the foo and second bar.

对于第三个案例,这会导致进行8次DML操作以更新与两个bars相关联的foo上的属性:

Update the active foo and set active to 0
Insert a new foo row with same foo_id, but new foo_version_id, foo_name
(repeat from case #2 starting at line 4)

SQL

在已知 foo_id 的情况下,我可以通过左连接 foofoo_barbar 中的 foo_version_idbar_version_id,查看特定 foo 的所有可能历史状态。

select f.foo_id, f.foo_version_id, f.foo_name, b.bar_id, b.bar_version_id, b.bar_name
FROM foo f, foo_bar fb, bar b
WHERE 1 = 1
    AND f.foo_version_id = fb.foo_version_id (+)
    AND fb.bar_version_id = b.bar_version_id (+)
ORDER BY f.foo_version_id, b.bar_version_id
;

foo_id | foo_version_id | foo_name | bar_id | bar_version_id | bar_name
     1 |              1 |        a |        |                |           -- 1) independent foo
     1 |              2 |        a |      1 |              2 |       b   -- 2) link foo to first bar
     1 |              3 |        a |      1 |              4 |       b   -- 3) link second bar to foo
     1 |              3 |        a |      2 |              5 |       b2  -- 3) link second bar to foo
     1 |              4 |        A |      1 |              6 |       b   -- 4) rename foo_name to A
     1 |              4 |        A |      2 |              7 |       b2  -- 4) rename foo_name to A

你能解释一下为什么需要这种程度的历史细节吗? - Rick James
@RickJames 我们有一个业务需求,要求用户能够回溯到过去任何时刻的 foobar 的任意配置。在这种情况下,foo 的定义包括所有相关的 bar,反之亦然。改变当前 foo 上的属性会改变相关 bar 的性质,反之亦然。 - Matthew Moisen
这个需求的“所有者”是否明白,最终的数据库系统将花费建设和维护的成本是原来的10倍? - Rick James
也许在昨天的备份中找到一个"折衷方案",倒回到那个时间点会够用吗? - Rick James
你在谈论一个时间数据库,它是在SQL-201111(ISO/IEC 9075:201)中添加的。据我所知,目前只有Oracle部分地实现了它。如果你想维护FK关系,那么你不会得到比这更好的选择。如果你不介意不强制执行FK关系,那么就变得容易得多了... - Ben
1个回答

1
你需要一个支持SQL:2011 Temporal(或类似专有系统)的时间数据库。
据我所知,没有开源数据库支持此功能。我已经向Posgres团队施压要求他们完成这个功能一段时间了。
这意味着你需要支付一些费用。以下数据库支持此功能:
IBM DB2 10+
Oracle 12c
MS SQL Server 2016

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接