【问题标题】:SQL structuring a bi-driectional graphSQL 构建双向图
【发布时间】:2011-11-28 13:15:05
【问题描述】:

我想要一个包含以下列的表:id、user_id1 和 user_id2。

基本上,这将重新生成一个朋友图,其中包含表示 user1 是 user2 的朋友的链接,反之亦然。

我的设置获得了一个用户,然后是他们的朋友列表。鉴于我不想在我的表中有额外的条目,我应该如何处理?

我想做类似的事情:insert into friendship (user_id1, user_id2) values (<id1>, <id2>) where ...

但我不确定如何在 SQL 中执行类似的条件逻辑

【问题讨论】:

  • 您是在问如何防止重复,还是在问如何检索特定用户的所有好友?
  • 两者。只是想找出最好的架构。

标签: mysql database-design


【解决方案1】:

为防止插入“逻辑上”相同的组合,您可以创建唯一索引:

 CREATE UNIQUE INDEX unique_pair 
      ON your_table (least(user_id1, user_id2), greatest(user_id1, user_id2));

这是标准的 ANSI SQL,应该适用于大多数 DBMS。如果您的 DBMS 不支持 leastgreatest,您可以使用 CASE 语句来实现相同的功能,但不是那么紧凑。

它是如何工作的?

通过在索引中始终将较低的值放在第一位,将较高的值放在第二位,元组 (2,1)(1,2) 都将被索引为 (1,2) - UNIQUE 完成其余的工作。

【讨论】:

  • 有趣!那么如果我尝试插入一对 (13, 10) 会发生什么?有什么方法可以捕捉到这样的错误吗?另外,如果我理解正确,这个唯一索引是对插入表的约束?
  • 这是 CHECK 约束的一个有趣的替代方案,甚至可以与 MySQL 一起使用。
  • 在这种情况下,添加 (13,10) 不是错误。它只是添加它并为其创建一个索引(10,13)。然后,如果您稍后尝试添加 (10,13),它也将具有 (10,13) 的索引,因此,它将被拒绝。
  • 嗯。我应该提到我正在使用 MySQL。当我尝试使用您提供的 unique_pair 索引解决方案时,它会引发错误#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'fb_id1, fb_id2), greatest(fb_id1, fb_id2))' at line 1...
  • 不,不适用于 MySQL。它不像大多数其他 DBMS 那样支持功能索引。
【解决方案2】:

您可以通过 CHECK 约束强制 user_id1 始终小于 user_id2

CHECK (user_id1 < user_id2)

大概是不允许人们成为自己的朋友。然后在插入之前确保 ID 的顺序正确。在提取朋友列表时,您仍然需要检查这两列:

select user_id2 from friendship where user_id1 = X
union all
select user_id1 from friendship where user_id2 = X

X 当然是您感兴趣的人。要查看两个人是否是朋友,只需将他们的 ID 以正确的顺序排列并选择即可。

【讨论】:

  • 将较小的值 id 作为 user_id1 有什么帮助?
  • @lollercoaster:这样可以避免重复。无向意味着(1,2)(2,1) 是同一条边,因此如果您强制(使用CHECK)第一个节点的ID 低于第二个节点,您将不会同时获得(1,2)(2,1),因为(2,1) 会违反检查约束。
  • 是的,不幸的是,CHECKs 和 CONSTRAINTs 不适用于 MySQL。它们被解析但在插入/更新调用时被忽略
  • @lollercoaster:您的评论是第一次提到 MySQL。您可能可以使用触发器来伪造它,切换到更好的数据库,或者假设您的代码没有 INSERT 或 UPDATE 错误并希望最好。
  • @lollercoaster:a_horse_with_no_name 和我都倾向于 PostgreSQL,它有更好的功能集,PostgreSQL 也有递归 CTE,可能对图形和树工作很方便。如果您想坚持使用 MySQL,那么从触发器引发异常的“标准”方法是,AFAIK,触发某种非法操作;像这样的东西可能会很好地工作而不会太丑:blogs.oracle.com/svetasmirnova/entry/how_to_raise_error_in
【解决方案3】:

如果索引正确,我认为存储user1, user2user2, user1 没有问题。

如果您确定只需要一行,那么我建议您始终将最低 ID 存储在 user1 中(甚至可能对该列施加约束以强制执行它),并且您将有一个看起来像的朋友查询这个:

SELECT user1 FROM friendship WHERE user2 = $user_id
UNION ALL
SELECT user2 FROM friendship WHERE user1 = $user_id

【讨论】:

  • 它使您的查询更简单。此外,如果您始终将最低值存储在 user1 中,则不能有重复项。如果用户 14 朋友用户 18,那么你存储 14,18。但如果用户 18 与用户 14 成为好友,那么您也将存储 14,18。
  • UNION ALL 可能是比 UNION 更好的调用,数据库的工作量更少,并且无论如何都不应该发生重复。
  • 是的,这就是为什么我赞成你的答案而不是我的答案。那,你还提供了实际的检查约束。
【解决方案4】:
INSERT INTO friendship (user_id1, user_id2) 
SELECT <id1>,<id2> FROM <special table with only one row, contents irrelvant>
WHERE ((<id1>,<id2>) NOT IN
       ((SELECT user_id1, user_id2 FROM friendship)
        UNION 
        (SELECT user_id2, user_id1 FROM friendship)))

为了从下到上解释这一点,我们首先在任一方向构建所有对的表。然后我们找出(&lt;id1&gt;, &lt;id2&gt;) 是否在其中。如果是,则第 2 行的 SELECT 查询不会返回任何元组,因此不会插入任何内容。如果不是,则 SELECT 查询返回一个元组(特别是元组 (&lt;id1&gt;,&lt;id2&gt;))并将其插入。

我认为 a_horse_with_no_name 的答案在这种特定情况下和大多数类似情况下可能是最好的答案,但如果有人在某些情况下不允许添加这样的索引或会导致问题(例如,如果是一个更复杂的表,其中只有一些行具有有意义的友谊条目,但您仍然需要唯一性),那么这个查询就可以完成这项工作。

【讨论】:

  • 我不明白只有一行的特殊表是做什么用的......?
  • 假设您正在执行一个 SELECT 查询,您只需要值 1。如果您执行 SELECT 1 FROM Friendship,您将在友谊表中的每一行得到一个 1。因此,当您使用 SELECT 来计算值时,您需要一个只有一行的表,以便您最多获得该值的一个副本。一些 RBDMS 有一个为此目的的特殊表,但我不知道您使用的是什么 RBDMS,所以我不知道该怎么称呼它。但是您可以创建任何只有一行的表并使用它。
猜你喜欢
  • 2019-03-26
  • 2011-11-16
  • 1970-01-01
  • 2019-03-09
  • 1970-01-01
  • 2012-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多