如何在一个选择查询中执行此操作?

时间:2013-04-09 17:00:22

标签: sql sqlalchemy

我需要显示帖子列表。对于每篇文章,我还需要展示:

  1. 有多少人"喜欢"帖子。
  2. 那些"喜欢"的三个名字。帖子(最好是观看用户的朋友)。
  3. 如果观看用户喜欢"这个帖子,我希望他/她成为三个中的一个。
  4. 我不知道如何在不查询for循环中的每个项目的情况下这样做,这被证明是非常慢的。确定缓存/非规范化会有所帮助,但我想知道是否可以这样做。 facebook是如何做到的?

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

    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

2 个答案:

答案 0 :(得分: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

您可以轻松加入子查询&#34;编号&#34;与表&#34;用户&#34;获取其他用户信息。还有额外的字段u2,u3来帮助查看正在发生的事情。你可以删除这些。

查询的一般概念:

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

2)列排名IF(visitor.user_id不为空,1,IF(friendships.user_id不为空,2,3)),为users_liked_posts中的每个用户分配排名。此查询按帖子和排名进行排序。

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

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

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

答案 1 :(得分:1)

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

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

祝你好运。

修改

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

以下是一些可能对您有用的Cypher查询示例。

统计有多少人喜欢帖子:

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

(实际上你应该使用一个原子计数器,相反,如果它是你将要查询的东西)

获得三个喜欢帖子的人 ,其中包含以下限制条件:

  1. 如果他/她喜欢该帖子,则第一个likingUser将始终是当前用户。
  2. 如果当前用户的朋友喜欢这个帖子,他们会在任何非朋友之前出现。
  3. START post=node({postId}), user=node({currentUserId}) 
    MATCH path = post<-[:like]-likingUser-[r?:friend*0..1]-user 
    RETURN likingUser, count(r) as rc, length(path) as len 
    ORDER BY rc desc, len asc
    LIMIT 3
    

    我会尝试解释上面的问题...如果可以的话。

    1. 首先抓住两个节点post和当前user
    2. 匹配喜欢帖子(likingUser
    3. 的所有用户
    4. 此外,测试是否存在长度为0或1的路径,该路径将likingUser通过友谊关系连接到当前user(长度为0的路径表示likingUser==user)。
    5. 现在,首先根据关系r是否存在来排序结果(如果likingUser是与userlikingUser==user的朋友,则会存在。因此,count(r)对于每个结果将为0或1。由于我们更喜欢count(r)==1的结果,因此我们将按降序对其进行排序。
    6. 接下来,执行辅助排序,如果他/她是结果集的一部分,则强制当前user到列表顶部。我们通过检查path的长度来做到这一点。当user==likingUser时,路径长度将短于user成为likingUser的朋友时的路径长度,因此我们可以使用length(path)强制user到达顶部按升序排序。
    7. 最后,我们将结果限制为仅排在前三位的结果。
    8. 希望这有点道理。作为旁注,通过分离查询,您实际上可以获得更好的性能。例如,一个查询以查看用户是否喜欢该帖子,然后另一个查询以获得最多三个喜欢该帖子的朋友,最后另一个查询以获得最多三个喜欢该帖子的非朋友。我说它可能更快,因为每个查询在得到三个结果后都会短路,而我写的大单查询必须考虑所有可能性,然后对它们进行排序。所以,请记住,仅仅因为您可以将多个问题组合到一个查询中,它实际上可能比多个查询更糟糕。