【问题标题】:One table or many? [closed]一桌还是多桌? [关闭]
【发布时间】:2008-10-14 08:39:06
【问题描述】:

我正在尝试设计一个应用程序来保存学术参考信息。问题是每种不同类型的参考资料(例如期刊文章、书籍、报纸文章等)都需要不同的信息。例如,期刊参考文献需要期刊标题和文章标题以及页码,而书籍需要出版商和出版日期,而期刊文章不需要。

因此,我应该将所有参考文献存储在数据库的一个表中,并且在它们不适用时将字段留空,还是应该有各种表,例如 BookReferences、JournalReferences、NewspaperReferences,并在每个表中放置适当的参考文献一。那么问题将是它会使搜索所有参考文献变得更加困难,而且编辑可能不得不更加独立地进行。

(顺便说一下,我计划在这个项目中使用 Ruby on Rails,但我怀疑这对这个设计问题有什么影响)

更新:

对此还有更多看法吗?我希望得到一个简单的答案,说一种特定的方法绝对被认为是“最好的”——但像往常一样,事情并不像这样简单。 Single-Table Inheritance 选项看起来很有趣,但我可以很容易地找到关于它的信息不多 - 我可能会在此站点上发布另一个关于此的问题。

我分为Olvak's answerCorey's answer。 Corey 的回答很好地说明了为什么 Olvak 不是最好的,但 Olvak 的回答很好地说明了为什么 Corey 不是最好的!我从来没有意识到这会如此困难......

非常感谢任何进一步的建议!

【问题讨论】:

  • 我真的很喜欢这个问题,谢谢。我一直在考虑与电子商务环境中的 Product 表有关的类似问题,这里的答案很容易适用于此。干杯。
  • 只是想知道:您希望拥有多少条记录?显然只是一个大概的数字。我认为这也应该是最终决定的一个因素。
  • 如果您在 Olvak 和 Corey 的答案之间挣扎,请查看投票。与 DBs 合作了 15 年,我更喜欢 Olvak 的解决方案。投票似乎也表明这也是正确的方法。
  • @Tom H:好点子。我绝对倾向于他的解决方案
  • @nickf:很好的问题——问题是我真的不知道。那里肯定会有数百个不同的参考资料(只是为了支持我自己的个人使用),但我计划将它作为一个公共 web 应用程序,以便它可以以一种疯狂的方式起飞(我们总是希望!)跨度>

标签: database database-design


【解决方案1】:

我希望为所有参考文献提供一个表,但对于元数据的附加表(如 BookReferences 等)并不适用于所有参考类型。

搜索和查询不会更困难 - 毕竟您可以像在单表解决方案中一样创建一个聚合所有信息的视图,然后进一步查询该视图。

将所有内容放在一个包含大量空值的表中似乎是更简单的解决方案,但实际上会带来很多麻烦。例如:使用单独的表,您可以为每个 BookReference 定义哪些字段是必需的,但如果所有内容都在一个表中,则每个字段都必须是可空的,因此是可选的。插入无效数据也会更容易,例如也错误地包含非空期刊名称的书籍参考。

编辑:有些人似乎害怕加入。 不要害怕连接!如果您在多个查询中使用完全相同的连接确实会很乏味,但在这种情况下连接应该在视图中定义,并且您的查询应该查询该视图。视图实际上是关系数据库中的基本抽象,您应该使用它们的原因与在代码中使用函数的原因相同:避免重复,以及封装和创建抽象。

编辑:有一些关于性能的 cmets。很难事先猜测 DB 模式的性能,因为它通常不直观。例如,几个表之间的连接很容易比单个表的全表扫描更快——这完全取决于查询的类型、数据的性质、可用的索引等等。此外,在许多数据库系统中,您可以使用物化视图等特性来优化不同查询的性能,而不会影响逻辑模型。恕我直言,“性能非规范化”如今主要是货物崇拜,除非您是 Google 或 Flickr。

【讨论】:

  • 你把话从我手里夺走了,小偷! :)
  • 文档管理方法(例如,由 Documentum 使用)
  • 如何链接到其他表格?例如,如果我的 Reference 表中的记录 1 是 Book Reference 并因此链接到 BookReference 表中的记录,我怎么知道在该表而不是 JournalReference 表中查找它?
  • 两个选项:1) 检查所有其他表以查看哪些(如果有)链接到参考 #1。 2)如果引用只能是一种类型,您可以添加一个字段来指示每条记录的类型。
  • 我同意这个设计。 BookReferences 将包含父 References 表的外键。这确实允许多个子表引用父表中的同一行,但它仍然是最好的折衷方案。
