我如何避开这种关系数据库设计的味道?

How do I get around this relational database design smell?

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

id string
name string

每个mediaType记录可以有多个"placements",可以很容易的设计如下:

展示位置

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

然而,根据媒体类型,展示位置可以包含不同的详细信息,因此如果我以这种方式设计架构,我可能会得到很多可为空的列。

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

一个展示位置

  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 找到展示位置,因为我必须查询所有 tables:

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

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

也许这是一个主观的解决方案。如果展示位置 table 没有太多列,则 ej: (detail_col_1, detail_col_2, detail_col_3.. detail_col_6) table 设计不是那么糟糕,我的意思是,它不取决于你有多少空列,也许它看起来很丑但它应该有效。现在,如果你想要一个复杂的方法,我会建议其中的一些:

  1. 包含 json 的简单展示位置 table:
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

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

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

The whole design feels like a bit of a design smell

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

  1. 您在每个 table 中有 ids 作为标识符。这会让你感到困惑,并使代码很容易搞砸。对于标识符:
  • 根据它识别的事物命名
    例如。 mediaType, placementCode(都是字符串,正确)
  • 它作为外键所在的位置,命名完全相同,这样就不会混淆列是什么,它引用的 PK 是什么

However depending on the mediaType, a placement can contain different details

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

if I designed the schema this way I may end up with a lot of nullable columns.

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

关系数据模型

注意 • 表示法

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

  • 我的IDEF1X Introduction是初学者必读的

注意•内容

  • 独家亚型

  • 每个 Placement 要么是 PlacementA 异或 PlacementB

  • 参考Subtype了解子类型实现的完整细节。

  • 关系键

  • 它们是字符串,如您所给。

  • 根据关系模型

    ,它们“由数据组成”
  • 这样的键是逻辑的,它们确保行是唯一的。

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

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

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

SQL

The drawback of this is how would I then find a placement by id as I'd have to query across all tables:

按上述升级,即:

The drawback of this is how would I then find the relevant Placement columns by the PK Placement, as I'd have to query across all tables:

首先,了解 SQL 非常适合关系数据库,但就其本质而言,它是一种低级语言。在现实世界中,我们大多数人都使用 IDE(我不知道有谁不这样做),因此它的繁琐程度大大降低,许多编码错误也被消除了。

我们必须直接编码 SQL 的地方,是的,这就是你必须做的。习惯它。这里只有两个table。

您的代码将不起作用,它假定列是相同的数据类型并且顺序相同(这是 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 组合被视为一个单元,一行。

CREATE VIEW PlacementA_V 
AS
    SELECT  Placement,
            MediaType,
            ColumnCommon,
            ColumnA1,
            ColumnA2
        FROM Placement  BASE
        JOIN PlacementA SUBA
            ON BASE.Placement = SUBA.Placement

评论

In Postgres, is there a way I could setup a constraint where the placement can ONLY exist in either PlacementA OR PlacementB and not both?

  1. 这就是排他性。
  • 如果您阅读链接的 Subtype 文档,我已在 SQL[中给出了完整的实施说明和技术细节,包括所有代码(点击每个文档中的链接)。它包括:
    .
    a CONSTRAINT 调用 FUNCTION
    .
 ALTER TABLE ProductBook  -- subtype
     ADD CONSTRAINT ProductBook_Excl_ck
         -- check an existential  condition, which calls
            -- function using PK & discriminator
         CHECK ( dbo.ValidateExclusive_fn ( ProductId, "B" ) = 1 )
 
  • 根据我的经验,我们 在 SQL 中拥有这种能力超过 15 年。
  1. Postgres 在很多方面都不 SQL 兼容。 None 的免费软件是 SQL 兼容的(他们对术语 SQL 的使用是不正确的)。它们没有服务器架构,大多数不提供 ACID 事务等。大多数不是真正的语言(正如 Codd 的十二条规则所要求的)。因此,不。具体来说,它不能从 DDL 中调用 Function(同样,因为它不是一种统一的语言,所以这里和那里的位不同)。

  2. 只要您了解并实施标准,例如 Open Architecture,以在您的特定数据库中达到可能的程度suite(不能称为平台,因为它没有服务器架构),那是你能做的最好的。

  3. 开放架构标准要求:

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

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

    • 在SQL中表示:
      BEGIN TRAN ... COMMIT/ROLLBACK TRAN
    • 的存储过程
    • 但在 Postgres 中意味着:
      应该是“原子”的函数
      (引用是因为它远不及 SQL ACID Transactions 中实现的 Atomic [ACID 中的 A 代表 Atomic] )
  1. 因此,在SQL中,取我在中给出的Function中的Exclusivity代码,并且:
  • 在每个“原子”函数中部署它 INSERT/DELETEs 到您假装的 sql 套件中的基类型或子类型 table。
    (我不允许更新密钥,请参阅上面的 CASCADE。)

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