优雅的规范化,无需添加字段,额外的表格。最好的关系
Posted
技术标签:
【中文标题】优雅的规范化,无需添加字段,额外的表格。最好的关系【英文标题】:Elegant normalization without adding fields, extra table. Best relationship 【发布时间】:2015-03-01 06:28:45 【问题描述】:我有 2 个表正在尝试规范化。问题是我不想创建一个带有新字段的临时表,尽管链接表可能有效。传达“任天堂”条目既是出版商又是开发商的最优雅方式是什么?我不希望“任天堂”被复制。我认为多对多关系可能是这里的关键。
我想强调,我绝对希望保留开发者和发布者表。我不介意在两者之间建立新关系。
这是我要规范化的 2 个表:
以下是我尝试过的解决方案(我不喜欢它):
【问题讨论】:
我不确定“优雅”的概念是否在数据库规范化中占有一席之地。关系模型基于数学,而不是美术;有一种叫做一阶谓词逻辑的东西,很少有人真正感兴趣,但简短的版本是:你有一个多对多的关系,不管你喜欢与否。使用规范化表表达这一点的唯一方法是使用链接表,句号。 @AirThomas & klandshome:我同意 RM 和精度,但这里没有规范化,也没有多对多关系,除了从两个表中派生的一个,它表达了“开发者 [id]是名为 [name] 的公司”和“发布者 [id] 是名为 [name] 的公司”。 这与规范化无关,也没有必要更改您的表。请参阅我的更新答案。 与您的评论相反,除了通过两个给定的关系外,问题 not“具有”M:M 关系。它和可以用给定的关系(其中有无限的数量)表达的所有其他关系都是多余的。这就是视图和查询给出的。 (表代表关系。如“E-R”中。)如果确定了不可导出的 M:M 关系,则不需要更改两个表。 (PS:关系模式是合乎逻辑的。关系模式作为“物理”是不了解关系模型的演示者、方法和工具的误解。) 【参考方案1】:你的两张桌子没有问题。
其实你只需要
developer(name) -- company [name] is a developer
publisher(name) -- company [name] is a publisher
您的更改与规范化无关。规范化永远不会创建新的列名。 “我不希望“任天堂”被复制”是误解。值出现在多个地方本身并没有错。请参阅 sqlvogel 和我自己 here 的答案。
但是:根据行在其中一个表中的含义,可能会有更好的设计来减少错误,因为这两个表的值可能是“受约束的”,即相互依赖。这与“冗余”有关,但它与约束有关,不涉及规范化。为了让我们解决这个问题,您必须根据世界形势准确地告诉我们一行进入每个表的时间。
如果您出于实现(依赖)原因(占用空间或操作速度以牺牲更多连接为代价)不想重复 字符串,则添加名称 ID 和字符串表(实际上是公司 ID 和名称)并用公司 ID 列和值替换旧名称列和值。但这不是规范化,这会使您的架构复杂化,以实现依赖于实现的数据优化权衡。 (并且您应该证明这是必要的并且有效。)
当前接受的answer(表格 Game_Company、Company_Role 和 Game_Company_Role)只是增加了很多冗余数据。就像您的问题添加了三个冗余表一样。最初的两张表已经说明了哪些公司是开发商,哪些是发行商。其他表只是这两个表的视图/查询!
如果您想要一个用于“[id] 标识名为 [name] 的公司...”的新表,那么这就是开发人员和发布者作为超类型公司的子类型的情况。搜索数据库子类型。见this answer。然后,您将使用公司 ID 而不是名称来识别公司。然后,您还可以通过使用公司 id 作为表 developer 和 publisher 以及其他任何地方而不是 developer_id 和 publisher_id 中的唯一列来进一步简化(!)。
“冗余”与出现在多个位置的值无关。它是关于多行说明应用程序的相同内容。当使用这样的设计时,有两个基本问题:说某些事情涉及多行(而规范化版本只涉及一行);并且没有办法一次只说一件事(规范化可以帮助解决)。如果您对 Nintendo 做出两个不同的独立陈述,那么您需要两张表,并且每张表中都提到了 Nintendo。重新创建有关应用程序的语句的行参见this。 (并在我的其他答案中搜索表的“声明”或标准“。)规范化有帮助,因为它用声明“...”的其他表替换了行状态为“... AND ...”形式的事物的表分别。参见this 和this。(标准化通常被错误地认为涉及或包括避免多个相似的列,避免值具有重复结构的列和/或用 id 替换字符串,但尽管这些可能是很好的设计理念,但它们'不是标准化。)
在 cmets、chat 和另一个答案中你给出了这个起点:
这是最简单的设计。 (我假设游戏标题不是唯一的,因此您需要 game_ids。)
-- game [game_id] with title [title] released on [release_date] is rated [rating]
game(game_id,title,release_date,rating)
game_developer(game_id,name) -- game [game_id] is developed by company [name]
game_publisher(game_id,name) -- game [game_id] is published by company [name]
game_platform(game_id,name) -- game [game_id] is on platform [name]
只有当您想要一个单独的公司列表以便公司可以在不开发或发布的情况下存在和/或可以拥有自己的数据时,您才需要添加:
company(name,...) -- [name] identifies a company
只有当您需要开发者和发布者的角色特定数据时,您才需要添加:
developer(name,...) -- developer [name] has ...
publisher(name,...) -- publisher [name] has ...
各个选项的相关外键一目了然。
你的版本都没有需要_id
s。您的第 2 版和第 3 版将不起作用,因为它们没有说明哪些公司开发了游戏或哪些公司发布了游戏。您不需要需要角色,但如果您有这些角色(版本 2),那么您需要一个表格“游戏 [game_id] 将公司 [name] 作为 [role]”。否则(版本 3)您需要“[game_id] 由公司 [name] 开发”和“游戏 [game_id] 由公司 [name] 发布”的表。无论您与我的设计有什么不同,问问自己为什么你有额外的结构,为什么没有它你可以做,以及(可能)为什么你会明确地想要它。
【讨论】:
没有“新列名”,名称已更改以避免使用 SQL 保留字TYPE
。如果它们代表相同的值,那么“值出现在多个地方”肯定存在问题。这是基本的第一范式,与“占用空间”无关; @klansdhome 告诉我们这两个“Nintendo”是同一家公司,如果您更改其中一个而不更改另一个,则将其放在两个表中可能会导致更新异常。
我没有说你的设计有;用户的“解决方案”引入了“类型”。我并没有说重复项永远不会是多余的,我说它们不一定是多余的。 (与 您自己的表 中的 id 一样。)我没有说规范化节省了空间;我说用 id 替换字符串和查找表可以 & 我说那是 not 规范化。 “这是基本的 1NF”是错误的;了解规范化,即通过其他重新分配列来替换表,以便新表加入旧表。两个表中没有更新异常;了解一个是什么。也请仔细阅读我的回答。
“‘我不希望“任天堂”被复制’是一种误解。值出现在多个地方本身并没有错。”这让人大开眼界。
@klandshome,是的,“两个 Nintendos”是否相同至关重要,因为如果它们相同,则将其放入两次不仅是多余的,而且违反了第一范式并使您的数据库容易受到攻击更新异常。请研究数据库规范化;这不是“用别人替换表”,而是为了防止您的数据变得不一致。
@klandshome 行中的值只会影响 DBA 通过在表中告诉您行状态的内容。 'Nintendo' 是一个值,使用它(关于公司或名称或具有该名称的公司)的行说什么取决于您作为 DBA,您必须告诉我们。显然,这两个“任天堂”的意思是相同的东西,并且没关系。说 Joe 是开发人员,而 Joe 是 pub 并不意味着或说有两个 Joe。它只是使用“乔”做了两个陈述,显然在这里他们都使用“乔”来指代唯一的乔。 (这里的 DourHighArch cmets 都是错误的。)【参考方案2】:
我想你想要这样的东西:
Game_Company
ID Name
1 Retro Studios
2 HAL Laboratories
3 Nintendo
...
Company_Role
ID Name
1 Developer
2 Publisher
...
Game_Company_Role
CompanyID RoleID
1 1
2 1
3 1
3 2
...
要获取具有“开发者”角色的所有公司的列表:
SELECT gc.name
FROM Game_Company gc JOIN Game_Company_Role gcr ON gcr.CompanyID=gc.ID
WHERE gcr.RoleID = 1
【讨论】:
嗯。所以我的解决方案是正确的,也是唯一的方法?我必须创建一个“角色”表吗?无法使其与 Developer 和 Publisher 表一起使用? Company_Role 下的字段也可以是“Type”而不是“Name”吗? 拥有单独的 Developer 和 Publisher 表使它们的行不同的数据;开发商 Nintendo 将是与 Publisher 中的完全不同的 Nintendo。你想要吗? 避免在你的架构中命名任何东西Type
;它是一个 SQL 保留字。
@klandshome & DourHighArch 这个添加的表完全是多余的,它可以表达为对两个表的查询。【参考方案3】:
这是解决问题的一种通用方法,可能会引起人们的兴趣。正如@Dour High Arch 在他的解决方案中指出的那样,开发者和发布者只是“派对”的角色。每个部分都有 0,1 或更多角色与给定的产品和角色可能重叠。这是好的和坏的。例如,一个产品可能由 5 个开发者开发,但最多由 1 个发布者发布。 我选择引入一个 serial_id 作为系统生成的 PK,但这不是强制性的。您可以将 3FK 用作 PK,而不是使用 serial_id。
请注意,将某一方作为不同实体类型的泛化并不总是好的,因为如果所有方不通用,则必须将 1 个或多个列设置为非强制性,但是,这在实际应用中很常见.
约定:
name_PK = 主键,
name_FK = 外键
【讨论】:
ERD 和 UML 有很多不错的工具,我用 Gliffy 在:gliffy.com 这一切没有任何好处,而且会不必要地增加复杂性。原始表格已经说明了哪些公司是开发商,哪些是出版商! @philipxy,我不同意你的评论。假设您要搜索名称“Konami”并且您不知道该名称是开发人员还是公司,您将不得不搜索 2 个表而不是一个。具有 2 个表的 CRUD 可能还必须处理两个表,这会导致更多代码。此外,我提到这是一种概括,不一定是物理实现。 (SELECT * FROM game_publisher UNION SELECT * FROM game_publisher) WHERE name = 'Konami'。 (当您的架构不包含比两个表更多的信息时,您怎么会认为这是不可能的?这两个表没有比您的设计更复杂的 CRUD 更新,因为它们之间没有约束并且它们是在 5NF 中。我知道你在概括;但说真的:当用户的原始表已经足够时?当然,在 cmets 中,OP 说他们想要一个 company(id,name,...) 表;但是最简单的解决方案是只是它和两个原件的投影。 Re the UNION:是的,两张桌子。但没有约束。与公司表和约束相比。 (但我发现这种权衡并不重要,因为原始表格已经足够了。)【参考方案4】:以下是 cmets 提出的三个最终解决方案。您可以看到表格从顶部的“未标准化”表格中分解出来。
规则如下:
1 个游戏可以有 1 个或多个开发者,1 个开发者可以有 1 个或多个游戏。 1 个游戏可以有 1 个或多个发行商,1 个发行商可以有 1 个或多个游戏。 1 个游戏可以有 1 个或多个平台,1 个平台可以有 1 个或多个游戏。版本 1
我将 2 个“Nintendo”条目留为红色。根据研究和实施,这在技术上并不是多余的数据。在philipxy的回答下查看我的cmets。这看起来简单而优雅。 4 个具有多对多关系的表。
这是关系图(4个表和3个链接表):
版本 2
第 1 版“重复”“Nintendo”,但第 2 版有一个“公司”表。比较两个不同的版本。什么是正确的方法?
第 3 版
这里是 philipxy 所说的子类型。这个版本怎么样?
【讨论】:
你是对的。但是您如何看待第 2 版? 我能看一下您关于不使用角色属性的确切含义吗?有点困惑。 如果您想将公司表添加到 1,那么您不需要使用表角色和公司角色公司以及列角色 ID 和名称。只需在开发者和发布者中用 company_id 替换名称即可。请参阅我的答案等等。我认为这很清楚。无论如何,你真的应该把这个答案的问题部分作为一个问题发布。 您需要开发和发布或(更复杂的)公司和角色信息。不管我们是否有公司,我们通常都需要它来获取关于公司的非开发/发布信息。正如我的回答中所解释的,它是一种超类型。对人们来说也是如此,即使他们可以是开发者/发布者。 我刚刚遇到了这个答案并更新了我的答案以解决您的“***非标准化”表和其他解决方案。 (我不记得当时看到第二个聊天请求。)以上是关于优雅的规范化,无需添加字段,额外的表格。最好的关系的主要内容,如果未能解决你的问题,请参考以下文章