【解决方案2】:

“一张大桌子让生活更轻松”:我已经看到了这样做的自然结果,作为一个 100 多列的表格,我可以告诉你,我发现使用它并不快乐。

主要问题是此类表的设计者倾向于忽略确保数据完整性所需的约束。例如,OP 说:

期刊参考需要期刊标题和文章标题以及页码,而书籍需要出版商和出版日期,而期刊文章不需要

...这意味着以下约束:

CONSTRAINT a_journal_must_have_a_journal_title
   CHECK ( type <> 'journal' OR journal_title IS NOT NULL );

CONSTRAINT a_journal_must_have_an_article_title 
   CHECK ( type <> 'journal' OR article_title IS NOT NULL );

CONSTRAINT a_journal_must_have_a_page_number 
   CHECK ( type <> 'journal' OR page_number IS NOT NULL );

CONSTRAINT a_journal_cannot_have_a_publisher 
   CHECK ( type <> 'journal' OR publisher IS NULL );

CONSTRAINT a_journal_cannot_have_a_publication_date 
   CHECK ( type <> 'journal' OR publication_date IS NULL );

CONSTRAINT a_book_cannot_have_a_journal_title 
   CHECK ( type <> 'book' OR journal_title IS NULL );

CONSTRAINT a_book_cannot_have_a_article_title 
   CHECK ( type <> 'book' OR article_title IS NULL );

CONSTRAINT a_book_cannot_have_a_page_number 
   CHECK ( type <> 'book' OR page_number IS NULL );

CONSTRAINT a_book_must_have_a_publisher 
   CHECK ( type <> 'book' OR publisher IS NOT NULL );

CONSTRAINT a_jbook_must_have_a_publication_date 
   CHECK ( type <> 'book' OR publication_date IS NOT NULL );

...我怀疑这只是冰山一角!

我希望在编写了数百个这样的约束之后,设计者可能会对所有那些可以为空的列重新考虑:)

