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

Posted

技术标签:

【中文标题】将多个值字段拆分为数据库中的行的理想方法?【英文标题】:ideal way to split up multiple values field into rows in database? 【发布时间】:2017-06-24 10:04:02 【问题描述】:

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

我从数据库规范化中了解到,我应该只为一个字段设置一个值,并且我应该将它们拆分为多个表,例如 song-artist 表、song-lyrist 表,其中以 song_id 作为连接它们的主键。

song table

song_id   |   title   |   date
  1            abc    |   2017

song-artist table

song_id   |   artist
  1            John
  1            Joe

但是有了这个设置,感觉我的整个歌曲表都脱节了。有没有更漂亮的拆分方式?

【问题讨论】:

视情况而定。你的“歌曲”表是读重还是写重?怎么查询的? 你想把所有关于歌曲的信息放在一张表里吗?您对表很大并且其中包含冗余数据还满意吗?您打算如何处理更改,例如更改歌曲名称或更改艺术家姓名? @ChetanRanpariya 使用当前设置,更改艺术家姓名已经很麻烦了。 @reddy 您使用的是哪个数据库供应商? 正确@MarcCompte 最好的方法是为歌曲、艺术家和作词家分别设置表格,并使用映射表将它们链接起来。这将遵循规范化规则,减少冗余并且更改值会更容易。 【参考方案1】:

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

漂亮是一个非常主观的术语。

设置 1

就传统的数据库关系模型而言,像这样的 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 的次数也一样多。该字段单元格是一个字符串字段。这实际上取决于字段的长度,但通常字符串字段比整数需要更多的磁盘字节数,因此重复文本字段通常比重复整数字段需要更多磁盘空间。

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

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

3) 刚性结构有其困难,但 1 个人与他/她的歌曲之间的关系不易被解释。它可能输入错误,但毫无疑问是哪些歌曲写了哪些艺术家。该系统甚至可以区分两位同名的艺术家。多亏了这一点,您可以应用规则来确保引用完整性(例如“当我从表 PERSON 中删除特定人时,在 SONG_ARTIST 中删除对特定人员的任何引用”)

即使你说你可以忍受名字的变化,我还是强烈建议你把这些人放在他们自己的桌子上,并在将他们与歌曲联系起来时引用他们。

设置 1.1

从上面的示例中,如果您想添加例如有关乐队/组的信息(或任何其他信息),您需要做的第一件事是分析该实体与数据库中每个其他实体之间的关系。

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

BAND
 ID       |   title 
  1       |  TheBand

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

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

要将乐队与其歌曲关联起来 (1:N),我们只需将 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

您可以决定这是您的应用程序需要知道的全部内容:“谁曾参与过乐队 X 的任何歌曲”。

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

人。 1 个人可能是许多乐队的核心组成部分,而 1 个乐队可能有许多核心组成部分 (N:M)。

如您所知,关系模型中的 N:M 关系必须通过使用第三张表来实现,该表将作为核心组件的乐队和人员放在一起。

另一个问题出现了,因为特定乐队的核心组件不是静态的,可能会随着时间而变化。您可以通过在表 BAND_CORE_COMPONENT 中添加开始日期和结束日期来解决此问题,因此您知道,对于乐队中的每个人,他/她何时开始以及何时结束,您可以询问数据库问题,例如: “谁是2012年1月乐队X的核心成员?”。

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 的一部分。我们还知道,John 作为 TheBand 的歌曲 abc 的作词家和音乐家参与其中,并将其作为核心组成部分(因为这首歌是 2017 年的,而 John 目前仍然是核心组成部分)。在同一首歌中,玛丽作为合作者参与其中,因为这首歌是 2017 年的,那时她还不是 TheBand 的核心成员。

设置 2

话虽如此,最流行和当前的关系数据库系统(例如最新版本的 mysql 或 PostgreSQL)包含一些新类型,可帮助您以不同的方式处理 N:M 关系并减少所需的表数量在您的设置中。

JSON 类型(MySQL 5.7.8 及以上,PostgreSQL 9.2 及以上)可用于存储 SONG 表中的关系。

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"]

有关 MySQL 中 JSON 类型的更多信息:Native JSON support in MYSQL 5.7 : what are the pros and cons of JSON data type in MYSQL?

【讨论】:

感谢您的解释,我想我会坚持设置 1,然后遵守规则,因为我的主机提供商也不在 MySQL 5.7 上。 好的,我在拆分值时会弹出更多问题。这首歌将有“团体”作为歌唱艺术家和个人歌手。所以我用group_idgroup_nameperson_id创建了一个“组”表,而“song_artist”表将有song_idperson_idgroup_id,后两者之一为NULL取决于歌曲数据。这个可以吗?还是应该将它们进一步拆分为 2 个表“song_artist”和“song_artist_group”? 并非如此。您首先需要明确说明您希望数据库存储什么。你想知道谁曾参与过A乐队的任何歌曲吗?或者你也想知道每个乐队的核心成员是谁?然后分析乐队与其他相关实体的关系。以我的回答中的SETUP 1.1 为例。

以上是关于将多个值字段拆分为数据库中的行的理想方法?的主要内容,如果未能解决你的问题,请参考以下文章

oracle 如何将一个字段里的值拆分为多个值显示出来

根据条件将单个数据行拆分为多个数据行的 SQL 脚本

用SQL拆分具有多个值的行

选择可能具有相同值的行的多个实例

使用 SSIS 将单个字段值拆分为第二个表中的多行

如何将值字段拆分为水晶报表中的更多字段?