【问题标题】:Database design - relations vs properties数据库设计 - 关系与属性
【发布时间】:2014-10-15 18:06:11
【问题描述】:

我在设计数据库 (SQL/MySQL) 时遇到问题。假设我们有一个用户,用户可以有很多朋友和很多帖子并填写一些关于他自己的数据。

很明显,对于friends,我们需要一个用于 n:n 关系的 pivot_table,对于 posts,我们需要创建一个具有 user_id (1:n) 关系的额外表。

所以我们需要users、user_friends 和posts 表。这是显而易见的。应该这样处理关系。

但现在让我们假设我们希望用户拥有以下数据:

name - text
description - text
marital status - select only one from list
favourite colour - select only one from list
hobby - select up to 3 from list

对于文本字段(名称、描述),很明显我们只需在 users 表中创建 varchar/text 列即可。

一般问题是:应该如何处理其他字段(从列表中选择)?我应该为它们创建关系还是应该用它们创建标准数据列?

在我看来,没有必要为此创建关系表,因为使用列表(选择)我们只限制用户实际上可以粘贴到数据库中。从理论上讲,我们可以允许用户手动输入他的颜色作为最喜欢的颜色(例如red,如果他输入错误,例如reds,我们将比较它会列出允许的colours)。性别也是如此——在我看来,当我们只拥有女人和男人并为其创建关系时,创建额外的表是没有意义的。

第一个数据库设计:

例如,我可以为属性创建以下列:

marital_status - int
fav_colour - int
hobby_1 - int
hobby_2 - int
hobby_3 - int

还有另一个表(甚至是 PHP 或其他语言中的普通数组),我在其中存储 fav_colour 的值 1 是例如红色,爱好的值 2 是音乐等等(我如何存储并不重要这些值在这里 - 我也可以使用 enum 类型)。

对我来说,这种态度的好处不是创建许多实际上是属性而不是关系的关系(正如我上面提到的),所以工作更少+更容易获取有关用户的信息 - 你不需要使用任何连接什么如果您有用户(例如 20 或 100 个这样的属性),这将很重要,我可以很容易地在用户表中搜索。缺点也很明显 - 数据没有标准化,对于任何多选(例如爱好)我需要创建 3 列,如果将来我决定用户不能选择 1 种颜色而是 2 或 3 种,我需要添加2 个额外的列。

另类数据库设计:

我创建了额外的表:colours、hobbies、marital_statuses,并创建了 3 个数据透视表:user_colours、user_hobbies、user_marital_statuses。缺点:连接多。优点 - 如果我创建了 3 个额外的数据透视表,我可以轻松地允许用户选择多达 10 种颜色,并且我根本不需要重新设计数据库。但也存在缺点 - 搜索困难、工作量大、连接次数多。

详细问题

所以总结一下 - 假设什么解决方案会更好:

  1. 我可能不会更改一个属性的最大数量(如果我决定允许最多 3 个爱好,这可能永远不会改变)
  2. 许多字段的选择列表相对较短(其中大多数少于 10 个)
  3. 我需要在这样的数据库中进行大量搜索。例如,有人想要搜索将 fav_colour 设置为红色并拥有爱好音乐的用户。

如果您有任何其他解决方案或优点/缺点,我很感激与我分享。

【问题讨论】:

  • 这是另一个选项。使用attributeName、attributeType、attributeValue、userId 创建一个属性表。这将允许您向用户添加任意​​数量的属性。避免在任何时候想到所需的新信息时进行破坏性架构更改。
  • @paqogomez:这是一种称为“实体属性值”的反模式,但可能是在这里伤害较小的解决方案。另一种选择是将“动态属性”存储为 JSON 或 XML 文档。但这使得在 SQL 中处理它们变得非常困难。第三种选择可能是升级到 Postgres 并使用 Postgres 的 NoSQL 功能,例如键/值数据类型 hstore 或内置 JSON 支持
  • @a_horse_with_no_name 我实际上有一条评论建议使用 NoSQL 选项,但将其删除。我认为这不是 OP 想要去的方向。然而,NoSQL 正是针对这种类型的数据。
  • @a_horse_with_no_name 如果 EAV 是“反模式”,那么在数据库中存储 JSON 或 XML 也是一种反模式,因为它违反了 1NF。

