MySQL - 从列B的前N个条目中选择列A的平均值

时间:2017-02-14 04:26:00

标签: mysql

我有一个评级表,每个用户每天可以添加一个评级。但是每个用户可能会在评分之间错过几天。

我希望得到rating的每个user_id前7个条目的平均created_at

我的表:

mysql> desc entries;
+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| id         | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| rating     | tinyint(4)       | NO   |     | NULL    |                |
| user_id    | int(10) unsigned | NO   | MUL | NULL    |                |
| created_at | timestamp        | YES  |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+

理想情况下我会得到类似的东西:

+------------+------------------+
| day        | average_rating   |
+------------+------------------+
| 1          | 2.53             |
+------------+------------------+
| 2          | 4.30             |
+------------+------------------+
| 3          | 3.67             |
+------------+------------------+
| 4          | 5.50             |
+------------+------------------+
| 5          | 7.23             |
+------------+------------------+
| 6          | 6.98             |
+------------+------------------+
| 7          | 7.22             |
+------------+------------------+

我能得到的最接近的是:

SELECT rating, user_id, created_at FROM entries ORDER BY user_id asc, created at desc

这根本不是很接近......

甚至可能吗?表演会不会很糟糕?每次加载网页时都需要运行一些东西,那么每天运行一次并保存结果会更好吗? (到另一张桌子!?)

编辑 - 第二次尝试

努力寻求解决方案,我认为这会得到每个用户第一天的评分:

select rating from entries where user_id in
    (select user_id from entries order by created_at limit 1);

但我明白了:

ERROR 1235 (42000): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'

所以现在我要和JOIN一起玩,看看是否有帮助。

编辑 - 第三次尝试,越来越近

I found this stackoverflow post,更接近我想要的。

select e1.* from entries e1 left join entries e2 
on (e1.user_id = e2.user_id and e1.created_at > e2.created_at) 
where e2.id is null;

它获得每个用户第一天的评分。

下一步是弄清楚如何获得第2天到第7天。我不能使用1.created_at > e2.created_at,所以我现在真的很困惑。

编辑 - 第四次尝试

好的,我认为这是不可能的。一旦我弄清楚如何关闭&#39;完整的群组。模式,我意识到我可能需要使用limit <user_id>, <day_num>的子查询,我得到了:

ERROR 1235 (42000): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'

我目前的方法是获取整个表格,并使用PHP计算每天的平均值。

1 个答案:

答案 0 :(得分:1)

如果我理解正确,您希望获取用户提供的最后7个评分,并按照评分日期排序。一个用户的最后7个评级可能会在不同的日期落到另一个用户,但无论日期如何,它们都将被平均在一起。

首先,我们需要按用户和日期对数据进行排序,并为每个用户提供自己的递增行数。我这样做是通过添加两个变量,一个用于最后一个用户ID,另一个用于行号:

select e.created_at, 
  e.rating, 
  if(@lastUser=user_id,@row := @row+1, @row:=1) as row, 
  @lastUser:= e.user_id as user_id 
from entries e, 
  ( select @row := 0, @lastUser := 0 ) vars 
order by e.user_id asc, 
  e.created_at desc;

如果前一个user_id不同,我们将行计数器重置为1.结果是:

+---------------------+--------+------+---------+
| created_at          | rating | row  | user_id |
+---------------------+--------+------+---------+
| 2017-01-10 00:00:00 |      1 |    1 |       1 |
| 2017-01-09 00:00:00 |      1 |    2 |       1 |
| 2017-01-08 00:00:00 |      1 |    3 |       1 |
| 2017-01-07 00:00:00 |      1 |    4 |       1 |
| 2017-01-06 00:00:00 |      1 |    5 |       1 |
| 2017-01-05 00:00:00 |      1 |    6 |       1 |
| 2017-01-04 00:00:00 |      1 |    7 |       1 |
| 2017-01-03 00:00:00 |      1 |    8 |       1 |
| 2017-01-02 00:00:00 |      1 |    9 |       1 |
| 2017-01-01 00:00:00 |      1 |   10 |       1 |
| 2017-01-13 00:00:00 |      1 |    1 |       2 |
| 2017-01-11 00:00:00 |      1 |    2 |       2 |
| 2017-01-09 00:00:00 |      1 |    3 |       2 |
| 2017-01-07 00:00:00 |      1 |    4 |       2 |
| 2017-01-05 00:00:00 |      1 |    5 |       2 |
| 2017-01-03 00:00:00 |      1 |    6 |       2 |
| 2017-01-01 00:00:00 |      1 |    7 |       2 |
| 2017-01-13 00:00:00 |      1 |    1 |       3 |
| 2017-01-01 00:00:00 |      1 |    2 |       3 |
| 2017-01-03 00:00:00 |      1 |    1 |       4 |
| 2017-01-01 00:00:00 |      1 |    2 |       4 |
| 2017-01-02 00:00:00 |      1 |    1 |       5 |
+---------------------+--------+------+---------+

我们现在只需将其包装在另一个语句中,以选择行号小于或等于7的平均值。

select e1.row day, avg(e1.rating) avg
from (
  select e.created_at,
    e.rating,
    if(@lastUser=user_id,@row := @row+1, @row:=1) as row,
    @lastUser:= e.user_id as user_id
  from entries e,
    ( select @row    := 0, @lastUser := 0 ) vars
  order by e.user_id asc,
    e.created_at desc) e1
where e1.row <=7 
group by e1.row;

输出:

+------+--------+
| day  | avg    |
+------+--------+
|    1 | 1.0000 |
|    2 | 1.0000 |
|    3 | 1.0000 |
|    4 | 1.0000 |
|    5 | 1.0000 |
|    6 | 1.0000 |
|    7 | 1.0000 |
+------+--------+