根据MySQL中的多列确定排名

时间:2018-11-25 06:11:57

标签: mysql sql sql-rank

我有一个包含3个字段的表,我想根据user_id和game_id对列进行排名。

这是SQL Fiddle: http://sqlfiddle.com/#!9/883e9d/1

我已经有桌子了

    node_t *create (char data) {    /* simply move allocation/initialization
                                       to a create function */
        node_t *node = new node_t;  /* allocate new node */

        node->data = data;          /* set node data */
        node->next = NULL;

        return node;
    }

    node_t *insert (char data) {

        node_t *node = create (data);   /* call create function */

        if (!head)                  /* if 1st node */
            head = node;
        else {              /* otherwise */
            iter = head;
            while (iter->next)      /* traditional iterate to find last */
                iter = iter->next;
            iter->next = node;      /* set new last */
        }

        return node;                /* return node */
    }

预期输出:

insert

到目前为止我的努力:

 user_id | game_id |   game_detial_sum  |
 --------|---------|--------------------|
 6       | 10      |  1000              |   
 6       | 11      |  260               |
 7       | 10      |  1200              |
 7       | 11      |  500               |
 7       | 12      |  360               |
 7       | 13      |  50                | 

编辑:(来自OP Comments):排序基于user_id | game_id | game_detial_sum | user_game_rank | --------|---------|--------------------|------------------| 6 | 10 | 1000 | 1 | 6 | 11 | 260 | 2 | 7 | 10 | 1200 | 1 | 7 | 11 | 500 | 2 | 7 | 12 | 360 | 3 | 7 | 13 | 50 | 4 |

的降序
  

game_detail的顺序

4 个答案:

答案 0 :(得分:6)

Derived TableFROM子句中的子查询)中,我们对数据进行排序,以使所有具有相同user_id值的行都聚在一起,并根据{ {1}}降序。

现在,我们使用此结果集并使用条件game_detail表达式来评估行编号。就像循环技术(我们在应用程序代码中使用的,例如:PHP)一样。我们将前一行的值存储在用户定义的变量中,然后对照前一行检查当前行的值。最终,我们将相应地分配行号。

编辑::基于MySQL docs和@Gordon Linoff的观察:

  

涉及用户变量的表达式的求值顺序为   未定义。例如,不能保证SELECT @a,@a:= @ a + 1   首先评估@a,然后执行分配。

我们需要评估行号,并将CASE..WHEN的值分配给同一表达式内的user_id变量。

@u

结果

SET @r := 0, @u := 0; 
SELECT
  @r := CASE WHEN @u = dt.user_id 
                  THEN @r + 1
             WHEN @u := dt.user_id /* Notice := instead of = */
                  THEN 1 
        END AS user_game_rank, 
  dt.user_id, 
  dt.game_detail, 
  dt.game_id 

FROM 
( SELECT user_id, game_id, game_detail
  FROM game_logs 
  ORDER BY user_id, game_detail DESC 
) AS dt 

View on DB Fiddle


最近发现的来自MySQL Docs的有趣笔记:

  

以前的MySQL版本可以为一个值分配一个值   SET以外的语句中的用户变量。此功能是   MySQL 8.0支持向后兼容,但要遵守   在将来的MySQL版本中删除。

另外,由于有一个SO成员,MySQL团队还访问了此博客:https://mysqlserverteam.com/row-numbering-ranking-how-to-use-less-user-variables-in-mysql-queries/

一般的观察结果是,在同一查询块中使用| user_game_rank | user_id | game_detail | game_id | | -------------- | ------- | ----------- | ------- | | 1 | 6 | 260 | 11 | | 2 | 6 | 100 | 10 | | 1 | 7 | 1200 | 10 | | 2 | 7 | 500 | 11 | | 3 | 7 | 260 | 12 | | 4 | 7 | 50 | 13 | 评估用户变量不会保证值始终正确。因此,MySQL优化器 可能会出现,并更改我们的假定的评估顺序。

解决此问题的最佳方法是升级到MySQL 8+并利用Row_Number()功能:

模式(MySQL v8.0)

ORDER BY

结果

SELECT user_id, 
       game_id, 
       game_detail, 
       ROW_NUMBER() OVER (PARTITION BY user_id 
                          ORDER BY game_detail DESC) AS user_game_rank 
FROM game_logs 
ORDER BY user_id, user_game_rank;

View on DB Fiddle

答案 1 :(得分:4)

MySQL 8.0之前的最佳解决方案如下:

select gl.*, 
       (@rn := if(@lastUserId = user_id, @rn + 1,
                  if(@lastUserId := user_id, 1, 1)
                 )
        ) as user_game_rank
from (select gl.*
      from game_logs gl
      order by gl.user_id, gl.game_detail desc
     ) gl cross join
     (select @rn := 0, @lastUserId := 0) params;

排序是在子查询中完成的。从MySQL 5.7开始,这是必需的。变量赋值都在一个表达式中,因此不同的表达式求值顺序无关紧要(并且MySQL不保证表达式的求值顺序)。

答案 2 :(得分:3)

SELECT user_id, game_id, game_detail, 
       CASE WHEN user_id = @lastUserId 
            THEN @rank := @rank + 1 
            ELSE @rank := 1 
       END As user_game_rank,
       @lastUserId := user_id
FROM game_logs
cross join (select @rank := 0, @lastUserId := 0) r
order by user_id, game_detail desc

SQLFiddle Demo

答案 3 :(得分:1)

您可以使用一个非常简单的相关子查询:

SELECT *, (
    SELECT COUNT(DISTINCT game_detail) + 1
    FROM game_logs AS x
    WHERE user_id = t.user_id AND game_detail > t.game_detail
) AS user_game_rank
FROM game_logs AS t
ORDER BY user_id, user_game_rank

DB Fiddle

它比用户变量慢,但可靠得多。只需要一个JOIN即可打破它们。