【讨论】:

    【解决方案3】:

    我的建议是从正确设计数据库开始,即使用规范化来确保表只包含关于一件事(书籍、期刊等)的数据,并且属性存储在正确的表中。

    如果将来它产生性能问题,您可以将其反规范化为更少的表,但这不太可能成为问题,除非您拥有庞大的数据库。

    创建一个包含所有引用的公共属性的表。

    创建单独的表来保存特定于每种引用类型的属性。

    另一个问题是您是否会多次引用单个作品,例如数百个对特定期刊的引用。然后,规范化会建议您有一个包含期刊(标题、作者、期刊)的表,一个包含特定于期刊(文章、页面)的参考信息的表,以及另一个包含所有参考文献通用数据的表(参考日期,参考类型)。

    【讨论】:

    • 哦!提出了一些我没有想到的有趣问题。谢谢:-)
    【解决方案4】:

    在添加需要额外字段的新引用类型时,具有“类型”字段的单个表会出现问题。扩展类型字段值没有问题,但您必须向表中添加列,为所有当前行填充默认值等。

    使用单独的表格可以轻松添加新的引用类型(并自动为其生成表单!),搜索也不会变得更加困难。

    【讨论】:

      【解决方案5】:

      Rails 支持单表继承和多态 ActiveRecord 类型。我建议研究一下这些 - ActiveRecord 对如何构建数据库有一些意见。

      【讨论】:

      • 我认为这是正确的想法。不过,单表继承模式并不是 Rails 特有的。
      【解决方案6】:

      我认为您必须提前了解每种解决方案的 SQL 会是什么样子。如果您进行了该练习,那么您会发现将所有内容放在一个表中是最容易编码的,并且可能会带来最佳性能。从一张表中分离出你想要的东西比把多张表中的东西放在一起更容易。

      假设 my-one-big-table 看起来像这样:

      1 个 ID
      2种
      3 领域常见的书籍和期刊
      4 特定领域的书籍
      5 特定领域的期刊

      如果我只是对书籍感兴趣,我可以创建一个视图,或者只是简单的 sql,如下所示:

      create view book as  
      select id, field_common-to-book-and-journal, field-specific-to-book
      from my-one-big-table
      where type = 'book'
      

      所以,当我需要时,很容易模拟数据在单独的表中。

      但是,如果我开始将数据放在单独的表中,那么我最终会像这样编写 SQL:

      select id, field-common-to-book-and-journal from books
      union
      select id, field-common-to-book-and-journal from journal-articles
      union
      .... etc, for each type
      

      我不了解其他数据库,但是在 SQL Server 中进行联合可能会很昂贵,并且在使用诸如 ntext 之类的数据类型时会受到限制。

      如果您遵循 olavk 的建议,那么用于在一个查询中组合类型的 SQL 最终将如下所示:

      select 
          common.id, 
          common.field-common-to-book-and-journal, 
          book.field-specific-to-book 
          journal.field-specific-to-journal
      from common-table common
      left outer join book-specific-table book on 
      left outer join journal-specific-table journal on
      ... etc, for each type
      

      我使用过使用所有这三种方式的系统,到目前为止,一张大桌子让生活更轻松。

      【讨论】:

      • 我完全同意。您还可以根据类型添加一些约束,以使列对于特定类型是必需的。这些缓解了“一切都可以为空”的问题。
      • Mat:如何根据类型进行约束?这可以在数据库本身中完成,还是必须由应用程序控制?
      • 我假设触发器会拒绝插入缺少字段的数据...
      • 当然,SQL 长什么样并不重要,你可以把它放到一个视图中并以这种方式使用它。
      • 我认为您的意思是“内部联接”而不是第一个查询示例中的联合?
      【解决方案7】:

      其中很多最好取决于有多少不同的字段和字段大小,您对总行大小有限制(这可以在某种程度上忽略,因为知道所有字段永远不会全部填写,但是一旦您到达页面太宽的地方,数据库中的实际存储最终会拆分信息,从而使检索时间更长。因此,如果信息很小并且(这很重要)不太可能发生太大变化(这将是一个罕见的事件需要添加尚未考虑的新类型信息),那么单表是更好的路线。如果表太宽或需要存储的数据类型可能会发生许多可能的变化,那么spearate table 会是更好的方法,虽然它总是很难正确查询。如果你经常想同时查询多种类型的引用,大表是一种更有效的方法。如果你通常只需要抓取一个一次,你输得很亮就拥有连接的效率而言。

      如果您选择使用单表路由,请确保在表上放置触发器,以执行每种数据类型的数据完整性规则。您将需要它,因为您不能依赖于使字段成为必填项。

      拥有单独表的一个问题是,直到运行时您才知道需要加入哪些表。这会让你进入我不喜欢的动态 SQl 领域(出于安全、效率和维护原因),或者让你对可能需要或不需要的表进行左连接,这是低效的。

      另一种可能性是将整个引用字符串存储在一个更大的字段中,并在连接记录并将信息发送到数据库之前使用用户界面检查以确保所有必需的部分都在那里。对于大多数需要所有信息但如果只需要提取部分数据的查询来说,这将是迄今为止最快的查询。它还依赖于通过用户界面插入的所有数据,这对您来说可能是也可能不是。老实说,我看不出你需要在哪里单独列出这些信息,所以这就是我可能会采取的方法。但我不了解您的业务规则,所以请持保留态度。

      【讨论】:

        【解决方案8】:

        还有另一种选择:我不会完全赞同,但它仍然是另一种选择:

        使用三个表:

        refs (id, title, refType)
        -- title of the reference, and what type of reference it is
        
        fieldDef (id, fieldName, refType, dataType)
        -- name of the field, which reference types it applies to, and
        -- what type of data is stored in these fields (ISDN number, date, etc)
        
        fields (refId, fieldId, value)
        -- where you actually add data to the references.
        

        refType 可以是引用的类型,如果将其设为整数,其值会以 2 的幂次方 (1, 2, 4, 8...) 递增,则可以将它们相加以形成位掩码fieldDef 表。

        优点:非常简单且可扩展。如果您想出另一种类型的引用,或现有引用类型的新字段类型,则可以非常快速地添加它。可以为每种参考类型自动生成表格。所有数据都存储在一个地方,这意味着您无需跟踪CRUD operations 的多个架构(架构?)

        缺点:这是制作 The Daily WTF 的素材。 Select 语句可能会变得非常混乱和复杂。数据库无法执行类型检查(例如:日期等),通用“值”字段不会针对存储在其中的数据进行优化。

        【讨论】:

        • 非常有趣的想法 - 但我可以看到它是如何导致 TheDailyWTF 的!
        • 我无法告诉您我们公司在 2000 年初决定使用极端版本的成本有多大。由此产生的基础架构可能被总共 5 名员工所理解。简单的事情在随后的几年里没有完成的机会成本是巨大的!
        • 正确 - 那时绝对不会使用此选项!谢谢你的建议。
        • 这被称为实体-属性-值,这是一个糟糕的主意。而且由于 OP 只有几个不同的子类型,EAV 是多余的。
        • 相反,我会说只有少数类型的系统是这种方法的最佳候选者。当然,我绝对不会将它用于比书目数据库大得多的东西。
        【解决方案9】:

        我不觉得加入表格的需要特别乏味;我会在这里采取更规范的方法。

        【讨论】:

          【解决方案10】:

          我的建议是一个表和一个“类型”字段

          【讨论】:

            【解决方案11】:

            您在询问数据库规范化。杰夫·阿特伍德在他的帖子Maybe Normalizing Isn't Normal 中写道。很好读。

            【讨论】:

            【解决方案12】:

            我过去最终做的是使用子类别:有一个包含所有 common 字段的表,然后是几个可以有零或一的表与“核心”表的关系。

            下面的例子类似于我们在“野外”使用的东西;它基本上构建了一个分层数据结构,其中每个节点可能是一个文件夹或文档:

            创建表节点( Id int 身份主键, ParentId int null 引用 Node.ParentId, 名称 varchar(50) 不为空, 说明 varchar(max) null ) 创建表文档( Id int 主键引用 Node.Id, FileExtension char(3) 不为空, MimeType varchar(50) 不为空, ContentLength bigint 不为空, FilePathOnDisk varchar(255) ) 创建表文件夹 ( Id int 主键引用 Node.Id, ReadOnly 位不为空 )

            所以你的GetFolder sproc 就可以了:

            选择 n.Id、n.ParentId、n.Name、n.Description、f.ReadOnly 从节点 n 加入文件夹 f ON n.Id = f.Id 其中 f.Id = @Id

            这很好地转化为基于类的继承:

            公共类文件夹:节点 { 公共布尔 IsReadOnly { 获取;放; } ...ETC }

            【讨论】:

              【解决方案13】:

              Olavk 提出了很好的观点,Corey 给出了非常详细的解释。不过,阅读 Corey 的信息,我对 Olavk 的回答有了一个结论。请记住,根据您对信息的处理方式,您最终可能会进行 2 阶段查询。找到该项目,然后针对每个参考,直接选择感兴趣的内容。

              还要考虑将所有内容存储在多个表中并从单个表中读取的想法。我为我拥有的大型数据库执行此操作,其中大多数查询都需要某些公共信息,但仍然需要完整的多表布局。插入被它们启动的触发器减慢了一点(在我的情况下,每个文件一个,每个文件负责插入多达一百万行),但我后来的选择查询可以从几分钟到个位数秒。

              数据仓库:)

              【讨论】:

                【解决方案14】:

                前段时间我和我的上级讨论了这些问题。当然,我无法证明“hierarchical multi-table 方法”(参见olavk's answer)更好,但我感觉到了!我总是会选择这种方法。一个包含实体共有的所有字段的根表,以及 1-1 个包含它们不共有的字段的子表。如果需要,这种方法可以扩展到更多的子表,只要业务逻辑和其他实体有一些东西就可以了。也就是说,我认为没有必要过火。

                我也反对在没有根表的情况下创建单独的“子”表,其中每个表都有相同字段的副本。我认为Corey's answer 建议将这种方法作为一个糟糕的多表模型的示例,他也批评了它。我想补充一点,必须编写连接不是主要问题。这根本不是问题,因为大多数数据库查询都有很多连接,这是正常的事情。与其他表建立关系很困难——你总是需要一个 Id 和一个 TypeId 来知道哪个表链接到它。在根表的情况下,您只需要 Id。

                【讨论】:

                  【解决方案15】:

                  两者如何? 吃蛋糕也吃吧!

                  在“一个大表”和“完全规范化”数据库之间还有另一个选择,它真正结合了两全其美:您可以使用称为 materialized views 的东西,它们就像视图一样灵活并且您根据需要查询尽可能多的表,设置所有连接等,但它们也像表一样,结果实际上存储在表中。

                  这样做的好处是,一旦您设置了它并决定何时刷新它(每次底层表 sis 发生变化,或者每晚只更改一次),您就不必再担心它了。您可以像查询一张大表一样查询物化视图(因为它是),并且性能会很快(比使用它后面的 select 语句要快)。最重要的是,您不必为维护数据完整性而烦恼。这就是数据库要处理的事情。

                  如果您没有开箱即用的数据库支持此功能,您仍然可以通过每天晚上将视图结果构建一个表格作为批处理作业来使用此想法。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2018-04-09
                    • 1970-01-01
                    • 1970-01-01
                    • 2012-05-23
                    • 2012-01-22
                    • 1970-01-01
                    • 2015-02-23
                    • 2017-02-07
                    相关资源
                    最近更新 更多