PostgreSQL在时间段内选择最高值

时间:2016-01-28 02:25:35

标签: sql ruby-on-rails postgresql greatest-n-per-group

使用Rails,我正在尝试执行SQL命令以返回每天包含特定用户最高值的行数组。

例如:

  user_id(integer) |        created_at(datetime)    | score(integer)
-------------------+--------------------------------+---------------
              1    |      "2015-07-27 21:35:24"     |         100
              1    |      "2015-07-27 21:35:24"     |         123
              2    |      "2015-07-27 21:35:24"     |         101
              2    |      "2015-07-27 21:35:24"     |         122
              3    |      "2015-07-27 21:35:24"     |         103
              3    |      "2015-07-27 21:35:24"     |         115
              1    |      "2015-07-26 21:35:24"     |         116
              1    |      "2015-07-26 21:35:24"     |         151
              2    |      "2015-07-26 21:35:24"     |         122
              2    |      "2015-07-26 21:35:24"     |         134
              3    |      "2015-07-26 21:35:24"     |         123
              3    |      "2015-07-26 21:35:24"     |         111
              1    |      "2015-07-25 21:35:24"     |         129
              1    |      "2015-07-25 21:35:24"     |         152
              2    |      "2015-07-25 21:35:24"     |         120
              2    |      "2015-07-25 21:35:24"     |         109
              3    |      "2015-07-25 21:35:24"     |         142
              3    |      "2015-07-25 21:35:24"     |         131

预期结果:

  user_id(integer) |        created_at(datetime)    | score(integer)
-------------------+--------------------------------+---------------
              1    |      "2015-07-27 21:35:24"     |         123
              2    |      "2015-07-27 21:35:24"     |         122
              3    |      "2015-07-27 21:35:24"     |         115
              1    |      "2015-07-26 21:35:24"     |         151
              2    |      "2015-07-26 21:35:24"     |         134
              3    |      "2015-07-26 21:35:24"     |         123
              1    |      "2015-07-25 21:35:24"     |         152
              2    |      "2015-07-25 21:35:24"     |         120
              3    |      "2015-07-25 21:35:24"     |         142

我一直在组合各种联接,having和其他方法,但无济于事。我无法过滤结果。我通过select每天的最大值取得了一点进展,但之后我无法按user_id基础过滤掉较低的值。我设法在Rails中使用多个group_bymap,但它超级慢,因为它必须重新遍历整个数组,并且由于有很多记录,它可能需要一些时间。

编辑:

我的解决方案如下:

    all_scores_in_time_period = UserScore
      .where("EXTRACT(MONTH FROM created_at) = ?", Date::MONTHNAMES.index(params[:month_control]))
      .where("EXTRACT(YEAR FROM created_at) = ?", params[:year_control])
      .select("DISTINCT ON(DATE(created_at), user_id) *")
      .order("DATE(created_at) desc")

首先按月/年过滤,然后按用户每天的最高得分返回用户列表。

1 个答案:

答案 0 :(得分:3)

您可以使用distinct on ()这是distinct运算符的Postgres扩展名:

select distinct on (user_id, created_at) user_id, created_at, score
from the_table
order by user_id, created_at, score desc;

如果您需要使用标准SQL的解决方案,也可以使用窗口函数来实现:

select user_id, created_at, score
from (
  select user_id, created_at, score, 
         row_number() over (partition by user_id, created_at order by score desc) as rn
  from the_table
) as t
order by user_id, created_at;

Postgres中distinct on()的解决方案通常更快。

使用窗口功能,您还可以处理关系:当用户在一天中具有相同(最高)得分时不止一次。 row_number()的解决方案每个只返回一行(user_id,created_at)。如果您希望所有行具有相同(最高)分数,则需要使用dense_rank()代替。

修改

如果要忽略timestamp列的时间部分,只需将其强制转换为日期:

created_at::date