2016-12-08 2 views
1

У меня есть следующая таблица (scores):Как выбрать максимальный балл от каждого отдельного пользователя в этой таблице?

id user date   score 
---|-----|------------|-------- 
1 | 10 | 11/01/2016 | 400 
2 | 10 | 11/03/2016 | 450 
5 | 17 | 10/03/2016 | 305 
3 | 13 | 09/03/2016 | 120 
4 | 17 | 11/03/2016 | 300 
6 | 13 | 08/03/2016 | 120 
7 | 13 | 11/12/2016 | 120 
8 | 13 | 09/01/2016 | 110 

Я хочу, чтобы выбрать max(score) для каждого отдельного пользователя, используя date как тай-брейк (в случае ничьей, самые последняя запись должна быть возвращена), так что результаты выглядеть следующим образом (наивысший балл для каждого пользователя, отсортированные по score в порядке убывания):

id user date   score 
---|-----|------------|-------- 
2 | 10 | 11/03/2016 | 450 
5 | 17 | 10/03/2016 | 305 
7 | 13 | 11/12/2016 | 120 

Я использую Postgres, и я не являюсь экспертом SQL любыми средствами. Я пытался что-то подобное следующему, который не работает, потому что не имеет id колонок включены в group by:

select scores.user, max(scores.score) as score, scores.id 
from scores 
group by scores.user 
order by score desc 

У меня есть ощущение, что мне нужно сделать суб-выбор, но Я не могу заставить соединение работать правильно. Я нашел How can I SELECT rows with MAX(Column value), DISTINCT by another column in SQL?, но я не могу заставить никого из решений работать для меня, потому что мне нужно вернуть строку id, и у меня есть возможность связать столбец date.

+0

@a_horse_with_no_name Вы правы, хорошо поймайте. скопируйте/вставьте ошибку с моей стороны. Я обновил ожидаемый результат. – user2719094

ответ

2

В Postgres обычно самый быстрый способ заключается в использовании distinct on()

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

То есть определенно много быстрее, чем любое решение, используя подзапрос с max() и обычно еще немного быстрее, чем эквивалентное решение с использованием функция окна (например, row_number())


Я использовал user_id для имени столбца, потому что user это зарезервированное слово, и я настоятельно рекомендую не использовать.

+0

Может быть, поп-дата отправляется в порядке, для тай-брейка. –

+0

В конечном счете, это было мое решение. Я думаю, что просто неправильно понял, как работает «отличный», и это привело меня к правильному пути. Благодаря! – user2719094

+0

@ user2719094: обратите внимание, что 'distinct on()' что-то другое, чем 'different'! –

1

Попробуйте это:

with 

-- get maximum scores by user 
maxscores as (
    select "user", max(score) as maxscore 
    from test 
    group by "user" 
), 

-- find the maximum date as the tie-breaker along with the above information 
maxdates as (
    select t."user", mx.maxscore, max(t."date") as maxdate 
    from test t 
    inner join maxscores mx 
    on mx."user" = t."user" 
    and mx.maxscore = t.score 
    group by t."user", mx.maxscore 
) 

-- select all columns based on the results of maxdates 
select t.* 
from test t 
inner join maxdates md 
    on md."user" = t."user" 
    and md.maxscore = t.score 
    and md.maxdate = t."date"; 

Объяснение

  • С КТР maxdates, давайте найдем максимальное количество баллов по каждому пользователю
  • Вернуться назад к столу. Получить записи, которые соответствуют пользователю и максимальный результат. Получить максимальную дату для этой комбинации пользователей/баллов
  • Вернитесь к столу. Получить строки, которые соответствуют пользователю, максимальный балл и максимальный срок мы извлеченный

Пример:

http://sqlfiddle.com/#!15/0f756/8 - без row_number

http://sqlfiddle.com/#!15/0f756/13 - с row_number

вы можете изменить запрос, как ты желаешь.

Тестовый случай

create table test (
    id int, 
    "user" int, 
    "date" date, 
    score int 
); 

insert into test values 
(1 , 10 , '11/01/2016' , 400 ) 
,(2 , 10 , '11/03/2016' , 450) 
,(5 , 17 , '10/03/2016' , 305 ) 
,(3 , 13 , '09/03/2016' , 120 ) 
,(4 , 17 , '11/03/2016' , 300) 
,(6 , 13 , '08/03/2016' , 120 ) 
,(7 , 13 , '11/12/2016' , 120 ) 
,(8 , 13 , '09/01/2016' , 110); 

Результат

| id | user |      date | score | 
|----|------|----------------------------|-------| 
| 2 | 10 | November, 03 2016 00:00:00 | 450 | 
| 5 | 17 | October, 03 2016 00:00:00 | 305 | 
| 7 | 13 | November, 12 2016 00:00:00 | 120 | 

Риск

Если у вас есть две записи с одинаковым счетом и дату для пользователя 13 (к примеру) , вы получите 2 записи пользователя 13.

Пример риска: http://sqlfiddle.com/#!15/cb86e/1

Чтобы уменьшить риск, вы могли бы использовать row_number() over() так:

with 
rankeddata as (
    select row_number() over (
    partition by 
     "user" 
    order by 
     "user", 
     score desc, 
     "date" desc) as sr, 
    t.* 
    from test t 
) 
select * from rankeddata where sr = 1; 

Результат сдержанные риска

| sr | id | user |      date | score | 
|----|----|------|----------------------------|-------| 
| 1 | 2 | 10 | November, 03 2016 00:00:00 | 450 | 
| 1 | 7 | 13 | November, 12 2016 00:00:00 | 120 | 
| 1 | 5 | 17 | October, 03 2016 00:00:00 | 305 | 
0

этот путь

create table test (
    id int, 
    "user" int, 
    "date" date, 
    score int 
); 

insert into test values 
(1 , 10 , '11/01/2016' , 400 ) 
,(2 , 10 , '11/03/2016' , 450) 
,(5 , 17 , '10/03/2016' , 305 ) 
,(3 , 13 , '09/03/2016' , 120 ) 
,(4 , 17 , '11/03/2016' , 300) 
,(6 , 13 , '08/03/2016' , 120 ) 
,(7 , 13 , '11/12/2016' , 120 ) 
,(8 , 13 , '09/01/2016' , 110); 


select * from test where id in (
select distinct(first_value(id) 
over(
partition by "user" order by score desc 
)) 
from test 
) 
+0

'отдельный' - ** нет ** a функция. 'distinct (a)' is ** точно ** то же самое, что 'distinct a' –