PARTITION: примеры (SQL)

Как работает PARTITION BY в оконных функциях MS SQL
Раздел: Функции работы с секционированием
PARTITION(N/A Syntax element): N/A

Функция PARTITION в MS SQL Server является частью синтаксиса предложения OVER() для оконных функций. Она не существует как отдельная функция, а используется для разделения набора строк на логические части (секции), внутри которых оконная функция выполняет вычисления независимо.

Предложение PARTITION BY в оконных функциях

Предложение PARTITION BY определяет столбцы, по которым происходит разделение результирующего набора на секции. Оно используется внутри предложения OVER() для таких функций как ROW_NUMBER(), RANK(), SUM(), AVG() и многих других.

Синтаксис в составе оконной функции:

window_function ( [ arguments ] ) OVER (
    [ PARTITION BY partition_expression, ... ]
    [ ORDER BY order_expression [ ASC | DESC ], ... ]
    [ ROWS | RANGE frame_clause ]
)

Аргументы PARTITION BY:

  • partition_expression: Выражение или список выражений (столбцов), по которым происходит группировка строк в секции. Если PARTITION BY не указано, вся результирующая выборка рассматривается как одна секция.

Возвращаемое значение: Само по себе PARTITION BY не возвращает значение. Оно изменяет логику работы оконной функции, заставляя ее вычисляться в пределах каждой секции, созданной на основе указанных столбцов. Результатом является новое значение для каждой строки, рассчитанное в контексте ее секции.

Когда применяется: При необходимости выполнить агрегатные или ранжирующие вычисления внутри логических групп строк без сворачивания результата в одну строку на группу (как делает GROUP BY). Типичные сценарии: расчет нарастающего итога внутри отдела, нумерация заказов для каждого клиента, определение доли продаж товара в его категории.

Базовые примеры использования

Пример 1: Нумерация строк внутри секции

-- Создание тестовой таблицы
CREATE TABLE #Sales (
    SaleID INT,
    ProductCategory VARCHAR(20),
    SaleAmount DECIMAL(10,2)
);

INSERT INTO #Sales VALUES
(1, 'Electronics', 1000),
(2, 'Electronics', 1500),
(3, 'Books', 300),
(4, 'Books', 200),
(5, 'Electronics', 800);

-- Нумерация продаж внутри каждой категории по возрастанию SaleID
SELECT
    SaleID,
    ProductCategory,
    SaleAmount,
    ROW_NUMBER() OVER (PARTITION BY ProductCategory ORDER BY SaleID) AS RowNumInCategory
FROM #Sales;
SaleID   ProductCategory SaleAmount RowNumInCategory
3        Books           300.00     1
4        Books           200.00     2
1        Electronics     1000.00    1
2        Electronics     1500.00    2
5        Electronics     800.00     3

Пример 2: Агрегация внутри секции

-- Расчет доли каждой продажи в общей сумме по ее категории
SELECT
    SaleID,
    ProductCategory,
    SaleAmount,
    SUM(SaleAmount) OVER (PARTITION BY ProductCategory) AS TotalInCategory,
    SaleAmount * 100.0 / SUM(SaleAmount) OVER (PARTITION BY ProductCategory) AS PercentOfCategory
FROM #Sales
ORDER BY ProductCategory, SaleID;
SaleID ProductCategory SaleAmount TotalInCategory PercentOfCategory
3      Books           300.00     500.00          60.000000000000
4      Books           200.00     500.00          40.000000000000
1      Electronics     1000.00    3300.00         30.303030303030
2      Electronics     1500.00    3300.00         45.454545454545
5      Electronics     800.00     3300.00         24.242424242424

Пример 3: Сравнение со средним по секции

-- Определение строк, где сумма продажи выше средней в своей категории
SELECT
    SaleID,
    ProductCategory,
    SaleAmount,
    AVG(SaleAmount) OVER (PARTITION BY ProductCategory) AS AvgInCategory
