【问题标题】:Table creation - move common fields to separate table表创建 - 将常用字段移动到单独的表
【发布时间】:2021-04-26 17:05:11
【问题描述】:

我有一个场景,三种类型的功能具有相同的字段集(除了它们的主键)。

以下是示例。我想知道,将公共字段分组在一个表中是否更好?如果我们创建一个公用表,如何给对应主键表的FK引用呢?更好的方法是什么?

tblCountry tblState tblCity
countryid StateId CityId
Name CountryId StateId
officiallanguage officiallanguage officiallanguage
officialFlag officialFlag officialFlag
officialFlower officialFlower officialFlower
officialAnimal officialAnimal officialAnimal
officialBird officialBird officialBird
... ... ...
... ... ...
... ... ...
etc etc etc

【问题讨论】:

  • 我推荐阅读database normalisation。基本上答案是肯定的。您可以像引用任何其他外键一样引用新记录。你可以很聪明,为所有 3 个实体使用同一个表,有一个类型列来区分它们,还有一个父列来链接到父级。
  • @DaleK 不仅仅是 聪明 使用同一张表,它的设计概念称为 Table per Hierarchy (TPH) 但是它强加了一个层管理回到应用层。实体框架和 NHibernate 之类的 ORM 使这个概念更容易,但这意味着您不能轻松地表达 tblCitytblState 之间的 FK 之类的特定关系,但不允许 tblCity 到 FK 到 tblCountry TBH 可以优化数据库,但其代价是将一些数据完整性任务转移到业务层。
  • 如果您包含更多详细信息,例如每个表的一组记录示例,这可能会吸引较少的固执己见的响应,并且可能不太可能被关闭。

标签: sql-server database-design


【解决方案1】:

你想要的第三范式(3NF)是好的。 从简单的角度来看(影响连接),它尽可能好。国家、州和城市之间的外键是显而易见的和微不足道的。

现在,为了避免复制某些列名,您可以将 country、state 和 city 这三个元素放入一个表中 - 有效地使其成为第二范式。这样一来,您的列名的含义就开始角色扮演了。我的意思是官方语言可以与国家、州或城市相关。从存储表设计来看,这不再明显。只能通过多列键的解释。

因此,简而言之,通过节省一些打字/复制,您将使用具有复杂含义的单个表格而不是使用具有明确含义的三个表格来复杂化任何进一步的工作。

现在对于数据选择,只有在没有别名的情况下,这才是一个问题。 考虑选择一个国家的城市的官方语言。

SELECT
  name,
  officialLanguage,
  name,
  officialLanguage
FROM city
  INNER JOIN state
    ON  state.stateid = city.stateid
  INNER JOIN country
    ON  country.countryid = state.countryid
;

这将失败,因为选择的列不明确。

现在考虑这个查询(缩短别名只是为了演示别名 - 我个人尝试使用最多 10 个字母的别名):

SELECT
  cit.name AS city_name,
  cit.officialLanguage AS city_language,
  cou.name AS country_name,
  cou.officialLanguage aS country_language
FROM city AS cit
  INNER JOIN state AS sta
    ON  sta.stateid = cit.stateid
  INNER JOIN country AS cou
    ON  cou.countryid = sta.countryid
;

非常简洁明了。我可以在不同的查询中使用国家表,而无需从包含国家、州和城市等混合对象的表中预先选择这些国家。

这种方法的唯一缺点是正确索引表的多重连接。

此外,由于世界上有很多国家、州和城市,这种单表方法可能会成为性能问题。

4NF(或至少 BCNF 或 3.5NF,因为它是已知的)最适合在连接中实现快速性能,但在正确索引时连接可能变得复杂(写入)。但是对于数据库引擎,这些是最容易阅读的。

2NF(或我称之为 Excel 表格)对于人类来说最容易阅读。这需要复杂的连接和/或条件(WHERE 子句)才能正确识别子集。

为了使数据库设计最佳使用至少 3NF 或更好,然后准备视图以将数据转回 2NF,以使您的数据具有人类可读性。