标签: mysql sql database database-design relational-database


【解决方案1】:

听起来您想对某些用户属性实施一些限制。例如,最喜欢的颜色必须是红色、绿色、蓝色、粉色、橙色等中的一种;婚姻状况必须是单身、离婚、已婚之一。

您已经描述了一种方法:查找表。如果可能的值是动态的并且需要持续维护,或者有许多可能的值,这是最好的方法。从你的描述来看,不是你的情况。您可能的值将是相当静态和简短的。

我建议使用 sql CHECK 约束。使用它,您可以控制字段的可能值。例如:

CREATE TABLE users
(
Name varchar(255) NOT NULL,
Description varchar(255),
Marital_Status varchar(10) NOT NULL,
Color varchar(10) NOT NULL,
CONSTRAINT chk_Color CHECK (Color in ('Red', 'Blue', 'Green', 'Orange')),
CONSTRAINT chk_Marriage CHECK (Marital_Status in ('Single', 'Married', 'Divorced'))
)

我没有检查这个 DDL 语句的语法,所以它可能包含标点错误。此外,您的特定 DBMS 的语法可能会有所不同。我认为这应该适用于 MySQL。

【讨论】:

  • 这可能很好,但是对于很多颜色来说,将​​它们放入约束中并不是很容易(如果需要,可以添加另一个)。存储 varchars 也会有一个大问题。如果该网站是多语言存储 varchars 我认为在这里不是一个好主意。
【解决方案2】:

如果用户可以经常更改喜欢的颜色/爱好,我会使用lookup 表,在我的示例中我将它们称为decode 表。 user/hobbies 和 user/colors 之间的所有关系都可以在 decode 表中找到。

由于您只能拥有 1 个marital status,因此很容易处理它是一对多的关系。

创建一个表Marital_Status,其中包含两个字段Id (pk) 和Status(varchar(n)) 不需要decode 表来查找marital status。

现在我建议创建一个表来保存colors 和一个表hobbies。同样的方式我们做marital status。

Hobbies

HobbyId, Hobby

Colors
ColorId, Color

当您需要添加/删除新的hobby/color 时,请在这些decode 表中进行操作。

您是否要为每个关系使用 1 个 decode 表或多个 ie 取决于您。 Hobby_Decode and Color_Decode等

我会解释使用1的场景。

使用以下字段创建您的解码表...

Decode

Item_Type varchar(n) --我们将在该字段中推送Hobby或Color

UserId int --self explanatory,保存用户的ID来“查找”

LookupId -- 将保存Hobby 或Color 的ID

让我创建一些示例数据,然后我们会解决这个问题。

Hobbies table数据

 | HobbyId | Hobby

      1      Studying 
      2      Doing Drugs
      3      Drinking     

Colors table数据

 | ColorId | Color

     1        Red 
     2        Blue

当我们这样做时,这是我们的用户表。

Users

 | UserId | Name

      1     Marcin 
      2     CSharper

我喜欢喝酒、吸毒和红色,你是个书呆子,所以你喜欢学习和蓝色。在我们的解码表中,我们将添加以下条目来表示它。

Decode

 | Item_Type| UserId | LookUpId

    'Hobby'      2        2
    'Hobby'      2        3
    'Color'      2        1
    'Hobby'      1        1
    'Color'      1        2      

查看该解码表并不能真正告诉我们任何信息。一旦我们将decode 表加入colors/hobbies,它就会很明显。

如果你想查看我所有的爱好和我最喜欢的颜色,查询将如下所示

注意:这是 SQL Server 语法而不是 mysql。

--Pull Hobbies
Select u.Name, dH.Item_Type as 'Favorite', h.Hobby as 'Item'
from User u
inner join decode dH on dH.UserId = u.UserId 
                     and dH.Item_Type = 'Hobby'
inner join Hobby h on h.HobbyId = dH.LookUpId
where u.UserId = 2 

--Union in Colors
Union

Select u.Name, dH.Item_Type as 'Favorite', h.Hobby 'Item'
from User u
inner join decode dC on dH.UserId = u.UserId 
                     and dH.Item_Type = 'Color'
