2017-02-14 75 views
0

我有一个评分表,其中每个用户每天可以添加一个评级。但每个用户可能会错过评级之间的几天。MySQL - 为B列的前N个条目选择列A的平均值

我想要得到平均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 

其中ISN一点都不太接近...

它甚至有可能吗?表演会很糟糕吗?这是每次加载网页时都需要运行的东西,那么每天只运行一次并保存结果会更好吗? (另一个表!?)

编辑 - 第二次尝试

争取解决工作,我认为这将得到的评价每个用户的第一天:

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,所以我现在非常困惑。

编辑 - 第四次尝试

好吧,我认为这是不可能的。有一次,我摸索出如何“通过整组”模式,关闭,我意识到我可能会需要使用子查询与limit <user_id>, <day_num>,为此,我得到:

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

我目前的方法是只得到了整个表,并使用PHP来计算每天的平均值。

+2

你会在这里需要行号,以及日历表中缺少的天,以填补一个给定的用户,他们应该发生。 –

+0

好的,谢谢 - 你是说在表中添加一个day_number列?不知道你的日历表是​​什么意思。如果user1在1月1日和次年1月2日第一次发布,而user2在3月1日和8月1日之后首先发布,那么无论发生什么时候,我都需要所有用户第一天和第二天的平均评分。 –

+0

缺少日期必须来自_somewhere_,因为它们不在您的原始表格中。行号也必须来自某个地方。 –

回答

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 | 
+---------------------+--------+------+---------+ 

我们现在简单地在另一个语句中包装这个选择平均行数小于或等于七的平均值。

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 | 
+------+--------+ 
+0

你先生是魔术师!谢谢你一千次,完美的回答,并且非常清楚地解释。 –

+0

为了正确工作,我只需做一点细微的改动 - 从底部的第三行开始,你有'e.created_at desc' - 我将它改为'e.created_at asc' - 但是再次感谢!我仍然如此印象深刻:) –

+0

我不知道你是否需要第一个或最后7天。但是,正如你发现改变排序顺序给出了正确的答案。很高兴我能帮上忙 – roblovelock