【讨论】:

    【解决方案2】:

    您正在寻求有关何时应考虑对架构进行进一步规范化的建议。

    This answer from @KnutBoehnert很详细,我的回答真的是对它的补充。

    规范化数据库结构的目标是不是将字段的重复名称减少到单个引用,而是减少存储在这些字段中的数据的重复。要为给定架构提供明确的答案,通常需要您提供一组记录,但是您的数据集足够简单,可以与我们可以讨论一些假设的现实世界规范相关联。

    这与 OO 编程中的继承或组合不同,我强烈建议您将这些字段封装为表示这些表中记录的对象之间的公共基类上的属性,或者封装为它们都组成的接口进入他们的结构。

    只有当 Country/State/City 的重复字段名称通常具有相同的值时,才有强有力的论据来重构此结构以引入一个单独的表来保存 Language、Flag、Flower、Animal、Bird 的值。

    如果您确实为此信息创建了一个单独的表,那么其中的记录多久会被重复使用一次?例如,有多少个不同的国家会引用相同的记录?很可能没有,因为每个国家/地区的国旗通常是唯一的,但所有这些字段的组合肯定会在每个国家/地区都是唯一的。对于状态,相同的陈述通常是正确的。如果没有重复使用这个新表中的记录,如果关系始终是 1:1,那么数据库和查询将按照您现在的方式留在表中进行优化。

    1:1 关系确实占有一席之地,特别是如果对于给定的概念记录存在不同的用例,其中一组字段与另一组隔离更新或查询,并且这两组具有非常不同的查询率,但是就其本身而言,无需任何进一步的支持理由,可以通过将两个表合并到一条记录中来简化模式中的 1:1 关系。

    例如,如果一个国家内的所有州和一个州内的所有城市都预期具有相同的语言,那么您根本不需要较低级别的字段,您可以使用连接来访问语言字段国家记录。但在现实世界中,有许多国家的州与整个国家的当地语言或方言不同,所以我认为这不适用于这个特定的模式。

    事实上,我认为您当前的架构没有理由改变,现在的结构甚至允许您使用空值来指示该值应该从父级别合并。

    例如,在澳大利亚,State 和 City 的所有记录的语言值很可能都是 English,我们可以使用空合并语句来避免在每个州输入和维护该值的需要,并且城市记录,因此在澳大利亚,在所有州和城市中,officialLanguage 字段中留下了 null 的值,我们仍然可以从父相关国家/地区合并它:

    SELECT
      country.Name AS country_name,
      country.officialLanguage AS country_language
      state.name AS state_name,
      IsNull(state.officialLanguage,country.officialLanguage) AS state_language
      city.name AS city_name,
      COALESCE(city.officialLanguage, state.officialLanguage, country.officialLanguage) AS city_language,
    FROM tblCity AS city
      INNER JOIN tblState AS state
        ON  state.Stateid = city.Stateid
      INNER JOIN tblCountry AS country
        ON  country.Countryid = state.Countryid
    

    以这种方式仅查询 city 表的记录可能如下所示:
    我不确定合并其他字段是否有意义,但可以这样做

    SELECT
      CityId,
      city.StateId,
      state.CountryId,
      ...
      
      COALESCE(city.officialLanguage, state.officialLanguage, country.officialLanguage) AS officialLanguage,
      COALESCE(city.officialFlag, state.officialFlag, country.officialFlag) AS officialFlag,
      COALESCE(city.officialFlower, state.officialFlower, country.officialFlower) AS officialFlower,
      COALESCE(city.officialAnimal, state.officialAnimal, country.officialAnimal) AS officialAnimal,
      COALESCE(city.officialBird, state.officialBird, country.officialBird) AS officialBird,
    FROM tblCity AS city
      INNER JOIN tblState AS state
        ON  state.Stateid = city.Stateid
      INNER JOIN tblCountry AS country
        ON  country.Countryid = state.Countryid
    

    在这种空合并概念的许多实际应用中,应用层会以不同的方式处理这些字段的显示,但对于这个问题,我们今天最关心的是存储。

    【讨论】:

    • 州和城市中使用不同语言的国家/地区示例:瑞士。瑞士德语、法语、意大利语和罗曼语。
    猜你喜欢
    • 2015-09-07
    • 1970-01-01
    • 2017-09-30
    • 1970-01-01
    • 2011-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多