数据库设计 - 文章、博客帖子、照片、故事。

34
我正在为一个至少包含4种不同对象类型(文章、博客帖子、照片、故事)的网站设计数据库,每个对象类型都有足够不同的数据需求以证明它们需要自己的表。我们希望用户能够对任何这些类型的内容发表评论。评论的数据要求简单,并且与评论所涉及的内容类型无关(即仅评论正文和作者的电子邮件)。
我想避免创建和管理4个或更多单独的评论表格的冗余,因此我想将所有评论存储在一个表格中,可能通过两列指定关系:一列用于标识父实体,另一列用于父行 ID。
但我不理解如何实现外键,因为外键建立在两个表之间建立关系(对吗?)。
考虑到所有这些,最好的方法是什么?
3个回答

46

以下是一种实现应用程序超类型/子类型表格的方法。

首先,创建超类型表。它包含所有子类型共有的列。

CREATE TABLE publications (
  pub_id INTEGER NOT NULL PRIMARY KEY,
  pub_type CHAR(1) CHECK (pub_type IN ('A', 'B', 'P', 'S')),
  pub_url VARCHAR(64) NOT NULL UNIQUE,
  CONSTRAINT publications_superkey UNIQUE (pub_id, pub_type)
);

接下来是几个子类型表。

CREATE TABLE articles (
  pub_id INTEGER NOT NULL,
  pub_type CHAR(1) DEFAULT 'A' CHECK (pub_type = 'A'),
  placeholder CHAR(1) NOT NULL, -- placeholder for other attributes of articles
  PRIMARY KEY (pub_id, pub_type),
  FOREIGN KEY (pub_id, pub_type) REFERENCES publications (pub_id, pub_type)
);

CREATE TABLE stories (
  pub_id INTEGER NOT NULL,
  pub_type CHAR(1) DEFAULT 'S' CHECK (pub_type = 'S'),
  placeholder CHAR(1) NOT NULL, -- placeholder for other attributes of stories
  PRIMARY KEY (pub_id, pub_type),
  FOREIGN KEY (pub_id, pub_type) REFERENCES publications (pub_id, pub_type)
);

这些子类型表中的CHECK()和FOREIGN KEY约束条件可以防止行引用超类型中错误的行。实际上,它将pub_id值在子类型之间进行分割,保证任何给定的pub_id只会出现在子类型表中的一个,并且需要在{publications.pub_id, publications.pub_type}列对上要么有一个PRIMARY KEY或NOT NULL UNIQUE约束。

评论表很简单。考虑到它的结构对于所有子类型都相同,所以可以引用超类型。

CREATE TABLE comments (
  pub_id INTEGER NOT NULL REFERENCES publications (pub_id),
  comment_timestamp TIMESTAMP NOT NULL DEFAULT now(),
  commenter_email VARCHAR(10) NOT NULL, -- Only allow people who have 
                                        -- really short email addresses
  comment_text VARCHAR(30) NOT NULL,    -- Keep 'em short!
  PRIMARY KEY (pub_id, comment_timestamp, commenter_email)
);

添加一点数据。

INSERT INTO publications VALUES
(1,'A', 'url 1 goes here'),
(2,'A', 'url 2 goes here'),
(3,'S', 'url 3 goes here');

INSERT INTO articles VALUES
(1,'A', 'A'),
(2,'A', 'B');

INSERT INTO stories VALUES
(3,'S', 'A');

INSERT INTO comments VALUES
(1, now(), 'a@b.com','You''re stupid'),
(1, now(), 'b@c.com', 'You''re stupid, too!');

现在你可以创建一个视图来显示所有文章并解析join。 对于每个子类型,您将执行相同的操作。

CREATE VIEW articles_all AS
SELECT P.*, A.placeholder
FROM publications P
INNER JOIN articles A ON (A.pub_id = P.pub_id)
你可能更喜欢类似于“published_articles”而不是“articles_all”这样的名称。
要选择一篇文章及其所有评论,你可以通过左连接这两个表来完成。(但是请看下面为什么你可能不会这样做。)
SELECT A.*, C.*
FROM articles_all A
LEFT JOIN comments C ON (A.pub_id = C.pub_id)
WHERE A.pub_id = 1;

