我有一个users
表,我想在两个任意用户之间定义“朋友”关系。
到目前为止,我已经使用了两种不同的方法:
friends
表包含user1
和user2
。搜索用户涉及的查询类似于
... WHERE @userid IN (`user1`,`user2`)
,效率不高friends
表包含from
和to
字段。启动朋友请求会在该方向上创建一行,如果已接受,则以相反方向插入第二行。还有一个status
列表示发生了这种情况,使搜索结果如下:... WHERE `user1`=@userid AND `status`=1
我对这些解决方案中的任何一个都不是特别满意。第一个看起来很麻烦IN
用法,第二个看起来很臃肿,有两行来定义一个链接。
这就是我在这里的原因。您对这样的链接有什么建议?请注意,我不需要再保存任何更多信息,我只需要两个相互关联的用户ID,最好是某种状态,如ENUM('pending','accepted','blocked')
,但这是可选的,具体取决于最佳设计是什么
答案 0 :(得分:7)
通常有两种方法:
存储每个朋友对一次,首先存储id最少的朋友。
CREATE TABLE
friend
(
l INT NOT NULL,
g INT NOT NULL,
PRIMARY KEY
(l, g),
KEY (g)
)
两种方式存储每个朋友对:
CREATE TABLE
(
user INT NOT NULL,
friend INT NOT NULL,
PRIMARY KEY
(user, friend)
)
要存储友谊状态,接受日期等其他字段,您通常会使用第二个表格,原因我将在下面介绍。
要检索每个用户的好友列表,请执行以下操作:
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
)。
EXPLAIN
如果我们使用us.uid
来匹配用户(因为它已编入索引):
答案 2 :(得分:0)
除了性能考虑之外,另一个选项可能是“朋友”表,其中一行代表朋友(无论哪种方式),以及为任何朋友生成两个结果行(每个方向一个)的视图行。在使用中,它会简化查询,因为它可以像“两行”解决方案一样使用,而每个“友谊”只需要一个数据行。
唯一的缺点可能是性能......取决于查询优化器的工作方式。
答案 3 :(得分:0)
我试图发挥创意,这里有一些结果。
比说起来容易,
表朋友上的简单触发器可以提供一个很好的服务,订购(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
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'