← Назад к вопросам

Что такое оконные функции в SQL? Приведите примеры использования.?

2.3 Middle🔥 141 комментариев
#SQL и базы данных

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Оконные функции в SQL

Оконные функции (Window Functions) — это функции, которые выполняют вычисления над набором строк (окном) данных, связанных с текущей строкой. В отличие от GROUP BY, оконные функции НЕ сворачивают результаты, а возвращают результат для каждой строки.

Основной синтаксис

SELECT
    column1,
    column2,
    aggregate_function() OVER (PARTITION BY column ORDER BY column) as result
FROM table_name;

Компоненты:

  • PARTITION BY — разбить данные на группы (опционально)
  • ORDER BY — порядок обработки строк внутри окна
  • ROWS/RANGE — определить границы окна (опционально)

Категории оконных функций

1. Агрегирующие функции

-- SUM, COUNT, AVG, MIN, MAX с OVER
SELECT 
    employee_id,
    salary,
    SUM(salary) OVER (PARTITION BY department) as dept_total,
    AVG(salary) OVER () as company_avg,
    COUNT(*) OVER (PARTITION BY department) as dept_employee_count
FROM employees;

-- Результат:
-- employee_id=1, salary=50000, dept_total=150000, company_avg=55000, dept_employee_count=3
-- employee_id=2, salary=60000, dept_total=150000, company_avg=55000, dept_employee_count=3
-- Каждой строке добавлены значения агрегаций!

2. Функции ранжирования

-- ROW_NUMBER() — порядковый номер (уникален)
-- RANK() — ранг с пропусками при равных значениях
-- DENSE_RANK() — ранг без пропусков

SELECT 
    employee_id,
    salary,
    ROW_NUMBER() OVER (ORDER BY salary DESC) as row_num,
    RANK() OVER (ORDER BY salary DESC) as rank,
    DENSE_RANK() OVER (ORDER BY salary DESC) as dense_rank
FROM employees;

-- Результат:
-- employee_id=1, salary=100000, row_num=1, rank=1, dense_rank=1
-- employee_id=2, salary=100000, row_num=2, rank=1, dense_rank=1
-- employee_id=3, salary=90000,  row_num=3, rank=3, dense_rank=2

3. Функции смещения (offset)

-- LAG() — получить значение из предыдущей строки
-- LEAD() — получить значение из следующей строки

SELECT 
    date,
    sales,
    LAG(sales) OVER (ORDER BY date) as previous_day_sales,
    LEAD(sales) OVER (ORDER BY date) as next_day_sales,
    sales - LAG(sales) OVER (ORDER BY date) as day_change
FROM daily_sales
ORDER BY date;

-- Результат:
-- date=2024-01-01, sales=1000, previous_day_sales=NULL, next_day_sales=1100, day_change=NULL
-- date=2024-01-02, sales=1100, previous_day_sales=1000, next_day_sales=1050, day_change=100
-- date=2024-01-03, sales=1050, previous_day_sales=1100, next_day_sales=NULL, day_change=-50

4. Функции распределения

-- PERCENT_RANK() — относительное ранг (0 до 1)
-- NTILE() — разделить на N групп (квартили, децили)

SELECT 
    employee_id,
    salary,
    PERCENT_RANK() OVER (ORDER BY salary) as percentile,
    NTILE(4) OVER (ORDER BY salary) as quartile
FROM employees;

-- Результат:
-- Quartile=1: нижние 25% по зарплате
-- Quartile=2: 25-50%
-- Quartile=3: 50-75%
-- Quartile=4: верхние 25%

Определение окна данных

-- ROWS — физическое количество строк
SELECT 
    date,
    sales,
    SUM(sales) OVER (
        ORDER BY date
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) as moving_avg_3_days
FROM sales;

-- Результат: сумма текущего и 2 предыдущих дней
-- date=2024-01-01, sales=100, moving_sum=100 (нет предыдущих)
-- date=2024-01-02, sales=110, moving_sum=210 (100+110)
-- date=2024-01-03, sales=120, moving_sum=330 (100+110+120)
-- date=2024-01-04, sales=130, moving_sum=360 (110+120+130)

