定义双向链接

时间:2013-06-05 00:28:22

标签: mysql

我有一个users表,我想在两个任意用户之间定义“朋友”关系。

到目前为止,我已经使用了两种不同的方法:

  1. friends表包含user1user2。搜索用户涉及的查询类似于 ... WHERE @userid IN (`user1`,`user2`),效率不高
  2. friends表包含fromto字段。启动朋友请求会在该方向上创建一行,如果已接受,则以相反方向插入第二行。还有一个status列表示发生了这种情况,使搜索结果如下:
    ... WHERE `user1`=@userid AND `status`=1
  3. 我对这些解决方案中的任何一个都不是特别满意。第一个看起来很麻烦IN用法,第二个看起来很臃肿,有两行来定义一个链接。

    这就是我在这里的原因。您对这样的链接有什么建议?请注意,我不需要再保存任何更多信息,我只需要两个相互关联的用户ID,最好是某种状态,如ENUM('pending','accepted','blocked'),但这是可选的,具体取决于最佳设计是什么

5 个答案:

答案 0 :(得分:7)

通常有两种方法:

  1. 存储每个朋友对一次,首先存储id最少的朋友。

    CREATE TABLE
            friend
            (
            l INT NOT NULL,
            g INT NOT NULL,
            PRIMARY KEY
                    (l, g),
            KEY (g)
            )
    
  2. 两种方式存储每个朋友对:

    CREATE TABLE
            (
            user INT NOT NULL,
            friend INT NOT NULL,
            PRIMARY KEY
                    (user, friend)
            )
    
  3. 要存储友谊状态,接受日期等其他字段,您通常会使用第二个表格,原因我将在下面介绍。

    要检索每个用户的好友列表,请执行以下操作:

    SELECT  CASE @myuserid WHEN l THEN g ELSE l END
    FROM    friend
    WHERE   l = @myuserid 
            OR
            g = @myuserid
    

    SELECT  g
    FROM    friend
    WHERE   l = @myuserid
    UNION
    SELECT  l
    FROM    friend
    WHERE   g = @myuserid
    

    第一个解决方案;和

    SELECT  friend
    FROM    friend
    WHERE   user = @friend
    

    要检查两个用户是否为朋友,请发出以下命令:

    SELECT  NULL
    FROM    friend
    WHERE   (l, g) =
            (
            CASE WHEN @user1 < @user2 THEN @user1 ELSE @user2 END,
            CASE WHEN @user1 > @user2 THEN @user1 ELSE @user2 END
            )
    

    SELECT  NULL
    FROM    friend
    WHERE   (user, friend) = (@user1, @user2)
    

    存储方面,两种解决方案几乎相同。第一个(最小/最大)解决方案存储两倍的行,但是,为了使其快速工作,您应该在g上有一个辅助索引,实际上,它必须存储g加上部分表的主键不在二级索引中(即l)。因此,每个记录有效地存储两次:一次在表格本身,再次在g的索引中。

    在性能方面,解决方案也几乎相同。但是,第一个需要两个索引搜索,然后是索引扫描(对于“所有朋友”),第二个只需要一个索引搜索,因此对于L / G解决方案,I / O量可能会略微增加。由于单个索引可能比两个独立索引更深一级,因此初步搜索可能需要一页读取更多,这可能会稍微减轻一些。与L / G相比,这可能会减慢“他们是朋友”对“两对”解决方案的疑问。


    至于额外数据的附加表,你很可能想要它,因为它通常比我上面描述的两个查询(通常仅用于历史目的)使用得少。

    它的布局还取决于您使用的查询类型。比方说,如果你想“显示我的最后十个友谊”,那么你可能希望将时间戳存储在“两对”中,这样​​你就不必进行文件分类等。

答案 1 :(得分:0)

考虑以下架构:

CREATE TABLE `users` (
  `uid` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(30) NOT NULL,
  PRIMARY KEY (`uid`)
);

INSERT INTO `users` (`uid`, `username`) VALUES
(1, 'h2ooooooo'),
(2, 'water'),
(3, 'liquid'),
(4, 'wet');


CREATE TABLE `friends` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `uid_from` int(10) unsigned NOT NULL,
  `uid_to` int(10) unsigned NOT NULL,
  `status` enum('pending','accepted','blocked') NOT NULL,
  PRIMARY KEY (`id`),
  KEY `uid_from` (`uid_from`),
  KEY `uid_to` (`uid_to`)
);

INSERT INTO `friends` (`id`, `uid_from`, `uid_to`, `status`) VALUES
(1, 1, 3, 'accepted'), -- h2ooooooo sent a friend request to liquid - accepted
(2, 1, 2, 'pending'), -- h2ooooooo sent a friend request to water - pending
(3, 4, 1, 'pending'), -- wet sent a friend request to h2ooooooo - pending
(4, 4, 2, 'pending'), -- wet sent a friend request to water - pending
(5, 3, 4, 'accepted'); -- liquid sent a friend request to wet - accepted

