0

我需要显示帖子列表。对于每个帖子,我还需要显示:

  1. 有多少人“喜欢”这个帖子。
  2. 三个“喜欢”帖子的人的名字(最好是查看用户的朋友)。
  3. 如果查看用户“喜欢”该帖子,我希望他/她成为三者之一。

如果不查询 for 循环中的每个项目,我不知道该怎么做,这被证明非常慢。当然缓存/非规范化会有所帮助,但我想知道是否可以这样做。脸书是如何做到的?

假设这个基本的数据库结构,有什么建议吗?

users
-----
id
username

posts
---------
id
user_id
content

friendships
-----------
user_id
friend_id
is_confirmed (bool)

users_liked_posts
-----------------
user_id
post_id

作为旁注,如果有人知道如何在 SQLAlchemy 中做到这一点,那将非常感激。

编辑:SQLFiddle http://sqlfiddle.com/#!2/9e703

4

2 回答 2

2

你可以在你的 sqlfiddle 中试试这个。条件“WHERE user_id = 2”需要将 2 替换为您当前的用户 ID。

SELECT numbered.*
FROM
 (SELECT ranked.*,
       IF (post_id=@prev_post,
           @n := @n + 1,
           @n := 1 AND @prev_post := post_id) as position
 FROM
   (SELECT users_liked_posts.post_id,
          users_liked_posts.user_id,
          visitor.user_id as u1,
          friendships.user_id as u2,
          IF (visitor.user_id is not null, 1, IF(friendships.user_id is not null, 2, 3)) as rank
   FROM   users_liked_posts
   INNER JOIN posts
   ON     posts.id = users_liked_posts.post_id
   LEFT JOIN friendships
   ON     users_liked_posts.user_id = friendships.user_id
   AND    friendships.friend_id = posts.user_id
   LEFT JOIN (SELECT post_id, user_id FROM users_liked_posts WHERE user_id = 2) visitor
   ON     users_liked_posts.post_id = visitor.post_id
   AND    users_liked_posts.user_id = visitor.user_id
   ORDER BY users_liked_posts.post_id, rank) as ranked
   JOIN
   (SELECT @n := 0, @prev_post := 0) as setup) as numbered
WHERE numbered.position < 4

您可以轻松地将“编号”子查询与“用户”表连接起来,以获取更多用户信息。有额外的字段 u2, u3 可以帮助查看正在发生的事情。您可以删除这些。

查询的总体思路:

1) 两次左加入 users_liked_posts。第一次仅限于当前访问者,创建子查询访问者。第二次仅限于朋友。

2) 列rank, IF (visitor.user_id is not null, 1, IF(friendships.user_id is not null, 2, 3)),为users_liked_posts中的每个用户分配一个rank。此查询按帖子和排名排序。

3)使用前一个作为子查询来创建相同的数据,但每个帖子都有用户的运行位置。

4)使用previous作为子查询来提取每个帖子的前3个位置。

不,这些步骤不能合并,特别是因为 MySQL 不允许 WHERE 条件中的别名使用计算列。

于 2013-04-09T17:41:11.550 回答
1

@koriander 给出了 SQL 答案,但至于 Facebook 是如何做到的,你已经部分回答了这个问题;他们使用高度非规范化的数据和缓存。此外,它们实现了原子计数器、内存中的边列表来执行图形遍历,而且它们肯定不使用关系数据库概念(如 JOIN),因为它们不能扩展。即使是它们运行的​​ MySQL 集群,本质上也只是键/值对,只有在缓存层丢失时才会被访问。

我可能会建议您使用图形数据库而不是 RDBS,例如neo4j

祝你好运。

编辑:

如果您对使用 Neo4j 感兴趣,那么您真的将不得不使用它。您可能会或可能不会发现它更容易来自 SQL 背景,但它肯定会提供更强大且可能更快的查询来执行图形遍历。

下面是几个可能对您有用的 Cypher 查询示例。

计算有多少人喜欢一个帖子:

START post=node({postId})
MATCH post<-[:like]-user
RETURN count(*)

(真的,你应该使用原子计数器,如果它是你要查询很多的东西)

获得三个喜欢帖子 的人,并具有以下限制:

  1. 如果他/她喜欢该帖子,第一个likingUser将始终是当前用户。
  2. 如果当前用户的朋友喜欢该帖子,他们将出现在任何非朋友之前。
开始帖子=节点({postId}),用户=节点({currentUserId})
匹配路径 = post<-[:like]-likeingUser-[r?:friend*0..1]-user
返回喜欢用户,计数(r)为rc,长度(路径)为len
ORDER BY rc desc, len asc
限制 3

我将尝试解释上述查询......如果可以的话。

  1. 首先抓取两个节点,thepost和 currentuser
  2. 匹配所有喜欢帖子的用户 ( likingUser)
  3. 此外,测试是否存在likingUser通过友谊关系连接到当前的长度为 0 或 1user的路径(长度为 0 的路径表示likingUser==user)。
  4. 现在,首先根据是否r存在关系对结果进行排序(如果likingUser是朋友user或 if 则存在likingUser==user)。因此,count(r)每个结果将是 0 或 1。由于我们更喜欢结果 where count(r)==1,因此我们将按降序对其进行排序。
  5. 接下来,执行第二次排序,user如果他/她是结果集的一部分,则强制当前列在列表的顶部。我们通过检查 的长度来做到这一点path。当 时user==likingUser,路径长度会比当user的朋友时更短likingUser,所以我们可以通过升序排序length(path)来强制到顶部。user
  6. 最后,我们将结果限制为仅前三个结果。

希望这有点道理。作为旁注,您实际上可以通过分离查询来获得更好的性能。例如,一个查询查看用户是否喜欢该帖子,然后另一个查询最多三个喜欢该帖子的朋友,最后另一个查询最多三个喜欢该帖子的非朋友。我说它可能会更快,因为每个查询在获得三个结果后都会短路,而我编写的大单查询必须考虑所有可能性,然后对它们进行排序。因此,请记住,仅仅因为您可以将多个问题组合到一个查询中,它实际上可能比多个查询执行得更差。

于 2013-04-09T17:47:43.237 回答