Существует таблица scores_score, которая содержит следующие столбцы:

id, player_name, value, created_at

Мне нужно получить N (100) лучших результатов, где:

  1. player_name должно быть уникальным для всех результатов
  2. должен быть возвращен только лучший результат для данного player_name
  3. результаты должны быть отфильтрованы по диапазону дат

Допустим, у меня есть следующие данные:

id      player_name    value          date
1       A               400        2016-09-10
2       B               200        2016-09-12
3       C               400        2016-09-15
4       C               500        2016-09-14
5       B               100        2016-09-20
6       A               6000       2015-01-01
7       B               1200       2016-09-29

И хотим показать лучших игроков с их результатами в период с 01.09.2016 по 20.09.2016. Я должен получить:

id      player_name    value          date
4       C               500        2016-09-14
1       A               400        2016-09-10
2       B               200        2016-09-12

Это мой подход к ее решению, но есть проблема во вложенном SELECT, поскольку он получает лучший результат игрока в целом за пределами диапазона дат.

SELECT b.*, a.*
FROM (SELECT player_name, max(value) AS max_value
      FROM scores_score
      GROUP BY player_name
      ORDER BY max(value) DESC) a
INNER JOIN scores_score b ON a.player_name = b.player_name AND a.max_value = b.value
WHERE CAST(b.created_at AS DATE) >= %(date_border)s
ORDER BY b.value DESC
LIMIT 100
3
Kamil Rykowski 6 Сен 2016 в 21:17

3 ответа

Лучший ответ

distinct on

select *
from (
    select distinct on (player_name) *
    from scores_score
    where date between '2016-09-01' and '2016-09-20'
    order by player_name, value desc
) s
order by value desc
limit 100
3
Clodoaldo Neto 6 Сен 2016 в 19:21

Это сработает и даст вам ожидаемый результат. Используйте оконную функцию row_number(), чтобы отметить наивысший результат для каждого игрока между датами (rn = 1), а затем упорядочите набор результатов по value по убыванию и, наконец, ограничьте вывод до 100 наивысших.

select 
  id, player_name, value, created_at
from (
  select
    id, player_name, value, created_at,
    row_number() over (partition by player_name order by value desc, id) as rn
  from scores_score
  where created_at between '2016-09-01' and '2016-09-20'
  ) ranks
where rn = 1
order by value desc
limit 100

Обратите внимание, что дополнительный столбец id для сортировки в функции row_number предназначен для разрешения связей (даже если он назначает только одно значение для каждой строки в разделе), в которых один и тот же игрок имеет две строки с равными значениями, которые находятся в пределах заданная дата. Это будет более старая запись, и если они отличаются датой created_at, вы увидите разницу в выводе :-)

1
Kamil Gosciminski 6 Сен 2016 в 19:27

Это немного громоздко, но должно работать. Сначала выберите только игроков и значения в пределах вашего диапазона дат (а). Затем выберите максимальное количество очков по игроку (b). Затем присоедините идентификатор и дату (c):

SELECT c.id, c.player_name, c.value, c.date
FROM
scores_score c
INNER JOIN
(SELECT player_name, max(value)
FROM
(SELECT player_name, value
FROM scores_score
WHERE date BETWEEN '2016-09-01' AND '2016-09-20') a
GROUP BY player_name) b
ON c.player_name = b.player_name
AND c.value = b.value
ORDER BY value
LIMIT 100

Протестировано здесь: http://sqlfiddle.com/#!9/10db42/6

0
kjmerf 6 Сен 2016 в 19:25