同一张表上的多对多关系

21
有趣的是我之前从未遇到过这个问题!
在我开始处理一个用户可以"关注"(社交网络)其他用户的系统时,我从未想过可以在一张表上拥有“多对多”的关系。
一个标准的查找表,在我使用它的方式中至少不适用于这里。 让我们保持简单:
用户表具有"id"和"name"列。
User_relationship 表具有 "uid1" 和 "uid2",分别表示“朋友”或“同伴”或“伙伴”或“其他什么”。
很快就会发现这里的问题- uid1 和 uid2 是来自同一张表的相同数据类型的相同列,这意味着唯一键变得不完善。
例如: uid1 = 1 uid2 = 2
与下面这个相同: uid1 = 2 uid2 = 1
因此,查询可能会返回2个或0条记录,如果查询执行错误,则可能会发生这种情况。
为了设计好一个表,在扫描整个表两次以检查现有值之前,我不想要这样。
是否有一些处理这个问题的技巧? 这是一个设计问题,我从未遇到过,它让我感到烦恼,因为我知道有一些简单的技巧可以使它起作用。
在你问之前,我还没有尝试任何方法,因为我已经看到我最喜欢的关联方式(查找表)不适合我的需求,我需要一些帮助-我在SO或Google上找不到任何东西 :(
提前感谢。

您可以使用与任何其他M-M关系相同的“连接器表”方法,在表格和自身之间建立M-M关系。 - Umbrella
尝试添加一些代码。哪个SELECT让你感到困扰? - Umbrella
1
这不是选择什么的问题,而是重复条目的问题。例如,用户1同时尝试添加用户2为好友,或者其他一些边缘情况,可能存在代表相同关系的两个条目。 - dudewad
更重要的是,(1,2),(2,1)复制会给你带来什么挑战?如果不看到你的尝试,这个问题是无法回答的。 - Umbrella
1
  1. UNIQUE KEY是否包含反向值集?我认为它不包括。
  2. 在我的脑海里,有重复的值集代表相同的数据是不好的设计。假设在第一次删除和第二次删除之间发生了某些事情,那么一半的值集仍然存在,这对我来说是非常痛苦的。你反对@Denis关于uid1始终小于uid2的评论吗?那似乎总是有效的。
- dudewad
显示剩余3条评论
4个回答

16
如果你描述的关系是对称的,比如“Bob是Joe的朋友”意味着“Joe也是Bob的朋友”,那么你可以确保在代码中较小的两个用户ID放在第一列,而较大的一个放在第二列。这种约束几乎确保了查找表中的记录是唯一的。这也意味着,在执行查找时,你通常必须搜索两列。
例如,如果你想获取Bob的所有朋友,你需要查询那些包含Bob ID的记录,无论在哪一列。这会导致更多的代码和可能对性能产生影响。
如果关系是非对称的,比如“Bob是Joe的朋友”不一定意味着“Joe也是Bob的朋友”,那么每对用户需要2个条目:Bob-Joe和Joe-Bob。这意味着你的查找表将包含两倍的条目,并且你的网站非常适合做跟踪 :D 当然,即使你的关系是对称的,你仍然可以选择应用这个系统。
使用这种方法,如果您想获取Bob的所有朋友,只需选择包含Bob ID的记录即可。这可能意味着更快的查找和更少的需要编写的代码,但同时还意味着你在数据库中占用更多的空间。

当然,人们的数据不会提供给他们没有列为好友的人。 :D - dudewad

11

这意味着独一无二的键变得有缺陷。

uid1 = 1 uid2 = 2

与以下内容相同:

uid1 = 2 uid2 = 1
不是这样的。例如,在Facebook上,我有很多客户发送了成为“朋友”的请求,但我从未接受过,因为他们只是泛泛之交。同样的情况,我可能会将一些人标记为最好的朋友,但他们没有回应,反之亦然。或者我可能正在忽略一些人,而他们并没有这样做。基本上,在(uid1,uid2)元组中有比仅仅ID更多的信息。在决定在你的表上添加例如uid1 < uid2的限制之前,请确保你永远不需要处理这样的情况。

2
是的,但是在这个问题的范围之外添加更多有关关系的数据,我只是询问即时关系(将“确认”的友谊和“最好”的友谊类型留出)。我确实喜欢uid1 < uid2的限制约束...那么这是一件坏事吗? - dudewad
如果您认为每个关系只需要一行,那么约束是强制不插入不必要的行的好方法 - 您可以确保所有元组都是唯一的。请注意,您仍然需要检查两列以获取所有关系。 - Denis de Bernardy
但是,从长远来看,无论你多么努力避免,你最终会存储任何可能出现的元信息。因此,我认为你应该为每个关系使用两行。X和Y是朋友并不等同于Y和X是朋友,即使在许多社交关系中我们假装它是这样的。 - Denis de Bernardy
1
@Umbrella:create table ([columns defs, etc...], check (uid1 < uid2)); -- http://dev.mysql.com/doc/refman/5.6/en/create-table.html - Denis de Bernardy
1
如果你仔细阅读了你发布的MySql文档链接,就会发现CHECK子句被解析但被所有存储引擎忽略,所以它不起作用。可以在action中查看。唯一强制执行这种约束的方法是使用BEFORE TRIGGER - peterm
显示剩余3条评论

2

我同意其他人所说的,为一段关系添加2个插入点(1:2和2:1)并不是一个坏主意。这实际上有助于扩展现代社交网络中常见的某些功能。我可以想到一些实际应用的情况,例如关系或其他属性的描述。当个人保持朋友关系时,他们对彼此保持不同的设置。在1:2的关系中,Bob正在关注Joe的更新,并将他添加到最好的朋友列表中(添加bff列),而在2:1的关系中,Joe没有将Bob添加到bff列表中,也不关心他的帖子(following列)。


2
这并不罕见。通常情况下,有一个表格,就像大多数多对多(many-many)的关系一样,由两个列组成,每个列都是两个表中的ID,这构成了主键。
正如您所说的userId1和userId2。如果需要,可以向关系添加属性(例如友情的分类)。
当用户1成为用户2的好友时,通常会有两个插入值(1、2)和(2、1)。
解除好友关系也是同样的道理,需要删除两个记录。
用户可能会把自己添加到朋友列表中,这可能对系统的实际运作非常重要。如果用户只能查看他朋友的照片,那么如果他不是自己的好友,则某些系统可能不允许他查看自己的照片。
这非常依赖于应用程序如何在数据库之上编写。

2
哦,那很有趣。你是说拥有两个相同关系的版本并不是坏事吗?这不意味着你需要一个比你想要的大两倍的表格吗?另外,你是从哪里知道这是“典型”的做法的?(只是好奇) - dudewad
1
我认为这是我采取的方法,虽然两列都包含用户ID,但我会将第一列作为发起者,第二列作为接收者。 - Matt
1
我想到了这一点,尽管有趣的是正是这种情况促使我开始问这个问题。在我看来,那些都是“技术上”重复的条目,而且这是不允许的。虽然我不是任何类型的DBA...但@Denis对限制列的评论如何?以便uid1始终小于uid2?您可以在插入代码中执行此操作。对此有什么想法? - dudewad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接