Mysql分组排名

Posted

技术标签:

【中文标题】Mysql分组排名【英文标题】:Mysql grouped ranking 【发布时间】:2013-01-17 02:03:41 【问题描述】:

与this question有关。

其实免得说我们要解决mysql中的分组排序问题。我们有一个表,每行代表一个实体,属于一个组。我们希望根据每个组单独的属性为每个实体分配一个等级。稍后我们可以对排名进行各种操作,比如要求每个组的前 10 个实体也满足另一个条件,等等。

例如,实体可以是根据他们最喜欢的编程语言属于不同“组”的程序员。然后每个程序员都有一个声誉(比如说在论坛上)。我们想添加一个额外的字段,该字段将是基于下降声誉的程序员排名。我们希望为每个组独立执行此操作。

gid | repu | name |
1       1    john
1       3    anna
2       2    scot
2       1    leni

成为

gid | repu | name | rank
1       3    anna      1
1       1    john      2
2       2    scot      1
2       1    leni      2

现在我们还要求我们不想使用基于会话变量的解决方案。是的,它们工作得很好,但它们显然违反了 mysql 的要求,即不在同一语句上读写会话变量。 (See here)

现在this post 中提出的解决方案说

-- SOL #1 (SELF-JOIN)
SELECT a.*, count(*) as row_number FROM test a
JOIN test b ON a.gid = b.gid AND a.repu <= b.repu
GROUP BY a.gid, a.repu

这几乎可以做到这一点。我的一些问题是,这是合法的 SQL 还是违反了任何标准或 mysql 怪癖?是否保证它可以在 mysql 上运行?

我读到的另一个解决方案 here 是,这对我来说更像是一种黑魔法,但看起来更优雅

-- SOL #2 (SUBQUERY)
SELECT t.* ,
    ( SELECT COUNT(*) + 1
        FROM test
        WHERE repu > t.repu AND gid = t.gid 
    ) AS rank
FROM test AS t
ORDER BY gid ASC, rank ASC  

这使用了一个引用外部表的子查询,并且也可以做到这一点。谁能解释一下这个是如何工作的?

此外,这里的问题与解决方案 #1 相同。

加上任何关于评估两个提议的解决方案的性能/兼容性的 cmets。

编辑:其他方法,供参考

来自this post 会话变量方法的一种变体。 警告:这是我要避免的。请注意,在单个语句中,@rand 和 @partition 会话变量被读取(在 WHEN 和 THEN 之后的情况下)和写入(在 THEN AND ELSE 之后的情况下以及初始化变量的下一个子查询中)。

-- SOL #3 (SESSION VARIABLES / ANTIPATTERN)
SELECT t.*, ( CASE gid
             WHEN @partition THEN @rank := @rank + 1 
             ELSE @rank := 1 AND @partition := gid ) AS rank
FROM test t, 
    (SELECT @rank := 0, @partition := '') tmp
ORDER BY gid ASC, repu DESC

这里还有一个基于集合的解决方案,相当复杂,由下面的一位同事发布。

-- SOL #4 (SET BASED)
SELECT x.*, FIND_IN_SET(CONCAT(x.gid,':',x.repu), y.c) rank 
    FROM test x 
    JOIN (
        SELECT GROUP_CONCAT(DISTINCT CONCAT(gid,':',repu) ORDER BY gid, repu DESC) c 
        FROM test GROUP BY gid
    ) y ON FIND_IN_SET(CONCAT(x.gid,':',x.repu), y.c)

【问题讨论】:

AFAIK,您描述的所有方法都是合法的。我的猜测是 variables 方法将是最快的,其次是您没有描述的方法,然后是子查询,然后是 join - 但我只是猜测。 能分享一下我没有定义的方法吗? 如果我记得的话我会... :-( 我认为它看起来像这样 - 但现在我来写它,我看不出它怎么可能比描述的其他方法更快......SELECT x.gid, x.repu, x.name, FIND_IN_SET(CONCAT(x.gid,':',x.repu),y.c) rank FROM test x JOIN (SELECT GROUP_CONCAT(DISTINCT CONCAT(gid,':',repu) ORDER BY gid,repu DESC) c FROM test GROUP BY gid) y ON FIND_IN_SET(CONCAT(x.gid,':',x.repu),y.c); 您应该明白 MySQL 的建议只是建议,但是如果您有一个包含组的表,其中组中的项目数相当大,您可以一直等到有效查询完成:请参阅***.com/questions/1313120/… 【参考方案1】:

JOIN 是合法的 MYSQL 语法。如果它不起作用,怀疑有人会将其标记为答案。

在子查询方面,它会比第一种解决方案快。查看EXPLAIN PLAN 将是了解这些查询执行的好主意。

还有另一种方法可以达到同样的效果:-

-- SOL #3:在这篇文章中以 30 票回答:

ROW_NUMBER() in MySQL

【讨论】:

实际上我对加入解决方案的担忧是 group by 是否可以使任何行意外合并。老实说 GROUP BY a.repu 让我很困惑。为什么我们需要按我们正在比较的值进行分组?我以前没有遇到过。通常 GROUP BY 用于表示一种类别的列。 关于 SOL#3,这是基于会话变量的变量,尽管 30 票反对 mysql 建议。

以上是关于Mysql分组排名的主要内容,如果未能解决你的问题,请参考以下文章

mysql5.7使用变量进行分组排名并筛选

mysql5.7使用变量进行分组排名并筛选

MySQL实现SQL Server排名函数

MySQL实现SQL Server排名函数

如何在交易数据中查询各个版本交易量前三的股票?(MySQL分组排名)

mysql分组排序取组内第一的数据行获取分组后,组内排名第一或最后的数据行。