FROM #Sales
WHERE SaleAmount > AVG(SaleAmount) OVER (PARTITION BY ProductCategory);
SaleID ProductCategory SaleAmount AvgInCategory
2      Electronics     1500.00    1100.00
3      Books           300.00     250.00

Похожие средства MS SQL Server

GROUP BY: Основная альтернатива для агрегации данных. В отличие от PARTITION BY, GROUP BY сворачивает строки, возвращая один результат на группу. PARTITION BY сохраняет детализацию исходных строк. GROUP BY предпочтительнее для итоговых отчетов, PARTITION BY — для аналитических расчетов без потери деталей.

APPLY операторы (CROSS/OUTER): Позволяют вызывать табличную функцию для каждой строки основного запроса. Могут быть альтернативой в сложных случаях, где логику окна нельзя выразить стандартной оконной функцией, но требуют больше ресурсов.

Коррелированные подзапросы: Могут эмулировать некоторые оконные вычисления (например, расчет суммы по группе), но часто менее производительны и сложнее читаются по сравнению с PARTITION BY.

Распространенные ошибки

Ошибка 1: Использование без оконной функции

PARTITION BY не является самостоятельной конструкцией.

-- Неправильно
SELECT SaleID, PARTITION BY ProductCategory FROM Sales;

-- Правильно
SELECT SaleID, ROW_NUMBER() OVER (PARTITION BY ProductCategory ORDER BY SaleID) FROM Sales;

Ошибка 2: Путаница с GROUP BY в одном запросе

При совместном использовании GROUP BY и оконных функций, последние вычисляются после группировки. Ошибка — попытка добавить неагрегированный столбец в SELECT без его включения в GROUP BY.

-- Ошибка: ProductName не входит в GROUP BY и не является аргументом агрегата.
SELECT
    CategoryID,
    ProductName,
    SUM(Quantity) OVER (PARTITION BY CategoryID) AS Total
FROM OrderDetails
GROUP BY CategoryID;

-- Возможное решение: убрать GROUP BY или добавить ProductName в GROUP BY.
SELECT
    CategoryID,
    ProductName,
    SUM(Quantity) OVER (PARTITION BY CategoryID) AS Total
FROM OrderDetails;

Ошибка 3: Некорректный порядок вычислений

Оконные функции выполняются после операций WHERE, GROUP BY, HAVING, но до ORDER BY. Попытка фильтрации по результату оконной функции в WHERE вызывает ошибку.

-- Ошибка: нельзя использовать псевдоним оконной функции в WHERE.
SELECT
    SaleID,
    ROW_NUMBER() OVER (ORDER BY SaleID) AS rn
FROM Sales
WHERE rn = 1;

-- Решение: использовать CTE или подзапрос.
WITH NumberedSales AS (
    SELECT SaleID, ROW_NUMBER() OVER (ORDER BY SaleID) AS rn
    FROM Sales
)
SELECT SaleID FROM NumberedSales WHERE rn = 1;

История изменений

Основная логика работы PARTITION BY в составе оконных функций остается неизменной с момента их появления в SQL Server 2005. Ключевые дополнения в последних версиях касаются расширения набора самих оконных функций и улучшения оптимизатора.

  • SQL Server 2012: Добавлены функции смещения: LAG(), LEAD(), FIRST_VALUE(), LAST_VALUE(), которые активно используют PARTITION BY.
  • SQL Server 2022: Введена функция GREATEST() и LEAST(), не являющиеся оконными, но оптимизатор запросов продолжает совершенствоваться для вычислений с PARTITION BY.

Изменений в синтаксисе или аргументах самого предложения PARTITION BY не было.

Расширенные примеры применения

Пример 1: Расчет скользящего среднего

Использование PARTITION BY вместе с фреймом ROWS.

