将多个值字段拆分为数据库中的行的理想方法?

ideal way to split up multiple values field into rows in database?

我有一个歌曲数据库,其中包含歌曲名称、艺术家、歌词作者等字段。有时一首歌可以有多个艺术家或多个歌词作者。

我从数据库规范化中了解到,一个字段应该只有一个值,我应该将它们分成多个 table,例如歌曲艺术家 table、歌曲歌词 table 其中 song_id 是连接它们的主键。

song table

song_id   |   title   |   date
  1            abc    |   2017

song-artist table

song_id   |   artist
  1            John
  1            Joe

但是有了这个设置,感觉我的整首歌 table 已经脱节了。有没有更漂亮的拆分方式?

But with this setup it feels like my whole song table has been disjointed. Is there a prettier way to split them?

漂亮是一个非常主观的词。

设置 1

就传统的数据库关系模型而言,"prettier" 对 N:M 关系的设置将是规范化关系,例如:

SONG (id, title, date)
PERSON (id, name)
SONG_ARTIST (song, person)
SONG_LYRICIST (song, person)

示例:

SONG
 ID       |   title   |   date
  1       |    abc    |   2017

PERSON
 ID       |    name
  1       |    John
  2       |    Mary

SONG_ARTIST
 SONG     |   person
  1       |     1
  1       |     2

SONG_LYRICIST
 SONG     |   person
  1       |     1

这是 N:M 关系的传统设置,它减少了 1) 存储数据所需的大小,2) 冗余风险以及 3) 更容易确保参照完整性。

1) 如果艺术家 John 创作了很多歌曲,则在您的设置中您键入 John 的次数相同。该字段单元格是一个字符串字段。这实际上取决于字段的长度,但通常字符串字段比整数字段需要更多的磁盘字节,因此重复文本字段通常需要比重复整数字段更多的磁盘space。

2) 冗余的风险之一与数据输入有关。如果您必须多次输入一个字符串,在某些时候您可能会拼错它,从而创建一个 "new" 艺术家。另一个风险与数据维护有关。比方说,您意识到自己打错了艺术家的名字。该艺术家创作了 10 首歌曲,his/her 姓名在您的数据库中出现了 10 次。您将不得不更改它 10 次,并且在大多数情况下,这项工作将需要手动完成(更多时间和风险)。

使用传统的关系设置,您只需键入一次艺术家姓名。如果你拼错了它会到处都拼错,但是如果你改变它它会自动为所有的人改变。

3) 死板的结构有其难处,但1个人与his/her首歌曲之间的关系不易被解读。它可能输入错误,但毫无疑问是哪首歌是哪个艺术家写的。该系统甚至能够区分两位名字相同的艺术家。由于这一点,您可以应用规则来确保参照完整性(例如 "delete in SONG_ARTIST any reference to a particular person when I remove it from the table PERSON")

尽管您说您可以接受名称更改,但我强烈建议您将这些人放在他们自己的 table 中,并在将他们与歌曲相关联时参考他们。

设置 1.1

从上面的例子来看,如果你想添加关于 bands/groups 的信息(或任何其他信息),你需要做的第一件事就是分析这个实体和每个其他实体之间的关系你的数据库。

假设 table BAND 的初始基本定义如下:

BAND
 ID       |   title 
  1       |  TheBand

让我们从最简单的部分开始:

  • 歌曲。 1 首歌曲属于 1 个乐队,但 1 个乐队可能有很多首歌曲 (1:N)

要将乐队与其歌曲 (1:N) 关联起来,我们只需在 table 歌曲中添加 band_id 作为外键。

SONG
 ID       |   title   |   date    |    band
  1       |    abc    |   2017    |      1

只有这样你才能列出一个乐队的所有歌曲。

SELECT song.id, song.title FROM song, band 
WHERE song.band=band.id AND band.id = 1

而且,由于我们知道每首歌曲的音乐家,我们还可以列出乐队中涉及的所有音乐家或词作者。