-- RANGE — по значениям
SELECT 
    date,
    sales,
    SUM(sales) OVER (
        ORDER BY date
        RANGE BETWEEN INTERVAL 7 DAY PRECEDING AND CURRENT ROW
    ) as weekly_sales
FROM sales;

-- Сумма за последние 7 дней

Практические примеры в Data Engineering

1. Расчет кумулятивной суммы (Running Total)

-- Кумулятивный доход по дням
SELECT 
    date,
    daily_revenue,
    SUM(daily_revenue) OVER (ORDER BY date) as cumulative_revenue
FROM daily_revenue
ORDER BY date;

2. Поиск дублей

-- Найти дублирующиеся записи
SELECT 
    customer_id,
    email,
    ROW_NUMBER() OVER (PARTITION BY email ORDER BY customer_id) as dup_number
FROM customers
HAVING dup_number > 1;

3. Процентиль и квартили

-- Разделить пользователей по уровню трат
SELECT 
    user_id,
    total_spent,
    NTILE(4) OVER (ORDER BY total_spent) as spending_quartile
FROM user_spending;

-- Quartile 1 = малотратящие
-- Quartile 4 = высокотратящие

4. Скользящее среднее

-- Скользящее среднее за 7 дней
SELECT 
    date,
    sales,
    AVG(sales) OVER (
        ORDER BY date
        ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
    ) as moving_avg_7_days
FROM daily_sales
ORDER BY date;

5. Сравнение с предыдущим периодом

-- Year-over-Year рост
SELECT 
    year,
    month,
    revenue,
    LAG(revenue) OVER (PARTITION BY month ORDER BY year) as revenue_prev_year,
    ROUND((revenue - LAG(revenue) OVER (PARTITION BY month ORDER BY year)) / 
          LAG(revenue) OVER (PARTITION BY month ORDER BY year) * 100, 2) as yoy_growth_pct
FROM monthly_revenue
ORDER BY year, month;

6. Ранжирование внутри групп

-- Топ 3 продукта по продажам в каждой категории
SELECT 
    category,
    product_name,
    sales,
    RANK() OVER (PARTITION BY category ORDER BY sales DESC) as rank_in_category
FROM products
WHERE RANK() OVER (PARTITION BY category ORDER BY sales DESC) <= 3;

7. Поиск первого и последнего значения

-- Первая и последняя цена продукта в месяц
SELECT 
    product_id,
    date,
    price,
    FIRST_VALUE(price) OVER (
        PARTITION BY product_id, EXTRACT(MONTH FROM date)
        ORDER BY date
    ) as month_start_price,
    LAST_VALUE(price) OVER (
        PARTITION BY product_id, EXTRACT(MONTH FROM date)
        ORDER BY date
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) as month_end_price
FROM price_history;

Сравнение: GROUP BY vs Window Function

-- GROUP BY: сворачивает результаты
SELECT category, COUNT(*) as total_products
FROM products
GROUP BY category;
-- Результат: 3 строки (одна на категорию)

-- Window Function: сохраняет исходные строки
SELECT 
    product_id,
    category,
    COUNT(*) OVER (PARTITION BY category) as category_count
FROM products;
-- Результат: все исходные строки + добавлен count для каждой

Производительность оконных функций

-- ПЛОХО: коррелированный подзапрос (медленно)
SELECT 
    employee_id,
    salary,
    (SELECT AVG(salary) FROM employees e2 WHERE e2.department = e1.department) as dept_avg
FROM employees e1;

-- ХОРОШО: оконная функция (быстро)
SELECT 
    employee_id,
    salary,
    AVG(salary) OVER (PARTITION BY department) as dept_avg
FROM employees;

Ключевые выводы

  • Оконные функции мощнее GROUP BY — сохраняют детальность данных
  • Используй PARTITION BY для разбиения на логические группы
  • ORDER BY критичен для функций LAG/LEAD и ранжирования
  • ROWS/RANGE контролируют границы окна (важно для скользящих агрегаций)
  • Производительность обычно лучше, чем коррелированные подзапросы

Оконные функции — это незаменимый инструмент для временных рядов, анализа когорт и построения сложных метрик в Data Engineering.