Во время работы над проектом мне потребовалось запрашивать данные определенного типа из таблицы PostgreSQL. Он должен был получить 3 верхних строки на основе определенного значения в строке в каждой категории, что в нашем случае должно было найти имена кандидатов с тремя лучшими оценками для каждой вакансии.

Это был особый тип запроса, потому что в данном случае мы не можем использовать limit. Первое, что приходит на ум при запросе этого типа, - это использовать оператор UNION, который является наивным решением и наименее эффективным, поскольку мы должны написать запрос, определяющий каждый идентификатор задания. Запрос будет выглядеть так:

select * from score_data where job_id=1 order by score desc limit 3
UNION
select * from score_data where job_id=2 order by score desc limit 3
UNION
select * from score_data where job_id=3 order by score desc limit 3
.
.
.
select * from score_data where job_id=n order by score desc limit 3

После некоторого исследования я пришел к Оконным функциям, которые, согласно определению:

Оконные функции предоставляют возможность выполнять вычисления для наборов строк, связанных с текущей строкой запроса.

Одна из функций - rank(), с помощью которой мы можем добавить новый столбец, который содержит ранг текущей строки в каждой группе.

Мы генерируем рейтинг, используя следующий запрос:

SELECT score_data.*, 
  rank() OVER (PARTITION BY job_id ORDER BY score DESC) 
  FROM score_data

В таблице score_data есть столбцы: id, job_id, scheme_id, score.
Давайте посмотрим, что происходит в приведенном выше запросе. OVER указывает, как разделить или окномить строки, внутри которых мы указали правило разделения. PARTITION BY разбивает таблицу на разделы на основе job_id и ORDER BY упорядочивает строки на основе score внутри раздела. Это добавляет новый столбец с именем rank в набор результатов. rank сохраняет ранг строки в своей группе.

Результат вышеуказанного запроса выглядит так:

Теперь нам нужно получить трех лучших кандидатов для каждого job_id, для которого мы запустим дополнительный запрос на выборку для этого выбора, где ранг будет ≤3.

select ranked_scores.* from 
(SELECT score_data.*,
  rank() OVER (PARTITION BY job_id ORDER BY score DESC)
  FROM score_data) ranked_scores 
where rank <=3

Что дает наш требуемый результат.

Если вы заметили что-то странное в любом из приведенных выше результатов, следите за обновлениями, я опишу в своем следующем посте.

Спасибо!