选择作为相关表的前N个结果的项目

时间:2010-07-01 19:15:16

标签: sql subquery limit greatest-n-per-group

假设我有一个问题被问到的游戏,人们发布得分的回复,并且前10个回复获胜。我有一个SQL数据库存储所有这些信息,所以我可能有表,如用户,问题和响应。 Responses表包含foreign_keys user_id和question_id,以及属性total_score。

显然,对于一个特定问题,我可以通过订单和限制检索前10个回复:

SELECT * FROM Responses WHERE question_id=? ORDER BY total_score DESC LIMIT 10;

我正在寻找的是一种方法,我可以为特定用户确定他们作为获胜者的所有回复的列表(在他们的特定问题的前10名)。以编程方式简单地逐步执行每个响应并查看它是否包含在其问题的前10个中,但我想优化它以便我不进行N + 1个查询,其中N是用户提交的响应数量

4 个答案:

答案 0 :(得分:3)

如果您使用Oracle,Microsoft SQL Server,DB2或PostgreSQL,这些数据库支持窗口函数。加入用户对同一问题的其他回复的回复。然后按问题划分并按降序排序。使用每个分区中的行号将该集限制为前10个中的行号。同时传递给定用户的user_id,以便您可以从前10个中选择它们,因为您只对给定用户的响应感兴趣。

SELECT *
FROM (
  SELECT r1.user_id AS given_user, r2.*,
    ROW_NUMBER() OVER (PARTITION BY r2.question_id ORDER BY r2.total_score DESC) AS rownum
  FROM Responses r1 JOIN Responses r2 ON r1.question_id = r2.question_id
  WHERE r1.user_id = ?
) t
WHERE rownum <= 10 AND user_id = given_user;

但是,如果您使用MySQL或SQLite或其他不支持窗口函数的数据库,则可以使用此不同的解决方案:

查询用户的回复,并使用联接来匹配具有更高分数的相应问题的其他回答(或在关系的情况下更早的PK)。逐个分组,并计算得分较高的回复数。如果计数小于10,那么用户的回答是每个问题的前10名。

SELECT r1.*
FROM Responses r1
LEFT OUTER JOIN Responses r2 ON r1.question_id = r2.question_id 
  AND (r1.total_score < r2.total_score 
    OR r1.total_score = r2.total_score AND r1.response_id > r2.response_id)
WHERE r1.user_id = ?
GROUP BY r1.question_id
HAVING COUNT(*) < 10;

答案 1 :(得分:2)

尝试使用嵌入式select语句。我今天无法访问数据库工具,因此无法确认语法/输出。只需进行适当的更改即可捕获所需的所有列。您还可以向主查询添加问题并加入回复。

select *
  from users
     , responses
 where users.user_id=responses.user_id
   and responses.response_id in (SELECT z.response_id 
                                   FROM Responses z 
                                  WHERE z.user_id = users.user_id 
                                 ORDER BY total_score DESC 
                                 LIMIT 10)

答案 2 :(得分:1)

或者您可以通过添加“IsTopPost”之类的其他字段来真正优化它。当有人投票时你必须更新热门帖子,但你的查询很简单:

SELECT * FROM Responses WHERE user_id=? and IsTopPost = 1

答案 3 :(得分:1)

我觉得这样的事情可以解决问题:

SELECT 
    user_id, question_id, response_id 
FROM 
    Responses AS r1 
WHERE 
    user_id = ?
AND
    response_id IN (SELECT response_id 
                    FROM Responses AS r2 
                    WHERE r2.question_id = r1.question_id 
                    ORDER BY total_score DESC LIMIT 10)

实际上,对于每个question_id,执行子查询,确定该question_id的前10个响应。

您可能需要考虑添加一个将某些回复标记为“获胜者”的列。这样,你可以简单地选择那些行并保存数据库,使其不必一遍又一遍地计算前10个。