Вычисление агрегатов
Вычисление промежуточных итогов
Иногда необходимо показать в отчете возрастающие значения, чтобы продемонстрировать прогресс некоторых видов операций. Например, возможно, вам нужно сгенерировать отчет о ежедневном росте продаж. Результат должен выглядеть следующим образом:
Date | Sales | Total Sales |
---|---|---|
7/1/2001 | $ 665262.96 | $ 665262.96 |
7/2/2001 | 15394.33 | 680657.29 |
7/3/2001 | 16588.46 | 697245.75 |
7/4/2001 | 7907.98 | 705153.72 |
7/5/2001 | 16588.46 | 721742.18 |
7/6/2001 | 15815.95 | 737558.13 |
7/7/2001 | 8680.48 | 746238.61 |
7/8/2001 | 8680.48 | 754919.10 |
7/9/2001 | 23105.31 | 778024.40 |
7/10/2001 | 11664.97 | 789689.37 |
7/11/2001 | 15815.95 | 805505.32 |
7/12/2001 | 15618.95 | 821124.28 |
7/13/2001 | 7907.98 | 829032.25 |
7/14/2001 | 27677.92 | 856710.17 |
7/15/2001 | 12409.84 | 869120.02 |
7/16/2001 | 15815.95 | 884935.97 |
… | … | … |
Как видите, значения в столбце Total Sales равно сумме итоговых сумм за все предыдущие даты плюс итоговая сумма текущей строки. Этот тип итоговой суммы называется промежуточным итогом, поскольку итоговая сумма рассчитывается до текущей суммы включительно.
Запрос для получения промежуточных итогов состоит из двух частей. Первая часть не представляет сложности: просто суммируем продажи, сгруппированные по дате:
SELECT OrderDate, SUM(TotalDue) AS Sales FROM Sales.SalesOrderHeader AS A GROUP BY OrderDate ORDER BY OrderDate
Нам нужно найти промежуточный итог для одной конкретной даты, для чего теоретически можно составить примерно такой запрос:
-Этот запрос не является синтаксически правильным запросом SQL SELECT SUM(TotalDue) AS Expr1 FROM Sales.SalesOrderHeader WHERE (OrderDate <= <Specific_Date>)
Имейте в виду, что этот запрос не является корректным сценарием T-SQL, он приводится только для объяснения того, как можно было бы высчитать промежуточный итог на определенный момент времени. Элемент <Specific_Date> не является корректным для T-SQL, он просто представляет конкретную дату в нашем теоретическом запросе. Чтобы претворить теорию в реальный T-SQL, мы можем воспользоваться полем OrderDate в качестве конкретной даты для каждой строки. (Сценарии из этого раздела можно найти в примерах в папке \SqlScripts в файле RunningTotalsExamplesFromText.sql )
Вычисляем промежуточные итоги при помощи подзапросов
- Воспользуйтесь подзапросом, чтобы получить все значения в одном результирующем наборе. Сохраните этот запрос как SubQueryMethod.sql.
SELECT OrderDate, SUM(TotalDue) AS Sales, ( SELECT SUM(TotalDue) FROM Sales.SalesOrderHeader WHERE (OrderDate <= A.OrderDate) ) AS [Actual Sales] FROM Sales.SalesOrderHeader AS A GROUP BY OrderDate ORDER BY OrderDate
- Посмотрим предполагаемый план выполнения этого запроса в SQL Server Management Studio. Мы видим два запроса, выполняющихся параллельно до получения результирующих наборов.
Давайте попробуем по-другому. Мы можем инкапсулировать промежуточный итог в определяемой пользователем функции.
Вычисляем промежуточный итог с помощью пользовательской функции
- Сначала создайте определяемую пользователем функцию, выполнив следующий сценарий.
CREATE FUNCTION SalesToDate ( @ThisDate datetime ) RETURNS money AS BEGIN RETURN (SELECT SUM(TotalDue) AS Expr1 FROM Sales.SalesOrderHeader WHERE (OrderDate <= @ThisDate)) END
- Затем выполните следующий запрос. Сохраните оба запроса в файл UserDefFunctionMethod.sql.
SELECT OrderDate, SUM(TotalDue) AS Sales, dbo.SalesToDate(A.OrderDate) AS [Actual sales] FROM Sales.SalesOrderHeader AS A GROUP BY OrderDate ORDER BY OrderDate
Какой метод лучше? Для сравнения выполните следующую процедуру.
Сравниваем производительность двух запросов
- В SQL Server Management Studio откройте меню Query (Запрос).
- Выберите команду Include Client Statistics (Включить статистику клиента).
- Выполните два набора запросов, которые вы сохранили во второй раз. Теперь вы увидите, что наряду с результатом доступна вкладка Client Statistics (Статистика клиента). Используйте предоставленную информацию для того, чтобы узнать стоимость двух различных видов запросов.
В следующей таблице показано подмножество статистических показателей двух запросов.
Метод с использованием подзапроса | Попытка 1 | Среднее |
---|---|---|
Время выполнения клиента | 10375 | 10375.000 |
Общее время выполнения | 10835 | 460.000 |
Время ожидания при ответе сервера | 460 | 10835.000 |
Метод с использованием пользовательской функции | Попытка 1 | Среднее |
Время выполнения клиента | 35677 | 35677.000 |
Общее время выполнения | 39502 | 39502.000 |
Время ожидания при ответе сервера | 3825 | 3825.000 |
Как видите, время выполнения для запроса, использующего пользовательскую функцию, в три раза больше, чем время выполнения для запроса, использующего подзапрос.
Вычисление статистических значений
Подсчет количества и суммы - не единственные доступные возможности агрегации. В запросах можно использовать также основные и расширенные статистические функции. Как и прежде, эти функции позволяют вам для получения результатов работать с базой данных напрямую, достигая превосходной эффективности обработки.
Эти функции можно использовать тем же способом, что и функцию SUM. Для удовлетворения дополнительных требований можно добавить фильтры и группировки. При фильтрации или группировке этих функций результирующее значение каждой функции будет отличаться в зависимости от ее определения. (Сценарии из этого раздела можно найти в примерах в папке \SqlScripts в файле StatisticalExamplesFromText.sql )
Использование функции AVG
Функция AVG возвращает среднее от всех значений, содержащихся в столбце, который указан в качестве аргумента. Функция AVG игнорирует значения NULL, поэтому если среди ваших данных присутствуют нулевые значения, это не изменит среднего значения, возвращаемого функцией.
Функция AVG в качестве аргумента принимает любые выражения, возвращающие численные значения. Аргументом может быть имя столбца или вычисление. Однако подзапросы или другие агрегатные функции в аргументе недопустимы.
Следующий пример - инструкция SELECT, которая возвращает среднюю цену продаж для каждого изделия:
SELECT Production.Product.Name AS Product, AVG(Sales.SalesOrderDetail.UnitPrice) AS [Avg Price] FROM Sales.SalesOrderDetail INNER JOIN Production.Product ON Sales.SalesOrderDetail.ProductID = Production.Product.ProductID GROUP BY Production.Product.Name ORDER BY Production.Product.Name
И еще одна инструкция SQL, возвращающая среднюю стоимость каждого изделия по месяцам и общую среднюю стоимость для каждого продукта.
SELECT CASE GROUPING(Production. Product .Name) WHEN 1 THEN "Global Average" ELSE Production. Product .Name END AS Product, CASE GROUPING( CONVERT(nvarchar(7), Sales.SalesOrderHeader.OrderDate, 111)) WHEN 1 THEN "Average" ELSE CONVERT(nvarchar(7), Sales.SalesOrderHeader.OrderDate, 111) END AS Period, AVG(Sales.SalesOrderDetail.UnitPrice) AS [Avg Price] FROM Sales.SalesOrderDetail INNER JOIN Production.Product ON Sales.SalesOrderDetail.ProductID = Production.Product.ProductID INNER JOIN Sales.SalesOrderHeader ON Sales.SalesOrderDetail.SalesOrderID = Sales.SalesOrderHeader.SalesOrderID GROUP BY Production.Product.Name, CONVERT(nvarchar(7), Sales.SalesOrderHeader.OrderDate, 111) WITH ROLLUP ORDER BY GROUPING(Production.Product.Name), Production.Product.Name, Period
Использование функций MIN и MAX
Эти две функции возвращают минимальное или максимальное значение выражения. И вновь, выражение, используемое в качестве аргумента для этих функций, может быть либо именем столбца, либо другим вычислением, кроме того, для форматирования результатов можно использовать операторы GROUP или ROLLUP. Выполните следующий сценарий, чтобы увидеть функции MIN и MAX в действии.
SELECT Production.Product.Name, MIN(Sales.SalesOrderDetail.UnitPrice) AS [Min Price], MAX(Sales.SalesOrderDetail.UnitPrice) AS [Max Price], AVG(Sales.SalesOrderDetail.UnitPrice) AS [Avg Price] FROM Sales.SalesOrderDetail INNER JOIN Production.Product ON Sales.SalesOrderDetail.ProductID = Production.Product.ProductID GROUP BY Production.Product.Name ORDER BY Production.Product.Name