我如何避开这种关系数据库设计的味道?
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 设计不是那么糟糕,我的意思是,它不取决于你有多少空列,也许它看起来很丑但它应该有效。现在,如果你想要一个复杂的方法,我会建议其中的一些:
- 包含 json 的简单展示位置 table:
MediaTypes
+ id
+ name
Placements
+ id
+ mediaTypeId
+ name
+ detail
详细地说,我可以将我的属性定义为 json,并为每种类型设置正确的值:
第 1 行:{'attr1':valx,'attr2':valy}
第 2 行:{'attr4':valz,'attr1':valw}
现在,这里的问题是查询过滤器(你不能)。如果您想保存额外的信息,这应该有效。
- 一种优雅的方式:
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
是的。它闻起来有两个原因。
- 您在每个 table 中有
ids
作为标识符。这会让你感到困惑,并使代码很容易搞砸。对于标识符:
- 根据它识别的事物命名
例如。 mediaType
, placementCode
(都是字符串,正确)
- 它作为外键所在的位置,命名完全相同,这样就不会混淆列是什么,它引用的 PK 是什么
However depending on the mediaType
, a placement can contain different details
- 你在逻辑上寻找的是或门。
在关系术语中,它是一个子类型,这里是一个独占子类型。
也就是说,具有完整的完整性和约束。
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?
- 这就是排他性。
- 如果您阅读链接的 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 年。
Postgres 在很多方面都不 SQL 兼容。 None 的免费软件是 SQL 兼容的(他们对术语 SQL 的使用是不正确的)。它们没有服务器架构,大多数不提供 ACID 事务等。大多数不是真正的语言(正如 Codd 的十二条规则所要求的)。因此,不。具体来说,它不能从 DDL 中调用 Function
(同样,因为它不是一种统一的语言,所以这里和那里的位不同)。
只要您了解并实施标准,例如 Open Architecture、,以在您的特定数据库中达到可能的程度suite(不能称为平台,因为它没有服务器架构),那是你能做的最好的。
开放架构标准要求:
没有直接INSERT/UPDATE/DELETE
到tables
您对数据库的所有写入都是通过 OLTP 事务完成的
- 在SQL中表示:
BEGIN TRAN ... COMMIT/ROLLBACK TRAN
的存储过程
- 但在 Postgres 中意味着:
应该是“原子”的函数
(引用是因为它远不及 SQL ACID Transactions 中实现的 Atomic [ACID 中的 A 代表 Atomic] )
- 因此,在SQL中,取我在中给出的Function中的Exclusivity代码,并且:
在每个“原子”函数中部署它 INSERT/DELETEs
到您假装的 sql 套件中的基类型或子类型 table。
(我不允许更新密钥,请参阅上面的 CASCADE
。)
当我们在这里时,必须提到,这样的“原子”函数同样需要有代码来确保 Basetype-Subtype 对是 INSERT/DELETEd 作为对或根本不是.
我有一个非常简单的 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 设计不是那么糟糕,我的意思是,它不取决于你有多少空列,也许它看起来很丑但它应该有效。现在,如果你想要一个复杂的方法,我会建议其中的一些:
- 包含 json 的简单展示位置 table:
MediaTypes
+ id
+ name
Placements
+ id
+ mediaTypeId
+ name
+ detail
详细地说,我可以将我的属性定义为 json,并为每种类型设置正确的值:
第 1 行:{'attr1':valx,'attr2':valy} 第 2 行:{'attr4':valz,'attr1':valw}
现在,这里的问题是查询过滤器(你不能)。如果您想保存额外的信息,这应该有效。
- 一种优雅的方式:
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
是的。它闻起来有两个原因。
- 您在每个 table 中有
ids
作为标识符。这会让你感到困惑,并使代码很容易搞砸。对于标识符:
- 根据它识别的事物命名
例如。mediaType
,placementCode
(都是字符串,正确) - 它作为外键所在的位置,命名完全相同,这样就不会混淆列是什么,它引用的 PK 是什么
However depending on the
mediaType
, a placement can contain different details
- 你在逻辑上寻找的是或门。
在关系术语中,它是一个子类型,这里是一个独占子类型。
也就是说,具有完整的完整性和约束。
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?
- 这就是排他性。
- 如果您阅读链接的 Subtype 文档,我已在 SQL[中给出了完整的实施说明和技术细节,包括所有代码(点击每个文档中的链接)。它包括:
.
aCONSTRAINT
调用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 年。
Postgres 在很多方面都不 SQL 兼容。 None 的免费软件是 SQL 兼容的(他们对术语 SQL 的使用是不正确的)。它们没有服务器架构,大多数不提供 ACID 事务等。大多数不是真正的语言(正如 Codd 的十二条规则所要求的)。因此,不。具体来说,它不能从 DDL 中调用
Function
(同样,因为它不是一种统一的语言,所以这里和那里的位不同)。只要您了解并实施标准,例如 Open Architecture、,以在您的特定数据库中达到可能的程度suite(不能称为平台,因为它没有服务器架构),那是你能做的最好的。
开放架构标准要求:
没有直接
INSERT/UPDATE/DELETE
到tables您对数据库的所有写入都是通过 OLTP 事务完成的
- 在SQL中表示:
BEGIN TRAN ... COMMIT/ROLLBACK TRAN
的存储过程
- 但在 Postgres 中意味着:
应该是“原子”的函数
(引用是因为它远不及 SQL ACID Transactions 中实现的 Atomic [ACID 中的 A 代表 Atomic] )
- 在SQL中表示:
- 因此,在SQL中,取我在中给出的Function中的Exclusivity代码,并且:
在每个“原子”函数中部署它
INSERT/DELETEs
到您假装的 sql 套件中的基类型或子类型 table。
(我不允许更新密钥,请参阅上面的CASCADE
。)当我们在这里时,必须提到,这样的“原子”函数同样需要有代码来确保 Basetype-Subtype 对是 INSERT/DELETEd 作为对或根本不是.