单个固定表,多列与灵活的抽象表

我想知道如果你有一个网站有十几个不同types的列表(商店,餐馆,俱乐部,酒店,活动),需要不同的领域,是否有一个好处,创build一个列定义像这样的表
示例商店:

shop_id | name | X | Y | city | district | area | metro | station | address | phone | email | website | opening_hours 

或者更类似于这个的抽象方法:

 object_id | name --------------- 1 | Messy Joe's 2 | Bate's Motel type_id | name --------------- 1 | hotel 2 | restaurant object_id | type_id --------------- 1 | 2 2 | 1 field_id | name | field_type --------------- 1 | address | text 2 | opening_hours | date 3 | speciality | text type_id | field_id --------------- 1 | 1 1 | 2 2 | 1 2 | 3 object_id | field_id | value 1 | 1 | 1st street.... 1 | 3 | English Cuisine 

当然,如果价值是预先定义的(例如:专业可以有自己的清单)

如果我采用抽象的方法,它可以非常灵活,但通过大量的连接,查询会变得更加复杂。 但是我不知道这是否会影响性能,执行这些“更复杂”的查询。

我很想知道这两种方法的优点和缺点。 我可以自己想像,但我没有经验来证实这一点。

有些问题需要澄清和解决才能进行合理的讨论。

先决条件

  1. 标签
    在一个要求精确的行业中,重要的是我们使用精确的标签,以避免混淆,以便我们可以沟通,而不必使用冗长的描述和限定词。

    你发布的FixedTables是非标准化的 。 公平的说,它可能是第三范式的尝试,但实际上它是一个平面文件,非标准化(非“非规范化”)。你已经发表的AbstractTables的含义是, 实体属性值 ,几乎是,但不完全是第六范式,因此比3NF更规范化,当然假设它是正确的。

    • 非标准化的平面文件不是“非规范化”的。 这是充满了重复(没有做任何事情去除重复组和重复列或解决依赖)和空,这是一个在许多方面的性能猪,并防止并发。

    • 为了达到Denormlaised,它必须首先被归一化,然后归一化由于某种原因退出了一点。 由于首先不是标准化,所以不能非规范化。 这简直是​​非正常化。

    • 它不能说是“表演”的非正规化,因为作为一个表演猪,它是performance的对立面。 那么,他们需要一个缺乏正式devise的理由],而“为了performance”就是这样。 即使是最小规模的正式审查也揭露了这种歪曲事实(但只有极less数人能够提供,所以它一直隐藏起来,直到他们得到一个局外人去解决,你猜到了,这是一个巨大的性能问题)。

    • 规范化的结构比非规范化的结构执行得好得多。 更多的标准化结构(EAV / 6NF)比标准化程度更低的结构(3NF / 5NF)performance更好。

    • 我同意OMG小马的主旨,但不是他们的标签和定义

    • 而不是说“ 不要”否定“除非你必须” ,我要说的是, “忠实地 规范化” 时期“”如果有性能问题,你没有正确地规范化“
  2. 维基
    正常forms和正常化的条目是一个完整的笑话。 具体来说,定义是不正确的; 他们混淆了范式; 他们对正常化的过程毫无头绪; 他们同样重视早已被揭穿的荒谬或有疑问的NFs。 其结果是,维基添加到一个已经很混乱,很less理解的主题。 所以,不要浪费你的时间。

    但是,为了进步,没有这个参考提供障碍,让我这样说。

    • 3NF的定义是稳定的,并没有改变。
    • 3NF和5NF之间的NFs有很多混淆。 事实是,这是过去15年来发展的一个领域, 许多组织,学者以及他们的产品有限的供应商跳过来创build一个新的“范式”来validation他们的产品。 所有服务的商业利益和学术不健全。 3NF在其原始的未受攻击的状态意图和保证某些属性。
    • 总的来说,5NF就是今天,3NF是15年前的意思,你可以跳过商业笑话和十二个左右的“特殊”(商业和伪学术)NF,其中一些是在Wiki中确定,甚至在混淆的条款。
  3. 由于您已经能够在您的文章中理解并实施EAV,因此您不会对以下内容有所了解。 当然,一个真正的关系模型是先决条件,强关键,等等。 第五范式是,因为我们正在跳过第四个:

    • 第三范式
      • 简而言之,每个表中的每个非键列与表的主键具有1 :: 1的关系,
      • 并没有其他非关键的专栏
    • 零数据重复(结果,如果规范化是努力进行的;不是通过智能或经验单独实现,或通过努力实现这一目标没有正式的过程)
    • 没有更新exception(当你在某个地方更新列时,不必更新位于其他地方的同一列;该列仅存在于一个地方)。
  4. 第六范式当然是第五范式,再加上:

    • 消除缺失的数据(列)。 这是Null Problem(也称为Handling Missing Values)的真正解决scheme,结果是一个没有Null的数据库。 (可以用5NF的标准和零替代品完成,但这不是最佳的。)如何解释和显示缺失的值是另一回事。
  5. EAV与第六范式
    我写的所有的数据库,除了一个,都是纯粹的5NF。 我曾与(pipe理,修复,增强)两个EAV数据库,我已经实现了一个真正的6NF数据库。 EAV是6NF的松散实现,通常由对Normalization和NFs没有很好把握的人来完成,但是谁能看到EAV的价值,并且需要EAV的灵活性。 你是一个完美的例子。 不同之处在于:因为它是松散的,而且由于实现者没有一个忠实的引用,所以他们只实现他们需要的东西,并且把它们全部写在代码中; 这最终是一个不一致的模型。

    而纯6NF实现确实有一个纯粹的学术参考点,因此它通常是更紧密和一致的。 通常这显示在两个可见的元素:
    • 6NF有一个包含元数据的目录,一切都在元数据中定义,而不是代码。 EAV没有一个,一切都在代码中(实施者跟踪对象和属性)。 很显然,一个目录简化了列,导航的添加,并允许形成公用事业。
    • 6NF了解之后,为Null问题提供了真正的解决scheme。 EAV实现者,因为他们缺less6NF上下文,处理代码中缺失的数据,不一致或者更糟,允许数据库中的空值。 6NF的实现者不允许Nulls,并且一致而优雅地处理丢失的数据,而不需要代码构造(对于Null处理;当然你仍然需要编写丢失数据的代码)。

      例如。 对于具有目录的6NF数据库,我有一组将会生成执行所有SELECT所需的SQL的过程,并且为所有用户提供5NF中的视图,所以他们不需要知道或理解底层的6NF结构。 他们被赶出目录。 因此变化很容易和自动化。 由于缺less目录,EAVtypes会手动执行此操作。

