每组最新N条记录的平均值

JV-*_*JV- 10 mysql average greatest-n-per-group limit-per-group

我当前的应用程序根据每个用户的所有记录计算点平均值:

SELECT `user_id`, AVG(`points`) AS pts 
FROM `players` 
WHERE `points` != 0 
GROUP BY `user_id`
Run Code Online (Sandbox Code Playgroud)

业务需求已更改,我需要根据每个用户的最近30条记录计算平均值.

相关表格具有以下结构:

桌子:球员; 列:player_id,user_id,match_id,points

表:用户; columns:user_id

以下查询不起作用,但它确实演示了我尝试实现的逻辑.

SELECT @user_id := u.`id`, (
    -- Calculate the average for last 30 records
    SELECT AVG(plr.`points`) 
    FROM (
        -- Select the last 30 records for evaluation
        SELECT p.`points` 
        FROM `players` AS p 
        WHERE p.`user_id`=@user_id 
        ORDER BY `match_id` DESC 
        LIMIT 30
    ) AS plr
) AS avg_points 
FROM `users` AS u
Run Code Online (Sandbox Code Playgroud)

是否有一种相当有效的方法来根据每个用户的最新30条记录计算平均值?

TMS*_*TMS 9

没有理由重新发明轮子和风险你有车,次优的代码.您的问题是普通的每组限制问题的微不足道的扩展.已经有经过测试和优化的解决方案来解决这个问题,我建议从这个资源中选择以下两个解决方案.这些查询为每个玩家生成最新的30条记录(为您的表重写):

select user_id, points
from players
where (
   select count(*) from players as p
   where p.user_id = players.user_id and p.player_id >= players.player_id
) <= 30;
Run Code Online (Sandbox Code Playgroud)

(只是为了确保我理解你的结构:我认为player_id是玩家表中的一个唯一键,并且一个用户可以作为多个玩家出现在这个表中.)

第二个经过测试和优化的解决方案是使用MySQL变量:

set @num := 0, @user_id := -1;

select user_id, points,
      @num := if(@user_id = user_id, @num + 1, 1) as row_number,
      @user_id := user_id as dummy
from players force index(user_id) /* optimization */
group by user_id, points, player_id /* player_id should be necessary here */
having row_number <= 30;
Run Code Online (Sandbox Code Playgroud)

第一个查询不是最优的(是二次的),而第二个查询是最优的(一次通过),但只能在MySQL中使用.这个选择由你.如果您选择第二种技术,请注意并使用您的密钥和数据库设置正确测试它; 他们建议在某些情况下可能会停止工作.

您的最终查询是微不足道的:

select user_id, avg(points)
from ( /* here goes one of the above solutions; 
          the "set" commands should go before this big query */ ) as t
group by user_id
Run Code Online (Sandbox Code Playgroud)

请注意,我没有在第一个查询中包含您的条件,(points != 0)因为我不太了解您的要求(您没有描述它),我也认为这个答案应该足够通用,以帮助其他有类似问题的人.


Sah*_*hah 8

试试这个:

SELECT user_id, AVG(points) AS pts 
FROM (SELECT user_id, IF(@uid = (@uid := user_id), @auto:=@auto + 1, @auto := 1) autoNo, points
      FROM players, (SELECT @uid := 0, @auto:= 1) A 
      WHERE points != 0 
      ORDER BY user_id, match_id DESC
     ) AS A 
WHERE autoNo <= 30
GROUP BY user_id;
Run Code Online (Sandbox Code Playgroud)