Сводные запросы и эффективная фильтрация данных в SQL: Полное руководство для студентов

В эпоху экспоненциального роста данных, когда каждая транзакция, каждое взаимодействие генерирует потоки информации, способность не просто собирать, но и эффективно анализировать эти данные становится краеугольным камнем успеха в любой отрасли. SQL — язык структурированных запросов — является тем мощным инструментом, который позволяет не только извлекать информацию из баз данных, но и трансформировать её, делая пригодной для принятия стратегических решений. Однако для того, чтобы перейти от простого извлечения к глубокому анализу, необходимо овладеть более сложными конструкциями, такими как сводные запросы и изощренные методы фильтрации. Данная работа призвана не просто изложить теоретические основы, но и вооружить студента практическим инструментарием для создания гибких, производительных и корректных SQL-запросов. Мы погрузимся в мир сводных таблиц, изучим тонкости агрегации данных, досконально разберем различия между операторами WHERE и HAVING, а также коснемся вопросов оптимизации, которые критически важны в работе с большими объемами информации. Цель — обеспечить глубокое понимание логики работы SQL, что станет прочным фундаментом для дальнейшего освоения баз данных и их применения в реальных проектах.

Сущность и назначение сводных запросов (Pivot Query) в SQL

Что такое Pivot Query и зачем он нужен?

В мире данных, где информация часто хранится в «длинном» или «транзакционном» формате (например, каждая продажа — отдельная строка), возникает насущная потребность в её реструктуризации для аналитических целей. Именно здесь на сцену выходит сводный запрос (Pivot Query) — мощная техника SQL, предназначенная для преобразования строк данных в столбцы. Представьте, что у вас есть таблица с информацией о продажах, где каждая запись содержит Регион, Месяц и СуммаПродаж. Для аналитика гораздо удобнее видеть картину, где строки представляют регионы, а столбцы — месяцы, с соответствующими суммами продаж на пересечении. Сводный запрос позволяет мгновенно превратить эту «длинную» таблицу в компактную и легко читаемую «широкую» таблицу, что значительно облегчает дальнейший анализ.

Основная цель сводных запросов — это агрегация и суммирование данных, представляя их в более интуитивном, структурированном табличном формате. Это значительно улучшает интерпретацию результатов запроса, особенно в контексте бизнес-аналитики и отчетности. Например, вместо того чтобы вручную суммировать продажи по каждому региону и месяцу, что может занимать до 8 часов, сводный запрос позволяет получить те же данные за 15 минут автоматизированного анализа. Это не просто экономия времени, это возможность мгновенно выявлять скрытые закономерности, сравнивать показатели и принимать оперативные управленческие решения, анализируя данные по нескольким измерениям одновременно.

Рассмотрим пример с продажами. Исходная таблица SalesData может выглядеть так:

Region Month SalesAmount
East Jan 100
East Feb 120
West Jan 150
West Feb 130

После применения сводного запроса, который «повернет» месяцы в столбцы, мы получим:

Region Jan Feb
East 100 120
West 150 130

Такой формат гораздо удобнее для сравнения производительности регионов по месяцам или для отслеживания динамики продаж.

Статические и динамические сводные таблицы: особенности применения

В зависимости от предсказуемости структуры данных и требований к гибкости отчета, сводные запросы могут быть реализованы статически или динамически.

Статические сводные таблицы имеют заранее предопределенные столбцы. Это означает, что вы точно знаете, какие значения из исходного столбца (который будет «повернут») станут заголовками новых столбцов. Они полезны, когда структура данных стабильна и не меняется со временем. Например, если вы всегда анализируете продажи по фиксированному набору месяцев (январь, февраль, март) или по известным категориям товаров, статический подход будет наиболее простым и эффективным.

Пример статической сводной таблицы:

SELECT Region, [Jan] AS JanuarySales, [Feb] AS FebruarySales
FROM (
    SELECT Region, Month, SalesAmount
    FROM SalesData
) AS SourceQuery
PIVOT (
    SUM(SalesAmount)
    FOR Month IN ([Jan], [Feb])
) AS PivotTable;

Динамические сводные таблицы, напротив, генерируют столбцы на основе самих данных. Это становится критически важным, когда количество или имена столбцов заранее неизвестны или часто меняются. Представьте отчет по продажам, где заголовки столбцов — это годы. Если данные постоянно обновляются, и каждый год добавляется новый, статическая таблица потребует постоянного изменения кода запроса. Динамическая сводная таблица решает эту проблему, предлагая адаптивное решение для развивающихся наборов данных.

Для создания динамических сводных таблиц обычно используется динамический SQL. Этот подход включает в себя несколько этапов:

  1. Извлечение уникальных значений, которые станут заголовками столбцов (например, список всех уникальных месяцев или годов из таблицы).
  2. Формирование строки SQL-запроса с использованием этих значений.
  3. Выполнение сформированного запроса.

Например, в SQL Server для генерации списка уникальных значений можно использовать функцию STUFF в сочетании с FOR XML PATH(''). Допустим, у нас есть таблица ProductSales со столбцами ProductCategory, SaleYear, Revenue. Мы хотим получить сводную таблицу, где строки — это категории продуктов, а столбцы — годы, с суммой выручки.

DECLARE @ColumnList NVARCHAR(MAX);
DECLARE @SQLQuery NVARCHAR(MAX);