我会使用以下内容:

SELECT
    fu.username as `friend_username`,
    fu.uid as `friend_uid`
FROM
    `users` as `us`
LEFT JOIN
    `friends` as `fr`
ON
    (fr.uid_from = us.uid OR fr.uid_to = us.uid)
LEFT JOIN
    `users` as `fu`
ON
    (fu.uid = fr.uid_from OR fu.uid = fr.uid_to)
WHERE
    fu.uid != us.uid
AND
    fr.status = 'accepted'
AND
    us.username = 'liquid'

结果:

friend_username | friend_uid
----------------|-----------
h2ooooooo       | 1
wet             | 4

此处us将是您要为朋友查询的用户,而fu将是用户朋友。您可以轻松更改WHERE语句,以便在您想要的任何内容中选择用户。如果您想查找用户未回答的朋友请求,则状态可以更改为待处理状态(并且只应加入uid_to)。

DEMO ON SQLFIDDLE

EXPLAIN如果我们使用us.uid来匹配用户(因为它已编入索引):

enter image description here

答案 2 :(得分:0)

除了性能考虑之外,另一个选项可能是“朋友”表,其中一行代表朋友(无论哪种方式),以及为任何朋友生成两个结果行(每个方向一个)的视图行。在使用中,它会简化查询,因为它可以像“两行”解决方案一样使用,而每个“友谊”只需要一个数据行。

唯一的缺点可能是性能......取决于查询优化器的工作方式。

答案 3 :(得分:0)

我试图发挥创意,这里有一些结果。

比说起来容易,


Diagram

朋友上的简单触发器可以提供一个很好的服务,订购(user1,user2)而不会伪造谁请求友谊。

CREATE TRIGGER `friends_insert` BEFORE INSERT ON friends
FOR EACH ROW BEGIN
  DECLARE X INT UNSIGNED;
  IF NEW.user1 > NEW.user2 THEN
    SET X = NEW.user1;
    SET NEW.user1 = NEW.user2;
    SET NEW.user2 = X;
    SET NEW.invited_by = 1;
  END IF;
END$$

最后,假设用户U的id = x。我们可以说U将表用户分为两部分:id 朋友之前,我们对其ID进行排序,因此某些信息不会被明确写入两次。 我们通过U的朋友的联合获得我们的用户U(id = x)的朋友,其id为&lt; x和id为&gt; X:

SELECT user1 AS `friend_id` FROM friends
  WHERE user1<@id AND user2=@id
UNION
SELECT user2 AS `friend_id` FROM friends
  WHERE user2>@id AND user1=@id;

这里的主要目标是查询性能。划分这两种情况将有助于MySQL为每种情况使用正确的索引 [问题的时间&amp;分歧。也许你想要完整的SQL;它显示为here]

答案 4 :(得分:0)

你可以尝试像这样的SQLFiddle:http://sqlfiddle.com/#!2/219dae/3/0

以下是代码:

SCHEMA:

-- This is the users table:
CREATE TABLE users
    (
     u_id int auto_increment, 
     username varchar(20),
     PRIMARY KEY (u_id) 
    );

INSERT INTO users (username)
VALUES ('user1'),
       ('user2'),
       ('user3'),
       ('user4'),
       ('user5');

-- This is the friends table:

CREATE TABLE friends
    (
     f_id int auto_increment,
     r_name varchar(20), -- the name of the user that requests for friendship
     a_name varchar(20), -- the name of the user that answers the friendship request
     status varchar(20), -- the status of the request
     PRIMARY KEY (f_id)
    );

-- below, user1 sends frind requests to user2, user3, user4 and user5; and receives one from user2:

INSERT INTO friends (r_name, a_name, status)
VALUES ('user1','user2', 'pending');

INSERT INTO friends (r_name, a_name, status)
VALUES ('user1','user3', 'pending');

INSERT INTO friends (r_name, a_name, status)
VALUES ('user1','user4', 'pending');

INSERT INTO friends (r_name, a_name, status)
VALUES ('user1','user5', 'pending');

INSERT INTO friends (r_name, a_name, status)
VALUES ('user2','user1', 'pending');

-- user1 accepts user2 request to be his friend:

UPDATE friends
SET status='accepted'
WHERE a_name='user1' AND r_name='user2';

-- user3 accepts user1 request to be his friend:

UPDATE friends
SET status='accepted'
WHERE a_name='user3' AND r_name='user1';

和SELECT:

-- here we select all friend requests that the user1 received and all friend requests that he made

SELECT r_name, a_name, status FROM users
INNER JOIN friends ON users.username=friends.a_name
WHERE username='user1'

UNION

SELECT r_name, a_name, status FROM users
INNER JOIN friends ON users.username=friends.r_name
WHERE username='user1'