现在,我们可以开始了

讨论

“如果价值是预先定义的(例如:专业可以有他们自己的列表),当然它可以是更抽象的”

当然。 但不要太“抽象”。 保持一致性,并以与其他列表相同的EAV(或6NF)方式实施此类列表。

“如果我采用抽象的方法,它可以非常灵活,但通过大量的连接,查询会变得更加复杂,但是我不知道这是否会影响性能,执行这些”更复杂“的查询。

  1. 关系数据库中的联结是行人。 问题不在于数据库,问题是SQL在处理连接时特别麻烦,尤其是复合键。
  2. EAV和6NF数据库有更多的联接,就像行人一样,不多不less。 如果您必须手动编码每个select,确定,繁琐变得非常麻烦。
  3. 整个问题可以通过以下方法来消除:(a)使用6NF进行EAV;(b)执行目录,从中可以(c)生成所有基本的SQL。 也消除了一整类的错误。
  4. 加盟有点费用是一个常见的神话。 完全错误。 这个连接是在编译时执行的,没有任何实质性的东西来“牺牲”CPU周期。 问题是正在连接的表的大小,而不是这些相同表之间的连接的成本。 以正确的PK⇢FK关系连接两个具有数百万行的表格,每个表格都具有适当的索引(父[FK]侧为唯一;在儿童侧是唯一的)是瞬时的; ; 子索引不是唯一的,但至less领先的列是有效的,它是慢的; 那里没有有用的指标,当然这很慢。 与join成本无关。 在返回多行的地方,瓶颈将是networking和磁盘布局; 不是连接处理。
  5. 所以你可以像你想的那样“复杂”,没有成本,SQL可以处理它。

