MySQL SELECT前10名并按此值排序

时间:2016-11-26 00:32:49

标签: php mysql sorting group-by inner-join

这是我在开发排名系统时遇到的一个有趣问题。

三张桌子:

  1. 用户(身份,年龄,......)
  2. 路线(id,难度,......)
  3. 记录(id,user_id,route_id,points,date,...)
  4. 目标是生成一个用户列表,按照他们收到的前10个日志的点数进行排序。对于每个用户,我必须找到他/她的前10个日志(按点排序),然后按此编号对所有用户进行排序。

    我还必须能够限制其他参数考虑的路线和用户 - >用户年龄,路线难度,日志日期。例如,在25岁以上添加难度为1-8的路径的所有用户中创建此排名列表,这些路径不超过一个月(日志)。

    到目前为止,这是我所得到的:  1.我知道如何根据总点数对用户进行选择和排序:

    SELECT 
        l.id, l.idr,
        u.name,..., ...,
        SUM(l.points) as totalPoints
    FROM logs l
    INNER JOIN routes r ON l.idr = r.id
    INNER JOIN users u ON d.idu = u.id
    WHERE
      /*
      All the conditions I need
      */
    
    GROUP BY u.id
    ORDER BY totalPoints DESC
    
    1. 为一个用户选择前10名:

      SELECT SUM(points)
      FROM (
          SELECT l.points as points
          FROM logs l
          INNER JOIN users u ON l.idu = u.id
          WHERE u.id = '1'
          LIMIT 10
      ) AS T
      
    2. 我只是不知道如何有效地将它们放在一起。我已经通过一些TEMPORARY TABLES解决了这个问题,并在PHP中使用while循环请求其他数据,但这非常非常慢且效率低下。随着数据库越来越大(日志~40 000,用户〜2 000,路由~1 000条记录),需要更有效的解决方案。

      正如我所提到的,我正在使用PHP,所以如果你有一些想法如何更快(更高效),不是通过创建一个很棒的查询,而是通过几个更聪明的小查询和PHP中的一些,这将是真的也是如此。

      感谢您的任何想法:)

1 个答案:

答案 0 :(得分:1)

考虑一个相关计数子查询来计算排名列,然后您可以将其用作外部查询中的过滤器。如果MySQL支持窗口函数,通常的方法是RANK OVER() CTE解决方案。

以下是用户的两个示例,然后是用户路由。请注意,对于每个分组级别,您添加WHERE子句以对GROUP BY派生表中的子查询和t列进行排名。每个特殊WHERE条件也是如此,因为它们必须在子查询中进行镜像。

用户 (每位用户的前十大日志)

SELECT main.`user`, main.totalPoints
FROM
  (SELECT t.name as `user`, SUM(t.points) as totalPoints
   FROM
      (SELECT l.id, l.idr, u.name, l.points,
             (SELECT Count(*) FROM logs sub
              WHERE sub.idu = l.idu
              AND sub.points >= l.points) AS user_rank 
       FROM logs l
       INNER JOIN users u ON l.idu = u.id) AS t
   WHERE t.user_rank <= 10
   GROUP BY t.name) AS main
ORDER BY main.totalPoints

用户和路线 (每个用户的每条路线前10个日志)

SELECT main.`user`, main.route, main.totalPoints
FROM
  (SELECT t.name as `user`, t.idr as route, SUM(t.points) as totalPoints
   FROM
      (SELECT l.id, l.idr, u.name, l.points,
             (SELECT Count(*) FROM logs sub
              WHERE sub.idu = l.idu
              AND sub.idr = l.idr
              AND sub.points >= l.points) AS user_route_rank 
       FROM logs l
       INNER JOIN routes r ON l.idr = r.id
       INNER JOIN users u ON l.idu = u.id) AS t
   WHERE t.user_route_rank <= 10
   GROUP BY t.name, t.idr) AS main
ORDER BY main.totalPoints

注意:

  1. TIES :此方法中包含ARE的记录点。有一些方法可以使用最低级的主键,如下所示:

    (SELECT Count(*) FROM logs sub
     WHERE sub.idu = l.idu
     AND sub.idr = l.idr
     AND (sub.points >= l.points
          OR sub.points = l.points AND sub.id <= l.id))  AS user_route_rank 
    
  2. GROUP BY :您的上述不完整GROUP BY聚合已被删除。 MySQL允许它,但在其他所有RDMS中都会失败。如果你有ANSI or ONLY FULL GROUP BY mode,MySQL就会引发错误,因为聚合查询的SELECT子句中的非聚合列也必须包含在GROUP BY中(尽管反向有效) ANSI)。