21

我正在考虑实现对象版本控制,同时需要同时拥有实时对象和草稿对象,并且可以利用某人在这方面的经验,因为我开始怀疑它是否可能在没有潜在可怕的黑客攻击的情况下实现。

为了示例,我将把它分解为带有标签的帖子,但我的用例更笼统(涉及缓慢变化的维度 - http://en.wikipedia.org/wiki/Slowly_changeing_dimension)。

假设您有一个 posts 表、一个 tags 表和一个 post2tag 表:

posts (
 id
)

tags (
 id
)

post2tag (
 post_id fkey posts(id),
 tag_id fkey tags(id)
)

我需要几件事:

  1. 能够准确显示帖子在任意日期时间的样子,包括已删除的行。
  2. 跟踪谁在编辑什么,以获得完整的审计跟踪。
  3. 需要一组物化视图(“实时”表)以保持引用完整性(即日志记录应该对开发人员透明)。
  4. 对于实时和最新的草稿行,需要适当的快速。
  5. 能够使草稿帖子与实时帖子共存。

我一直在研究各种选择。到目前为止,我想出的最好的(没有第 4 点/第 5 点)看起来有点像 SCD 类型 6 混合设置,但不是当前的布尔值,而是当前行的物化视图。出于所有意图和目的,它看起来像这样:

posts (
 id pkey,
 public,
 created_at,
 updated_at,
 updated_by
)

post_revs (
 id,
 rev pkey,
 public,
 created_at,
 created_by,
 deleted_at
)

tags (
 id pkey,
 public,
 created_at,
 updated_at,
 updated_by
)


tag_revs (
 id,
 public,
 rev pkey,
 created_at,
 created_by,
 deleted_at
)

post2tag (
 post_id fkey posts(id),
 tag_id fkey tags(id),
 public,
 created_at,
 updated_at,
 updated_by
)

post2tag_revs (
 post_id,
 tag_id,
 post_rev fkey post_revs(rev), -- the rev when the relation started
 tag_rev fkey tag_revs(rev), -- the rev when the relation started
 public,
 created_at,
 created_by,
 deleted_at,
 pkey (post_rev, tag_rev)
)

我正在使用 pg_temporal 来维护期间的索引(created_at,deleted_at)。我使用触发器使各种表保持同步。Yada yada yada...我创建了触发器,允许取消对帖子/标签的编辑,这样草稿就可以存储到 revs 中而不被发布。它工作得很好。

除非我需要担心 post2tag 上的草稿行相关关系。在那种情况下,所有的地狱都崩溃了,这向我暗示我在那里有某种设计问题。但我的想法已经用完了......

我考虑过引入数据重复(即为每个草案修订引入 n 个 post2tag 行)。这种工作,但往往比我想要的要慢得多。

我考虑过为“最后的草稿”引入草稿表,但这很快就会变得非常难看。

我考虑过各种各样的标志...

所以问题:在行版本控制的环境中,是否有一种普遍接受的方法来管理实时行和非实时行?如果没有,您尝试过什么并取得了相当大的成功?

4

5 回答 5

11

锚模型是实现时间 dB 的好方法——也请参阅Wikipedia 文章。需要一些时间来适应,但工作得很好。有一个在线建模工具,如果您加载提供的 XML 文件[File -> Load Model from Local File] ,您应该会看到类似这样的内容 - 也可以使用[Layout --> Togle Names].

在此处输入图像描述

[Generate --> SQL Code]将为表、视图和时间点函数生成 DDL。代码很长,这里就不贴了。检查代码——您可能需要为您的数据库修改它。

这是要加载到建模工具中的文件。

<schema>
<knot mnemonic="EXP" descriptor="Expired" identity="smallint" dataRange="char(1)">
<identity generator="true"/>
<layout x="713.96" y="511.22" fixed="true"/>
</knot>
<anchor mnemonic="US" descriptor="User" identity="int">
<identity generator="true"/>
<attribute mnemonic="USN" descriptor="UserName" dataRange="varchar(32)">
<layout x="923.38" y="206.54" fixed="true"/>
</attribute>
<layout x="891.00" y="242.00" fixed="true"/>
</anchor>
<anchor mnemonic="PO" descriptor="Post" identity="int">
<identity generator="true"/>
<attribute mnemonic="TIT" descriptor="Title" dataRange="varchar(2)">
<layout x="828.00" y="562.00" fixed="true"/>
</attribute>
<layout x="855.00" y="471.00" fixed="true"/>
</anchor>
<anchor mnemonic="TG" descriptor="Tag" identity="int">
<identity generator="true"/>
<attribute mnemonic="TGT" descriptor="TagText" dataRange="varchar(32)">
<layout x="551.26" y="331.69" fixed="true"/>
</attribute>
<layout x="637.29" y="263.43" fixed="true"/>
</anchor>
<anchor mnemonic="BO" descriptor="Body" identity="int">
<identity generator="true"/>
<attribute mnemonic="BOT" descriptor="BodyText" dataRange="varchar(max)">
<layout x="1161.00" y="491.00" fixed="true"/>
</attribute>
<layout x="1052.00" y="465.00" fixed="true"/>
</anchor>
<tie timeRange="datetime">
<anchorRole role="IsTagged" type="PO" identifier="true"/>
<anchorRole role="IsAttached" type="TG" identifier="true"/>
<anchorRole role="BYAuthor" type="US" identifier="false"/>
<knotRole role="Until" type="EXP" identifier="false"/>
<layout x="722.00" y="397.00" fixed="true"/>
</tie>
<tie timeRange="datetime">
<anchorRole role="Contains" type="PO" identifier="true"/>
<anchorRole role="ContainedIn" type="BO" identifier="false"/>
<layout x="975.00" y="576.00" fixed="true"/>
</tie>
<tie>
<anchorRole role="CreatedBy" type="TG" identifier="true"/>
<anchorRole role="Author" type="US" identifier="false"/>
<layout x="755.10" y="195.17" fixed="true"/>
</tie>
<tie>
<anchorRole role="CreatedBy" type="PO" identifier="true"/>
<anchorRole role="Author" type="US" identifier="false"/>
<layout x="890.69" y="369.09" fixed="true"/>
</tie>
<tie>
<anchorRole role="ModifiedBy" type="BO" identifier="true"/>
<anchorRole role="Author" type="US" identifier="false"/>
<layout x="1061.81" y="322.34" fixed="true"/>
</tie>
</schema>
于 2011-06-12T15:23:31.893 回答
2

我已经使用 SCD 类型 2 和 PostgreSQL 规则和触发器实现了一个时态数据库,并将其包装在 ActiveRecord 的自包含包中:http: //github.com/ifad/chronomodel

但是,该设计独立于语言/框架 - 您可以手动创建规则和触发器,数据库将负责其余的工作。看看https://github.com/ifad/chronomodel/blob/master/README.sql

还包括使用几何运算符对时间数据进行有效索引和查询作为奖励。:-)

