在 PostgreSQL 中存储一对多或多对多关系的最佳方式是啥?

Posted

技术标签:

【中文标题】在 PostgreSQL 中存储一对多或多对多关系的最佳方式是啥?【英文标题】:What is the best way to store one-to-many or many-to-many relationships in PostgreSQL?在 PostgreSQL 中存储一对多或多对多关系的最佳方式是什么? 【发布时间】:2012-01-05 21:40:49 【问题描述】:

我目前正在将开源聊天 (AJAX Chat) 集成到另一个项目中。现在,默认情况下,聊天只是从文件中获取其有效用户和有效频道,但显然当您拥有一个用户不断变化的数据库时,这并不理想。

所以,我想让聊天直接从数据库中加载用户和频道信息。我在想设计应该如下(如果你有不同的感觉,请告诉我):

我们有一些聊天渠道(公共、营销等) 然后我们将组分配到渠道(例如 PR 团队 1、IT 人员等...) 然后我们的用户属于组,并且在某些情况下被直接分配到频道。

我正在考虑用这样的表格来实现上述内容:

频道表:

|----|Channel_Name||Channel_ID||Groups_Assigned||Users_Assigned|----|  
|----|---Public---||-----0----||---1,2,3,4,5---||-----3,4------|----|  
.  
.  
.etc...

注意:组分配表包含分配给频道的组的组 ID,而分配的用户包含不属于分配组的用户的 ID。

组表:

|----|Group_Name||Group_ID||Users_Assigned|----|  
|----|---Team1--||----0---||------5,10----|----|  
.  
.  
.etc...  

抱歉,表格画得不好。

现在,通过上述实现,当用户登录时,程序将获取用户 id(从 users 表中),然后在 groups 表中搜索包含该用户 id 的所有组,最后搜索所有频道的频道表,这些频道包含组(用户所属的组)或用户直接分配给他们的频道。

我的想法是可以的,但是好像有点,呃,效率低下。因为我必须以1,2,3.... 格式存储分配的ID(组和用户),所以我必须使用phpexplode() 或其他一些可以搜索字符串的PostgreSQL 函数。我很可能会存储一组组,然后循环遍历它们,一次一行,这对我来说似乎真的很慢。

或者,我可以为每个用户创建一个布尔列,但这会导致列太多,而且我不想在每次创建用户时都创建一个新列。

那么,你们会怎么做呢?而且,如果出于某种疯狂的原因,您碰巧同意我最初的想法,那么您能否帮我弄清楚如何实际编写代码来实际执行它。

感谢您的宝贵时间,祝您有美好的一天。

【问题讨论】:

你的直觉是正确的,这不是一个好的设计。阅读此 is-storing-a-comma-separated-list-in-a-database-column-really-that-bad:***.com/questions/3653462/… 【参考方案1】:

是的,存储逗号分隔的数字字符串并尝试在数据库中搜索给定数字的效率很低。有关这方面的更多信息,请参阅我对Is storing a comma separated list in a database column really that bad?的回答

相反,您应该使用交集表来存储用户和组之间以及组和频道之间的多对多关系。然后您的搜索将受益于索引,并且您可以使用连接返回组或频道表。

【讨论】:

【参考方案2】:

我会选择多一张表而不是 1,2,3,4,5 值,因为它们难以阅读。从channels 表中删除Groups_Assigned 并将其以一对多的格式放入单独的表中:

Channel_id  Group_id
----------  --------
0           1
0           2
0           3
0           4
0           5

我将创建另一个表作为将在此处加入此表的组,并保存有关每个 group_id 的信息。然后就是编写能够根据需要读取此设计的查询。

【讨论】:

我以 groups_assigned 为例,同样的逻辑在 user_assigned 中也是最好的。可以将频道和组 'user_assigned' 指向单个 'user_assigned' 表 谢谢,我可能最终会做这样的事情。我曾想过这个解决方案,但由于某种原因,我愚蠢的大脑拒绝了它。 嘿,不傻...曾几何时我也会这样做。我相信我从这样的论坛中学到了其他东西,呵呵,圈子还在继续。如果你是从线性的东西来设计数据库的……请记住数组在 SQL 中很糟糕。从代码的易用性和性能的角度来看,这种形式的一对多关系更容易阅读【参考方案3】:

一种可能的解决方案:

Channel
------------
Channel_Id
Channel_Name
PRIMARY KEY (Channel_Id)

PersonGrouping(我更喜欢 UserGroup,因为某些系统使用它们作为关键字)可以被认为是超类型 Entity 的子类型。这将有助于以后只有一个 Assignment 表。

Entity
------------
Entity_Id
PRIMARY KEY (Entity_Id)

Person  --- ( User )
------------
Person_Id
Person_Name
--- other data about persons/users
PRIMARY KEY (Person_Id)
FOREIGN KEY (Person_Id)
  REFERENCES Entity(Entity_Id)

Grouping   --- ( Group )
------------
Grouping_Id
Grouping_Name
--- other data about groups
PRIMARY KEY (Grouping_Id)
FOREIGN KEY (Grouping_Id)
  REFERENCES Entity(Entity_Id)

这将用于Person - Grouping 关联:

Belongs --- ( Person Belongs In Grouping )
------------
Person_Id
Grouping_Id
PRIMARY KEY (Person_Id, Grouping_Id)
FOREIGN KEY (Person_Id)
  REFERENCES Person(Person_Id)
FOREIGN KEY (Grouping_Id)
  REFERENCES Grouping(Grouping_Id)

以及分配给频道的关联表

Assignment ( Entity is Assigned to Channel )
------------
Entity_Id
Channel_Id
PRIMARY KEY (Entity_Id, Channel_Id)
FOREIGN KEY (Entity_Id)
  REFERENCES Entity(Entity_Id)
FOREIGN KEY (Channel_Id)
  REFERENCES Channel(Channel_Id)

当然,您可以摆脱Entity 表并拥有两个关联表,一个用于Person to Channel,一个用于Group to Channel 分配。

【讨论】:

哦,这是一个不错的解决方案,但是我已经有一个填充的用户表,所以我觉得我必须问:“当我已经有一个外键时,添加外键有多安全? users 表和 300 多个其他表(我知道我必须先访问实体,然后引用它们)。除此之外,我同意你和其他人的回答,即我应该使用交集表。 如果您有许多表引用您的用户表,则无需更改这些引用。如果您已经有用户和组表并且您想添加一个实体表,那将很棘手(在两个表之一中,必须更改主键值,因此 Users.User_Id 或 @987654336 @ 以及对这些值的所有引用)。 另外,你INSERT 程序进入用户和组必须进行调整(首先插入实体,然后使用新的 PK 并插入用户或组)。 哦,对了,忘了INSERT's,改变它会花费太多的工作,但是感谢您的解决方案,如果我从头开始做一些东西,我会记住它。 在这种情况下,您应该使用 2 个交集表,一个用于 User-Channel 关联,一个用于 Group-Channel

以上是关于在 PostgreSQL 中存储一对多或多对多关系的最佳方式是啥?的主要内容,如果未能解决你的问题,请参考以下文章

思考具有两个外键的一对多或多对多关系的正确方法是啥?

连接空间连接与关联

数据库设计心得

数据库设计 - 多对多或一对多“交易”系统?

一篇搞定SQL语句

在多对一中使用 Spring Data JPA 保持错误关系