在网页界面中,你可能不会真的这样做,因为数据库管理系统需要返回与评论数相等的文章副本。但在某些应用程序中,这样做是有意义的。在适用的应用程序中,你将为每个子类型使用一个可更新视图,并且应用程序代码大部分时间都将使用可更新视图。


一个更常见的超类型/子类型的业务应用涉及“参与者”(超类型),“组织”和“个人”(子类型,非正式地称为公司人员)。像上面例子中的“评论”一样,地址与超类型相关,因为所有子类型(组织和个人)都有地址。


7
@Snake:这不是个坏主意,但对于联接操作来说并非必需品,而且你也不需要动态SQL(动态SQL存在一些风险,尽可能避免使用)。对于“n”个子类型的超类/子类型设计,需要“n+1”个基本表和“n”个视图。每个“n”个视图将超类型表与一个子类型表进行连接;然后客户端使用这些视图,而不是基本表。(如果必要,编写触发器,以便您可以通过视图执行插入、更新、删除操作。)一旦您拥有了一个命名良好且可更新的视图,您就不需要自己读取该列。它只是帮助SQL维护数据完整性的工具。 - Mike Sherrill 'Cat Recall'
3
@Catcall:谢谢。我清楚了视图的使用。我有点知道,但没有想好这个问题。 另一个问题:我的计划是依靠“Publications”表来生成“Pub_Id”,然后将其复制到子类型表中,因此该值在每个表内部将是唯一的,因此我不需要在“Publications”表上设置复合主键。这样做有道理吗? - Faust
7
@Snake:我理解你的意思,但如果你使用SQL数据库管理系统,就需要那个列和复合键。那个CHAR(1)列在超类型和每个子类型中实现,并作为复合键和外键引用的一部分使用,确保超类型表中的每一行只能连接到一个且仅一个子类型表中的行。如果没有它,你可以在每个子类型表中插入相同的pub_id,这使得超类型/子类型设计变得毫无意义。 - Mike Sherrill 'Cat Recall'
3
@CatCall:回到最后一个问题:考虑到pubtype在主键中的作用,是否有性能或其他原因要将其保留为1个字符?如果是这样,那么使用int类型会更好吗? (顺便说一句:感谢您的时间和耐心。请原谅我对这些事情的“密度”。我的经验主要是前端,但这次我是一个人的项目;不过,幸运的是,我成功游说了很多时间,所以可以仔细考虑这些事情。) - Faust
6
@Snake:CHAR(1)足够长,可以保证可读性,而且占用的空间比整数小。你可能会得到略微更好的CHAR(1)性能,或者你可能会得到略微更好的整数性能。但我通常更喜欢使用文本而不是数字,因为我发现阅读文本更容易。记住(A)文章、(B)博客帖子、(S)故事比记住(1)文章、(2)博客帖子、(3)故事更容易。我不介意花时间;你也不应该介意给我每个回复点赞,包括我的原始答案。;) - Mike Sherrill 'Cat Recall'
显示剩余11条评论

7
你可以在数据库设计中使用超类型/子类型来避免这个问题。为图片、视频、笔记创建一个超类型,然后链接到该超类型。将所有共同的列保留在超类型表中。
以下是几个类似问题/答案以及模型的链接:
- One - Two - Three - Four

4
你的意思是创建图像、视频笔记等子类型,而不是超类型吗? - Pixelated

3

我认为最好为评论单独创建4+个表格,或者您可以使用联接表格。一个表格用于所有的评论...例如:博客表、评论表、博客评论表。这样可以让您拥有外键。

Blog
--------
Blog_id
{other fields}

Blog_Comment
--------------
Blog_id
Comment_id


Comment
------------
Comment_id
{other fields}

1
这是我推荐的设计。最后一种可能性(是否需要这样的东西取决于您的设计)是在评论表或博客/视频/等表中的每个条目中添加一个UserID,以便可以检索特定用户的所有评论(再次强调,如果这符合您的设计要求,并且可能需要根据具体的设计要求进行一些调整...)。UserID最终应该放在哪里可能需要一些思考。 - XIVSolutions

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