inner join Color c on c.ColorId = dH.LookUpId
where u.UserId = 2 

你的输出看起来像

|    Name    |    Favorite   |     Item 

   CSharper         Hobby         Drinking
   CSharper         Hobby         Doing Drugs
   CSharper         Color         Red

如果这样设置,那么更改/更新人们最喜欢的爱好和颜色非常容易。 decode 表将处理所有这些。它只需要对该表进行简单的输入或删除。而且通过这种方式,用户可以拥有无​​限数量的最喜欢的爱好和颜色,因为驱动它的是解码表,而不是用户表定义。

如果我们想找到所有喜欢蓝色的用户,稍微操作一下您的示例查询 和饮用查询看起来像。

Select u.Name
from User u 
inner join decode d on d.UserId = u.UserId
inner join Hobby h on h.HobbyId = d.LookUpId and d.Item_Type = 'Hobby'
inner join Color c on C.ColorId = d.LookUpId and d.Item_Type = 'Color'
where h.Hobby = 'drinking' and c.Color = 'blue'

这样的连接是完全可以接受的。

【讨论】:

  • 嗯,会不会很复杂?如果我想获取一个用户的所有属性怎么办。在这里,我们有颜色和多达 3 个爱好。但是如果有超过 20 个这样的relations 呢?会不会太复杂,影响速度?
  • 不,一点也不,我在一家金融公司工作,相信我,至少可以说查询很繁重,而且效果很好。您可以在 UserId 和 Item_type 上添加一个索引来加快速度。如果您愿意,您可以将其拆分并使用多个解码表,在您的情况下它可能会更容易且更具可读性。如果您说用户可以拥有不同数量的收藏 X,我不知道更简单的解决方案。不断更改用户表以合并多个收藏的 X 并不是最佳选择。保存每个关系的解码表将起作用。
【解决方案3】:

除非确实需要,否则您希望避免额外的表和连接。这正是枚举的用途。枚举在内部存储为整数,并且在使用中看起来像具有约束值的字符串。

create table users (
  user_id bigint unsigned not null auto_increment primary key,
  name varchar(255) not null,
  description varchar(255),
  marital_status enum('single', 'married'),
  favorite_color enum('red', 'green', 'blue'),
  hobby1 enum('painter', 'doctor', 'lawyer'),
  hobby2 enum('painter', 'doctor', 'lawyer'),
  hobby3 enum('painter', 'doctor', 'lawyer')
);

插入一个值:insert into table users (name, marital_status) values ('Jack', 'single');

此语句将失败:insert into table users (name, marital_status) values ('Jack', 'abcd');

修改列表是一个简单快速的操作: alter table users modify marital_status enum('divorced', 'single', 'married');

【讨论】:

  • 好的,但是数据验证呢?如何检查允许哪些值?例如,用户将填写颜色 abc。我可以查询枚举字段以获取允许的值吗?还有一个额外的问题——如何为他们存储翻译?假设我们在页面上使用超过 1 种语言,那么名称应该在其他表/数据中复制以存储在那里翻译
  • 您可以使用“show create table 'tablename'”或查询information_schema 来获取枚举中允许的值。对于翻译,您希望在枚举中使用与语言无关的值,并且翻译将是一个单独的表。
【解决方案4】:

无论你选择哪个好,不要太依赖规范化。

但对我来说,我会选择 5 张桌子 users、marital_status、colours、hobbies、user_hobbies

CREATE TABLE users (
  user_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  description VARCHAR(255),
  marital_status INT,
  fav_colour INT
)

CREATE TABLE marital_status (
  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255) NOT NULL
)

CREATE TABLE colours (
  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  code VARCHAR(7)
)

CREATE TABLE hobbies (
  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255) NOT NULL
)

CREATE TABLE user_hobbies (
  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT,
  hobby_id INT
)

对于数据透视表,我建议将它们与应用程序分开创建/填充,例如使用命令行或消息队列(使用 crontab 功能)

【讨论】:

    猜你喜欢
    • 2011-05-04
    • 2011-05-27
    • 1970-01-01
    • 2011-05-05
    • 2014-11-30
    • 1970-01-01
    • 2010-09-14
    • 1970-01-01
    相关资源
    最近更新 更多