JavaRush /Курсы /SQL SELF /Типичные ошибки при использовании оконных функций

Типичные ошибки при использовании оконных функций

SQL SELF
30 уровень , 4 лекция
Открыта

Теперь пришло время поговорить о сложностях, которые могут возникнуть при работе с оконными функциями. Как обычно, в программировании (и в жизни) лучше учиться на чужих ошибках. Будем разбирать типичные ошибки, которыми грешат новички и иногда даже опытные разработчики, и учиться их избегать.

Ошибка №1: Неправильное использование PARTITION BY

Одна из самых частых ошибок — это забыть или неверно задать параметр PARTITION BY, особенно если вы хотите разделить данные на группы. Без него PostgreSQL будет рассматривать все строки как одну большую группу, из-за чего результаты могут быть совсем не такими, как вы ожидаете.

Допустим, у нас есть таблица sales, которая содержит данные о продажах:

id region month total
1 North 2023-01 1000
2 South 2023-01 800
3 North 2023-02 1200
4 South 2023-02 900

Вы хотите найти накопительную сумму (SUM()) продаж по месяцам для каждого региона. Можно написать такой запрос:

SELECT
    region,
    month,
    SUM(total) OVER (ORDER BY month) AS running_total
FROM 
    sales;

Результат:

region month running_total
North 2023-01 1000
South 2023-01 1800
North 2023-02 3000
South 2023-02 3900

На первый взгляд кажется, что всё хорошо. Но результат явно не соответствует ожиданиям, так как накопительная сумма считается не по регионам, а для всех строк сразу. Проблема в том, что мы забыли добавить PARTITION BY region.

Исправленный код:

SELECT 
    region,
    month,
    SUM(total) OVER (PARTITION BY region ORDER BY month) AS running_total
FROM 
    sales;

Результат:

region month running_total
North 2023-01 1000
North 2023-02 2200
South 2023-01 800
South 2023-02 1700

Теперь всё работает правильно: данные группируются по регионам, а накопительная сумма рассчитывается отдельно для каждого региона.

Ошибка №2: Некорректный порядок в ORDER BY

ORDER BY внутри OVER() контролирует порядок строк в рамках окна. Если порядок задан неверно, результаты будут неожиданными.

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

SELECT
    month,
    total,
    SUM(total) OVER (ORDER BY month DESC) AS running_total
FROM 
    sales;

Результат:

month total running_total
2023-02 1200 1200
2023-02 900 2100
2023-01 1000 3100
2023-01 800 3900

На первый взгляд всё правильно, но обратите внимание: строки сгруппированы по месяцам, а не суммируются корректно из-за убывающего порядка строк. Вот почему результаты путаные.

Исправление: перепишите запрос, чтобы использовать правильный порядок ORDER BY:

SELECT
    month,
    total,
    SUM(total) OVER (ORDER BY month ASC) AS running_total
FROM 
    sales;

Ошибка №3: Использование оконных функций без индексов

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

Пример: у нас есть таблица large_sales с миллионами строк, и мы хотим вычислить ранги продаж:

SELECT
    id,
    total,
    RANK() OVER (ORDER BY total DESC) AS rank
FROM 
    large_sales;

На небольших данных запрос может выполняться быстро, но на больших объёмах это может занять вечность.

Исправление: добавьте индекс на столбец, используемый в ORDER BY:

CREATE INDEX idx_total ON large_sales(total DESC);

Теперь запрос будет выполняться значительно быстрее.

Ошибка №4: Непонимание окна, заданного ROWS или RANGE

При использовании ROWS и RANGE важно понимать, как они вычисляют окно строк. Неправильное понимание этих ключевых слов может привести к неожиданным результатам.

Пример: вы хотите вычислить скользящее среднее продаж за текущий месяц и два предыдущих:

SELECT
    month,
    AVG(total) OVER (ORDER BY month ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM 
    sales;

Если вместо ROWS вы укажете RANGE:

SELECT
    month,
    AVG(total) OVER (ORDER BY month RANGE BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM 
    sales;

Результат будет отличаться, так как RANGE работает с диапазонами значений, а не с конкретным количеством строк.

Ошибка №5: Избыточное использование оконных функций

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

Пример:

SELECT 
    id,
    total,
    SUM(total) OVER (PARTITION BY region) AS region_total,
    SUM(total) OVER (PARTITION BY region) / COUNT(total) OVER (PARTITION BY region) AS region_avg
FROM 
    sales;

Здесь SUM(total) и COUNT(total) вычисляются несколько раз для каждой строки.

Исправление: сократите запрос с использованием подзапросов или CTE:

WITH cte_region_totals AS (
    SELECT 
        region,
        SUM(total) AS region_total,
        COUNT(total) AS region_count
    FROM 
        sales
    GROUP BY 
        region
)
SELECT 
    s.id,
    s.total,
    t.region_total,
    t.region_total / t.region_count AS region_avg
FROM 
    sales s
JOIN 
    cte_region_totals t ON s.region = t.region;

Советы по избеганию ошибок

Проверяйте PARTITION BY и ORDER BY: всегда уточняйте, правильно ли задано окно.

Индексируйте данные: особенно если используете сортировку (ORDER BY) или фильтрацию.

Используйте CTE для многократных вычислений: это поможет сократить дублирующиеся шаги.

Смотрите на план выполнения: используйте EXPLAIN и EXPLAIN ANALYZE, чтобы понять, как запрос обрабатывается PostgreSQL.

Тестируйте на реальных данных: убедитесь, что результаты соответствуют вашим ожиданиям и задачи решаются корректно.

2
Задача
SQL SELF, 30 уровень, 4 лекция
Недоступна
Использование ORDER BY в оконных функциях
Использование ORDER BY в оконных функциях
1
Опрос
Настройка оконного фрейма, 30 уровень, 4 лекция
Недоступен
Настройка оконного фрейма
Настройка оконного фрейма
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