Загрузка данных
SET search_path = study;
-- =====================================================================
-- 3.1. Работа с агрегатными функциями
-- Для каждого месяца 2024 года выведите количество проектов
-- =====================================================================
-- 3.1.1. Получим список месяцев
SET search_path = study;
WITH months AS (
SELECT 'январь' AS month_nm, 1 AS month_nus
UNION ALL SELECT 'февраль', 2
UNION ALL SELECT 'март', 3
UNION ALL SELECT 'апрель', 4
UNION ALL SELECT 'май', 5
UNION ALL SELECT 'июнь', 6
UNION ALL SELECT 'июль', 7
UNION ALL SELECT 'август', 8
UNION ALL SELECT 'сентябрь', 9
UNION ALL SELECT 'октябрь', 10
UNION ALL SELECT 'ноябрь', 11
UNION ALL SELECT 'декабрь', 12
)
SELECT months.month_nus,
months.month_nm,
COUNT(projects.id) AS project_cnt
FROM months
LEFT JOIN projects
ON months.month_nus = EXTRACT(MONTH FROM projects.start_date)
AND EXTRACT(YEAR FROM projects.start_date) = 2026
GROUP BY months.month_nus, months.month_nm
ORDER BY months.month_nus;
-- =====================================================================
-- 3.2. Задания для выполнения
-- =====================================================================
-- ---------------------------------------------------------------------
-- 3.2.1. Порядковый номер проекта для каждого дня:
-- ROW_NUMBER() с окном по месяцу проекта и сортировкой по дню
-- ---------------------------------------------------------------------
SET search_path = study;
SELECT id,
project_name,
start_date,
ROW_NUMBER() OVER (PARTITION BY date_trunc('month', start_date)
ORDER BY EXTRACT(DAY FROM start_date)) AS rn
FROM projects
ORDER BY start_date, rn;
-- ---------------------------------------------------------------------
-- 3.2.2. Тот же запрос с RANK(), DENSE_RANK(), NTILE()
-- ---------------------------------------------------------------------
SELECT id,
project_name,
start_date,
ROW_NUMBER() OVER w AS rn,
RANK() OVER w AS rnk,
DENSE_RANK() OVER w AS dense_rnk,
NTILE(3) OVER w AS ntile_3
FROM projects
WINDOW w AS (PARTITION BY date_trunc('month', start_date)
ORDER BY EXTRACT(DAY FROM start_date))
ORDER BY start_date, rn;
/* Разница между функциями (видна на проектах, начатых в один день):
ROW_NUMBER() — всегда уникальный номер 1,2,3,4… ; при равных днях
порядок между «одинаковыми» строками произвольный.
RANK() — равные значения получают одинаковый ранг, после
группы одинаковых остаётся «дыра»: 1,2,2,4…
DENSE_RANK() — равные значения получают одинаковый ранг, но без
пропусков: 1,2,2,3…
NTILE(n) — делит строки окна на n групп примерно равного размера
и возвращает номер группы (1..n); от равенства значений
не зависит, нужен для разбиения на квантили/корзины. */
-- ---------------------------------------------------------------------
-- 3.2.3. Аналог запроса 3.1 с применением оконной функции
-- ---------------------------------------------------------------------
WITH months(month_num, month_nm) AS (
VALUES (1, 'Январь'), (2, 'Февраль'), (3, 'Март'), (4, 'Апрель'),
(5, 'Май'), (6, 'Июнь'), (7, 'Июль'), (8, 'Август'),
(9, 'Сентябрь'), (10, 'Октябрь'), (11, 'Ноябрь'), (12, 'Декабрь')
)
SELECT DISTINCT
m.month_num,
m.month_nm,
COUNT(p.id) OVER (PARTITION BY m.month_num) AS project_cnt
FROM months m
LEFT JOIN projects p
ON EXTRACT(MONTH FROM p.start_date) = m.month_num
AND EXTRACT(YEAR FROM p.start_date) = 2026
ORDER BY m.month_num;
-- Оконная функция не схлопывает строки, поэтому дубликаты убираем DISTINCT.
-- ---------------------------------------------------------------------
-- 3.2.4. Номер каждого студента в группе (сортировка по фамилии и имени)
-- ---------------------------------------------------------------------
SELECT s.group_number,
ROW_NUMBER() OVER (PARTITION BY s.group_number
ORDER BY s.last_name, s.first_name) AS num_in_group,
s.last_name,
s.first_name
FROM students s
ORDER BY s.group_number, num_in_group;
-- ---------------------------------------------------------------------
-- 3.2.5. В каждом месяце — начало работы над проектами указанной команды
-- (команда задаётся в WHERE, здесь idkom = 1)
-- ---------------------------------------------------------------------
SELECT DISTINCT
c.team,
date_trunc('month', p.start_date)::date AS month_start,
FIRST_VALUE(p.project_name) OVER w AS first_project,
FIRST_VALUE(p.start_date) OVER w AS work_start,
COUNT(*) OVER (PARTITION BY date_trunc('month', p.start_date)) AS projects_in_month
FROM projects p
JOIN team c ON c.idkom = p.idkom
WHERE p.idkom = 1
WINDOW w AS (PARTITION BY date_trunc('month', p.start_date) ORDER BY p.start_date)
ORDER BY month_start;
-- ---------------------------------------------------------------------
-- 3.2.6. Разница между стоимостью проекта и средней стоимостью всех проектов
-- ---------------------------------------------------------------------
SELECT id,
project_name,
price::numeric AS price,
ROUND(AVG(price::numeric) OVER (), 2) AS avg_price,
ROUND(price::numeric - AVG(price::numeric) OVER (), 2) AS diff_from_avg
FROM projects
ORDER BY diff_from_avg DESC;
-- ---------------------------------------------------------------------
-- 3.2.7. Поименованное окно: ранг проекта по оценке и накопительная
-- сумма баллов в рамках трека (трек = тип проекта, project_type)
-- ---------------------------------------------------------------------
SELECT project_type AS track,
project_name,
total_score,
RANK() OVER w AS rank_in_track,
SUM(total_score) OVER (w ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW) AS cum_score_in_track
FROM v_project_score
WINDOW w AS (PARTITION BY project_type ORDER BY total_score DESC)
ORDER BY track, rank_in_track;
-- ---------------------------------------------------------------------
-- 3.2.8. Нарастающая сумма баллов проектов, отсортированных по баллам:
-- фрейм ROWS и фрейм RANGE
-- ---------------------------------------------------------------------
-- Запрос 1: ROWS
SELECT project_name,
total_score,
SUM(total_score) OVER (ORDER BY total_score
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cum_rows
FROM v_project_score
ORDER BY total_score;
-- Запрос 2: RANGE
SELECT project_name,
total_score,
SUM(total_score) OVER (ORDER BY total_score
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cum_range
FROM v_project_score
ORDER BY total_score;
-- Для наглядного сравнения — оба столбца в одном запросе
SELECT project_name,
total_score,
SUM(total_score) OVER (ORDER BY total_score
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cum_rows,
SUM(total_score) OVER (ORDER BY total_score
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cum_range
FROM v_project_score
ORDER BY total_score;
/* Объяснение.
ROWS — «физический» фрейм: от первой строки до ТЕКУЩЕЙ СТРОКИ.
Сумма растёт на каждой строке, даже если баллы одинаковые.
RANGE — «логический» фрейм: от начала до ТЕКУЩЕГО ЗНАЧЕНИЯ ключа
сортировки включительно. Строки с одинаковыми баллами являются
«равноправными» (peers) и все сразу попадают во фрейм, поэтому
для них сумма одинакова — значение во втором столбце дублируется
и равно сумме по последней строке группы одинаковых баллов.
Пример: баллы 10, 15, 15, 20
ROWS : 10, 25, 40, 60
RANGE: 10, 40, 40, 60
Без явного указания фрейма при наличии ORDER BY по умолчанию
используется RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. */
-- ---------------------------------------------------------------------
-- 3.2.9. Накопительная сумма стоимости для каждого статуса проектов
-- ---------------------------------------------------------------------
SELECT status,
project_name,
start_date,
price::numeric AS price,
SUM(price::numeric) OVER (PARTITION BY status
ORDER BY start_date, id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cum_price
FROM projects
ORDER BY status, start_date, id;
-- ---------------------------------------------------------------------
-- 3.2.10. Скользящее среднее оценок: текущий и два предыдущих проекта
-- (по дате начала), фрейм ROWS
-- ---------------------------------------------------------------------
SELECT project_name,
start_date,
avg_score,
ROUND(AVG(avg_score) OVER (ORDER BY start_date, id
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW), 2) AS moving_avg_3
FROM v_project_score
ORDER BY start_date, id;
-- ---------------------------------------------------------------------
-- 3.2.12. RANGE с интервалом дат: сумма стоимости и средняя оценка
-- проектов за последние 3 месяца относительно даты начала проекта
-- ---------------------------------------------------------------------
SELECT project_name,
start_date,
price,
avg_score,
SUM(price) OVER w3 AS sum_price_3m,
ROUND(AVG(avg_score) OVER w3, 2) AS avg_score_3m,
COUNT(*) OVER w3 AS projects_3m
FROM v_project_score
WINDOW w3 AS (ORDER BY start_date
RANGE BETWEEN INTERVAL '3 months' PRECEDING AND CURRENT ROW)
ORDER BY start_date;