Пример sql
-- Скользящее среднее по 3 последним продажам для каждого товара
SELECT
    ProductID,
    SaleDate,
    Amount,
    AVG(Amount) OVER (
        PARTITION BY ProductID
        ORDER BY SaleDate
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS MovingAvg3
FROM Sales
ORDER BY ProductID, SaleDate;

Пример 2: Поиск дубликатов сложных данных

Пример sql
-- Поиск полностью дублирующихся строк по нескольким полям
WITH CTE_Duplicates AS (
    SELECT
        *,
        ROW_NUMBER() OVER (
            PARTITION BY CustomerID, OrderDate, TotalAmount
            ORDER BY (SELECT NULL)
        ) AS DupNum
    FROM Orders
)
SELECT * FROM CTE_Duplicates WHERE DupNum > 1;

Пример 3: Иерархические проценты внутри групп

Пример sql
-- Расчет вклада каждой подкатегории в сумму по основной категории и общей сумме
SELECT
    MainCategory,
    SubCategory,
    Revenue,
    SUM(Revenue) OVER (PARTITION BY MainCategory) AS CatTotal,
    Revenue * 100.0 / SUM(Revenue) OVER (PARTITION BY MainCategory) AS PercentOfCategory,
    Revenue * 100.0 / SUM(Revenue) OVER () AS PercentOfGrandTotal
FROM CategoryRevenue
ORDER BY MainCategory, Revenue DESC;

Пример 4: Одновременное использование нескольких секций

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

Пример sql
-- Сравнение продаж сотрудника с результатами его отдела и общими результатами
SELECT
    EmployeeID,
    DepartmentID,
    Year,
    Sales,
    AVG(Sales) OVER (PARTITION BY DepartmentID, Year) AS AvgDeptYear,
    AVG(Sales) OVER (PARTITION BY Year) AS AvgCompanyYear,
    Sales - AVG(Sales) OVER (PARTITION BY DepartmentID, Year) AS DiffFromDept
FROM EmployeeSales;

Пример 5: Использование с агрегатными функциями без ORDER BY

Для функций SUM, AVG и других агрегатов в качестве оконных, ORDER BY не является обязательным в PARTITION BY. Это позволяет получить одно и то же агрегированное значение для всех строк секции.

Пример sql
-- Добавление общего итога по отделу к каждой строке без нарастающего итога
SELECT
    EmpID,
    Department,
    Salary,
    SUM(Salary) OVER (PARTITION BY Department) AS DeptSalaryFund
FROM Employees;

Аналоги в других СУБД

Oracle

Синтаксис оконных функций с PARTITION BY практически идентичен SQL Server и появился раньше.

SELECT
    employee_id,
    department_id,
    salary,
    AVG(salary) OVER (PARTITION BY department_id) AS avg_dept_sal
FROM employees;

PostgreSQL

Полная поддержка стандартного синтаксиса оконных функций, включая PARTITION BY. Поведение аналогично.

SELECT
    product_id,
    category,
    price,
    RANK() OVER (PARTITION BY category ORDER BY price DESC) AS rank_in_cat
FROM products;

MySQL (с версии 8.0+)

Поддерживает оконные функции с предложением PARTITION BY в полном объеме, синтаксис соответствует стандарту.

SELECT
    order_id,
    customer_id,
    amount,
    SUM(amount) OVER (PARTITION BY customer_id ORDER BY order_date) AS running_total
FROM orders;

SQLite (с версии 3.28.0+)

Добавлена базовая поддержка оконных функций, включая PARTITION BY.

SELECT
    name,
    year,
    revenue,
    LAG(revenue) OVER (PARTITION BY name ORDER BY year) AS prev_revenue
FROM sales;

Отличия: В более старых версиях MySQL, SQLite или в Sybase ASE оконные функции могли отсутствовать. В таких случаях используют подзапросы или временные таблицы для эмуляции логики.

MS SQL PARTITION function comments

En
PARTITION Used in the CREATE PARTITION FUNCTION and ALTER PARTITION FUNCTION to specify boundary values