mysql 8.0之后可以使用内置的ROW_NUMBER()函数

mysql 8.0以前没有这样的函数,因此我们需要手动的来实现这个。

其实实现的方法也很简单。

问题

假设我们现在有一张数据量很大的表(千万以上),比如说一张活动表。表里记录了每一种活动每次发奖的明细,现在我们想知道每个活动获得积分最高的10个用户。

create table (
	`id` #主键
	`activity_id` #活动id
	`uid` #用户id
	`money` #发奖金额
	`insert_time` #发奖时间
	······
)

解决方案

拿到问题第一时间肯定想的是根据uid和activity_id来group by 然后sum(money)。

这样可以得到每个用户在每个活动在我们规定范围内的发奖金额,接下来就是想办法找出每个group 里面top 10的用户。

既然要按照金额取top 10,那么肯定需要对金额排序,order by sum(money) desc是肯定需要的。

但是这样会导致一个问题,比如活动1和活动2的产出量级类似,那很有可能会出现这种情况:

activity_id | uid  | money |
2			| 10004| 100   | 
1			| 10003| 100   | 
2			| 10002| 100   | 
1			| 10001| 101   | 
3			| 10001| 100   | 
1			| 10001| 100   | 
1			| 10001| 100   |
2			| 10001| 100   | 
··· ···

在整个的大排序下,活动是乱序的,因此我们要先order by activity_id,这样得到的就是

activity_id | uid  | money |
1			| 10003| 100   | 
1			| 10003| 100   |
1			| 10001| 100   | 
1			| 10001| 100   | 
1			| 10001| 101   | 
2			| 10004| 100   | 
2			| 10002| 100   | 
2			| 10001| 100   |
3			| 10001| 100   | 
···· ···

这样处理之后在整个大的排序中金额并不是有序的,而是分成了每个活动内是有序的。那么要怎么取出每个活动中的top n呢。

既然每个活动的组内是有序的,那么如果我们可以给每个组内的数据从1开始依次编号,编号记为num,那么num <= n的数据就是我们需要的数据,我们需要实现这种效果:

num | activity_id | uid  | money |
1   | 1			 | 10003| 100   | 
2   | 1			 | 10003| 100   |
3   | 1			 | 10001| 100   | 
4   | 1			 | 10001| 100   | 
5   | 1			 | 10001| 101   | 
1   | 2			 | 10004| 100   | 
2   | 2			 | 10002| 100   | 
3   | 2			 | 10001| 100   |
1   | 3			 | 10001| 100   | 
···· ···

这个时候我们只需要套一层select ,找出满足num <= n的即可,比如我们需要的是每组前2的数据,只需要满足num <= 2就可以了

所以我们现在要实现这种效果。实现的方式很简单,其实如果在正常的代码中大家都知道该怎么做。

  1. 判断当前的activity_id有没有变化
  2. 如果没有变化,那么num++,如果不再是上一次拿到的activity_id了,那么num=1

所以需要用到两个变量来分别存储上一个activity_id和num

最终的完成的代码:

SELECT num, activity_id, uid, money
FROM (
	SELECT @row_number := IF(@last_activity_id = activity_id, @row_number + 1, 1) AS num
		, @last_activity_id := activity_id AS activity_id, uid, money
	FROM (
		SELECT activity_id, uid, sum(money) AS money
		FROM my_table
		WHERE insert_time >= xxxxxx
			AND insert_time < xxxxxx
		GROUP BY activity_id, uid
		ORDER BY activity_id, sum(money) DESC
	) b
) a
WHERE a.num <= 10

遇到的小问题

这段代码我直接在mysql命令行执行的时候是没有问题的,但是在项目中嵌入代码之后执行有问题,查出来的所有数据的num都=1,检查了之后发现是因为定义的这两个局部变量@row_number, @last_activity_id取不到。

具体原因没有查到,有大佬知道可能原因的可以分享一下

解决的办法是在进行这个查询之前先对这两个局部变量初始化一下:

select @row_number := 1 , @last_activity_id := ''

每次拿到的数据库连接是一个session级别的,只要连接不close掉就可以取到之前创建的变量,可以解决上面的问题.

还有一种方式是以事务的形式,
begin
设置变量(set @row_num = 1, last_activity_id = ‘’)
select查询
end