【问题标题】:How do I get around this relational database design smell?如何解决这种关系数据库设计的味道?
【发布时间】:2020-03-10 18:12:08
【问题描述】:

我有一个非常简单的 mediaTypes 表,其中包含以下列:

id string
name string

每个 mediaType 记录可以有许多“位置”,可以很容易地设计如下:

展示位置

id string
mediaTypeId string (links to mediaTypes.id)
name string
detail_col_1
detail_col_2
...etc

但是,根据媒体类型,一个展示位置可以包含不同的细节,所以如果我以这种方式设计架构,我最终可能会得到很多可为空的列。

为了解决这个问题,我可以有一个 aPlacements 表和一个 bPlacements 表来匹配每种不同的媒体类型。

展示位置

  id string
  mediaTypeId string (links to mediaTypes.id)
  name string
  placement_details_relevant_to_media_type_col_1
  placement_details_relevant_to_media_type_col_2

bPlacements

  id string
  mediaTypeId string (links to mediaTypes.id)
  name string
  placement_details_relevant_to_media_type_col_1
  placement_details_relevant_to_media_type_col_2

这样做的缺点是我将如何通过 id 找到一个展示位置,因为我必须在所有表中进行查询:

SELECT * FROM aPlacements WHERE id = '1234'
UNION ALL
SELECT * FROM bPlacements WHERE id = '1234'
etc

整个设计感觉有点设计味道。有关如何清理此架构的任何建议?

【问题讨论】:

  • 这是一个常见问题解答--DB/SQL 子类型化/继承/多态性。在考虑发布之前,请始终使用谷歌搜索您的错误消息或您的问题/问题/目标的许多清晰、简洁和精确的措辞,有或没有您的特定字符串/名称和站点:stackoverflow.com 和标签,并阅读许多答案。如果您发布问题,请使用一个短语作为标题。请参阅How to Ask 和投票箭头鼠标悬停文本。
  • 这能回答你的问题吗? How can you represent inheritance in a database?

标签: database-design relational-database rdbms


【解决方案1】:

注意“关系”数据库标签。

整个设计有点设计的味道

是的。闻起来有两个原因。

  1. 您在每个表中都有ids 作为标识符。这会让你感到困惑,并让代码很容易搞砸。对于标识符:
  • 为其识别的事物命名
    例如。 mediaTypeplacementCode(它们是字符串,没错)
  • 作为外键的位置,将其命名为完全相同,这样就不会混淆列是什么以及它引用的 PK

但是根据mediaType,展示位置可以包含不同的详细信息

  1. 从逻辑上讲,您正在寻找的是一个或门
    在关系术语中,它是一个子类型,这里是一个独占子类型
    也就是说,具有完整的完整性和约束。
    mediaType鉴别器

如果我以这种方式设计架构,我最终可能会得到很多可为空的列。

是的,你是对的。可空列表示建模练习 Normalisation 不完整。两个子类型表是正确的。

关系数据模型

注意•符号

  • 我所有的数据模型都在 IDEF1X 中呈现,这是自 1993 年以来为关系数据库建模的标准

  • 我的IDEF1X Introduction是初学者的必备读物。

注意•内容

  • 专有子类型

  • 每个Placement 要么是PlacementA x 要么是PlacementB

  • 请参阅 Subtype 了解有关子类型实现的完整详细信息。

  • 关系键

  • 正如你所给出的,它们是字符串。

  • 根据关系模型的要求,它们是“由数据组成的”。

  • 这样的键是逻辑的,它们确保行是唯一的。

  • 他们还提供了 关系完整性(与参照完整性不同),在这个小数据模型中无法在此处显示。

  • 请注意,系统制造的IDs 不是数据,用户看不到,是物理的,指向记录(不是逻辑行)。它们提供记录唯一性,但不提供行唯一性。他们无法提供关系完整性。

  • RM 要求行(不是记录)是唯一的。

SQL

这样做的缺点是我将如何通过 id 找到一个展示位置,因为我必须在所有表中进行查询:

按上述升级,即:

这样做的缺点是我如何通过 PK Placement 找到相关的 Placement 列,因为我必须查询所有表:

首先,了解 SQL 非常适用于关系数据库,但它本质上是一种低级语言。现实世界中的大多数人都使用 IDE(我不知道有人没有),因此它的许多繁琐都得到了缓解,并且消除了许多编码错误。

我们必须直接编写 SQL 代码,是的,这就是你必须要做的。习惯它。这里只有两张桌子。

您的代码将不起作用,它假定列是相同的数据类型并且顺序相同(这是 UNION 所必需的)。没有。

  • 不要强迫他们这样做,只是为了让你的 UNION 成功。稍后,很可能在一个或另一个子类型中存在额外的列,然后您的代码将在其部署的任何地方严重损坏。

  • 对于已实现的代码,切勿在 SELECT 中使用星号(仅适用于开发)。这保证了数据库更改时的失败。始终使用列列表,并且只请求您需要的列。

SELECT Placement,
       ColumnA1,
       ColumnA2,
       ColumnB1 = "",
       ColumnB2 = "",
       ColumnB3 = ""  
    FROM  PlacementA  
    WHERE Placement = 'ABCD'  
