2016-01-28 1 views
2

Используя Rails, я пытаюсь выполнить команду SQL, чтобы вернуть массив строк, которые содержат максимальное значение для определенного пользователя в день.PostgreSQL Выберите наибольшее значение в течение периода времени

Например:

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. Мне удалось сделать это с помощью нескольких group_by и map в Rails, но он очень медленный, так как он должен повторить итерацию по всему массиву, и поскольку в нем много записей, это может занять некоторое время.

EDIT:

Мое решение было следующим:

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") 

Это первые фильтры по месяц/год, а затем возвращает список пользователей, их наивысший балл для каждого пользователя в день.

+0

'21: 35: 24' на все дни предотвращает тщательное тестирование решений (поскольку он эффективно преобразует временную метку до настоящего времени), пожалуйста, укажите более реальные временные метки времени. – Dzenly

+0

@ Dzenly - Время не было необходимым. Он должен был быть отфильтрован «датой» в любом случае, а не с датами. Я включил время, чтобы попытаться быть более явным, но вы правы - это немного путает основной момент. – Rockster160

ответ

2

Вы можете использовать distinct on(), который является расширением Postgres к оператору distinct:

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; 

решение с distinct on(), как правило, быстрее в Postgres.

С помощью функции окна вы также можете обрабатывать галстуки: когда пользователь имеет один и тот же (самый высокий) балл более одного раза в день. Решение с row_number() вернет только одну строку за (user_id, created_at). Если вы хотите, чтобы все строки имели одинаковый (самый высокий) балл, вместо этого вам нужно использовать dense_rank().

Редактировать

Если вы хотите, чтобы игнорировать временную часть столбца временных меток, просто бросить его на сегодняшний день:

created_at::date 
+0

'явный на' то, что я искал. Спасибо! – Rockster160