于 2013-02-01T11:05:10.617 回答
1

post2tag_revs 的问题在于它试图表达两个根本不同的概念。

应用于草稿修订后的标签仅适用于该一个修订,除非该修订曾经发布过。

一旦发布了标签(即与已发布的帖子修订相关联),它就会应用于帖子的每个未来修订,直到它被撤销。

并且与已发布的修订关联或取消关联不一定与正在发布的修订同时发生,除非您通过克隆修订人为地强制执行此操作,以便您可以关联标签添加或删除......

我会通过使 post2tag_revs.post_rev 仅与草稿标签相关来更改模型。发布修订版(并且标签生效)后,我将使用时间戳列来标记已发布有效性的开始和结束。您可能需要也可能不需要新的 post2tag_revs 条目来表示此更改。

正如您所指出的,这使得这种关系是双向的。您可以通过向 post2tag 添加一个布尔值来指示该标签当前与帖子相关联,从而提高“正常”情况下的性能。

于 2011-06-11T21:00:29.657 回答
1

我想我搞定了。基本上,您将一个(唯一)草稿字段添加到相关表中,然后您处理草稿,就好像它们是新的帖子/标签/等一样:

posts (
 id pkey,
 public,
 created_at stamptz,
 updated_at stamptz,
 updated_by int,
 draft int fkey posts (id) unique
)

post_revs (
 id,
 public,
 created_at,
 created_by,
 deleted_at,
 pkey (id, created_at)
)

tags (
 id pkey,
 public,
 created_at,
 updated_at,
 updated_by,
 draft fkey tags (id) unique
)


tag_revs (
 id,
 public,
 created_at,
 created_by,
 deleted_at,
 pkey (id, created_at)
)

post2tag (
 post_id fkey posts(id),
 tag_id fkey tags(id),
 public,
 created_at,
 updated_at,
 updated_by,
 pkey (post_id, tag_id)
)

post2tag_revs (
 post_id,
 tag_id,
 public,
 created_at,
 created_by,
 deleted_at,
 pkey (post_id, tag_id, created_at)
)
于 2011-06-12T14:54:23.750 回答
0

仅使用 3 个表:posts、tags 和 post2tag。

将 start_time 和 end_time 列添加到所有表。为 key、start_time 和 end_time 添加唯一索引。为 end_time 为空的键添加唯一索引。添加触发器。

对于电流:

SELECT ... WHERE end_time IS NULL

当时:

WHERE (SELECT CASE WHEN end_time IS NULL
THEN (start_time <= at_time)
ELSE (start_time <= at_time AND end_time > at_time)
END)

由于功能索引,当前数据的搜索并不慢。

编辑:

CREATE UNIQUE INDEX ... ON post2tag (post_id, tag_id) WHERE end_time IS NULL;
CREATE UNIQUE INDEX ... ON post2tag (post_id, tag_id, start_time, end_time);

FOREIGN KEY (post_id, start_time, end_time) REFERENCES posts (post_id, start_time, end_time) ON DELETE CASCADE ON UPDATE CASCADE;
FOREIGN KEY (tag_id, start_time, end_time) REFERENCES tags (tag_id, start_time, end_time) ON DELETE CASCADE ON UPDATE CASCADE;
于 2011-06-12T02:44:51.623 回答