Этюды об отчетности. Штрихи к портрету SQL Server Reporting

advertisement
Алексей Шуленин, Microsoft
Этюды об отчетности. Штрихи к портрету SQL Server Reporting Services
2008 R2.
О Reporting Services SQL Server 2000 нам уже как-то доводилось беседовать в рамках данной
конференции. С тех пор накопилось много нового и интересного: службы отчетности успели
обновиться в составе SQL Server 2005, 2008 и на подходе версия 2008 R2. В рамках настоящего
доклада я не замахивался по понятным причинам на кумулятивную картину. Даже рассмотреть то
новое, что относится к R2, с надлежащим уровнем детализации едва ли позволило уложиться в
отведенные временные рамки. Поэтому адресую интересующихся к официальной документации, а
здесь мы разберем избранные улучшения в R2. Я думаю, всем известен сайт Microsoft Connect, где
каждый желающий волен заявить свои пожелания по функциональности относительно того или иного
продукта. Там же можно поддержать чужие заявки, если с ними ощущается солидарность. Решение о
реализации и приоритет зависят в том числе от количества голосов в поддержку. Многочисленные
пожелания трудящихся по поводу добавления или совершенствования характеристик Reporting
Services нашли отражение в R2, и в данном материале будут рассмотрены некоторые такие народные
улучшения. Материал составлен на основе недавних (март-апрель 2010 г.) публикаций в блоге
http://blogs.technet.com/isv_team/.
Пользовательское именование листов при экспорте из Reporting Services
2008 R2 в Excel
Ранее экспорт многостраничного отчета в Excel давал стандартные имена листов Sheet1, Sheet2 , ...
(или Лист1, Лист2, ... в русской версии). Не всегда это соответствовало ожиданиям. Простейший
пример - когда справочник клиентов или продуктов или и т.д. разбивается по алфавиту, на каждой
странице находятся продукты, начинающиеся с одной и той же буквы (или двух букв или трех,
неважно), и мы хотим включить эту букву в название Excelного листа. До недавних пор для этих целей
мы бы изобразили небольшой скриптец из двух частей, первая часть которого дергает веб-сервис
Reporting Services, давая ему команду срендерить интересующий отчет в Excel, а вторая часть заходит
в получившийся Excelный файл при помощи старой доброй СОМовской (в смысле, Automation)
модели и переименовывает имеющиеся в нем листы в соответствии с нужным правилом. Посмотрим,
как это делается в R2.
В качестве набора данных будет выступать список продуктов из любимой базы AdventureWorks:
SELECT
FROM
WHERE
ProductID, Name, Color, ListPrice, Size, Weight, Class
Production.Product
(ListPrice > 0)
Набор данных будет выводиться в виде таблицы
Рис.1
внутри которой нужно поставить Page Break перед каждой новой буквой, с которой будет начинаться
Name. Вводим в табликсе новую группу по строкам:
Рис.2
в которой группировка будет происходить по первой букве поля Name:
Рис.3
и уберем колонку, которая автоматически создалась в табликсе под эту группу:
Рис.4
В свойствах группы говорим, что каждыйновый экземпляр группы, т.е. группа с буквы "А", группа с
буквы "Б" и т.д., должен начинаться с новой страницы:
Рис.5
Теперь идем в свойства группы, которые не в диалоговом окне, а в панели свойств, и устанавливаем в
св-ве PageName правило именования страниц. Пусть страницы называются так же, как и выражение,
на основе которого производится разбиение на группы, т.е. по первой букве поля Name (см. Рис.3):
Рис.6
Публикуем отчет, запускаем и экспортим результаты в Excel:
Рис.7
Мы видим, что листы в получившемся Excelном файле называются в соответствии с заданным
правилом:
Рис.8
Замечания.
Наиболее внимательные посчитали количество листов в Excel (12) и заметили, что оно не совпадает с
тем, что мы видели при рендеринге в HTML (16). Это потому, что на рис.7 подмешиваются еще
физические разрывы в зависимости от выставленной длины страницы. Зайдите в редакторе отчета в
меню Report ->Report Properties и поставьте на закладке Page Setup размер страницы побольше.
Правило именования страниц поддерживается для экспорта в Excel, потому что в нем листы могут
иметь осмысленные наименования. Допустим, в Wordе страницы не умеют называться, они могут
только нумероваться, поэтому какое правило именования страниц ни стояло в Reporting Services, при
экспорте в Word мы получим только номера страниц.
Динамическое изменение отчета в зависимости от формата экспорта
В качестве иллюстрации создадим примитивнейший отчет из одного текстбокса
Рис.1
который мы тем не менее продеплоим на Report Server.
Рис.2
Теперь, если его, скажем, выполнить, появится следующая красивая надпись:
Рис.3
RPL - это Report Page Layout, введенное в 2008-м расширение рендеринга, чтобы распределить
нагрузку между сервером и клиентом. До 2008-го весь рендеринг делался на сервере, а клиенты,
например, элемент управления ReportViewer, ничего не делали, только получали готовый формат и
его отображали. В 2008-м на вход ReportViewerа льется стрим промежуточного формата RPL, который
он уже сам на месте преобразует в HTML. Но речь сейчас не об этом. Давайте экспортнем отчетец
куда-нибудь, например, в тот же самый Excel.
Рис.4
Распахиваем комбобокс с возможными форматами экспорта и со словами "крэкс-пэкс-фэкс"
выбираем Excel. Вот, что получилось.
Рис.5
Правда, интересно? А если на Рис.4 выбрать Word?
Рис.6
А если pdf?
Рис.7
Увлекательное занятие, можно долго развлекаться. Магия, как все давно догадались, зарыта в
placeholdere на Рис.1, который использует появившуюся в 2008 R2 новую глобальную переменную
RenderFormat.Name
Рис.8
дающую возможность подстроить по желанию внешний вид отчета при экспорте в тот или иной
формат.
Поворот текста на 270 градусов
Ориентация в текстбоксе управляется св-вом WritingMode, которое до сих пор могло принимать
значения Horizontal или Vertical. Вертикальная ориентация означает поворот текста на 90 градусов (по
часовой), т.е. текст располагается сверху книзу. Помнится, на покойном форуме sqlclub.ru участник
smoke 27 декабря 2008 г. открыл очень увлекательную дискуссию, как заставить его писаться снизу
вверх. Вот где по-настоящему чувствуется размах народной смекалки, которой никакой Новый год не
помеха. Были перебраны самые разные способы - от простого image вплоть до изобретения
собственных фонтов или вставку фиктивного пустого графика (подписи вдоль оси свободно
поворачиваются на произвольное число градусов), что одинаково неудобно. Наконец, в 2008 R2 в
список значений WritingMode была добавлена опция Rotate270, так что больше на эту тему
извращаться не придется. Достаточно выделить текстбокс (текстбокс, не текст внутри него), найти
среди его свойств свойство под названием WritingMode, распахнуть комбобокс его возможных
значений и выбрать среди них Rotate270.
Инфокривые и другие ячеечные графики
Инфокривые - это более-менее принятый перевод английского оригинала sparklines. Не могу сказать,
что, на мой субъективный взгляд, он удачный, т.к. на ум сразу приходят инфопрямые, инфостолбики и
пр., но, как говорится, хоть горшком назови, был бы смысл понятен. По смыслу, это маленькие
графики без осей, подписей и легенды, предназначенные умещаться в одной ячейке таблицы, чтобы
за единый взгляд получить представление о поведении некоторой численной характеристики.
Абсолютно типовым сценарием для использования разряда является селлсет о двух измерениях.
Первое пускается вниз по оси Y, рядом с ним, допустим, отображается мера, свернутая по второму
измерению, а рядом с ней делаем еще одну ячейку, в которой в виде графика-разряда раскрываем
поведение меры вдоль второго измерения. Первое измерение в этом графике, разумеется, будет
порезано по своему мемберу из первой ячейки. Кто-нибудь понял, что я сказал? Неважно, сейчас
будет все предельно ясно. Открываем новый проект в Report Designer, создаем новый датасет против
БД AdventureWorksDW2008R2. Кто не знает, где она берется, читают здесь. В
AdventureWorksDW2008R2 нас будут интересовать следующие таблицы:
Рис.1
Датасет будет получаться из следующего запроса:
select pc.EnglishProductCategoryName Род, psc.EnglishProductSubcategoryName Вид,
d.CalendarYear Год, d.MonthNumberOfYear Месяц, sum(s.SalesAmount) Деньги from
dbo.FactInternetSales s
join dbo.DimProduct p on s.ProductKey = p.ProductKey
join dbo.DimProductSubcategory psc on p.ProductSubcategoryKey =
psc.ProductSubcategoryKey
join dbo.DimProductCategory pc on psc.ProductCategoryKey = pc.ProductCategoryKey
join dbo.DimDate d on s.OrderDateKey = d.DateKey
group by pc.EnglishProductCategoryName, psc.EnglishProductSubcategoryName,
d.CalendarYear, d.MonthNumberOfYear
order by 1, 2, 3, 4
Скрипт 1
Рис.2
В результате запроса, как мы видим, получается подкубик из двух измерений: Товар с уровнями Род и
Вид и Время с уровнями Год и Месяц. По детальному уровню Продукт делается свертка, т.к. он нужен
только для связывания с фактами и непосредственно в отчете представлен не будет. Деньги являются
мерой.
В Report Designere создадим матрицу. Перетащите из датасета (левая панель) в первую колонку поле
Род. Если панель с датасетом не видна, скажите в меню View -> Report Data (последний пункт), он же
Ctrl-Alt-D.
Рис.3
Теперь ступайте в панель групп внизу. Если она не видна, нажмите обведенную на Рис.3 кнопку.
Добавьте к группе Род, автоматически образовавшейся при перенесении поля Род из датасета в
матрицу, дочернее группирование по полю Вид.
Рис.4
Перенесите поле Деньги в ячейку фактов с блеклой серой надписью Data. По умолчанию в кач-ве
агрегатной ф-ции будет выбрана сумма. Отцентрируйте заголовки колонок табликса, покрасьте их в
жирный цвет, наведите прочие красивости по вкусу.
Рис.5
Смотрите, какой классный отчет у нас с вами получился.
Рис.6
Отчет показывает суммы продаж в разрезе по уровням измерения Товар. Однако в датасете Рис.2
имеется еще измерение Время. Давайте его задействуем в графике-разряде, чтобы показать, как
сумма продаж по каждому товару складывалась из продаж по периодам времени.
Добавьте в таблицу еще одну колонку в пределах группы Вид:
Рис.7
Перетащите в нее из панели инструментов элемент управления Разряд.
Рис.8
Выберем тип графика, допустим, линии:
Рис.9
Кликните на ячейку, в которую вы поместили разряд, чтобы справа появилась всплывающая панель
его свойств Chart Data. В верхнюю часть (Values) перетащите из датасета поле Деньги. В среднюю
часть (Category Groups = ось Х) перетащите поля Год и Месяц. Год нужен для того, чтобы месяца за
разные годы не перемешивались, т.к. поле Месяц - не содержит признака года, это просто номер
месяца. Наличие родительской группы Год позволит разделить месяцы по годам. Нижнюю часть
(Series Groups) оставьте пустой - у нас не будет несколько графиков на одной координатной
плоскости.
Рис.10
Для пущей красоты можете разделить заголовочную ячейку, которую она по умолчанию сделала
общей, т.к. масштаб группирования добавленной колонки совпадал с соседней, и надписать колонку
с графиками чем-нибудь вроде "Динамика продаж".
Рис.11
Все, смотрим Preview:
Рис.12
По мне, так красота неописуемая. Отчет показывает продажи в разрезе по товарной продукции, в то
же время колонка Динамика продаж содержит график, позволяющий судить о динамике продаж
каждого товара в разрезе по времени. Сравните, допустим, график в первой строчке (Accessories - Bike
Racks) с помесячными цифрами Рис.2. По-моему, оно.
Инфокривые и пользовательские агрегаты
Экий хитрый, скажете вы, прочитав предыдущий пункт. Описанный сценарий предполагает, что мы
заносим в репортинг развернутую по обоим измерениям таблицу фактов, которую он сам группирует
по времени и продуктам. Это логично, т.к. иначе как он построит график вдоль времени в каждой
строчке? В то же время данный подход предполагает (коль скоро таблица фактов развернута), что
агрегаты считаются внутри репортинга. Как быть, если в колонке "Деньги" на Рис.12 предыдущего
пункта по бизнес-логике требуется не сумма, а какой-нибудь более хитрый агрегат, реализовать
который средствами репортинга проблематично?
Предположим, имеется некоторый датасет, показывающий пользовательский агрегат вдоль
измерения "Продукты". Например, последние ненулевые месячные продажи:
if object_id('LastNonEmptyMonthBySubcategory', 'V') is not null drop view
LastNonEmptyMonthBySubcategory
go
create view LastNonEmptyMonthBySubcategory as
with cte(Род, Вид, Год, Месяц, n, ПользАгрегат) as (
select pc.EnglishProductCategoryName, psc.EnglishProductSubcategoryName,
d.CalendarYear, d.MonthNumberOfYear,
row_number() over (partition by pc.EnglishProductCategoryName,
psc.EnglishProductSubcategoryName order by d.CalendarYear desc,
d.MonthNumberOfYear desc),
sum(s.SalesAmount) from dbo.FactInternetSales s
join dbo.DimProduct p on s.ProductKey = p.ProductKey
join dbo.DimProductSubcategory psc on p.ProductSubcategoryKey =
psc.ProductSubcategoryKey
join dbo.DimProductCategory pc on psc.ProductCategoryKey = pc.ProductCategoryKey
join dbo.DimDate d on s.OrderDateKey = d.DateKey
group by pc.EnglishProductCategoryName, psc.EnglishProductSubcategoryName,
d.CalendarYear, d.MonthNumberOfYear
)
select Род, Вид, ПользАгрегат from cte where n = 1
go
select * from LastNonEmptyMonthBySubcategory
Скрипт 1
Наверное, пример можно было придумать лучше, т.к. подобный агрегат, как и сумму, тоже можно
реализовать внутри репортинга, но не будем на это отвлекаться. Пусть имеется датасет вида
"Категория продукта", "Подкатегория", "Некоторая величина", который бы мы хотели отобразить в
отчете, а как эта величина посчитана - дело, по большому счету, десятое.
Рис.1
В отчете мы бы хотели добавить к датасету колонку со спарклайном, отражающим поведение
некоторой (вообще говоря, другой) численной характеристики для каждой подкатегории продукта
вдоль ортогонального измерения. Это означает, что нам нужно в датасете умножить измерение
Продукт на это измерение и добавить к произведению новую меру. Пусть ортогональным
произведением, которое протянется вдоль оси Х спарклайна, как и в предыдущем пункте, остается
Время, а численной характеристикой (ось Y) будут продажи. Добавляем продажи в разрезе по
времени и подкатегориям в датасет Рис.1, сджойнив его с датасетом Скрипт 1 из предыдущего
пункта:
with ДатасетИзПредыдущегоПоста as (
select pc.EnglishProductCategoryName Род, psc.EnglishProductSubcategoryName Вид,
d.CalendarYear Год, d.MonthNumberOfYear Месяц, sum(s.SalesAmount) Деньги from
dbo.FactInternetSales s
join dbo.DimProduct p on s.ProductKey = p.ProductKey
join dbo.DimProductSubcategory psc on p.ProductSubcategoryKey =
psc.ProductSubcategoryKey
join dbo.DimProductCategory pc on psc.ProductCategoryKey = pc.ProductCategoryKey
join dbo.DimDate d on s.OrderDateKey = d.DateKey
group by pc.EnglishProductCategoryName, psc.EnglishProductSubcategoryName,
d.CalendarYear, d.MonthNumberOfYear
)
select t1.Род, t1.Вид, t2.Год, t2.Месяц, t1.ПользАгрегат, t2.Деньги from
LastNonEmptyMonthBySubcategory t1
join ДатасетИзПредыдущегоПоста t2 on t1.Род = t2.Род and t1.Вид = t2.Вид
order by 1, 2, 3, 4
Скрипт 2
Рис.2
Пользовательский агрегат внутри каждой подкатегории продукта является константой вдоль времени,
т.к., по условию, он зависит только от измерения Продукт. Замечательно. Переносим датасет в отчет.
Аналогично Рис.3-5 предыдущего пункта, сделайте матрицу, сгруппированную по категории продукта
(Род), подкатегории (Вид) и перенесите в ячейку с серой надписью Data поле ПользАгрегат. По
умолчанию ему будет присвоена агрегатная функция Sum. Измените ее на
=First(Fields!ПользАгрегат.Value, "Вид")
Скрипт 3
Рис.3
Выражение означает, что вместо суммирования мы будем брать первое значение внутри каждой
группы подкатегорий, которая у нас называется Вид. Как отмечалось выше (Рис.2), внутри
подкатегорий (в разрезе по времени) ПользАгрегат не меняется, так что тут без разницы, первое
значение брать в группе или последнее.
Справа от колонки Вид добавляем в матрицу новую колонку в пределах этой группы:
Рис.4
и кладем в нее sparkline аналогично Рис.8-10 предыдущего пункта.
Рис.5
По оси Y пускаем поле Деньги, по оси Х - сгруппированные по годам месяцы. Смотрим в Preview, что
получилось:
Рис.6
По-моему, в аккурат то, что заказывали.
Рассмотрим второй способ, который с точки зрения проектирования отчета практически ничем не
будет отличаться от того, что мы только что проделали, т.к. состоит в том, чтобы считать
пользовательские агрегаты не в реляционной базе, а в кубике. На основе реляционной базы
AdventureWorksDW2008R2 имеется многомерная база, которую можно взять все там же. Напишем
MDX-запрос, делающий идейно ровно то же, что и Скрипт 1 + Скрипт 2: он выдает подкатегории
продуктов с некоторым пользовательским агрегатом LastNonEmptyMonth (это основа будущей
матрицы в репортинге) и внутри каждой подкатегории разворачивает еще динамику продаж по
месяцам года (это по чему будет строиться спарклайн в отдельной ячейке напротив каждой
подкатегории):
with member Measures.LastNonEmptyMonth as
Tail(nonempty([Date].[Calendar].[Month].Members * [Measures].[Internet Sales
Amount]), 1).Item(0)
select {[Measures].[Internet Sales Amount], Measures.LastNonEmptyMonth} on 0,
nonempty ([Product].[Product Categories].[Subcategory].Members *
[Date].[Calendar].[Month].Members, [Measures].[Internet Sales Amount]) on 1
from [Adventure Works]
Скрипт 4
Рис.7
Аналогично, перетаскиваем этот датасет в отчет, предварительно заведя в отчете новый источник
данных для Analysis Services:
Рис.8
Разработчики Reporting Services постарались максимально облегчить ввод MDX-запросов и создали
специальный дизайнер для их построения, поэтому просто так текст запроса ввести нельзя. Можно
нажать на значок функции, но тогда текст запроса будет считаться выражением, и список полей
недоступен.
Рис.9
Приходится все-таки зайти в дизайнер запросов (кнопка Query Designer), отжать в строке меню значок
Design Mode, после чего становится возможно ввести текст произвольного MDX-запроса и его
выполнить:
Рис.10
Обратите внимание, что датасет получается полнее, чем мы видели на Рис.7. В нем присутствуют не
только члены заказанных в запросе уровней измерений, например, Subcategory, но и автоматически
подтягиваются колонки MEMBER_NAME с родительских уровней, например, Category.
Совершенно аналогично кидаем на отчет матрицу, натаскиваем в нее построчную группу из поля
Category и дочернюю по отношению к ней из поля Subcategory, переносим в область Data поле
LastNonEmptyMonth, символизирующее собой пользовательский агрегат по подкатегориям, заходим
у него в Expression и убираем функцию Sum, которую автоматически норовит подставить Report
Designer.
Рис.11
Добавляем справа еще одну колонку в пределах текущей группы:
Рис.12
в которую переносим Sparkline, пуская аналогично Рис.5 поле Internet Sales Amount вдоль
вертикальной оси графика, а Calendar_Year и Month - вдоль горизонтальной.
Рис.13
Рассплитим аналогично Рис.11 предыдущего пункта заголовочную ячейку Last Non Empty Month и
вобьем над колонкой графиков заголовок "Динамика продаж". Отцентрируем и выделим жирным
цветом заголовки, отформатируем числовую ячейку =Fields!LastNonEmptyMonth.Value:
Рис.14
Смотрим в Preview, что получилось:
Рис.15
Операция прошла хорошо, жаль только, что больной об этом не узнает. Мне кажется, внешний вид
графиков не очень совпадает с тем, что мы видели на Рис.6. Например, там напротив Bike Racks тренд
в конце шел на спад, а здесь, наоборот, радостно растет. По цифрам (см. Рис.2, колонки Год, Месяц,
Деньги или Рис.7, Internet Sales Amount ) выходит, что в первом случае графики больше походили на
правду, а здесь sparkline показывает какую-то лажу. Чтобы выяснить, в чем дело, можно
проконвертировать sparkline в полноценный chart с подписями вдоль осей, но мы поступим подругому. Добавим в матрицу поля Calendar_Year и Month в виде колонок после Subcategory:
Рис.16
Все посмотрели на колонку Month и все поняли. По какой-то причине она решила месяцы
упорядочить по алфавиту, хотя в датасете (Рис.10) все нормально.
Привычно отправляемся в свойства sparkline и выбираем свойства Category Group, соответствующей
Month. Встаем на закладку Sorting, в ней - на строчку Sort by [Month] и жмем кнопку Delete.
Рис.17
Возвращаемся к матрице и удаляем из нее за ненадобностью колонки Calendar Year и Month вместе с
ассоциированными с ними группами, т.к. мы уже разобрались, где тут собака порылась.
Рис.18
Смотрим в очередной раз Preview:
Рис.19
По-моему на этот раз все нормально.
Связывание двух датасетов в Reporting Services 2008 R2
Среди пожеланий трудящихся к функциональности Reporting Services было иметь возможность
джойнить наборы данных не внутри источника, откуда они приходят, а в Report Builder / Report
Designer непосредственно в ходе подготовки отчета и вне зависимости от источников. Другим, не
менее страстным пожеланием была возможность иметь в одном табликсе данные из более, чем
одного датасета. Как следствие, теперь в регион данных можно заносить связанную информацию из
другого датасета. Это достигается при помощи трех новых функций - Lookup, Multilookup и LookupSet.
Первая определена в документации так: функция Lookup позволяет извлечь значение из указанного
набора данных, состоящего из пар «имя-значение» с отношением один к одному. берет значение
колонки из первого. На самом деле, отношение может быть 1:n или n:1, без разницы. Далее
говорится: при первом совпадении исходного и целевого выражений вычисляет результирующее
выражение для этой строки в наборе данных. Т.е. она берет значение из заданной колонки первого
датасета и ищет его внутри заданной колонки второго. Как только такое встретилось, поиск
прекращается. Функция возвращает значение из колонки второго датасета, заданной в ее третьем
параметре. Рассмотрим на примере.
В предыдущем пункте мы имели датасет Скрипт 2, описывающий продажи в разрезе по времени и
товарам. Товарное измерение пускалось вдоль вертикальной оси, а измерение времени
использовалось в качестве горизонтальной оси в графиках sparkline в каждой строке товара. Там же
имелся датасет Скрипт 1, состоящий только из товарного измерения и некоторого пользовательского
агрегата вдоль него. Мы создали общий датасет, сджойнив первый запрос со вторым и отобразив с
его помощью пользовательские агрегаты и тренды во времени для товарного измерения. Теперь я
предлагаю не создавать общий датасет. Оставим два исходных, "сджойнив" их при помощи функции
Lookup.
Первый датасет:
Рис.1
SELECT
pc.EnglishProductCategoryName AS Род,
psc.EnglishProductSubcategoryName AS Вид, d.CalendarYear AS Год,
d.MonthNumberOfYear AS Месяц,
SUM(s.SalesAmount) AS Деньги
FROM
FactInternetSales AS s INNER JOIN
DimProduct AS p ON s.ProductKey = p.ProductKey INNER
JOIN
DimProductSubcategory AS psc ON p.ProductSubcategoryKey
= psc.ProductSubcategoryKey INNER JOIN
DimProductCategory AS pc ON psc.ProductCategoryKey =
pc.ProductCategoryKey INNER JOIN
DimDate AS d ON s.OrderDateKey = d.DateKey
GROUP BY pc.EnglishProductCategoryName, psc.EnglishProductSubcategoryName,
d.CalendarYear, d.MonthNumberOfYear
ORDER BY Род, Вид, Год, Месяц
Скрипт 1
Второй датасет:
Рис.2
WITH cte(Род, Вид, Год, Месяц, n, ПользАгрегат) AS
(SELECT pc.EnglishProductCategoryName, psc.EnglishProductSubcategoryName, d
.CalendarYear,
d .MonthNumberOfYear, row_number() OVER (partition BY
pc.EnglishProductCategoryName,
psc.EnglishProductSubcategoryName ORDER BY d .CalendarYear DESC, d
.MonthNumberOfYear DESC), sum(s.SalesAmount)
FROM dbo.FactInternetSales s JOIN
dbo.DimProduct p ON s.ProductKey = p.ProductKey JOIN
dbo.DimProductSubcategory psc ON p.ProductSubcategoryKey =
psc.ProductSubcategoryKey JOIN
dbo.DimProductCategory pc ON psc.ProductCategoryKey =
pc.ProductCategoryKey JOIN
dbo.DimDate d ON s.OrderDateKey = d .DateKey
GROUP BY pc.EnglishProductCategoryName, psc.EnglishProductSubcategoryName, d
.CalendarYear, d .MonthNumberOfYear)
SELECT Род, Вид, ПользАгрегат FROM cte WHERE n = 1
Скрипт 2
Сейчас можно не вчитываться в тексты запросов. Лучше посмотреть на результаты. Основным
набором данных является Продажи_по_товарам_и_времени (Рис.1). В матрице отчета будет явно
фигурировать измерение Товар (уровни Род и Вид). Измерение Время (уровни Год и Месяц) в
матрице будет свернуто. Оно получит отражение в графиках sparkline против каждого вида товара. В
предыдущем посте речь шла о том, что при свертке мы не хотим использовать стандартные
агрегатные функции Reporting Services. Предположим, для каждого вида товара имеется уже готовый
посчитаный агрегат - см.набор данных Продажи_за_последний_непустой_месяц_по_товарам.
Остается связать между собой эти наборы по Товару и отобразить готовый агрегат из второго набора в
каждой строке первого. Вместо джойна в источнике данных будем использовать функцию Lookup в
Report Designer. В содержание колонки ПользАгрегат вместо Рис.6 предыдущего поста напишем
выражение:
Рис.3
=Lookup (
Fields!Род.Value.PadRight(100) + Fields!Вид.Value,
Fields!Род.Value.PadRight(100) + Fields!Вид.Value,
Fields!ПользАгрегат.Value,
"Продажи_за_последний_непустой_месяц_по_товарам"
)
Скрипт 3
Первый аргумент функции Lookup принимает значение "ключа" из исходного набора данных.
Композитный ключ не поддерживается. Поскольку в данном случае ключевыми полями являются Род
и Вид, приходится сделать из них скаляр по типу вычисляемой колонки, сопоставив каждой паре
значений Род, Вид уникальное значение. Второй аргумент содержит значение ключа в целевом
(lookup-) наборе данных, которому должен соответствовать первый. Третий - это значение какого
поля из целевого (lookup-) набора данных возвратит функция в случае нахождения соответствия.
Наконец, последний, четвертый - это, собственно, имя целевого (lookup-) набора данных, чуждого по
отношению к данному табликсу, значение из которого (третий аргумент) тем не менее требуется в
него положить. Запускаем отчет и видим, что все работает:.
Рис.4
Функция Multilookup отличается тем, что вместо скалярного значения в кач-ве первого аргумента
принимает массив значений, для каждого из которых делает ровно то же, что и рассмотренная только
что Lookup. Соответственно, на выходе тоже получается не одно соответствие, а массив значений из
поля (аргумент 3) датасета (аргумент 4). Функция LookupSet, как гласит документация, обрабатывает
связи 1:n, т.е. ищет не первое соответствие, как Lookup, а все с данным значением ключа. В качестве
первого аргумента она, как и Lookup принимает скалярное значение из набора данных (датасета)
данного региона данных (табликса), а на выходе, как и в Multilookup, получается массив.
***
Мы разобрали только считанные улучшения SQL Server Reporting Services 2008 R2, которыми,
естественно, не ограничивается весь спектр добавленной функциональности. Ввиду ограниченного
объема не были затронуты такие замечательные вещи, как кэширование наборов данных,
позволяющее повысить производительность при разработке и использовании отчета, поддержка
AJAX в элементе управления ReportViewer; не был рассмотрен целый пласт новых возможностей,
связанных с интеграцией с ESRI и геопространственными расширениями SQL Server, позволяющих
создавать картографические отчеты и тем самым резко повысить информативность и наглядность
представления отчетной информации, а также многие другие нововведения. Информацию о них
можно получить на сайте официальной документации SQL Server Reporting Services
(http://msdn.microsoft.com/ru-ru/library/ms159106(v=SQL.105).aspx), и, разумеется, мы продолжим ее
освещать в ходе дальнейших мероприятий.
Download