我很想知道这两种方法的优点和缺点。 我可以自己想像,但我没有经验来证实这一点。

  1. 对于那些没有进展的人来说,5NF(或3NF)是最简单和最好的,在实施,易用性(开发人员和用户),维护方面。 缺点是,每次添加列时,都必须更改数据库结构(表DDL)。 这很好,有些情况下,但在大多数情况下,由于变更控制,相当繁重。 其次,你必须改变现有的代码(代码处理新的列不计算,因为这是一个必要的):在实施好的标准,这是最小化; 他们缺席的范围是不可预测的。

  2. EAV(这是你发布的),允许添加列没有DDL的变化。 这是人们select它的唯一原因。 (处理新列的代码不计算在内,因为这是必须的)。 如果实施得好,不会影响现有的代码; 如果没有,它会的。 但是你需要有能力的开发者。 当EAV执行得不好时,比起5NF做得糟糕的糟糕,但并没有比大多数数据库所存在的非标准化更糟糕(被误解为“非规范化的性能”)。 当然,更重要的是(比5NF / 3NF)更强大的事务处理上下文,因为列更分散。 同样,保留声明性参照完整性也是非常重要的:我所看到的混乱在很大程度上是由于开发人员删除了DRI,因为它变得“难以维护”,结果就像你所想象的那样,一个数据母亲堆满了重复的3NF / 5NF行和列。 不一致的Null处理。

  3. 假设服务器已按照预期目的进行了合理configuration,则性能没有差别。 (好吧,有些特定的优化只有在6NF才有可能,但在其他NF中是不可能的,但是我认为这超出了这个线程的范围。)而且,严重的EAV会导致不必要的瓶颈, Unnormalised。

  4. 当然,如果你使用EAV,我推荐更多的手续。 买足够的钱; 跟6NF一起去; 实施目录; 生成SQL的实用程序; 意见; 处理缺失的数据一致; 完全消除空值。 这样可以降低开发人员的质量隐患。 他们可以忘记EAV / 6NF深奥的问题,使用视图,并专注于应用逻辑。

请原谅这篇长文。

在你的问题中,你同时提出了至less两个主要问题。 这两个问题是EAV和gen-spec。

首先,我们来谈谈EAV。 你的最后一张表(object_id,field_id,value)本质上是一个EAV。 EAV有一个上行空间,而EAV有一个下行空间。 好处在于,结构非常通用,几乎可以容纳任何描述几乎任何主题的数据。 这意味着您可以进行devise和实施,不需要数据分析和对主题的理解,也不用担心错误的假设。 不利的一面是,在获取数据库之前,您必须执行您跳过的数据分析,以便提出任何含义的查询。 这比search效率要严重得多。 但是,您也将在检索效率方面遇到可怕的问题。 只有两种方式来了解这个陷阱:通过它来实现它,或者从那些已经有过的人那里读到它。 我build议阅读。

其次,你有一个gen-spec案例。 你的表(object_id,type_id)捕获一个gen-spec(generalization-specialization)模式,以及相关的表格。 如果我不得不在酒店和餐馆之间进行概括,我可以称之为“公共住宿”或“场地”。 但是我不确定我是否理解你的情况,而且你可能正在为比这两个名字所暗示的更普遍的东西开车。 毕竟,你在列表中包含了“事件”,事件并不是我脑海中的一种场合。

在之前的回复中,我已经把其他人引用到gen-spec和关系模型的读物上。
当两张表非常相似时,他们应该什么时候合并?

但是我不愿意把你的方向发给你,因为我不清楚在构build数据库之前你想要创build一个关系数据模型。 一个数据体和一个相同数据的EAV模型的关系模型几乎完全相互矛盾。 在我看来,您必须先做出select,然后才能探索如何在关系数据模型中expressiongen-spec。

“抽象”方法更好地被称为“规范化”,看起来像第三范式(3NF)。

另一个被称为“非规范化”,并且可以是一个有效的性能选项…当您使用规范化方法遇到速度问题时,而不是之前。

你如何在代码中表示清单? 我想猜测Listing作为一个超types, ShopRestuarant等作为子types?

假设如此,这是如何将子types映射到关系数据库的情况。 通常有三种select:

  • 选项1:每个子types的单个表,在每个表(名称,ID等)中重复公共属性。
  • 选项2:所有对象的单个表格(您的单个表格方法)
  • 选项3:超级types的表和每个子types的表

没有普遍正确的解决scheme。 我的首选一般是从选项3开始; 它提供了一个intituitive结构来处理,很好的规范化,可以很容易地扩展。 它意味着一个单一的连接来检索每个实例 – 但是RDBMS对于连接进行了很好的优化,所以在实际中并不会真正导致性能问题。

如果其他表需要引用所有超types实例(外键扩散),则选项2对于查询(无连接)的性能可能会更高。

第一种select一看似乎是最高性能的,尽pipe有两个注意事项:(1)它没有改变的灵活性。 如果添加新的子types(以及不同的属性),则需要更改表结构并将其迁移。 (2)效率可能比现在低。 由于表格人口稀less,一些数据库不能特别有效地存储。 因此,它可能比选项1效率低 – 因为查询引擎可以加快比search浮点稀疏表空间的速度。

select哪个真正归结为知道您的问题的细节。 我build议阅读一些选项: 这篇文章是一个很好的开始。

心连心

当你开始需要大量不同的实体(甚至在…之前)时,一个nosql解决scheme将比任何一个select都简单得多。 只需存储每个实体/logging与您需要的确切字段。

 { "id": 1, "type":"Restaurant", "name":"Messy Joe", "address":"1 Main St.", "tags":["asian","fusion","casual"] }