时态数据库设计,带有扭曲(实时与草稿行)

时间:2011-06-11 20:16:13

标签: sql database postgresql database-design anchor-modeling

我正在寻求实现对象版本控制,需要同时拥有实时和草稿对象,并且可以使用来自某人经验的见解,因为我开始怀疑它是否可能没有潜在的可怕的黑客。

为了示例,我会将其分解为带标签的帖子,但我的用例更为通用(涉及缓慢变化的维度 - http://en.wikipedia.org/wiki/Slowly_changing_dimension)。

假设您有一个帖子表,一个标签表和一个post2tag表:

posts (
 id
)

tags (
 id
)

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

我需要做一些事情:

  1. 能够准确显示帖子在任意日期时间的样子,包括已删除的行。
  2. 跟踪谁正在编辑什么,以获得完整的审计跟踪。
  3. 需要一组物化视图(“实时”表)以保持参照完整性(即日志记录对开发人员应该是透明的)。
  4. 对于实时最新的草稿行,需要适当快速。
  5. 能够将一个草稿帖子与一个实时帖子共存。
  6. 我一直在研究各种选择。到目前为止,我提出的最好的(没有点#4 /#5)看起来有点像SCD type6-hybrid设置,但是没有当前布尔值,而是当前行的物化视图。出于所有意图和目的,它看起来像这样:

    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 ...我创建了触发器,允许取消对帖子/标签的编辑,使草稿存储到转录中而不发布。它很棒。

    除了,我需要担心post2tag上的草稿行相关关系。在那种情况下,所有的地狱都破裂了,这暗示着我在那里遇到了某种设计问题。但是我的想法已经不多了......

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

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

    我考虑过各种各样的旗帜......

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

5 个答案:

答案 0 :(得分:10)

Anchor modeling是实现时间dB的好方法 - 请参阅 Wikipedia article 。 需要一些时间来习惯,但工作得很好。 有 online modeling tool ,如果您加载提供的XML文件[File -> Load Model from Local File] 你应该看到这样的东西 - 也可以使用[Layout --> Togle Names]

enter image description here

[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>

答案 1 :(得分:2)

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

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

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

答案 2 :(得分:1)

post2tag_revs有一个问题,就是它试图表达两个根本不同的概念。

应用于草稿修订版的标签只适用于该修订版,除非该修订版已发布。

一旦标签发布(即与已发布的修订版相关联),它将适用于该帖子的每个未来版本,直至其被撤销。

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

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

正如您所指出的,这会产生这种关系bi-temporal。您可以通过在post2tag中添加一个布尔值来提高“正常”情况下的性能,以指示该标记当前与帖子相关联。

答案 3 :(得分: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)
)

答案 4 :(得分:0)

仅使用3个表:帖子,标签和post2tag。

将start_time和end_time列添加到所有表。为key,start_time和end_time添加唯一索引。为end_time为null的键添加唯一索引。添加trigers。

目前:

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;