【问题标题】:MySQL SELECT top 10 and sort by this valueMySQL SELECT 前 10 并按此值排序
【发布时间】:2016-11-26 00:32:49
【问题描述】:

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

三个表:

  1. 用户(ID、年龄、...)
  2. 路线(id、难度……)
  3. 日志(id、user_id、route_id、点数、日期……)

我们的目标是生成一个用户列表,按照他们在前 10 条日志中获得的分数排序。对于每个用户,我必须找到他/她的前 10 个日志(按点排序),然后按此数字对所有用户进行排序。

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

这是我目前所获得的: 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
    

我只是不知道如何有效地将这些组合在一起。我已经通过一些临时表解决了这个问题,并在 PHP 的 while 循环中请求了额外的数据,但这非常非常缓慢且效率低下。随着数据库越来越大(日志 ~ 40 000,用户 ~ 2 000,路由 ~ 1 000 条记录),需要更有效的解决方案。

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

感谢您的任何想法:)

【问题讨论】:

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


【解决方案1】:

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

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

用户 (每个用户的前 10 名总体日志)

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:此方法中包含并列的日志点。有一些决胜局的方法,例如采用最低主键,如下所示:

    (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 中有效)。

【讨论】:

    猜你喜欢
    • 2017-01-06
    • 1970-01-01
    • 2016-01-08
    • 2011-11-05
    • 1970-01-01
    • 2013-02-04
    • 2011-01-12
    • 1970-01-01
    相关资源
    最近更新 更多