Динамическое вращение с кубами и обработчиками событий в SQL Server
Вступление
В этой статье объясняется создание динамических стержней. Оно начинается с простого стержня. Второй шаг - добавить строки для расчета итогов. В этом случае вы можете видеть, что TransAct SQL имеет обработчики событий, и в сочетании с командой WITH CUBE они очень удобны. К сожалению, команда PIVOT в SQL Server (2005 и выше) работает с именами именованных столбцов. Чтобы сделать ее динамичной, нужно добавить немного программирования.
Бэкграунд
Один из наших клиентов всегда загружал дельту данных в нашу систему, пока не узнал, что можно ежемесячно добавлять все данные в нашу систему. Мы хотели увидеть, насколько большим стал рост системы. Месяц, когда он начинал загружать полные файлы вместо дельта-файлов, значительно показывал результат. Когда мы создали график данных Excel, все были поражены. Сложив итоги, был создан простой управленческий отчет, который очень просто рассказал нашему клиенту о том, на что способна наша система.
Использование кода
База данных, используемая для этой статьи - AdventureWorks, которую можно найти здесь. Запросы, прикрепленные к этому проекту, могут выполняться в SQL Server Management Studio (SSMS) для SqlServer 2008 R2. Имя базы данных Adventureworks - AdventureWorksDW2008R2.
Начало
Большинство разработчиков знают некоторый SQL, и когда им нужно создать запрос, показывающий соответствие между двумя вещами, возникает такой запрос:
SELECT var1, var2, count(var2) from table1 group by var1, var2
Если мы посмотрим на базу данных AdventureWorksDW2008R2, таблица FactSalesQuota может привести к следующему вопросу. На основе полей CalendarYear, CalendarQuarter и SalesAmountQuota можно выяснить, какая сумма была продана за квартал в год. Запрос в начале может быть таким:
SELECT [CalendarYear],[CalendarQuarter],[SalesAmountQuota] _
FROM [AdventureWorksDW2008R2].[dbo].[FactSalesQuota]
В результате получается длинный список, который «улучшается» с помощью функции SUM () и GROUP BY. Но 3 столбца ничего не говорят. Актуальность не видна.
Как начать получать динамическое вращение
«Улучшенный» запрос - это начало динамического вращения. Если мы хотим увидеть эффективный результат между CalendarYear и CalendarQuarter, у нас есть четыре шага:
- Сохраните результат запроса во временной таблице.
- Найдите уникальные значения в столбцах CalendarQuarter и установите их как имена столбцов в varchar.
- Создайте команду pivot (на основе команды pivot с использованием определенных имен столбцов) в varchar.
- Выполните созданную команду pivot.
- Мы сказали четыре шага, но шаг 5 часто забывают: бросьте свой временную таблицу.
И теперь в коде
Начните устанавливать результат запроса во временной таблице:
SELECT [CalendarYear],[CalendarQuarter],SUM([SalesAmountQuota]) as SalesAmount
INTO #tempPivotTable
FROM [AdventureWorksDW2008R2].[dbo].[FactSalesQuota]
GROUP BY [CalendarYear],[CalendarQuarter]
Затем создайте имена столбцов на основе уникальных значений в столбце CalendarQuarter. Каждое значение должно быть приведено к типу varchar и помещено в скобки, и ваш столбец готов! Для создания @Columns в основном используется @columns = @columns + '[' + '.....' + ']'. Опасность в этом коде заключается в том, что когда '['+' ..... '+'] 'не работает, вы не знаете, чему будет равно @columns. Поэтому лучше использовать команду STUFF. Это одна функция, и этот результат помещается в нашу переменную:
DECLARE @columns VARCHAR(8000)
SELECT @columns = STUFF(( SELECT DISTINCT TOP 100 PERCENT
'],[' + cast([CalendarQuarter] as varchar)
FROM #tempPivotTable AS t2
ORDER BY '],[' + cast([CalendarQuarter] as varchar)
FOR XML PATH('')
), 1, 2, '') + ']'
Затем создайте запрос для сбора данных для сводной таблицы. Помните, что вам нужна агрегатная функция для работы сводки. В этом случае мы можем использовать как MIN (), так и MAX (), поскольку это одна сумма в квартал в год.
DECLARE @query VARCHAR(8000)
SET @query = '
SELECT *
FROM #tempPivotTable
PIVOT
(
MAX(SalesAmount)
FOR [CalendarQuarter]
IN (' + @columns + ')
)
AS p '
Выполните созданный запрос:
EXECUTE(@query)
Сбросьте свою временную таблицу. В противном случае вы не сможете выполнить этот запрос два раза подряд.
DROP TABLE #tempPivotTable
На изображении проекта вы можете увидеть результат. Если вы видите значение NULL в поле, вы знаете, что комбинация недоступна.
Станьте профессионалом динамического вращения
Приятно иметь стержень, но следующий вопрос придет от вашего менеджера. Продаем ли мы больше в год, и продаем ли мы больше за квартал, основываясь на годах? Пришло время подсчитать строки во время выполнения запроса. Сначала нам нужна команда WITH CUBE в нашем базовом запросе.
SELECT [CalendarYear],[CalendarQuarter],SUM([SalesAmountQuota]) as SalesAmount
INTO #tempPivotTable
FROM [AdventureWorksDW2008R2].[dbo].[FactSalesQuota]
GROUP BY [CalendarYear],[CalendarQuarter]
WITH CUBE
Если вы запустите запрос, строка будет добавлена сверху, начиная с NULL, а затем значения. Он представляет собой суммированное значение за квартал за эти годы. Но на первую часть вопроса нет ответа: продаем ли мы больше в год? На данный момент нам нужны обработчики событий в транзакционном SQL. Расслабьтесь! Мы поможем. Требуется обработчик события 'grouping'. Когда group by выполняется на сервере SQL, вы хотите, чтобы в первом столбце отображалось 'Total', в противном случае - календарный год. Таким образом, нам также нужна функция 'case when', чтобы выполнить это 'if'. Ваш основной запрос изменяется:
SELECT CASE WHEN GROUPING(cast([CalendarYear] as varchar)) = 1
THEN 'Total'
ELSE cast([CalendarYear] as varchar)
END
as [CalendarYear],
[CalendarQuarter],SUM([SalesAmountQuota]) as SalesAmount
INTO #tempPivotTable
FROM [AdventureWorksDW2008R2].[dbo].[FactSalesQuota]
GROUP BY cast([CalendarYear] as varchar),[CalendarQuarter]
WITH CUBE
В этом случае 'Total' - это varchar, а CalendarYear - integer. Вот почему добавляется приведение к varchar (также в group by). Вот почему нам немного повезло! Если бы calendaryear имел другое значение, например, «year of the name_an_animal» в качестве отправной точки, строка Total НАЧИНАЕТ результат. Решение дается в конце этой статьи. Если необходимо добавить столбец Total, он должен быть в коллекции CalendarQuarter. Поэтому нам также нужна группировка для CalendarQuarter. Базовый запрос снова расширяется:
SELECT CASE WHEN GROUPING(cast([CalendarYear] as varchar)) = 1
THEN 'Total'
ELSE cast([CalendarYear] as varchar)
END
as [CalendarYear],
CASE WHEN GROUPING(cast([CalendarQuarter] as varchar)) = 1
THEN 'Total'
ELSE cast(CalendarQuarter as varchar)
END
as [CalendarQuarter],
SUM([SalesAmountQuota]) as SalesAmount
INTO #tempPivotTable
FROM [AdventureWorksDW2008R2].[dbo].[FactSalesQuota]
GROUP BY cast([CalendarYear] as varchar), cast([CalendarQuarter] as varchar)
WITH CUBE
Если вы выполните общий запрос, вы увидите, что:
- Квартал 3 - самый продаваемый квартал
- Мы продаем больше каждый год, и последний год еще не закончен, но тенденции уже понятны
- Ваш менеджер будет счастлив
- В одно мгновение вы видите ценность ваших данных
Общий запрос:
SELECT CASE WHEN GROUPING(cast([CalendarYear] as varchar)) = 1
THEN 'Total'
ELSE cast([CalendarYear] as varchar)
END
as [CalendarYear],
CASE WHEN GROUPING(cast([CalendarQuarter] as varchar)) = 1
THEN 'Total'
ELSE cast(CalendarQuarter as varchar)
END
as [CalendarQuarter],
SUM([SalesAmountQuota]) as SalesAmount
INTO #tempPivotTable
FROM [AdventureWorksDW2008R2].[dbo].[FactSalesQuota]
GROUP BY cast([CalendarYear] as varchar), cast([CalendarQuarter] as varchar)
WITH CUBE
DECLARE @columns VARCHAR(8000)
SELECT @columns = STUFF(( SELECT DISTINCT TOP 100 PERCENT
'],[' + cast([CalendarQuarter] as varchar)
FROM #tempPivotTable AS t2
ORDER BY '],[' + cast([CalendarQuarter] as varchar)
FOR XML PATH('')
), 1, 2, '') + ']'
DECLARE @query VARCHAR(8000)
SET @query = '
SELECT *
FROM #tempPivotTable
PIVOT
(
MAX(SalesAmount)
FOR [CalendarQuarter]
IN (' + @columns + ')
)
AS p '
EXECUTE(@query)
DROP TABLE #tempPivotTable
Решение для Total Row в нижней части
Дело в том, что «Total» начинается с «t», а «u, v, w, x, y, z» может привести к итоговой строке в середине вашего набора результатов. В этом случае вы должны посмотреть на используемое сопоставление в базе данных. В нашем случае это: Latin1_General_CI_AS. Если бы вы посмотрели сюда, вы могли бы видеть, что символ 161 находится после 'z'. Мы знаем, что слово «Total» теперь выглядит некрасиво, но строка и / или столбец четко различимы. Однако эффект заключается в том, что второй столбец и первая строка представляют итоговые значения. Это выглядит более уродливо, но если вы начнете «Total» с «ZZ», вы всегда можете закончить итоговые строки и столбцы.
Последние мысли
Если бы вместо квартала год использовался для имен столбцов, вы бы действительно увидели всю мощь этого запроса. Если данные за 2009 год были добавлены в таблицу, этот год сразу же появится в виде нового столбца в наборе результатов. Это делает такие запросы довольно удобными для:
- Управленческих отчетов
- Быстрого сканирования значений в случае проблем
- Получение реальной ценности ваших данных видимыми