-- Шаг 1: Извлекаем уникальные годы и формируем список столбцов
SELECT @ColumnList = STUFF((
    SELECT DISTINCT ',' + QUOTENAME(SaleYear)
    FROM ProductSales
    ORDER BY QUOTENAME(SaleYear)
    FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '');

-- Шаг 2: Формируем динамический SQL-запрос
SET @SQLQuery =
    N'SELECT ProductCategory, ' + @ColumnList +
    N' FROM ProductSales P
      PIVOT(
          SUM(Revenue)
          FOR SaleYear IN (' + @ColumnList + N')
      ) AS PivotTable;';

-- Шаг 3: Выполняем динамический запрос
EXECUTE sp_executesql @SQLQuery;

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

Основные SQL-операторы и агрегатные функции для создания сводных запросов

Для построения эффективных сводных запросов необходимо глубоко понимать работу ключевых SQL-операторов и агрегатных функций. Эти элементы являются строительными блоками, позволяющими трансформировать сырые данные в осмысленную аналитическую информацию.

Агрегатные функции: SUM, AVG, COUNT, MAX, MIN

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

Рассмотрим основные агрегатные функции, их синтаксис и применение:

  1. SUM([DISTINCT] <выражение>)

    • Назначение: Вычисляет сумму числовых значений в группе.
    • Особенность: Работает исключительно с числовыми типами данных. Опция DISTINCT позволяет суммировать только уникальные значения.
    • Пример: Вычисление общей суммы продаж.
    SELECT SUM(SalesAmount) AS TotalSales
    FROM Orders;
    

    Если нам нужно суммировать только уникальные суммы, что редко встречается на практике, но возможно:

    SELECT SUM(DISTINCT SalesAmount) AS UniqueTotalSales
    FROM Orders;
    
  2. AVG([DISTINCT] <выражение>)

    • Назначение: Вычисляет среднее значение числовых значений в группе.
    • Особенность: Игнорирует значения NULL. Опция DISTINCT усредняет только уникальные значения.
    • Пример: Расчет средней цены продукта.
    SELECT AVG(Price) AS AveragePrice
    FROM Products;
    
  3. COUNT([DISTINCT] <выражение> | *)

    • Назначение: Подсчитывает количество элементов в группе.
    • Особенность:
      • COUNT(*): Подсчитывает все строки в группе, включая те, что содержат NULL. Это самый быстрый вариант, если вам просто нужно количество строк.
      • COUNT(<столбец>): Подсчитывает количество не-NULL значений в указанном столбце.
      • COUNT(DISTINCT <столбец>): Подсчитывает количество уникальных не-NULL значений в указанном столбце.
    • Пример: Подсчет общего количества заказов и уникальных клиентов.
    SELECT
        COUNT(*) AS TotalOrders,
        COUNT(DISTINCT CustomerID) AS UniqueCustomers
    FROM Orders;
    
  4. MAX(<выражение>)

    • Назначение: Находит максимальное значение в наборе.
    • Особенность: Работает с числовыми, строковыми и дато-временными типами данных. Игнорирует NULL.
    • Пример: Нахождение самой высокой цены продукта.
    SELECT MAX(Price) AS MaxPrice
    FROM Products;
    
  5. MIN(<выражение>)

    • Назначение: Находит минимальное значение в наборе.
    • Особенность: Аналогично MAX, работает с различными типами данных и игнорирует NULL.
    • Пример: Нахождение самой низкой цены продукта.
    SELECT MIN(Price) AS MinPrice
    FROM Products;
    

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

Оператор PIVOT (SQL Server, Oracle)

Оператор PIVOT был введен в SQL Server 2005 (Transact-SQL) как мощное расширение синтаксиса для табличных выражений в предложении FROM. Он упрощает создание сводных отчетов, делая код более читаемым и лаконичным по сравнению с условной агрегацией, о которой речь пойдет далее.

Синтаксис оператора PIVOT в SQL Server выглядит следующим образом:

SELECT [ <non-pivoted column> [ AS <column name> ] , ] ...
       [ <first pivoted column> [ AS <column name> ] ,
       [ <second pivoted column> [ AS <column name> ] , ] ...
       [ <last pivoted column> [ AS <column name> ] ] ]
FROM
    ( <SELECT query that produces the data> ) AS <alias for the source query>
PIVOT
    (
        <aggregation function> ( <column being aggregated> )
        FOR <column that contains the values that become column headers>
        IN ( <first pivoted column> , <second pivoted column> , ... <last pivoted column> )
    ) AS <alias for the pivot table>;

Разберем компоненты этого синтаксиса:

  1. <SELECT query that produces the data>: Это подзапрос (или Common Table Expression, CTE), который является источником данных для операции PIVOT. Он должен возвращать как минимум три типа столбцов:
    • Столбцы, которые будут оставаться в строках (группирующие столбцы).
    • Столбец, значения которого будут агрегироваться.
    • Столбец, уникальные значения которого станут заголовками новых столбцов.
  2. AS <alias for the source query>: Псевдоним для подзапроса, обязательный для его использования в предложении FROM.
  3. PIVOT (...): Сам оператор PIVOT.
    • <aggregation function> ( <column being aggregated> ): Здесь указывается агрегатная функция (например, SUM, AVG, COUNT), которая будет применяться к значениям столбца, который вы хотите агрегировать (например, SalesAmount).
    • FOR <column that contains the values that become column headers>: Указывает столбец из исходного подзапроса, уникальные значения которого станут новыми заголовками столбцов в сводной таблице.
    • IN ( <first pivoted column>, ... ): Перечисляет конкретные значения из столбца, указанного в FOR, которые должны стать новыми столбцами. Это статический список. Если вы не укажете значение, оно не появится как столбец.
  4. AS <alias for the pivot table>: Псевдоним для результата операции PIVOT, также обязательный.
  5. SELECT ... FROM ... PIVOT ...: Внешний SELECT выбирает столбцы для финального вывода. Он может включать как «неповернутые» столбцы (те, что остались в строках), так и новые, «повернутые» столбцы, которые были созданы оператором PIVOT.

Пример использования PIVOT:

Предположим, у нас есть таблица ProductSales со следующими данными:

ProductCategory SaleYear Revenue
Electronics 2022 1000
Clothing 2022 500
Electronics 2023 1200
Clothing 2023 600
Books 2022 300
Books 2023 400

Мы хотим получить сводную таблицу, показывающую выручку по категориям товаров за 2022 и 2023 годы.

SELECT ProductCategory, [2022] AS Revenue_2022, [2023] AS Revenue_2023
FROM (
    SELECT ProductCategory, SaleYear, Revenue
    FROM ProductSales
) AS SourceData
PIVOT (
    SUM(Revenue)
    FOR SaleYear IN ([2022], [2023])
) AS PivotResult;

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

ProductCategory Revenue_2022 Revenue_2023
Books 300 400
Clothing 500 600
Electronics 1000 1200

Этот пример наглядно демонстрирует, как PIVOT трансформирует строки (годы) в столбцы, агрегируя данные (суммируя выручку) по категориям продуктов.

Условная агрегация с использованием CASE (для других СУБД)

Что делать, если ваша СУБД (например, PostgreSQL, MySQL до версии 8.0, или более ранние версии SQL Server) не поддерживает оператор PIVOT? Или если вам требуется более сложная логика поворота, которую PIVOT напрямую не может обработать? В таких случаях на помощь приходит условная агрегация с использованием оператора CASE. Этот метод является универсальным и работает во всех СУБД, поддерживающих стандарт SQL.

Идея заключается в том, чтобы внутри агрегатной функции (например, SUM или COUNT) использовать CASE выражение для условного включения значений в агрегацию.

Пример условной агрегации:

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

ProductCategory SaleYear Revenue
Electronics 2022 1000
Clothing 2022 500
Electronics 2023 1200
Clothing 2023 600
Books 2022 300
Books 2023 400

Мы хотим получить тот же результат, что и с оператором PIVOT:

SELECT
    ProductCategory,
    SUM(CASE WHEN SaleYear = 2022 THEN Revenue ELSE 0 END) AS Revenue_2022,
    SUM(CASE WHEN SaleYear = 2023 THEN Revenue ELSE 0 END) AS Revenue_2023
FROM ProductSales
GROUP BY ProductCategory;

Разбор запроса:

  • SELECT ProductCategory: Этот столбец будет группирующим, то есть для каждой уникальной категории будет выведена отдельная строка.
  • SUM(CASE WHEN SaleYear = 2022 THEN Revenue ELSE 0 END) AS Revenue_2022: Это ключевая часть. Для каждого ProductCategory мы суммируем Revenue только в том случае, если SaleYear равен 2022. В противном случае CASE возвращает 0, который не влияет на сумму для других лет. Таким образом, мы получаем столбец Revenue_2022.
  • Аналогично, SUM(CASE WHEN SaleYear = 2023 THEN Revenue ELSE 0 END) AS Revenue_2023 создает столбец для 2023 года.
  • FROM ProductSales: Указывает исходную таблицу.
  • GROUP BY ProductCategory: Группирует результаты по категориям продуктов, чтобы агрегатные функции (SUM) применялись к каждой категории отдельно.

Результат выполнения этого запроса будет идентичен результату с PIVOT:

ProductCategory Revenue_2022 Revenue_2023
Books 300 400
Clothing 500 600
Electronics 1000 1200

Хотя метод CASE может быть более многословным, он предоставляет большую гибкость в сложных сценариях, позволяя применять более изощренную логику в условиях агрегации. Это универсальный инструмент, который должен быть в арсенале каждого SQL-разработчика.

Фильтрация данных: WHERE против HAVING и полный порядок выполнения запросов

Эффективная фильтрация данных — это ключевой аспект работы с SQL, который напрямую влияет на корректность и производительность запросов. В SQL для фильтрации используются два основных предложения: WHERE и HAVING. Хотя оба они служат для ограничения выводимых данных, их назначение, область применения и, что самое важное, место в логическом порядке выполнения запроса кардинально отличаются.

WHERE: фильтрация до группировки

Предложение WHERE — это ваш первый эшелон обороны при работе с данными. Оно используется для фильтрации отдельных строк данных до их группировки. Это означает, что WHERE работает непосредственно с исходными записями в таблице, отсеивая те, которые не удовлетворяют заданному критерию, еще до того, как эти записи будут объединены в группы для агрегации.

Ключевые особенности WHERE:

  • Применение: Применяется к каждой отдельной строке, возвращаемой из FROM и JOIN операций.
  • Ограничения: В предложении WHERE категорически нельзя использовать агрегатные функции (SUM(), COUNT(), AVG() и т. д.). Почему? Потому что WHERE применяется на этапе выборки данных, когда агрегаты еще не вычислены. База данных не знает SUM(Salary) для группы, пока не соберёт все строки и не произведет группировку.
  • Влияние на производительность: Использование WHERE для предварительной фильтрации является важнейшей стратегией для улучшения производительности. Сокращая объем данных, которые затем будут обрабатываться операциями GROUP BY и PIVOT, мы значительно уменьшаем нагрузку на СУБД.

Пример использования WHERE:

Представим таблицу Employees:

EmployeeID DepartmentID Salary StartDate
1 101 50000 2022-03-15
2 102 60000 2023-01-10
3 101 55000 2023-05-01
4 103 70000 2024-02-20
5 102 62000 2022-11-25

Если мы хотим получить среднюю зарплату сотрудников, принятых после 2023 года, мы используем WHERE:

SELECT DepartmentID, AVG(Salary) AS AverageSalary
FROM Employees
WHERE StartDate > '2023-01-01'
GROUP BY DepartmentID;

В этом запросе WHERE StartDate > '2023-01-01' отфильтрует сотрудников (EmployeeID 1, 5), чья дата начала работы предшествует 2023 году, до того как будет произведена какая-либо группировка или агрегация.

HAVING: фильтрация групп после агрегации

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

Ключевые особенности HAVING:

  • Применение: Применяется к результатам группировки, то есть к каждой группе, сформированной оператором GROUP BY.
  • Возможности: В предложении HAVING можно и нужно использовать агрегатные функции (SUM(), COUNT(), AVG() и т. д.), так как на этом этапе агрегаты уже вычислены.
  • Примеры условий: COUNT() > 10 (группа должна содержать более 10 элементов), AVG(Salary) > 50000 (средняя зарплата в группе должна превышать 50000).

Пример использования HAVING:

Продолжим работу с таблицей Employees. Если мы хотим найти отделы, в которых общая зарплата сотрудников превышает 100000:

SELECT DepartmentID, SUM(Salary) AS TotalSalary
FROM Employees
GROUP BY DepartmentID
HAVING SUM(Salary) > 100000;

Здесь GROUP BY DepartmentID сначала сгруппирует сотрудников по отделам и вычислит SUM(Salary) для каждого отдела. Затем HAVING SUM(Salary) > 100000 отфильтрует эти группы, оставляя только те, где общая зарплата превышает 100000.

Совместное использование WHERE и HAVING:

Часто WHERE и HAVING используются вместе для многоуровневой фильтрации.

Пример: Найти отделы, в которых суммарная зарплата сотрудников, принятых после 2023 года, превышает 100000.

SELECT DepartmentID, SUM(Salary) AS TotalSalary
FROM Employees
WHERE StartDate > '2023-01-01' -- Сначала отфильтровать сотрудников по дате
GROUP BY DepartmentID
HAVING SUM(Salary) > 100000;  -- Затем отфильтровать группы по агрегированной зарплате

Логический порядок выполнения SQL-запроса

Понимание того, как SQL-сервер обрабатывает запрос, является фундаментальным для правильного использования WHERE и HAVING и предотвращения логических ошибок. Несмотря на то, что мы пишем запрос в определенном синтаксическом порядке (например, SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY), логический порядок выполнения на сервере совершенно иной.

Стандартный логический порядок выполнения SQL-запроса:

  1. FROM: Определяет исходные таблицы, участвующие в запросе. Это первый шаг, который формирует базовый набор данных.
  2. JOIN (включая ON условие): Выполняет операции соединения таблиц, определенные в предложении FROM. На этом этапе формируется промежуточный набор данных, объединяющий строки из разных таблиц.
  3. WHERE: Применяет условия фильтрации к каждой отдельной строке, полученной после FROM и JOIN. Отсеиваются строки, не соответствующие условиям. На этом этапе агрегатные функции еще не доступны.
  4. GROUP BY: Группирует оставшиеся строки в наборы, исходя из значений указанных столбцов. Для каждой уникальной комбинации значений в столбцах GROUP BY создается одна группа.
  5. CUBE / ROLLUP (если используются): Расширяют функциональность GROUP BY, генерируя дополнительные агрегатные строки для промежуточных и общих итогов.
  6. HAVING: Применяет условия фильтрации к группам, созданным GROUP BY. На этом этапе агрегатные функции уже вычислены и могут быть использованы в условиях. Отсеиваются группы, не соответствующие условиям.
  7. SELECT: Выбирает и вычисляет выражения в списке SELECT. Здесь формируются финальные столбцы результата, включая псевдонимы и вычисленные агрегаты.
  8. DISTINCT (если используется): Удаляет дублирующиеся строки из набора результатов, если они идентичны по всем выбранным столбцам.
  9. ORDER BY: Сортирует финальный набор результатов по указанным столбцам.
  10. TOP / OFFSET / LIMIT (если используются): Ограничивает количество возвращаемых строк.

Почему это важно? Понимание этого порядка объясняет, почему WHERE не может использовать агрегатные функции, а HAVING может. Предварительная фильтрация с WHERE значительно улучшает производительность, уменьшая объем данных, которые достигают ресурсоемких операций GROUP BY и HAVING. Глубокое освоение этого логического порядка является ключом к написанию не только корректных, но и высокопроизводительных SQL-запросов, что является признаком профессионального аналитика данных.

Построение сводных запросов с группировкой по нескольким полям

Зачастую аналитические задачи требуют более сложного представления данных, чем простое сведение по одному измерению. Нам может потребоваться сгруппировать данные по нескольким полям одновременно, а затем «повернуть» одно из этих измерений в столбцы. SQL предлагает эффективные способы для решения таких многомерных задач, будь то с помощью специализированного оператора PIVOT или универсального подхода с CASE выражениями.

Группировка по нескольким полям с PIVOT

Когда СУБД поддерживает оператор PIVOT (как в SQL Server или Oracle), создание сводных запросов с группировкой по нескольким полям становится относительно прямолинейной задачей. Ключ к этому — правильное формирование подзапроса, который служит источником данных для PIVOT операции.

Основной принцип: Все столбцы из подзапроса, которые не используются в конструкции FOR ... IN оператора PIVOT (то есть, не становятся заголовками новых столбцов), автоматически становятся группирующими столбцами для финального вывода.

Рассмотрим пример. Допустим, у нас есть таблица CitySales со следующими данными о продажах:

City Region Year Sales
New York East 2019 1000
New York East 2020 1200
Chicago Midwest 2019 800
Chicago Midwest 2020 950
Los Angeles West 2019 1500
Los Angeles West 2020 1700
Boston East 2019 700
Boston East 2020 850

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

SELECT
    city,
    region,
    [2019] AS Sales_2019,
    [2020] AS Sales_2020
FROM (
    -- Подзапрос, который предоставит данные для PIVOT
    -- City и Region останутся группирующими столбцами
    -- Year будет повернут в столбцы
    -- Sales будет агрегироваться
    SELECT city, region, year, sales
    FROM CitySales
) AS src
PIVOT (
    -- Суммируем продажи
    SUM(sales)
    -- Поворачиваем столбец 'year'
    FOR year IN ([2019], [2020])
) AS pvt_table
ORDER BY city, region;

Разбор запроса:

  1. Внутренний подзапрос (SELECT city, region, year, sales FROM CitySales) AS src: Этот подзапрос извлекает все необходимые столбцы: city, region (которые будут группирующими), year (который будет «повернут» в заголовки столбцов) и sales (который будет агрегироваться).
  2. PIVOT (SUM(sales) FOR year IN ([2019], [2020])) AS pvt_table: Оператор PIVOT агрегирует SUM(sales). Он берет уникальные значения из столбца year (в данном случае 2019 и 2020) и превращает их в новые столбцы.
  3. Внешний SELECT city, region, [2019] AS Sales_2019, [2020] AS Sales_2020: Выбирает city и region как группирующие столбцы, а также новые столбцы, созданные PIVOT (с присвоением более читаемых псевдонимов).

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

city region Sales_2019 Sales_2020
Boston East 700 850
Chicago Midwest 800 950
Los Angeles West 1500 1700
New York East 1000 1200

Таким образом, PIVOT элегантно обрабатывает многомерную группировку, когда одни поля остаются в строках, а другие превращаются в столбцы.

Условная агрегация с CASE для многомерной группировки

Если оператор PIVOT недоступен или требуется более сложная логика, которую PIVOT не может выразить, условная агрегация с CASE выражениями остается универсальным и мощным инструментом. Этот подход также легко адаптируется для многомерной группировки.

Идея та же: использовать CASE внутри агрегатных функций для условного включения значений, но теперь мы будем группировать по нескольким столбцам в предложении GROUP BY.

Вернемся к примеру с таблицей CitySales:

City Region Year Sales
New York East 2019 1000
New York East 2020 1200
Chicago Midwest 2019 800
Chicago Midwest 2020 950
Los Angeles West 2019 1500
Los Angeles West 2020 1700
Boston East 2019 700
Boston East 2020 850

Мы хотим получить тот же результат, группируя по City и Region, и «поворачивая» Year в столбцы.

SELECT
    City,
    Region,
    SUM(CASE WHEN Year = 2019 THEN Sales ELSE 0 END) AS Sales_2019,
    SUM(CASE WHEN Year = 2020 THEN Sales ELSE 0 END) AS Sales_2020
FROM CitySales
GROUP BY City, Region -- Группировка по нескольким полям
ORDER BY City, Region;

Разбор запроса:

  1. SELECT City, Region, ...: Выбираем City и Region как поля, по которым будет производиться группировка и которые останутся в строках.
  2. SUM(CASE WHEN Year = 2019 THEN Sales ELSE 0 END) AS Sales_2019: Создаем столбец для 2019 года. Для каждой группы (City, Region) мы суммируем Sales только для тех записей, где Year равен 2019.
  3. SUM(CASE WHEN Year = 2020 THEN Sales ELSE 0 END) AS Sales_2020: Аналогично создаем столбец для 2020 года.
  4. FROM CitySales: Исходная таблица.
  5. GROUP BY City, Region: Это ключевой момент для многомерной группировки. Агрегатные функции (SUM с CASE) будут вычисляться для каждой уникальной комбинации City и Region.

Результат выполнения этого запроса будет идентичен предыдущему:

City Region Sales_2019 Sales_2020
Boston East 700 850
Chicago Midwest 800 950
Los Angeles West 1500 1700
New York East 1000 1200

Как видно, метод с CASE выражениями является гибким и функциональным эквивалентом PIVOT для многомерной группировки, работающим в любой СУБД. Выбор между PIVOT и CASE часто сводится к предпочтениям синтаксиса, читаемости кода и, в некоторых случаях, специфике оптимизатора конкретной СУБД.

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

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

Использование агрегатных функций в WHERE

Это, пожалуй, одна из самых частых ошибок, с которой сталкиваются новички и даже опытные пользователи SQL, забывшие о логическом порядке выполнения запроса. Попытка использовать агрегатные функции (SUM(), COUNT(), AVG() и т.д.) в предложении WHERE всегда приведет к синтаксической ошибке.

Почему это ошибка?

Как мы уже обсуждали, WHERE выполняется на ранней стадии обработки запроса, до того, как данные будут сгруппированы, и, соответственно, до того, как будут вычислены какие-либо агрегатные значения. В момент выполнения WHERE база данных работает с отдельными строками, а не с группами.

Пример ошибочного запроса:

SELECT DepartmentID, SUM(Salary) AS TotalSalary
FROM Employees
WHERE SUM(Salary) > 100000 -- ОШИБКА: Агрегатная функция в WHERE
GROUP BY DepartmentID;

Этот запрос выдаст ошибку, потому что SUM(Salary) является агрегатной функцией, которая не может быть использована в WHERE.

Как предотвратить:

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

Корректное решение:

SELECT DepartmentID, SUM(Salary) AS TotalSalary
FROM Employees
GROUP BY DepartmentID
HAVING SUM(Salary) > 100000; -- Корректное использование HAVING

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

Неверное понимание порядка выполнения запросов

Логические ошибки, возникающие из-за неверного понимания порядка выполнения SQL-запросов, являются наиболее коварными. Они не приводят к синтаксическим ошибкам, но выдают неверные результаты, что может иметь серьезные последствия для анализа и принятия решений. Например, если вы перепутаете порядок WHERE и HAVING, ваш запрос может выполняться, но выдавать совершенно не те данные, которые вы ожидаете.

Типичные сценарии:

  • Неправильная фильтрация до/после агрегации: Если вы хотите отфильтровать отдельные строки, но используете HAVING вместо WHERE (или наоборот, пытаетесь применить агрегатный фильтр через WHERE), результат будет искажен.
  • Влияние JOIN: Неверное понимание того, когда происходит JOIN относительно WHERE, может привести к избыточному количеству строк перед фильтрацией, что замедлит запрос и может дать неверные агрегаты.
  • Использование псевдонимов: Как будет сказано ниже, порядок выполнения влияет на то, когда псевдонимы становятся доступными.

Пример логической ошибки:

Предположим, вы хотите получить среднюю зарплату только тех сотрудников, которые были наняты после 2023 года, и чтобы средняя зарплата в их отделах была выше 60000.

Ошибочный подход (если бы WHERE применялся после HAVING, что не так):

SELECT DepartmentID, AVG(Salary) AS AvgSalary
FROM Employees
GROUP BY DepartmentID
HAVING AVG(Salary) > 60000
WHERE StartDate > '2023-01-01'; -- Синтаксически неверно, но даже если бы было возможно, логика была бы нарушена

Как предотвратить:

Всегда держите в голове логический порядок выполнения запроса (FROM -> JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY). Это позволит вам правильно располагать условия фильтрации и избегать ошибок, связанных с порядком операций. Для каждого условия задавайте себе вопрос: «На каком этапе выполнения запроса эти данные доступны и в каком виде (отдельные строки или группы)?»

Ошибки с псевдонимами и NULL-значениями

  1. Неправильное использование псевдонимов в HAVING:
    Предложение SELECT выполняется после HAVING. Это означает, что псевдонимы, присвоенные столбцам или агрегатным функциям в SELECT выражении, недоступны для использования в HAVING.

    Пример ошибочного запроса:

    SELECT DepartmentID, SUM(Salary) AS TotalSalary
    FROM Employees
    GROUP BY DepartmentID
    HAVING TotalSalary > 100000; -- ОШИБКА: Псевдоним 'TotalSalary' недоступен в HAVING
    

    Как предотвратить:
    В HAVING всегда используйте полные агрегатные выражения, а не их псевдонимы.

    Корректное решение:

    SELECT DepartmentID, SUM(Salary) AS TotalSalary
    FROM Employees
    GROUP BY DepartmentID
    HAVING SUM(Salary) > 100000; -- Корректное использование агрегатной функции
    
  2. Некорректная обработка NULL-значений:
    NULL в SQL означает «неизвестное» или «отсутствующее» значение и ведет себя иначе, чем обычные значения. Сравнение с NULL с использованием операторов =, !=, >, < всегда возвращает UNKNOWN, что интерпретируется как FALSE в условиях WHERE или HAVING.

    Пример ошибочного запроса:

    SELECT EmployeeID, EmployeeName
    FROM Employees
    WHERE DepartmentID = NULL; -- ОШИБКА: Неправильное сравнение с NULL
    

    Этот запрос не вернет ни одной строки, даже если в таблице есть записи с DepartmentID равным NULL.

    Как предотвратить:
    Для проверки на NULL всегда используйте операторы IS NULL или IS NOT NULL.

    Корректное решение:

    SELECT EmployeeID, EmployeeName
    FROM Employees
    WHERE DepartmentID IS NULL; -- Корректное сравнение с NULL
    

    Также помните, что большинство агрегатных функций (кроме COUNT(*)) игнорируют NULL-значения. Если вам нужно включить NULL в агрегацию, возможно, потребуется использовать функции COALESCE или ISNULL для замены NULL на другое значение перед агрегацией.

    Пример с PIVOT и WHERE:
    Если вы используете оператор PIVOT в SQL Server и хотите применить условие WHERE к данным *перед* поворотом, это условие должно быть помещено в подзапрос, который является источником данных для PIVOT.

    Корректный пример:

    SELECT *
    FROM (
        -- WHERE условие применяется здесь, до операции PIVOT
        SELECT Category, Product, SalesAmount, SaleYear
        FROM ProductSalesData
        WHERE SaleYear IN (2022, 2023) -- Фильтруем данные перед поворотом
    ) AS SourceData
    PIVOT (
        SUM(SalesAmount)
        FOR SaleYear IN ([2022], [2023])
    ) AS PivotedSales;
    

Избегая этих распространенных ошибок и глубоко понимая логику выполнения SQL-запросов, вы сможете писать более надежные, точные и производительные аналитические запросы.

Оптимизация производительности сводных запросов: продвинутые техники

В мире больших данных, где объемы информации исчисляются терабайтами и петабайтами, производительность SQL-запросов становится не просто желательной, а критиче��ки необходимой. Неоптимизированные сводные запросы, особенно те, что оперируют с огромными наборами данных, могут значительно замедлять работу системы, вызывать перегрузку сервера и приводить к сбоям. Поэтому каждый аналитик и разработчик должен владеть продвинутыми техниками оптимизации.

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

Ключ к оптимизации любого SQL-запроса лежит в понимании того, как база данных собирается его выполнить. Для этого используются планы выполнения запросов. План выполнения — это детализированная последовательность шагов, которые оптимизатор SQL Server (или другой СУБД) выбирает для обработки вашего запроса. Это своего рода «дорожная карта» выполнения, которая показывает:

  • Последовательность обращения к таблицам: В каком порядке СУБД будет считывать данные из ваших таблиц.
  • Методы извлечения данных: Используются ли сканирование таблицы (table scan), поиск по индексу (index seek) или сканирование индекса (index scan).
  • Методы соединения таблиц: Как соединяются таблицы (например, Hash Join, Merge Join, Nested Loops Join).
  • Операции фильтрации, сортировки и агрегации: Как данные фильтруются, сортируются и агрегируются на каждом этапе.

Как просмотреть планы выполнения:

  • SQL Server: Вы можете использовать SET SHOWPLAN_ALL ON (для текстового плана) или SET STATISTICS PROFILE ON (для более подробного вывода). В SSMS (SQL Server Management Studio) можно просто нажать Ctrl+L для отображения оценочного плана или Ctrl+M для отображения фактического плана после выполнения запроса.
  • PostgreSQL: Используется команда EXPLAIN <ваш запрос>; для получения оценочного плана или EXPLAIN ANALYZE <ваш запрос>; для получения фактического плана с подробной статистикой выполнения.

Интерпретация плана выполнения позволяет выявить «узкие места»: где запрос тратит больше всего времени, какие операции являются наиболее ресурсоемкими (например, полное сканирование больших таблиц, дорогостоящие сортировки). Например, если в плане вы видите операцию «Table Scan» для большой таблицы, когда ожидаете «Index Seek», это может указывать на проблему с индексацией.

Стратегии ограничения и предварительной фильтрации данных

Самый эффективный способ оптимизации — это работа с меньшим объемом данных. Чем меньше строк и столбцов база данных должна обрабатывать, тем быстрее будет выполнен запрос.

  1. Предварительная фильтрация с WHERE: Как уже упоминалось, WHERE является вашим первым и самым мощным инструментом. Используйте его для максимально возможного сокращения объема данных до того, как они достигнут ресурсоемких операций GROUP BY, PIVOT или HAVING.
-- Неоптимизированный (сначала агрегируются все данные, потом фильтруются группы)
SELECT DepartmentID, SUM(Salary) AS TotalSalary
FROM Employees
GROUP BY DepartmentID
HAVING SUM(Salary) > 100000 AND DepartmentID IN (101, 102);

-- Оптимизированный (сначала фильтруются нужные отделы, потом агрегируются)
SELECT DepartmentID, SUM(Salary) AS TotalSalary
FROM Employees
WHERE DepartmentID IN (101, 102) -- Предварительная фильтрация
GROUP BY DepartmentID
HAVING SUM(Salary) > 100000;

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

  1. Ограничение столбцов (SELECT * vs. SELECT [конкретные_столбцы]): Избегайте использования SELECT * в подзапросах или основном запросе, если вам не нужны все столбцы. Извлечение только необходимых столбцов уменьшает объем данных, передаваемых между сервером и клиентом, а также объем данных, которые необходимо обрабатывать на промежуточных этапах.
-- Неэффективно: извлекает все столбцы
SELECT *
FROM (
    SELECT * FROM LargeTable WHERE Condition
) AS SubQuery;

-- Эффективно: извлекает только необходимые столбцы
SELECT RequiredColumn1, RequiredColumn2
FROM (
    SELECT RequiredColumn1, RequiredColumn2, OtherColumnForCondition FROM LargeTable WHERE Condition
) AS SubQuery;

Применение различных типов индексов для оптимизации агрегации

Индексы — это как оглавление в книге: они позволяют СУБД быстро находить нужные данные, не просматривая всю таблицу. Правильное использование индексов является одним из наиболее мощных способов оптимизации производительности.

Для оптимизации сводных запросов могут использоваться различные типы индексов:

  1. Кластеризованные индексы: Определяют физический порядок хранения строк в таблице. Одна таблица может иметь только один кластеризованный индекс. Они значительно ускоряют запросы, возвращающие диапазоны значений (например, WHERE Date BETWEEN '2023-01-01' AND '2023-12-31') и запросы, использующие ORDER BY или GROUP BY по столбцам индекса.
  2. Некластеризованные индексы: Создают отдельную структуру для быстрого поиска, не изменяя физический порядок строк. Таблица может иметь множество некластеризованных индексов. Они полезны для столбцов, используемых в условиях WHERE, JOIN и ORDER BY.
  3. Фильтруемые индексы (Filtered Indexes) (SQL Server): Это некластеризованные индексы, включающие предложение WHERE. Они индексируют только часть строк таблицы, уменьшая размер индекса, сокращая время обслуживания и улучшая производительность конкретных запросов, которые попадают под условие фильтра. Например, индекс на Status = 'Active' для очень большой таблицы.
  4. Покрывающие индексы (Covering Indexes): Это некластеризованные индексы, которые содержат все столбцы, необходимые для выполнения запроса, либо как часть ключа индекса, либо в предложении INCLUDE. Это позволяет СУБД выполнить запрос, не обращаясь к основной таблице (Table Lookups), что значительно ускоряет выполнение. Особенно полезны для запросов, включающих GROUP BY и агрегацию.
    • Пример покрывающего индекса для сводного запроса:
      Если запрос группирует по DepartmentID и суммирует Salary, покрывающий индекс мог бы быть создан по (DepartmentID) с включенным столбцом Salary:
      CREATE NONCLUSTERED INDEX IX_DepartmentSalary ON Employees (DepartmentID) INCLUDE (Salary);
  5. Колоночные индексы (Columnstore Indexes) (SQL Server, PostgreSQL): Оптимизированы для аналитики и обработки больших объемов данных (Data Warehousing). Они хранят данные в столбцовом формате, что позволяет значительно ускорить запросы, работающие с большими агрегациями, фильтрациями и соединениями, так как они обеспечивают высокое сжатие и эффективное сканирование только необходимых столбцов.
  6. Составные индексы (Composite Indexes): Строятся по нескольким столбцам. Эффективны, когда поисковые запросы используют два или более столбцов в условиях WHERE или GROUP BY. Порядок столбцов в таком индексе крайне важен: сначала должны идти столбцы с высокой избирательностью (уникальностью), затем остальные.
    • Пример составного индекса для сводного запроса с группировкой по нескольким полям:
      Если вы группируете по City и Region, и поворачиваете Year, индекс может быть создан по (City, Region, Year) с включенным Sales:
      CREATE NONCLUSTERED INDEX IX_CityRegionYearSales ON CitySales (City, Region, Year) INCLUDE (Sales);

Оптимизатор запросов SQL Server рассматривает множество вариантов выполнения запроса, оценивает их стоимость и выбирает тот, который считает наиболее эффективным. Однако не всегда выбор оптимизатора является оптимальным, поэтому понимание индексов и их влияния позволяет разработчику «направлять» оптимизатор.

Сравнительный анализ PIVOT vs. CASE с точки зрения производительности

Вопрос о том, что быстрее — специализированный оператор PIVOT или универсальная условная агрегация с CASE выражениями — часто возникает у разработчиков. Ответ не всегда однозначен и зависит от конкретной СУБД, версии, сложности запроса и объема данных.

  • PIVOT:
    • Преимущества: Более лаконичный и читаемый синтаксис, что может быть плюсом для поддержки кода. Оптимизатор запросов СУБД, как правило, хорошо «знает», как работать с оператором PIVOT, и может применять к нему специфические оптимизации.
    • Производительность: В простых сценариях PIVOT и SUM(CASE WHEN ...) часто генерируют очень схожие планы выполнения запросов и демонстрируют крайне близкие характеристики производительности. Оптимизатор запросов SQL Server зачастую внутренне преобразует операции PIVOT в выражения CASE.
    • Ограничения: Требует явного перечисления столбцов для поворота (статический IN список), что может быть проблемой для динамических данных. Менее гибок для сложных, нестандартных условий агрегации.
  • CASE выражения внутри агрегатных функций:
    • Преимущества: Универсальность (работает во всех СУБД), большая гибкость для реализации сложной логики поворота и условной агрегации. Позволяет легко создавать динамические сводные таблицы с помощью динамического SQL.
    • Производительность: Как уже упоминалось, в большинстве случаев производительность очень близка к PIVOT. Разница в производительности может сокращаться с увеличением числа столбцов для агрегации, так как увеличивается сложность CASE выражений.
    • Ограничения: Более многословный синтаксис, особенно при большом количестве столбцов для поворота.

Когда что использовать:

  • Используйте PIVOT, если:
    • Ваша СУБД его поддерживает (SQL Server, Oracle).
    • Количество и имена столбцов для поворота заранее известны и относительно стабильны (статический PIVOT).
    • Вы цените более чистый и компактный синтаксис.
  • Используйте CASE выражения, если:
    • Ваша СУБД не поддерживает PIVOT (например, PostgreSQL).
    • Вам требуется динамическое создание столбцов (для этого понадобится динамический SQL).
    • Логика агрегации очень сложна и не может быть выражена простым PIVOT (например, комбинированные условия для каждого нового столбца).
    • Вы хотите обеспечить максимальную переносимость кода между различными СУБД.

В заключение, оптимизация сводных запросов — это многогранный процесс, который требует не только знания синтаксиса, но и глубокого понимания внутренних механизмов работы СУБД, умения читать планы выполнения запросов и стратегически применять различные методы индексации и фильтрации.

Заключение

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

Мы детально разобрали основные агрегатные функции — SUM, AVG, COUNT, MAX, MIN — которые являются фундаментом для любых вычислений по группам данных. Исследовали специализированный оператор PIVOT, его синтаксис и применение в СУБД, таких как SQL Server и Oracle, а также рассмотрели универсальный метод условной агрегации с CASE выражениями, который позволяет создавать сводные таблицы в любой СУБД.

Критически важным блоком стало разграничение между операторами WHERE и HAVING. Благодаря доскональному анализу логического порядка выполнения SQL-запросов, мы выяснили, почему WHERE применяется к отдельным строкам до группировки, а HAVING — к уже агрегированным группам. Это понимание является краеугольным камнем для написания корректных и производительных запросов.

Мы также освоили методы построения сводных запросов с группировкой по нескольким полям, продемонстрировав, как оператор PIVOT, так и CASE-выражения справляются с многомерным анализом данных.

Не менее важным аспектом стало изучение типичных ошибок: попытки использовать агрегатные функции в WHERE, неверное понимание порядка выполнения запросов, а также проблемы с псевдонимами и NULL-значениями. Знание этих подводных камней и методов их предотвращения является залогом написания надежного и безошибочного кода.

Наконец, мы углубились в техники оптимизации производительности, рассмотрев, как планы выполнения запросов помогают выявлять «узкие места», какую роль играет предварительная фильтрация и ограничение данных, и как различные типы индексов могут радикально ускорить выполнение сложных агрегаций. Сравнительный анализ производительности PIVOT и CASE выражений дополнил наше понимание того, когда и какой подход следует выбирать.

В заключение хочется подчеркнуть: глубокое понимание SQL — это не просто умение писать запросы. Это способность мыслить логически, анализировать структуру данных и предвидеть, как база данных будет обрабатывать ваш запрос. Освоение сводных запросов и эффективной фильтрации — это значительный шаг на пути к становлению компетентным специалистом в области баз данных и анализа данных. Надеемся, что этот материал послужит надежным руководством и прочной основой для успешного выполнения контрольных работ и дальнейшего профессионального роста.

Список использованной литературы

  1. В чём разница между HAVING и WHERE? : r/SQL. URL: https://www.reddit.com/r/SQL/comments/o4d962/в_чём_разница_между_having_и_where/ (дата обращения: 07.11.2025).
  2. Различия между HAVING и WHERE в SQL без GROUP BY // Sky.pro. URL: https://sky.pro/media/razlichiya-mezhdu-having-i-where-v-sql/ (дата обращения: 07.11.2025).
  3. В чëм разница между HAVING и WHERE в SQL // Ответов Mail.ru. URL: https://otvety.mail.ru/question/233045236 (дата обращения: 07.11.2025).
  4. Чем отличается WHERE от HAVING? // Otus. URL: https://otus.ru/nest/449/ (дата обращения: 07.11.2025).
  5. Объясните разницу между WHERE и HAVING // SQL Academy. URL: https://sql-academy.org/ru/lessons/8-questions-from-interview-where-vs-having (дата обращения: 07.11.2025).
  6. Агрегатные функции — Интерактивный курс по SQL // Kaktus.io. URL: https://kaktus.io/courses/sql-basic/aggregate-functions (дата обращения: 07.11.2025).
  7. Агрегатные функции (Transact-SQL) // SQL Server. URL: https://learn.microsoft.com/ru-ru/sql/t-sql/functions/aggregate-functions-transact-sql?view=sql-server-ver16 (дата обращения: 07.11.2025).
  8. Агрегатные функции в SQL: что это такое, суть понятия, какие бывают, примеры запросов // Sky.pro. URL: https://sky.pro/media/agregatnye-funkcii-v-sql/ (дата обращения: 07.11.2025).
  9. Агрегатные функции // SQL — Хекслет. URL: https://ru.hexlet.io/courses/sql-basics/lessons/aggregate-functions/theory_unit (дата обращения: 07.11.2025).
  10. Использование операторов PIVOT и UNPIVOT // SQL Server. URL: https://learn.microsoft.com/ru-ru/sql/t-sql/queries/from-using-pivot-and-unpivot?view=sql-server-ver16 (дата обращения: 07.11.2025).
  11. Оператор PIVOT // SQL Tutorial. URL: https://sql-tutorial.ru/p/pivot-operator (дата обращения: 07.11.2025).
  12. Обзор плана выполнения // SQL Server. URL: https://learn.microsoft.com/ru-ru/sql/relational-databases/performance/display-estimated-execution-plan?view=sql-server-ver16 (дата обращения: 07.11.2025).
  13. Execution sequence of Group By, Having and Where clause in SQL Server? // Stack Overflow. URL: https://stackoverflow.com/questions/1109968/execution-sequence-of-group-by-having-and-where-clause-in-sql-server (дата обращения: 07.11.2025).
  14. PIVOT SQL Server // SQL Tutorial.ru. URL: https://www.sqltutorial.ru/pivot-sql-server/ (дата обращения: 07.11.2025).
  15. Операторы PIVOT и UNPIVOT // SQL Tutorial. URL: https://sql-tutorial.ru/p/pivot-unpivot (дата обращения: 07.11.2025).
  16. How to Use SQL PIVOT // DataCamp. URL: https://www.datacamp.com/tutorial/sql-pivot (дата обращения: 07.11.2025).
  17. Что такое план выполнения и как его найти в PostgreSQL // SQL-Ex.ru. URL: https://sql-ex.ru/blog/chto-takoe-plan-vyipolneniya-i-kak-ego-nayti-v-postgresql (дата обращения: 07.11.2025).
  18. SQL Server How to show multiple columns in Pivot // Microsoft Learn. URL: https://learn.microsoft.com/en-us/answers/questions/797886/sql-server-how-to-show-multiple-columns-in-pivot (дата обращения: 07.11.2025).
  19. SQL PIVOT: An In-Depth Look At Pivoting Data In SQL // DbVisualizer. URL: https://dbvisualizer.com/blog/sql-pivot-an-in-depth-look-at-pivoting-data-in-sql/ (дата обращения: 07.11.2025).
  20. Pivot and Unpivot in SQL // GeeksforGeeks. URL: https://www.geeksforgeeks.org/pivot-and-unpivot-in-sql/ (дата обращения: 07.11.2025).
  21. PIVOT и UNPIVOT в Transact-SQL – описание и примеры использования операторов // Info-Comp.ru. URL: https://info-comp.ru/programmirovanie/200-pivot-unpivot-transact-sql.html (дата обращения: 07.11.2025).
  22. PIVOT // Хабр. URL: https://habr.com/ru/articles/207062/ (дата обращения: 07.11.2025).
  23. Устраняем ошибку «агрегатные функции в WHERE» в SQL // Skypro. URL: https://sky.pro/media/agregiruyushie-funkcii-v-where/ (дата обращения: 07.11.2025).
  24. Оператор HAVING — Интерактивный курс по SQL // Kaktus.io. URL: https://kaktus.io/courses/sql-basic/having-operator (дата обращения: 07.11.2025).
  25. Фильтрация c помощью оператора HAVING // SQL — Хекслет. URL: https://ru.hexlet.io/courses/sql-basics/lessons/having/theory_unit (дата обращения: 07.11.2025).
  26. How to use SQL Pivot function in SQL Server // PopSQL. URL: https://popsql.com/learn/sql-pivot (дата обращения: 07.11.2025).
  27. Mastering SQL Pivot Function in SQL Server: Tips & Best Practices // RisingWave. URL: https://www.risingwave.com/blog/mastering-sql-pivot-function-in-sql-server/ (дата обращения: 07.11.2025).
  28. Ошибка при использовании HAVING // Stack Overflow на русском. URL: https://ru.stackoverflow.com/questions/183188/%D0%9E%D1%88%D0%B8%D0%B1%D0%BA%D0%B0-%D0%BF%D1%80%D0%B8-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B8-having (дата обращения: 07.11.2025).
  29. 7 распространенных ошибок в SQL-запросах, которые делал каждый (почти) // Habr. URL: https://habr.com/ru/articles/690022/ (дата обращения: 07.11.2025).
  30. T-SQL pivot where clause // Stack Overflow. URL: https://stackoverflow.com/questions/35508828/t-sql-pivot-where-clause (дата обращения: 07.11.2025).
  31. Solving the SQL WHERE Clause Issue in Pivot Operations // YouTube. URL: https://www.youtube.com/watch?v=d_k20c7n-n4 (дата обращения: 07.11.2025).
  32. Pivot in SQL Server with where clause // Stack Overflow. URL: https://stackoverflow.com/questions/42220138/pivot-in-sql-server-with-where-clause (дата обращения: 07.11.2025).
  33. Pivot sql query with where clause // Stack Overflow. URL: https://stackoverflow.com/questions/44773448/pivot-sql-query-with-where-clause (дата обращения: 07.11.2025).

Похожие записи