--
UNION
--
SELECT Placement,
       "",
       "",
       ColumnB1,
       ColumnB2,
       ColumnB3  
    FROM  PlacementB  
    WHERE Placement = 'ABCD'

查看

关系模型及其数据子语言 SQL 具有 视图 的概念。这就是人们将如何使用它的方式。每个 Basetype 和 Subtype 组合都被视为一个单元,一行。

创建视图放置A_V 作为 选择放置, 媒体类型, 普通栏目, A1 列, A2 栏 从安置基地 加入安置A SUBA ON BASE.Placement = SUBA.Placement

评论

在 Postgres 中,有没有一种方法可以设置一个约束条件,即展示位置只能存在于 PlacementA 或 PlacementB 中,而不能同时存在于两者中?

  1. 这就是排他性。
  • 如果您阅读了链接的 Subtype 文档,我已经给出了完整的解释和技术细节以实现 in SQL,包括所有代码(按照每个文档)。它包括:
    .
    一个调用FUNCTIONCONSTRAINT
    .
ALTER TABLE ProductBook -- 子类型 添加约束 ProductBook_Excl_ck -- 检查存在条件,它调用 -- 使用PK和鉴别器的功能 检查 (dbo.ValidateExclusive_fn (ProductId, "B") = 1)
  • 根据我的经验,我们在 SQL 方面拥有超过 15 年的经验。
  1. Postgres 在许多方面都不兼容 SQL。没有一个免费软件是符合 SQL 的(他们对 SQL 一词的使用是不正确的)。它们没有服务器架构,大多数不提供 ACID 事务等。大多数不是真正的语言(如 Codd 的十二条规则所要求的)。因此,没有。具体来说,它不能从 DDL 中调用Function(同样,因为它不是一种统一的语言,所以这里和那里是不同的位)。

  2. 只要您理解和实施标准,例如 Open Architecture在您的特定数据库套件中可能达到的程度(它不能被标记为平台,因为它没有服务器架构),这是你能做的最好的。

  3. 开放架构标准要求:

  • 没有直接INSERT/UPDATE/DELETE 到表

  • 您对数据库的所有写入都是通过 OLTP 事务完成的

    • 在 SQL 中的意思是:
      带有BEGIN TRAN ... COMMIT/ROLLBACK TRAN 的存储过程
    • 但在 Postgres 中意味着:
      应该是“原子”的函数
      (引用是因为它远不及在 SQL ACID Transactions 中实现的 Atomic [ACID 中的 A 代表 Atomic])
  1. 因此,取我在 in SQL 中给出的函数中的排他性代码,并且:
  • 将它部署到每个“原子”函数中,INSERT/DELETEs 到您的假装 sql 套件中的 Basetype 或 Subtype 表。
    (我不允许对 Key 进行 UPDATE,请参阅上面的 CASCADE。)

  • 当我们在这里时,必须提到的是,这样的“原子”函数同样需要有代码来确保 Basetype-Subtype 对被 INSERT/DELETEd 作为对或根本不被插入。

【讨论】:

    【解决方案2】:

    也许这是一个主观的解决方案。如果 Placements 表没有太多列,ej: (detail_col_1, detail_col_2, detail_col_3.. detail_col_6) 表设计还不错,我的意思是,它不取决于你有多少空列,也许它看起来很丑但是它应该工作。现在,如果您想要一个复杂的方法,我会建议其中一些:

    1. 包含 json 的简单展示位置表:
    MediaTypes
    + id
    + name
    
    Placements
    + id
    + mediaTypeId
    + name
    + detail
    

    详细来说,我可以将我的属性定义为 json,并为每种类型设置正确的值:

    第 1 行:{'attr1': valx, 'attr2': valy} 第 2 行:{'attr4': valz, 'attr1': valw}

    现在,这里的问题是查询过滤器(你不能)。如果您想保存额外信息,这应该可以工作。

    1. 优雅的方式:
    MediaTypes
    + id
    + name
    
    Placements
    + id
    + mediaTypeId
    + name
    
    DetailAttributes //table of attributes for any type 
    + id
    + name
    + mediaTypeId
    
    PlacementDetailAttributes //many to many rel between DetailAttributes&Placements
    + placementId
    + detailAttributeId
    + value
    

    使用这种方法,您可以根据需要添加许多属性。按属性查询过滤器也应该工作!

    【讨论】:

    • 谢谢@brayan。我认为虽然这两种解决方案都可以工作,但使用 JSON 我不会获得参照完整性,而使用第二种解决方案我必须将 PlacementDetailAttributes 中的值列作为所有内容的文本字段,所以我会丢失使用其他数据类型。
    • @Craig。那是正确的。在列中使用 JSON 或 CSV 会破坏第一范式“每个域 [列] 必须是 [平台中可用的数据类型] 的原子”。因此,它打破了从 1NF 流出的一切:2NF 是不可能的;无 DRI;查询不方便;等等。简单地说,这样的“数据库”不是关系型的,不是数据库。它只是一个特定于应用程序的存储。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-28
    • 2011-12-20
    • 1970-01-01
    • 2019-09-16
    相关资源
    最近更新 更多