SELECT person.id, person.name, song.title 
FROM song, band, song_artist, person 
WHERE song.band=band.id AND song_artist.song=song.id 
AND person.id=song_artist.person AND band.id = 1

您可以决定这就是您的应用程序需要知道的全部内容:"who has ever been involved in any song from band X"。

否则,您可能想考虑到乐队经常邀请其他音乐家演奏特定歌曲,但这些音乐家并不是乐队的真正成员。如果您认为您的应用程序需要能够区分谁只是在乐队中协作,谁属于乐队的核心,那么您需要定义人与乐队之间的直接关系。

  • 个人。 1个人可能是多个band的核心组件,1个band可能有多个核心组件(N:M)。

如您所知,关系模型中的 N:M 关系必须使用第三个 table 来实现,它将把乐队和作为核心组成部分的人放在一起。

另一个问题出现了,因为特定频段的核心组件不是静态的,可能会随时间变化。您可以通过向 table BAND_CORE_COMPONENT 添加开始日期和结束日期来解决此问题,因此您知道,对于乐队中的每个人,he/she 何时开始以及何时 he/she 完成后可以问数据库问题如:"who were the core components of band X in january 2012?".

BAND
 ID       |   title 
  1       |  TheBand

SONG
 ID       |   title   |   date    |    band
  1       |    abc    |   2017    |      1

PERSON
 ID       |    name
  1       |    John
  2       |    Mary

SONG_ARTIST
 SONG     |   person
  1       |     1
  1       |     2

SONG_LYRICIST
 SONG     |   person
  1       |     1

BAND_CORE_COMPONENTS
 BAND     |   person   |   started    |    ended
  1       |     2      |  2010-01-01  |  2016-06-01
  1       |     1      |  2012-01-01  |    *null*

在这里你知道从 2010 年初到 2016 年年中,Mary 曾经是 TheBand 的核心成员。我们也知道 John 加入较晚(2012 年)并且仍然是 TheBand 的一部分。我们也知道约翰作为作词人和音乐人参与了 TheBand 的歌曲 abc 并作为核心组件(因为这首歌的日期是 2017 年,约翰目前仍然是核心组件)。在同一首歌中,玛丽以合作者的身份参与其中,因为这首歌的日期是 2017 年,当时她还不是 TheBand 的核心成员。

设置 2

也就是说,最流行和当前的关系数据库系统,例如 MySQL 或最新版本的 PostgreSQL,包含一些新类型,可帮助您以不同的方式处理 N:M 关系方式并减少设置中所需的 tables 的数量

JSON 类型(MySQL 5.7.8 及更高版本,PostgreSQL 9.2 及更高版本)可用于存储 SONG table.

中的关系
SONG
 ID       |   title   |   date    |              artists
  1       |    abc    |   2017    |  {"lyrics": [1], "music": [1,2]}

PERSON
 ID       |    name
  1       |    John
  2       |    Mary

甚至:

SONG
 ID       |   title   |   date    |              artists
  1       |    abc    |   2017    |  {"lyrics": [1], "music": {"voice": [1], "guitar": [2]}}

PERSON
 ID       |    name
  1       |    John
  2       |    Mary

这与其他设置具有相似的优点(减少冗余并保持引用完整性,不太确定磁盘使用情况)但似乎更容易阅读。

它引入了一个新的管理风险:如您所见,字段 artists 允许您在其中存储任何 JSON,因此 JSON 结构可能在不同的地方有所不同行,如果发生这种情况,则数据的结构完整性将被破坏,您的应用程序将不得不处理这个问题。

以下示例存储相同的信息,但使用完全不同的 JSON 结构。

SONG
 ID       |   title   |   date    |              artists
  1       |    abc    |   2017    |  {"lyrics": [1], "music": {"voice": [1], "guitar": [2]}}
  2       |    def    |   2016    |  {"lyrics": [1], "music": [{"person": 1, "instrument": "voice"}, {"person": 2, "instrument": "guitar"}]}

有关 JSON 的更多信息,请输入 MySQL: