DAX для профессионалов: теория и практика: Выведи свои аналитические навыки в Microsoft Power BI на новый уровень 9785937001672, 9781801078511

В книге излагаются основы моделирования данных с точки зрения языка DAX. Разбираются реальные бизнес-сценарии, связанные

156 5 23MB

Russian Pages 404 Year 2023

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

DAX для профессионалов: теория и практика: Выведи свои аналитические навыки в Microsoft Power BI на новый уровень
 9785937001672, 9781801078511

Table of contents :
Оглавление
Об авторах
О редакторе
Предисловие от издательства
Предисловие
Для кого эта книга
Структура книги
Как извлечь максимум из книги
Сопроводительные файлы
Цветные изображения
Условные обозначения
Часть I. Введение
Глава 1.1. DAX в бизнес-аналитике
Пятислойная модель бизнес-аналитики
Аналитика предприятия и аналитика конечных пользователей
Для чего используется DAX и где его найти
Excel
Power BI
SQL Server Analysis Services
Azure Analysis Services
Инструменты для разработки моделей и написания выражений DAX
Сделано при помощи DAX: визуальные элементы и интерактивные отчеты
Подходы к разработке решений
Использование Power BI для ускорения разработки аналитических решений
Мы не знаем точно, что нам нужно
Наши данные некорректны
Цикл цифровой трансформации
Заключение
Глава 1.2. Разработка модели
Колоночное хранение данных
Реляционные базы данных
Колоночные базы данных
Типы данных и кодирование
Связи
Данные в Excel
Реляционные базы данных
Реляционная модель Power BI
Свойства связей
Активные и неактивные связи
Направление фильтрации
Кратность
Оптимальная структура модели данных
Схемы «звезда» и «снежинка»
Проблемы, присущие схеме «звезда»
Концепции реляционных моделей, которых нужно избегать при моделировании в Power BI
Независимые измерения
Только одна таблица фактов
Хранилище данных как единый источник истины
Использование связей типа «многие ко многим»
Производительность модели и использование памяти
Заключение
Глава 1.3. Применение DAX
Вычисляемые столбцы
Вычисляемые таблицы
Меры
Фильтры безопасности
Запросы DAX
Таблицы дат
Создание таблицы дат
Практические советы по использованию языка DAX
Первым делом меры!
Создавайте явные меры
Используйте базовые меры в качестве строительных блоков
Прячьте от пользователей ненужные им элементы модели
Не смешивайте данные и меры – создайте для мер отдельную таблицу
Типы таблиц
Заключение
Глава 1.4. Контексты и фильтры
Введение в контексты DAX
Контекст строки
Контекст запроса
Контекст фильтра
Обнаружение фильтров
Сравнение контекста запроса и фильтра с контекстом строки
Фильтрация в DAX: использование функции CALCULATE
Шаг 1. Настройка контекста фильтра
Шаг 2. Удаление существующих фильтров
Шаг 3. Применение новых фильтров
Шаг 4. Вычисление выражения в новом контексте
Удаление фильтров при помощи функций группы ALL
Функции логики операций со временем
Изменение поведения связей
Табличные функции в DAX
Табличные агрегации
Использование виртуальных таблиц
Контекст в табличных функциях
Производительность при использовании табличных функций
Фильтрация при помощи табличных функций
Использование функции CALCULATETABLE
Фильтры и таблицы
Использование функции TREATAS
Переменные в DAX
Заключение
Часть II. Применение DAX на реальных примерах
Глава 2.1. Безопасность в DAX
Знакомство с безопасностью на уровне строк (RLS)
Роли безопасности
Фильтры безопасности в DAX
Динамическая безопасность на уровне строк
Особенности моделирования данных с RLS
Проверка ролей безопасности
Тестирование отчетов с динамическим подключением
Модель для имперсонализации
Добавление таблицы pImpersonation в модель данных
Добавление тестовой роли безопасности
Собираем все вместе
Иерархическая безопасность с применением функций PATH
Иерархические таблицы
Знакомство с функциями группы PATH
PATH
PATHCONTAINS
PATHLENGTH
PATHITEM
PATHITEMREVERSE
Использование функций PATH совместно с RLS
Продвинутая навигация по иерархии в RLS
Безопасность атрибутов
Применение безопасности атрибутов
Безопасность на уровне объектов и ее ограничения
Динамическая защита атрибутов: безопасность на уровне значений
Безопасность на уровне значений: моделирование данных
Безопасность на уровне значений: фильтры безопасности
Безопасность на уровне значений: сложные сценарии
Разработка моделей данных с безопасностью на уровне значений
Защита уровней агрегации
Меры не могут быть защищены, а таблицы фактов – могут
Ограничение гранулярности таблицы фактов
Защита уровней агрегации с помощью составных моделей данных
Сочетание защиты уровней агрегации с безопасностью на уровне значений
Защита уровня агрегации как атрибута
Заключение
Глава 2.2. Динамическое изменение визуализации
Рассматриваемый пример
Динамические меры
Базовые меры показателей
Создание вспомогательной таблицы
Создание динамической меры
Динамический выбор показателя и используемой даты
Динамические метки
Описание сценария
Создание вспомогательной таблицы
Создание меры DAX с использованием динамических меток
Комбинирование динамических меток с динамическими вычислениями
Заключение
Глава 2.3. Альтернативные календари
Недельный и григорианский календари
Что такое недельный календарь?
Номера недель
Периоды
Кварталы
Годы
Создание недельного календаря
Настройка дат
Определение даты начала года
Определение даты конца года
Создание дополнительных столбцов в таблице дат
Анализ с применением логики операций со временем для недельного календаря
Модель Power BI
Расчет нарастающих итогов с начала года
Расчет приростов продаж
Расчет скользящих средних продаж за отчетный год
Поддержание актуальности отчетов
Таблица Date Selection
Создание вариантов выбора в таблице
Использование вариантов выбора дат в мерах
Заключение
Глава 2.4. Использование механизма AutoExist
Модель данных Power BI
Как Power BI визуализирует данные из модели
Фильтры и контекст
Как использование мер способно изменить поведение визуального элемента
Знакомство с запросами DAX визуальных элементов
Что такое AutoExist и как он работает
Использование нескольких фильтров в визуальном элементе
Как AutoExist позволяет оптимизировать вычисление формул DAX
Пример: пропущенные рабочие дни
Условия задачи
Структура модели
Анализ сумм заказов
Расширение таблицы Calendar
Анализ рабочих дней
Куда делся мой рабочий день?
Как решить проблему пропущенных рабочих дней
Источник проблемы
Изменение структуры модели для обхода механизма AutoExist
Всегда учитывайте контекст
Исправляем ошибку с рабочими днями
Оптимизация производительности отчетов с AutoExist
Гранулярность в таблицах фактов
Фильтрация в нескольких таблицах фактов
Оптимизация структуры модели данных
Оптимизация визуальных элементов
Заключение
Глава 2.5. Взаимодействие внутри компании
Моделирование процесса ведения продаж в компании QuantoBikes
Процесс ведения продаж
Модель данных Power BI
Взаимоотношения между филиалами одной компании
Представление с филиалами против сводного представления
Сопоставление внутренних продаж и покупок
Визуализация внутренних движений компании
Будущие продажи
Анализ одиночных продаж
Анализ долгосрочных продаж
Анализ старых заказов
Анализ текущих заказов
Оптимизация расчета для текущих заказов
Анализ излишков по счетам
Анализ своевременных заказов
Коррекция для запаздывающих заказов
Дальнейшая оптимизация
Тестирование сложных вычислений
Заключение
Глава 2.6. Исследуем будущее: прогнозирование и будущая стоимость
Финансовые расчеты
Приведенная стоимость и чистая приведенная стоимость
Внутренняя норма доходности
Финансовые функции DAX
Бизнес-сценарий и модель данных
Подбор приемлемой ставки и индексов
Вычисление будущей стоимости
Первоначальные инвестиции и остаточная стоимость
Нерегулярные денежные потоки
Повторяющиеся денежные потоки
Положительные и отрицательные денежные потоки
Вычисление чистой приведенной стоимости
Вычисление внутренней нормы доходности
Расчет аренды, покрывающей издержки
Поиск суммы покрывающей издержки аренды с помощью аппроксимации
Оптимизация аппроксимационного подхода
Заключение
Глава 2.7. Анализ состояния запасов
Моделирование данных, ориентированных на статусы
Гранулярность складских запасов
Базовые складские расчеты
Целевой уровень запасов
Прогнозирование складских запасов
Два типа прогнозирования
Прогнозирование продаж как инструмент предсказания изменений запасов
Экстраполяция как инструмент предсказания изменений запасов
Вычисление долгосрочных складских запасов
Использование целевого уровня запасов на основе прогноза
Использование линейной регрессии при экстраполяции товарных запасов
Заключение
Глава 2.8. Планирование кадровых ресурсов
Модель данных Power BI для планирования кадровых ресурсов
Расчет показателей продажи
Оптимизация расчета показателей продажи
Вычисление требуемых объемов ресурсов для реализованных проектов
Работа с итоговыми значениями
Оптимизация вычисления трудовых ресурсов
Оптимизация модели данных Power BI
Уровни агрегации
Заключение
Предметный указатель

Citation preview

Extreme DAX Take your Power BI and Microsoft data analytics skills to the next level Michiel Rozema Henk Vlootman

BIRMINGHAM—MUMBAI

DAX для профессионалов: теория и практика Выведи свои аналитические навыки в Microsoft Power BI на новый уровень Мишель Розема Хенк Влотман

Москва, 2023

УДК 004.42DAX ББК 32.97 В57

В57

Влотман Х., Розема М. DAX для профессионалов: теория и практика: Выведи свои аналитические навыки в Microsoft Power BI на новый уровень / пер. с англ. А. Ю. Гинько. – М.: ДМК Пресс, 2023. – 404 с.: ил. ISBN 978-5-93700-167-2 В книге излагаются основы моделирования данных с точки зрения языка DAX. Разбираются реальные бизнес-сценарии, связанные с учетом складских запасов, прогнозированием, взаимодействием между отделами в рамках компании и безопасностью данных. Прорабатываются нюансы моделирования данных и распространенные ошибки, допускаемые при построении сложных агрегаций. Издание поможет бизнес-аналитикам в построении мощных аналитических решений на основе данных с использованием всего потенциала доступных инструментов.

УДК 004.42DAX ББК 32.97 First published in the English language under the title Extreme DAX – (9781801078511) Все права защищены. Любая часть этой книги не может быть воспроизведена в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.

ISBN (анг.) 978-1-80107-851-1 ISBN (рус.) 978-5-93700-167-2

Copyright © Packt Publishing 2022 © Оформление, издание, перевод, ДМК Пресс, 2023

Оглавление Об авторах.........................................................................................12 О редакторе.......................................................................................13 Предисловие от издательства ..........................................................14 Предисловие .....................................................................................15

ЧАСТЬ I ВВЕДЕНИЕ ...................................................................... 19 Глава 1.1 DAX в бизнес-аналитике ................................................ 20 Пятислойная модель бизнес-аналитики ........................................21 Аналитика предприятия и аналитика конечных пользователей .... 22 Для чего используется DAX и где его найти ...................................24 Excel ..............................................................................................25 Power BI ........................................................................................25 SQL Server Analysis Services ........................................................25 Azure Analysis Services.................................................................26 Инструменты для разработки моделей и написания выражений DAX ................................................................................26 Сделано при помощи DAX: визуальные элементы и интерактивные отчеты .................................................................27 Подходы к разработке решений ......................................................30 Использование Power BI для ускорения разработки аналитических решений .............................................................31 Мы не знаем точно, что нам нужно ...........................................32 Наши данные некорректны ........................................................33 Цикл цифровой трансформации .....................................................33 Заключение .......................................................................................35

Глава 1.2 Разработка модели ......................................................... 36 Колоночное хранение данных .........................................................36 Реляционные базы данных .........................................................36 Колоночные базы данных ...........................................................37 Типы данных и кодирование...........................................................38 Связи .................................................................................................41 Данные в Excel .............................................................................41 Реляционные базы данных .........................................................42 Реляционная модель Power BI ....................................................43 Свойства связей...........................................................................44 Активные и неактивные связи ..............................................45 Направление фильтрации .....................................................46 Кратность .....................................................................................49 Оглавление

5

Оптимальная структура модели данных ........................................50 Схемы «звезда» и «снежинка» ....................................................50 Проблемы, присущие схеме «звезда» ........................................51 Концепции реляционных моделей, которых нужно избегать при моделировании в Power BI ...................................52 Независимые измерения .......................................................52 Только одна таблица фактов..................................................54 Хранилище данных как единый источник истины .............55 Использование связей типа «многие ко многим» ...............56 Производительность модели и использование памяти ................57 Заключение .......................................................................................59

Глава 1.3 Применение DAX ............................................................. 60 Вычисляемые столбцы .....................................................................60 Вычисляемые таблицы.....................................................................62 Меры..................................................................................................64 Фильтры безопасности ....................................................................66 Запросы DAX .....................................................................................66 Таблицы дат ......................................................................................68 Создание таблицы дат.................................................................69 Практические советы по использованию языка DAX ....................71 Первым делом меры! ..................................................................71 Создавайте явные меры..............................................................71 Используйте базовые меры в качестве строительных блоков .... 71 Прячьте от пользователей ненужные им элементы модели ....72 Не смешивайте данные и меры – создайте для мер отдельную таблицу ......................................................................72 Типы таблиц .....................................................................................74 Заключение .......................................................................................75

Глава 1.4 Контексты и фильтры ..................................................... 76 Введение в контексты DAX ..............................................................77 Контекст строки...........................................................................77 Контекст запроса .........................................................................79 Контекст фильтра ........................................................................81 Обнаружение фильтров ..............................................................82 Сравнение контекста запроса и фильтра с контекстом строки...........................................................................................83 Фильтрация в DAX: использование функции CALCULATE ............83 Шаг 1. Настройка контекста фильтра .........................................85 Шаг 2. Удаление существующих фильтров ................................87 Шаг 3. Применение новых фильтров .........................................87 Шаг 4. Вычисление выражения в новом контексте...................88 Удаление фильтров при помощи функций группы ALL ...........90 Функции логики операций со временем ........................................93 Изменение поведения связей .........................................................96 6

Оглавление

Табличные функции в DAX ..............................................................98 Табличные агрегации..................................................................98 Использование виртуальных таблиц .......................................100 Контекст в табличных функциях..............................................102 Производительность при использовании табличных функций ..................................................................105 Фильтрация при помощи табличных функций ...........................106 Использование функции CALCULATETABLE ...........................107 Фильтры и таблицы ...................................................................108 Использование функции TREATAS...........................................112 Переменные в DAX .........................................................................113 Заключение .....................................................................................116

ЧАСТЬ II ПРИМЕНЕНИЕ DAX НА РЕАЛЬНЫХ ПРИМЕРАХ ......................................... 117 Глава 2.1 Безопасность в DAX ......................................................118 Знакомство с безопасностью на уровне строк (RLS)....................118 Роли безопасности ....................................................................119 Фильтры безопасности в DAX...................................................121 Динамическая безопасность на уровне строк.........................122 Особенности моделирования данных с RLS ............................125 Проверка ролей безопасности..................................................130 Тестирование отчетов с динамическим подключением ........132 Модель для имперсонализации ..........................................133 Добавление таблицы pImpersonation в модель данных ....134 Добавление тестовой роли безопасности ...........................134 Собираем все вместе ............................................................136 Иерархическая безопасность с применением функций PATH ....137 Иерархические таблицы ...........................................................137 Знакомство с функциями группы PATH ..................................138 PATH ......................................................................................138 PATHCONTAINS.....................................................................139 PATHLENGTH ........................................................................139 PATHITEM..............................................................................139 PATHITEMREVERSE ..............................................................139 Использование функций PATH совместно с RLS .....................140 Продвинутая навигация по иерархии в RLS............................140 Безопасность атрибутов.................................................................143 Применение безопасности атрибутов .....................................144 Безопасность на уровне объектов и ее ограничения ..............144 Динамическая защита атрибутов: безопасность на уровне значений...................................................................145 Безопасность на уровне значений: моделирование данных ............................................................146 Безопасность на уровне значений: фильтры безопасности ..149 Безопасность на уровне значений: сложные сценарии .........150 Оглавление

7

Разработка моделей данных с безопасностью на уровне значений...................................................................153 Защита уровней агрегации ............................................................153 Меры не могут быть защищены, а таблицы фактов – могут ..... 154 Ограничение гранулярности таблицы фактов ........................154 Защита уровней агрегации с помощью составных моделей данных ........................................................................155 Сочетание защиты уровней агрегации с безопасностью на уровне значений...................................................................158 Защита уровня агрегации как атрибута ..................................161 Заключение .....................................................................................165

Глава 2.2 Динамическое изменение визуализации .................166 Рассматриваемый пример .............................................................167 Динамические меры ......................................................................169 Базовые меры показателей ......................................................169 Создание вспомогательной таблицы .......................................171 Создание динамической меры .................................................172 Динамический выбор показателя и используемой даты .......174 Динамические метки .....................................................................179 Описание сценария ...................................................................180 Создание вспомогательной таблицы .......................................180 Создание меры DAX с использованием динамических меток ..........................................................................................182 Комбинирование динамических меток с динамическими вычислениями ................................................................................184 Заключение .....................................................................................186

Глава 2.3 Альтернативные календари ........................................ 187 Недельный и григорианский календари ......................................187 Что такое недельный календарь? .............................................188 Номера недель ...........................................................................188 Периоды .....................................................................................190 Кварталы ....................................................................................190 Годы ............................................................................................191 Создание недельного календаря ...................................................191 Настройка дат ............................................................................191 Определение даты начала года ................................................193 Определение даты конца года..................................................194 Создание дополнительных столбцов в таблице дат ...............196 Анализ с применением логики операций со временем для недельного календаря .............................................................199 Модель Power BI .........................................................................199 Расчет нарастающих итогов с начала года ..............................201 Расчет приростов продаж .........................................................203 Расчет скользящих средних продаж за отчетный год ............207 Поддержание актуальности отчетов .............................................211 8

Оглавление

Таблица Date Selection ..............................................................211 Создание вариантов выбора в таблице ...................................214 Использование вариантов выбора дат в мерах.......................217 Заключение .....................................................................................219

Глава 2.4 Использование механизма AutoExist ........................220 Модель данных Power BI ................................................................220 Как Power BI визуализирует данные из модели ...........................222 Фильтры и контекст ..................................................................222 Как использование мер способно изменить поведение визуального элемента ...............................................................224 Знакомство с запросами DAX визуальных элементов............226 Что такое AutoExist и как он работает ..........................................229 Использование нескольких фильтров в визуальном элементе.....................................................................................229 Как AutoExist позволяет оптимизировать вычисление формул DAX ..........................................................231 Пример: пропущенные рабочие дни ............................................233 Условия задачи ..........................................................................234 Структура модели ......................................................................234 Анализ сумм заказов.................................................................235 Расширение таблицы Calendar .................................................238 Анализ рабочих дней ................................................................240 Куда делся мой рабочий день? .................................................241 Как решить проблему пропущенных рабочих дней ....................242 Источник проблемы ..................................................................243 Изменение структуры модели для обхода  механизма AutoExist .................................................................244 Всегда учитывайте контекст.....................................................244 Исправляем ошибку с рабочими днями ..................................248 Оптимизация производительности отчетов с AutoExist.............250 Гранулярность в таблицах фактов ............................................250 Фильтрация в нескольких таблицах фактов ............................251 Оптимизация структуры модели данных................................254 Оптимизация визуальных элементов .....................................255 Заключение .....................................................................................257

Глава 2.5 Взаимодействие внутри компании ............................258 Моделирование процесса ведения продаж в компании QuantoBikes .....................................................................................258 Процесс ведения продаж ..........................................................259 Модель данных Power BI ...........................................................260 Взаимоотношения между филиалами одной компании .............262 Представление с филиалами против сводного представления ...........................................................................263 Сопоставление внутренних продаж и покупок ......................264 Визуализация внутренних движений компании ....................268 Оглавление

9

Будущие продажи ...........................................................................273 Анализ одиночных продаж.......................................................273 Анализ долгосрочных продаж ..................................................276 Анализ старых заказов..............................................................278 Анализ текущих заказов ...........................................................282 Оптимизация расчета для текущих заказов ...........................288 Анализ излишков по счетам.....................................................290 Анализ своевременных заказов ...............................................294 Коррекция для запаздывающих заказов .................................296 Дальнейшая оптимизация........................................................299 Тестирование сложных вычислений ........................................301 Заключение .....................................................................................303

Глава 2.6 Исследуем будущее: прогнозирование и будущая стоимость ....................................................305 Финансовые расчеты .....................................................................306 Приведенная стоимость и чистая приведенная стоимость .... 307 Внутренняя норма доходности ................................................308 Финансовые функции DAX ............................................................308 Бизнес-сценарий и модель данных ..............................................311 Подбор приемлемой ставки и индексов ..................................314 Вычисление будущей стоимости...................................................316 Первоначальные инвестиции и остаточная стоимость..........316 Нерегулярные денежные потоки .............................................317 Повторяющиеся денежные потоки ..........................................318 Положительные и отрицательные денежные потоки.............321 Вычисление чистой приведенной стоимости ..............................322 Вычисление внутренней нормы доходности ...............................326 Расчет аренды, покрывающей издержки .....................................328 Поиск суммы покрывающей издержки аренды с помощью аппроксимации .....................................................329 Оптимизация аппроксимационного подхода.........................333 Заключение .....................................................................................338

Глава 2.7 Анализ состояния запасов ...........................................339 Моделирование данных, ориентированных на статусы .............339 Гранулярность складских запасов............................................344 Базовые складские расчеты ...........................................................346 Целевой уровень запасов ..........................................................349 Прогнозирование складских запасов ...........................................355 Два типа прогнозирования.......................................................355 Прогнозирование продаж как инструмент предсказания изменений запасов ......................................356 Экстраполяция как инструмент предсказания изменений запасов ..............................................................360 Вычисление долгосрочных складских запасов .......................366 10

Оглавление

Использование целевого уровня запасов на основе прогноза ...................................................................370 Использование линейной регрессии при экстраполяции товарных запасов.....................................372 Заключение .....................................................................................376

Глава 2.8 Планирование кадровых ресурсов ............................ 377 Модель данных Power BI для планирования кадровых ресурсов .........................................................................377 Расчет показателей продажи .........................................................380 Оптимизация расчета показателей продажи..........................383 Вычисление требуемых объемов ресурсов для реализованных проектов ........................................................385 Работа с итоговыми значениями .............................................389 Оптимизация вычисления трудовых ресурсов .......................390 Оптимизация модели данных Power BI ........................................393 Уровни агрегации ...........................................................................396 Заключение .....................................................................................397

Предметный указатель ..................................................................399

Об авторах Мишель Розема (Michiel Rozema) – один из ведущих специалистов в области Power BI из Нидерландов. Он обладает степенью магистра по математике и работает в сфере информационных технологий в роли управляющего и консультанта уже более 25 лет. В течение восьми лет Мишель возглавлял отдел аналитики в Microsoft Netherlands, запустив в стране платформу Power BI. Является автором двух книг на голландском языке, посвященных Power Pivot и Power BI. Также состоит в числе основателей официальной группы голландских пользователей Power BI и организаторов Power BI Summer School. Выступает на различных конференциях по Power BI. В 2019 году Мишель впервые стал обладателем статуса Microsoft MVP и вместе с соавтором книги Хенком Влотманом, также обладающим статусом MVP, основал компанию Quanto, специализирующуюся на консультациях в области Power BI. Хенк Влотман (Henk Vlootman)  – старший бизнес-консультант в  области Power Platform, Power BI и Excel. Является обладателем статуса Microsoft MVP с 2013 года и сооснователем официальной группы голландских пользователей Power BI и Power BI Summer School. Выступает в роли спикера на многочисленных конференциях, посвященных Power BI, по всему миру. Хенк написал две книги по Excel и две по Power Pivot/Power BI. Карьеру начал в 1992 году, основав собственную компанию, после чего добился успеха в роли консультанта по Excel. В настоящее время вместе с соавтором книги Мишелем Роземой управляет компанией Quanto, специализирующейся на Power BI.

О редакторе Грег Деклер (Greg Deckler) – обладатель статуса MVP в категории Data Platform и активный член сообщества информационных технологий в Колумбусе, штат Огайо. Является основателем пользовательской группы Columbus Azure ML and Power BI User Group (CAMLPUG) и  спикером многочисленных конференций в США. Будучи активным блогером и членом сообщества, много помогает новичкам в области Power BI. Имеет в своем активе более 180 загрузок в Power BI Quick Measures Gallery и более 5000 ответов на вопросы пользователей. Грег является заместителем председателя отдела облачных технологий в региональной компании Fusion Alliance. Его перу принадлежат три книги, посвященные Power BI: Learn Power BI, DAX Cookbook и Power BI Cookbook, второе издание. Также он разработал внешний инструмент для Power BI Desktop под названием Microsoft Hates Greg's Quick Measures и записывает видео по Power BI на своем канале в YouTube. Я бы хотел поблагодарить моего сына, семью и все сообщество Power BI за их поддержку.

Предисловие от издательства Отзывы и пожелания Мы всегда рады отзывам наших читателей. Расскажите нам, что вы думаете об этой книге – что понравилось или, может быть, не понравилось. Отзывы важны для нас, чтобы выпускать книги, которые будут для вас максимально полезны. Вы можете написать отзыв на нашем сайте www.dmkpress.com, зайдя на страницу книги и оставив комментарий в разделе «Отзывы и рецензии». Также можно послать письмо главному редактору по адресу [email protected]; при этом укажите название книги в теме письма. Если вы являетесь экспертом в какой-либо области и заинтересованы в написании новой книги, заполните форму на нашем сайте по адресу http://dmkpress.com/ authors/publish_book/ или напишите в издательство по адресу [email protected].

Список опечаток Хотя мы приняли все возможные меры для того, чтобы обеспечить высокое качество наших текстов, ошибки все равно случаются. Если вы найдете ошибку в одной из наших книг – возможно, ошибку в основном тексте или программном коде, – мы будем очень благодарны, если вы сообщите нам о ней. Сделав это, вы избавите других читателей от недопонимания и поможете нам улучшить последующие издания этой книги. Если вы найдете какие-либо ошибки в  коде, пожалуйста, сообщите о  них главному редактору по адресу [email protected], и мы исправим это в следующих тиражах.

Нарушение авторских прав Пиратство в  интернете по-прежнему остается насущной проблемой. Издательство «ДМК Пресс» очень серьезно относится к  вопросам защиты авторских прав и лицензирования. Если вы столкнетесь в интернете с незаконной публикацией какой-либо из наших книг, пожалуйста, пришлите нам ссылку на интернет-ресурс, чтобы мы могли применить санкции. Ссылку на подозрительные материалы можно прислать по адресу [email protected]. Мы высоко ценим любую помощь по защите наших авторов, благодаря которой мы можем предоставлять вам качественные материалы. 14

Предисловие от издательства

Предисловие Прочитав эту книгу, вы сможете вывести свои аналитические навыки работы с платформой Power BI на новый качественный уровень. Вы узнаете об истинной мощи языка запросов DAX и научитесь строить на его основе эффективные решения в практической области.

Для кого эта книга Если вы – аналитик со знанием DAX в Power BI или других аналитических инструментов Microsoft, эта книга поможет вам выйти на новый уровень владения языком DAX и начать использовать аналитические модели гораздо более эффективно. Эта книга не для новичков – практический опыт работы с DAX вам все-таки понадобится.

Структура книги Глава 1.1. DAX в бизнес-аналитике. В этой главе мы рассмотрим сферу бизнес-аналитики в целом и аналитические модели в современных решениях, которым отводится центральная роль в этой области. Модели Power BI идеально подходят для применения в этой среде, и не в последнюю очередь благодаря мощи языка DAX. Глава 1.2. Разработка модели. Во второй главе книги мы поговорим о фундаментальных концепциях, лежащих в основе моделей Power BI. Вы узнаете, чем модели данных в Power BI принципиально отличаются от других систем управления данными и как выглядит оптимальная модель в этой платформе. Глава 1.3. Применение DAX. В этой главе мы пройдемся по концепциям, реализуемым в Power BI с помощью языка DAX, в числе которых – вычисляемые столбцы и таблицы, меры, правила безопасности и  запросы. Мы также дадим несколько полезных советов по работе с DAX. Глава 1.4. Контексты и фильтры. Здесь мы поговорим о контексте строки, контексте запроса и контексте фильтра, а также обсудим, какую роль играют контексты при вычислении формул DAX. Кроме того, мы посмотрим, как можно преобразовывать контексты при помощи функции CALCULATE, добавляя и удаляя фильтры из существующих контекстов. В дополнение мы познакомимся с функциями логики операций со временем и  табличными функциями DAX, узнаем о связи между таблицами и фильтрами, а также поговорим о переменных DAX. Все эти главы будут посвящены базовым концепциям моделирования данных и языка DAX, чтобы подготовить вас к более глубокому погружению в тему. Предисловие

15

Во  второй части книги мы рассмотрим более продвинутые приемы работы с DAX на примерах из реальной практики, с которыми мы сталкивались и продолжаем сталкиваться в своей работе. Глава 2.1. Безопасность в DAX. В этой главе мы поговорим о концепциях безопасности в моделях Power BI и роли DAX в этой области. Мы подробно обсудим механизм безопасности на уровне строк, роли безопасности, иерархии, атрибуты и уровни агрегации в процессе моделирования данных с применением DAX. Глава  2.2. Динамическое изменение визуализации. Здесь мы рассмотрим использование таблиц-помощников и  функции SWITCH для перехвата пользовательского ввода. Мы продемонстрируем богатые возможности динамического изменения привязки данных с помощью DAX с целью создания высокодинамичных визуальных элементов. В зависимости от ваших намерений таблица-помощник может содержать всего несколько строк с  параметрами или большой список значений на основе других данных из модели Power BI. Глава 2.3. Альтернативные календари. В этой главе мы поговорим о реализации логики операций со временем в  случаях, когда используемый вами календарь отличается от стандартного григорианского, применение которого подразумевается по умолчанию в моделях Power BI. В конце главы мы предложим более гибкую альтернативу фильтру Power BI с относительными датами, которая будет поддерживать работу с нестандартными календарями. Глава 2.4. Использование механизма AutoExist. Здесь мы поговорим о том, какие именно вычисления производятся при наполнении визуального элемента данными из модели Power BI. Понимание принципов работы механизма AutoExist позволит вам лучше разобраться в том, почему иногда данные в визуальных элементах не соответствуют вашим ожиданиям. Кроме того, вы узнаете, что именно этому полезному механизму мы обязаны быстротой формирования отчетов с визуальными элементами, в которых задействовано множество столбцов из разных таблиц. Глава 2.5. Взаимодействие внутри компании. В этой главе обсуждаются такие распространенные бизнес-сценарии, как организация взаимодействия в рамках одной компании и консолидированные представления, а также выставление счетов по открытым продажам. Мы узнаем, как отслеживать состояние контекста, строить меры на DAX и проводить углубленный анализ. Глава 2.6. Исследуем будущее: прогнозирование и будущая стоимость. В данной главе мы поговорим о финансовых показателях для анализа прогноза инвестиций. Мы  обсудим такие распространенные метрики, как будущая стоимость, приведенная стоимость, чистая приведенная стоимость и внутренняя норма доходности, а также функции DAX для их расчета: XNPV и XIRR. Кроме того, мы познакомимся с параметрами What-if и научимся использовать их в сложных вычислениях. Глава 2.7. Анализ состояния запасов. Здесь мы рассмотрим способы анализа складских запасов, хотя все указанные приемы могут быть с легкостью применены к  анализу любых данных, связанных со статусами. Мы  обсудим варианты моделирования таких данных, научимся рассчитывать состояние запасов на любой момент времени и сравнивать фактические значения с целевыми. Также мы заглянем в будущее и научимся строить прогнозы в DAX, в том числе при помощи линейной регрессии. 16

Предисловие

Глава 2.8. Планирование кадровых ресурсов. В  заключительной главе книги мы поговорим об анализе потребности организации в человеческих ресурсах (выраженных в штатных единицах) при подготовке к проектам. С технической точки зрения мы на этом примере научимся работать с несколькими таблицами фактов, к которым нужно будет обращаться одновременно для получения требуемого результата. При этом нашей целью будет не только извлечение корректных цифр, но и оптимизация этого процесса.

Как извлечь максимум из книги Как мы уже упоминали, для комфортного чтения этой книги вы должны обладать определенным практическим опытом работы с DAX и желанием развивать его и узнать что-то новое о применении этого языка запросов в сложных нестандартных сценариях. Таким образом, мы не будем подробно рассказывать о  том, как работают функции DAX и  какие аргументы они принимают, если речь не идет об исключительных случаях, поскольку всю эту вводную информацию можно легко найти в документации. Все модели данных, обсуждаемые в этой книге, можно загрузить из хранилища GitHub по адресу https://github.com/PacktPublishing/Extreme-DAX и со страницы книги. Кроме того, в каждой главе мы будем давать ссылку на конкретный файл или файлы PBIX, с которыми будем работать. Мы рекомендуем вам параллельно с чтением разбирать указанные файлы самостоятельно. Для открытия файлов вы можете использовать последнюю версию Power BI Desktop. Примеры всех визуализаций и запросов, показанные в этой книге, реализованы в сопроводительных файлах.

Сопроводительные файлы Сопроводительные файлы к книге можно загрузить со страницы на сайте издательства и из хранилища GitHub по адресу https://github.com/PacktPublishing/ Extreme-DAX.

Цветные изображения По следующей ссылке вы можете скачать в виде PDF все рисунки и диаграммы, использованные в книге: https://static.packt-cdn.com/downloads/9781801078511_ ColorImages.pdf.

Условные обозначения На протяжении книги мы будем использовать следующие условные обозначения и шрифты. Предисловие

17

Код в тексте: так в тексте книги мы будем выделять код на языке DAX. Пример: «Применение неактивных связей позволит вам избежать многократного включения выражения ALL('Calendar') в код DAX». Блоки кода будут выделены следующим образом: Total Sales = CALCULATE( SUM(fProjectSales[Budget]), USERELATIONSHIP(fProjectSales[StartDate], 'Calendar'[Date]) )

Для привлечения внимания к  важной части текста мы будем использовать курсив. Пример: «Обратите внимание, что связь между таблицами fProjectSales и Calendar мы сделали неактивной. Причина в том, что в большинстве вычислений мы не  будем группировать проекты по дате начала, а вместо этого будем распределять результаты по целому временному интервалу, начинающемуся с этой даты». Новые термины, важные слова и текст, который вы видите на экране, будут подсвечены жирным шрифтом, например: «Раздел Workloads содержит настройки, связанные с производительностью». Предупреждения и важные примечания будут выводиться так.

Ч АС Т Ь I

Введение

ГЛ А В А 1.1

DAX в бизнес-аналитике Нет сомнений в том, что в наше время одним из главных активов любой организации является информация. Мы прекрасно ощущаем это на себе, когда компании выстраиваются в очередь в погоне за нашими личными данными. И это происходит не от того, что мы им интересны как личности (хотя, разумеется, среди нас есть и выдающиеся люди), просто в совокупности данные о нас всех позволяют компаниям делать важные выводы и тем самым повышать эффективность своего бизнеса. И это верно не только применительно к коммерческим структурам. Общественные организации, клиники и университеты также активно ведут сбор информации в  целях улучшения качества своей деятельности. Так или иначе, сегодня именно информация является двигателем прогресса и инноваций. В то же время процесс преобразования исходных данных в  информацию, а затем в аналитические сведения может быть весьма сложным и утомительным. Он включает в себя сбор данных из самых разных источников, исследование скрытых структур и зависимостей в них с учетом требуемого контекста. Именно поэтому задачи по анализу данных традиционно отдаются на откуп отделам информационных технологий, хотя это далеко не всегда бывает оптимально, поскольку речь зачастую идет о бизнес-аналитике, и знание специфики ведения бизнеса здесь крайне важно. Эта книга посвящена одному из мощнейших средств аналитики на сегодняшний день – DAX (Data Analysis eXpressions – выражения для анализа данных). Мы предполагаем, что у вас уже есть определенный опыт работы с этим языком запросов и вы хотите улучшить свои навыки. А для этого очень важно понимать, для чего DAX подходит идеально, а для чего – нет. Кроме того, вы должны выработать привычку использовать DAX везде, где он может проявить себя наилучшим образом. В этой главе мы рассмотрим основные концепции, которые помогут вам заложить эти основы. Темы, которые будут рассмотрены в этой главе: „ пятислойная модель бизнес-аналитики; „ аналитика предприятия и аналитика конечных пользователей; „ для чего используется DAX и где его найти; „ инструменты для разработки моделей и написания выражений DAX; „ сделано при помощи DAX: визуальные элементы и  интерактивные отчеты; „ подходы к разработке решений; „ цикл цифровой трансформации. 20

ГлаВа 1.1

DAX в бизнес-аналитике

Пятислойная модель бизнес-аналитики Чтобы можно было обсуждать аналитику структурированно и всеобъемлюще, мы разработали простую схему, описывающую основные компоненты и ответственности в рамках аналитического решения. И имя для схемы, представленной на рис. 1.1.1, выбрано тоже простое – пятислойная модель бизнес-аналитики.

Распространение

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

Визуализация

Просмотр результатов, извлечение выводов и работа с отчетами и дашбордами

Анализ

Определение бизнес-представлений и реализация вычислений в аналитических моделях

Подготовка

Преобразование, очистка и объединение данных

Подключение

Исследование данных и их извлечение из источников Рис. 1.1.1. Пятислойная модель бизнес-аналитики

Первый и нижний слой – Подключение – знаменует собой отправную точку для аналитики: если вам нужно проанализировать данные, для начала их необходимо как-то получить. Источниками при этом могут быть файлы Excel, текстовые файлы, полноценные базы данных или серверы в интернете. Обычно исходные данные поставляются не в самом лучшем виде для анализа, особенно если они происходят из разных источников. И для исправления этой неприятности служит второй слой на нашей схеме – Подготовка. Этот важный процесс может включать в себя различные действия, такие как преобразование типов, трансформация данных, восстановление исторических сведений или их сопоставление на основе ключевых атрибутов. Получение чистых и опрятных наборов данных на этапах подключения и подготовки может отнять очень много сил и времени. Создание хранилища данных (data warehouse), – а именно этим занимаются в отделе ИТ при подготовке данных,  – зачастую выливается в  разработку сопутствующих проектов, которые длятся годами. Но самое грустное, что к моменту готовности хранилища все понимают, что мир давно ушел вперед и оно утратило свою актуальность. После приведения исходных данных в формат, готовый для обработки, приходит черед третьего слоя под названием Анализ. Именно здесь бьется сердце всего аналитического решения в  целом. Построение аналитических моделей позволяет осуществлять срез и  фильтрацию данных, рассчитывать агрегаты всех видов и добавлять вычисления для получения специфических выводов. ГлаВа 1.1

DAX в бизнес-аналитике

21

На слое Визуализация происходит создание отчетов и  дашбордов на основе созданных ранее аналитических моделей. Мы назвали этот слой Визуализация, а  не  просто Вывод, поскольку для нас принципиально не  просто предоставить пользователю результаты, а отобразить их таким образом, чтобы он мог сделать важные выводы и  сконцентрировать внимание на ключевых показателях. Классические отчеты со страницами детализации не очень подходят для извлечения важных сведений, и пользователю нередко приходится полностью выгружать отчет в Excel и там агрегировать нужные ему показатели. Действительно эффективная визуализация данных позволяет пользователю точно выделить для себя наиболее важные составляющие и при необходимости погрузиться в детальные сведения прямо из отчета. На верхнем слое – Распространение – выполняется осуществление доступа пользователей к отчетам и дашбордам по заранее установленным правилам. Строите ли вы свое аналитическое решение в Excel, используете ли Power BI, разрабатываете собственную систему бизнес-аналитики уровня предприятия или вовсе не используете никакие системы автоматизации, вы так или иначе будете проходить сквозь все пять слоев. При должной организации процесса разработки аналитического решения все слои четко отделены друг от друга, и  все преобразования данных выполняются в  нужном слое. Это позволяет, в  частности, избежать многократного дублирования логики. К  тому же четкое разграничение между слоями поможет легко и безболезненно справиться с возникшими изменениями – к примеру, при смене источников данных. Как вы увидите далее в этой главе, DAX живет именно в слое Анализ и тесно связан с  соседними слоями Подготовка и  Визуализация. Визуализации мы посвятим отдельный раздел, а  пока нам необходимо разобраться, кто и  что делает в бизнес-аналитике.

Аналитика предприятия и аналитика конечных пользователей Компании в наше время становятся все более зависимы от данных. В частности, они нередко оценивают свою деятельность, обращаясь к дашбордам с ключевыми показателями эффективности (key performance indicators – KPI). Обычно эти дашборды строятся, исходя из очень высоких стандартов – в высших эшелонах организаций тщательно обсуждают долгосрочную стратегию и бизнес-процессы, способы измерения KPI и варианты их отслеживания. В результате утвержденные дашборды с  выводом этих ключевых показателей могут долгое время не меняться, а их созданием и поддержкой занимается центральный отдел информационных технологий или специализированный центр бизнес-аналитики. Также следствием зависимости компаний от данных является тот факт, что каждое принимаемое решение основывается исключительно на полученных цифрах. В результате появляется необходимость в более динамичной аналитике, способной отвечать на оперативные, не запланированные заранее запросы. Такой вид аналитики получил название аналитика самообслуживания (selfservice BI), что предполагает возможность построения полноценных бизнесрешений без вмешательства отдела ИТ. Взглянув еще раз на нашу схему пя22

ГлаВа 1.1

DAX в бизнес-аналитике

тислойной модели бизнес-аналитики, вы можете понять, что это практически невозможно. Мало у кого из пользователей есть необходимые знания, навыки и время, чтобы обслуживать все пять слоев. В идеале конечные пользователи и отдел информационных технологий должны дополнять друг друга, занимаясь приоритетными для них задачами. Мы различаем два вида аналитики: аналитику предприятия (enterprise BI) и аналитику конечных пользователей (end-user BI). В первом случае вся аналитическая нагрузка по созданию и  поддержке решений ложится на плечи отдела ИТ, а в качестве технической базы для обслуживания большого количества пользователей используются масштабируемые серверные системы и  облачные платформы. При этом большое внимание уделяется качеству данных, а все сопутствующие процессы хорошо выстроены и отлажены. Что касается аналитики конечных пользователей, то здесь, как ясно из названия, ключевую роль играют бизнес-пользователи. Для качественного выполнения своей повседневной работы им необходимо быстро получать те или иные данные из общей системы, и они это делают. Многие из них пытаются строить собственные аналитические решения в Excel, однако сложность структуры документов в Excel в конечном счете приводит к еще большим временным затратам на поддержку актуальности данных и масштабирование решения. К тому же Excel не предназначен для обработки сколько-нибудь больших данных. Зная это, компания Microsoft расширила возможности Excel за счет Power Query и Power Pivot как средств подготовки и анализа данных, что позволило превратить этот инструмент в подобие полноценной аналитической платформы для конечного пользователя. В результате совокупность этих инструментов вылилась в продукт, который мы сегодня называем Power BI. Прелесть аналитической платформы Microsoft состоит в том, что в рамках нее инструменты аналитики предприятия и  аналитики конечного пользователя могут работать совместно без каких-либо сложностей. И движущей силой этого процесса выступает технология, лежащая в основе Power BI. По сути, с ее появлением мы получили возможность сочетать ранее разрозненные ветви аналитики в одной архитектуре с разными уровнями возможностей самообслуживания, что показано на рис. 1.1.2. Распространение

Работа с отчетами

Работа с отчетами

Работа с отчетами

Работа с отчетами

Визуализация

Корпоративные отчеты и дашборды

Построение отчетов на базе существующей модели

Построение отчетов на базе существующей модели

Построение отчетов на базе существующей модели

Корпоративные Корпоративные аналитические модели аналитические модели

Построение аналитических моделей

Построение аналитических моделей

Хранилище данных, ETL, озеро данных

Хранилище данных, ETL, озеро данных

Анализ Подготовка

Подключение

Хранилище данных, ETL, озеро данных

Сбор и подготовка данных

Свобода, ответственность, опыт

Рис. 1.1.2. Интеграция аналитики предприятия и аналитики конечного пользователя

ГлаВа 1.1 DAX в бизнес-аналитике

23

Наша пятислойная модель идеально подходит для демонстрации возможностей пользователей в  рамках системы самообслуживания. Пользователи, нуждающиеся в собственной аналитике, могут интегрироваться в общую аналитическую систему на любом уровне. К примеру, они могут ограничиться созданием собственных визуализаций на базе существующих (корпоративных) аналитических моделей или строить свои аналитические модели на основе централизованно подготовленных наборов данных. Наконец, опытные бизнес-пользователи могут самостоятельно собирать и  подготавливать данные, объединять их с корпоративными данными и создавать свои модели и отчеты. Более того, в своей системе они могут использовать объекты, созданные другими пользователями. Очевидно, что при движении вниз сквозь описанные выше слои значительно повышается и уровень сложности выполняемых пользователем операций. И речь идет не только о компетенциях, которыми, безусловно, он должен обладать, но и об ответственности в виде следования корпоративным стандартам и  инструкциям. Централизованные отделы ИТ и  бизнес-аналитики должны всячески способствовать тому, чтобы избранные пользователи могли строить собственные аналитические решения. При правильной организации это ведет к  созданию среды, в  которой все пользователи оказываются в  выигрыше от доступа к нужной им аналитической информации. Мы называем это коллективной аналитикой (collective analytics). Power BI предлагает возможности по реализации коллективной аналитики. Одним из таких инструментов является DAX, который способен наделить пользователей возможностью не только создавать свои собственные аналитические решения, но и успешно встраиваться в разработку решений корпоративного уровня. О последней возможности мы поговорим в одном из следующих разделов, а пока нужно напомнить вам, что такое DAX и где его найти.

Для чего используется DAX и где его найти В аналитических решениях, построенных на базе платформы Microsoft, DAX присутствует на слое Анализ. DAX живет внутри аналитических моделей в виде языка написания формул для определения вычислений и прочей логики. По сути, модели и DAX являются двумя сторонами одной и той же монеты: дизайн модели непосредственно влияет на сложность кода DAX, а ваши навыки в области DAX определяют вид и сложность модели (о ключевых концепциях моделирования данных мы будем говорить в главе 1.2). Мощь языка DAX кроется в его потрясающих возможностях в плане агрегации данных. В состав DAX входит множество функций и конструкций, помогающих выстроить агрегацию идеальным образом для получения необходимой аналитики. В прошлом далеко не все типы агрегаций можно было рассчитывать напрямую – вместо этого они должны были быть реализованы на этапе подготовки данных. К примеру, накопительные итоги с начала года могут быть рассчитаны в DAX при помощи одной функции, тогда как в Excel или традиционных инструментах отчетности вам понадобится настроить немало метрик, показывающих, какие транзакции принадлежат к  периоду накопления. 24

ГлаВа 1.1 DAX в бизнес-аналитике

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

Excel Начиная с версии 2010 Microsoft Excel предоставляет пользователям возможности по моделированию данных с помощью надстройки Power Pivot. Power Pivot, также именуемая Моделью данных (Data Model) в Excel, представляет собой полноценную аналитическую модель на основе DAX.

Power BI Самой продвинутой платформой для работы с данными в линейке компании Microsoft является, безусловно, Power BI. Впервые этот инструмент был представлен в качестве надстройки в Office 365, но в 2015 году был выделен в отдельный продукт. Аналитическая модель в Power BI называется набором данных Power BI (Power BI dataset) или просто набором данных, и именно здесь обитает DAX. Наборы данных Power BI и  другие объекты платформы функционируют в  рамках облачной службы Power BI (Power BI cloud service), доступ к  которой осуществляется с помощью сайта Power BI – powerbi.com. В числе других точек доступа к службе Power BI можно выделить мобильное приложение Power BI, платформу Microsoft Teams и даже пользовательские приложения, использующие для встраивания особую версию службы Power BI Embedded. Кроме того, есть еще Power BI Report Server – серверное программное обеспечение для клиентов, которые не могут или не хотят пользоваться облачными службами.

SQL Server Analysis Services Серверная платформа для работы с данными Microsoft SQL Server содержит аналитический компонент под названием Analysis Services (SSAS). Появившись на стыке веков под именем OLAP Services, SSAS на протяжении многих лет предГлаВа 1.1 DAX в бизнес-аналитике

25

ставлял собой классический сервер OLAP (Online Analytical Processing – оперативная аналитическая обработка данных), ныне известный как Многомерная модель (Multidimensional). Начиная с  версии SQL Server  2012 был представлен новый аналитический подход, получивший название Табличная модель (Tabular), в основе которого лежал язык DAX. Вас может заинтересовать вопрос о  различиях между двумя этими аналитическими концепциями, используемыми в  SSAS. Подробности этой темы выходят за рамки данной книги, но главное отличие состоит в  том, что многомерная модель основывается на классической реляционной концепции хранения данных, которая нелучшим образом подходит для выполнения агрегаций и сложных вычислений применительно к по-настоящему большим объемам данных. По сути, многомерная модель или куб (cube) выполняют все эти расчеты заранее, в момент обработки модели. В результате эта концепция стала значительно уступать в гибкости и динамике табличной модели данных, в которой благодаря мощи DAX все необходимые вычисления могут выполняться на лету, а разработчик отчетов не ограничен в работе только теми уровнями агрегации, которые были реализованы проектировщиком модели.

Azure Analysis Services Azure Analysis Services (AAS) представляет собой полностью управляемую облачную службу, в основе которой лежит тот же табличный движок, что и в основе SSAS. Разница лишь в том, что служба AAS работает исключительно в облаке, что позволяет компании не заботиться об обслуживании баз данных и серверов. Также это решение обладает большой гибкостью, поскольку вы можете при необходимости в любой момент масштабировать свои ресурсы, чтобы они отвечали вашим требованиям. Как мы уже сказали, аналитическая модель на основе DAX присутствует в разных продуктах под разными именами: Power Pivot, модель данных, набор данных или табличная модель. В результате мы столкнулись с определенными терминологическими сложностями при написании книги. Поскольку главным образом мы будем говорить о Power BI, мы решили использовать в книге термин модель Power BI (Power BI model) или просто модель. Термин аналитическая модель мы используем только при описании пятислойной структуры аналитики.

Инструменты для разработки моделей и написания выражений DAX Вы можете сами сделать выбор в пользу того или иного инструмента для работы с DAX в зависимости от целевой платформы модели: 26

ГлаВа 1.1

DAX в бизнес-аналитике

„ для работы с моделью Power Pivot вы можете использовать Excel с одноименной надстройкой; „ для набора данных Power BI подойдет Power BI Desktop; Стоит заметить, что фактически существует три версии Power BI Desktop. Одна из них может быть загружена с сайта Power BI по адресу powerbi.com. Вторая устанавливается из магазина Windows Store и автоматически обновляется, как и любое другое программное обеспечение из этого источника. А с учетом того, что обновления Power BI Desktop выпускаются почти ежемесячно, это немаловажно, хотя иногда бывает неприятно, когда обновления касаются областей, которые не вызывали вопросов. При желании вы можете установить на компьютер две версии одновременно. Третья версия Power BI Desktop, которая также может быть загружена с сайта Power BI, представляет собой специальную редакцию для работы с Power BI Report Server. „ для работы с моделью Tabular в SSAS или AAS вы можете использовать инструмент Visual Studio, предоставляющий богатые возможности для профессиональной разработки, включая интеграцию с системой контроля версий, возможность написания скриптов и совместимость; „ для наборов данных Power BI в емкости Power BI Premium вы можете выбирать между использованием Power BI Desktop и Visual Studio. Можно вести разработку посредством конечных точек XMLA (XMLA endpoint) – техники, реализованной в Power BI Premium и позволяющей набору данных Power BI извне выглядеть точно так же, как модель Tabular; „ также вы можете воспользоваться инструментами от сообщества, в частности Tabular Editor и DAX Studio. Они могут быть полностью интегрированы в Power BI Desktop. При написании данной книги мы использовали «чистую» установку Power BI Desktop – бесплатную, такую же, какая уже наверняка установлена у вас. Каждый читатель книги может без труда загрузить Power BI Desktop и с его помощью проверить все примеры из файлов, находящихся в репозитории GitHub по адресу https://github.com/PacktPublishing/Extreme-DAX и на странице книги.

Сделано при помощи DAX: визуальные элементы и интерактивные отчеты При обсуждении пятислойной модели бизнес-аналитики мы вскользь отметили важность визуальных отчетов. Но эффективное построение отчетов возможно только на основе моделей, использующих всю мощь DAX. ГлаВа 1.1

DAX в бизнес-аналитике

27

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

Рис. 1.1.3. Данные о продажах в табличном представлении

Можете ли вы быстро сказать, какие проблемы или возможности для развития есть у отчетной организации? Если да, наши поздравления – вы просто блестяще умеете работать с цифрами! Но большинство людей склонны анализировать более визуально оформленные отчеты. На рис. 1.1.4 приведен вариант графического представления тех же исходных данных. При взгляде на эту диаграмму становится очевидно, что один из товаров продается гораздо лучше остальных. Это очень интересный и  ценный вывод, позволяющий судить о том, что если компании удастся вывести продажи остальных товаров на тот же уровень, ее финансовое состояние значительно улучшится. Кроме того, данный вывод позволяет задуматься о том, что привлекательного есть в  этом товаре, пользующемся таким огромным спросом. Может, его всегда приобретает один и тот же покупатель? Или с ним связана какая-то специфическая география продаж? И как обстоят дела с маржинальностью по этой товарной единице? Быть может, такая его популярность связана с тем, что мы торгуем им себе в убыток? Таким образом, здесь мы имеем дело с  обычными причинно-следственными связями, постоянно сопровождающими процесс принятия решений на основе визуальных отчетов. Аналитические выводы всегда порождают новые вопросы, ответы на которые ведут к следующим изысканиям. 28

ГлаВа 1.1 DAX в бизнес-аналитике

Рис. 1.1.4. Данные о продажах в визуальном представлении

Как удовлетворяется этот спрос на новые ответы на практике? Традиционно разработчики старались разместить в одном отчете как можно больше информации. Причиной для этого было то, что на формирование новых отчетов требуется время. В  Power BI применяется принципиально иной подход с добавлением отчетам интерактивности, который стал возможен благодаря мощи языка запросов DAX.

Визуализация

Интерактивность

Рис. 1.1.5. Цикл визуализации и интерактивности

Интерактивность позволяет пользователю отчета самому углубляться в детализацию отчета и находить ответы на все новые вопросы, возникающие в процессе анализа. Это делает переход от простого чтения отчетов к их глубокому осмыслению более естественным. Но для того чтобы эта система работала, необходимо обеспечить быстрое формирование новых визуализаций на основе существующих отчетов. Применительно к визуальным отчетам в Power BI, извлекающим содержимое из модели данных, это означает, что модель должна достаточно быстро предоставлять данные для отчетов. В  свою очередь, эффективность модели данных напрямую зависит от ее структуры и  реализованного в  ней кода DAX. Так что связь между быстродействием запросов DAX и уровнем комфорта пользователей отчетов есть, и она абсолютно прямая!

ГлаВа 1.1

DAX в бизнес-аналитике

29

Подходы к разработке решений Модели данных Power BI и  DAX позволяют использовать иной подход к  разработке аналитических решений  – с  гораздо большим вовлечением бизнеспользователей. Это способно существенно повысить ценность извлекаемых выводов для компании в целом. Традиционный подход к созданию аналитических систем с централизацией ИТ-решений начинается с подключения к данным и их подготовки. На первый взгляд, здесь все логично – в конце концов, нам нужны исходные данные для получения аналитических выводов. Распространение Визуализация анализ Подготовка Подключение Рис. 1.1.6. Традиционный подход к разработке аналитических решений

Этот подход обычно материализуется в создании корпоративного хранилища данных. Идея наличия такого хранилища состоит, как ясно из названия, в хранении всех данных организации в одном месте и использовании их в качестве основы для отчетов. Понятно, что это может быть непросто, поскольку компании могут пользоваться большим количеством источников с  абсолютным разнообразием содержащихся в них данных. Традиционно хранилище реализуется с использованием реляционных систем управления базами данных, а это означает, что все исходные данные организации должны вписываться в схему базы данных в рамках единого хранилища. Упомянутое разнообразие исходных данных приводит к повышению сложности общей схемы хранилища. Кроме того, при смене  или добавлении источника новые данные также должны быть совместимы с существующей схемой –  в противном случае придется менять хранилище для удовлетворения требований всех источников. Вследствие этого проекты, в основе которых лежат хранилища данных, часто славятся продолжительным временем реализации и высокой стоимостью. Многие специалисты построили свою карьеру на хранилищах данных, но не меньшее их количество сломали свои карьеры при внедрении подобных решений. К тому же у традиционного подхода существует и более фундаментальный недостаток. Начиная с источников данных и постепенно поднимаясь по сту30

ГлаВа 1.1

DAX в бизнес-аналитике

пенькам пятислойной модели, вы рискуете упустить важные и очень актуальные выводы, относящиеся к бизнесу. Несмотря на то что большинство проектов на основе хранилищ данных в процессе построения так или иначе учитывают существующие бизнес-требования, в реальности во многих случаях они в итоге отходят на второй план. Техническая сложность подобных проектов слишком высока, чтобы бизнес мог принимать в их разработке активное участие. В результате к окончанию работы над хранилищем данных (или к моменту его первого выпуска в рабочем окружении) оно зачастую уже не соответствует актуальным требованиям бизнеса. В ожидании выхода корпоративных отчетов в организациях нередко применяется тактика «теневой информатизации», при которой пользователи вооружаются любыми доступными им средствами (обычно это Excel) и данными, до которых только могут дотянуться, чтобы строить собственные аналитические решения. И хотя эта тактика может помочь в плане получения быстрой актуальной аналитики, в долгосрочной перспективе она не способна решить проблемы организации с подходом к созданию аналитических решений. Качество данных (а  значит, и достоверность сделанных на их основе выводов) в  этом случае будет под вопросом, и риск принять неверное решение может оказаться слишком велик.

Использование Power BI для ускорения разработки аналитических решений Взамен подхода, описанного выше, Power BI позволяет двигаться в обе стороны в рамках пятислойной модели, как показано на рис. 1.1.7. Распространение Визуализация анализ Подготовка Подключение Рис. 1.1.7. Подход к разработке аналитических решений с помощью Power BI

Здесь мы используем Power BI не только в  качестве целевой платформы для нашего решения, но и  как инструмент для упрощения и  рационализации нашего проекта. Применяя Power BI, вы можете воспользоваться всеми специфическими возможностями этого инструмента, включая быстрое создание отчетов и  получение ценных выводов вне  зависимости от местонахождения исходных данных. Модели данных Power BI и DAX идеально подходят для таких задач. ГлаВа 1.1

DAX в бизнес-аналитике

31

Этот подход базируется на двух основных принципах: 1. Мы не знаем точно, что нам нужно. 2. Наши данные некорректны. Следствием этих принципов является невозможность сделать все правильно с первого раза. Вместо этого вам придется внедрять решение итеративно – методом последовательных ошибок и улучшений.

Мы не знаем точно, что нам нужно Этот принцип означает отсутствие ожиданий от владельца бизнеса или от вас самих в этой роли, что вы предоставите корректные спецификации для отчетов. Если вы когда-нибудь сталкивались в работе с аналитическими системами, то вам должно быть это знакомо. И даже если вам кажется, что вы точно знаете, чего хотите от отчета, скорее всего, вы не  учли всех деталей. А  если учли, то наверняка в процессе разработки системы возникнет недопонимание в отношении реализации конкретного решения по причине отсутствия у разработчика необходимых знаний в области требуемого бизнес-контекста. Как следствие нет большого смысла тратить слишком много времени на сбор требований, а также запись и подтверждение спецификаций. Вы можете просто принять к сведению, что первый созданный отчет все равно будет неправильным, так что лучше побыстрее создать его, чтобы как можно скорее ошибиться. Получается, что легче исправить все возможные ошибки и недочеты на практике, чем тратить время на абстрактное формулирование всех необходимых требований. Описываемый подход можно формализовать при помощи множественных итераций с  совместной сессией в  конце для демонстрации прототипа и  сбора обратной связи. Мы  называем такие собрания сессиями определения бизнес-дизайна (business design sessions), что подчеркивает их назначение. Это не обычные сессии для сбора обратной связи или демонстрации первой версии продукта, здесь важен факт объединения усилий для достижения результата. В зависимости от ваших навыков в Power BI и DAX вы можете потратить два и более дней на разработку прототипа. Итоги сессии определения бизнес-дизайна используются впоследствии в качестве входных критериев для следующей итерации, как показано на рис. 1.1.8. Пользователь

Распространение Визуализация Прототип

Подготовка

Обратная связь

анализ

Подключение

Улучшение

Рис. 1.1.8. Итеративный сбор требований

32

ГлаВа 1.1

DAX в бизнес-аналитике

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

Наши данные некорректны Второй принцип для вас, скорее всего, новостью не станет. Конечно же, наши данные некорректны! Причина этого в  том, что реальные бизнес-процессы намного более сложны и  запутанны в  сравнении с  нашей возможностью их смоделировать. А если учитывать, что информационные системы разрабатываются с целью отразить то, как должны выглядеть бизнес-процессы в реальности, становится очевидно, что на пути автоматизации этих процессов непременно возникнет фундаментальная дилемма. Либо система будет реализовывать строгий контроль за качеством данных, в результате чего вы сможете вводить в нее только данные, в точности соответствующие разработанным процессам, либо будет допускать гибкость в  отношении ввода данных, не  отвечающих в  точности идеальному течению процессов. Чаще всего выбор делается в  пользу второго варианта. Это обусловливает присутствие в типичных бизнес-системах возможности вводить пользовательские данные и наличие обходных путей. Как следствие данные из этих систем не всегда могут соответствовать вашим ожиданиям. И ситуация осложняется, когда ваши данные содержатся в электронных таблицах или других файлах! В традиционных аналитических решениях ненадежные данные бывает очень сложно определить и исправить. Причина состоит в том, что такие системы хранят только агрегированные данные и не позволяют пользователям уточнять их при помощи детализированных сведений. И здесь на арене появляется Power BI, технологии моделирования в  котором настолько мощны и  совершенны, что в  большинстве случаев позволяют загружать данные в  модель без всякой предварительной агрегации. Визуальные и интерактивные отчеты позволяют делать аналитические выводы на основе пользовательской агрегации данных (при помощи DAX) и опускаться до требуемого уровня детализации. При использовании итеративного подхода к разработке аналитических решений после первых нескольких итераций в результатах неминуемо будет много ошибок. Но опытные бизнес-пользователи обычно очень быстро способны их обнаружить. На первых стадиях недостатки в агрегированных данных устраняются именно так, но с продвижением по итерациям качество данных значительно улучшается. Возможность просматривать в отчетах Power BI детализированную информацию помогает повысить доверие к данным в новых аналитических решениях.

Цикл цифровой трансформации До сих пор мы говорили о том, что необходимо для преодоления пути от сырых данных до аналитических выводов, и о роли в этом процессе моделей данных ГлаВа 1.1 DAX в бизнес-аналитике

33

Power BI и DAX. Как видите, для получения ценных сведений для бизнеса недостаточно просто подключиться к данным и подготовить их. Вся важная аналитика проистекает из визуальных и интерактивных отчетов. В то же время финансовое положение компании не улучшится, если просто сидеть и смотреть на красивые отчеты. Важно то, как вы обращаетесь с полученными аналитическими выводами. Иными словами, вам необходимо предпринять какие-то действия для извлечения пользы из аналитических решений. Также вам не помешает наличие механизма оценки эффекта от этих действий – либо в автоматическом режиме, либо путем сбора обратной связи от пользователей посредством какой-то информационной системы. Результатом этого опять же будут данные. Все это приводит нас к циклу цифровой трансформации (digital transformation cycle), или улучшения бизнеса на основе данных. Выводы

Действия

Данные

Меры / обратная связь Рис. 1.1.9. Цикл цифровой трансформации

Power BI прекрасно проявляет себя на поприще преобразования исходных данных в аналитические выводы, но он не приспособлен для покрытия второй половины цикла трансформации, показанного на рис. 1.1.9. Здесь необходимы инструменты и технологии, обладающие возможностями ввода и обновления данных и объединяющие системы и людей. Именно по этой причине компания Microsoft сделала Power BI частью объемной платформы под названием Power Platform. Power Platform охватывает полный цикл цифровой трансформации и  опирается на единые принципы, включающие главенствующую роль бизнес-пользователя, легкий ввод данных и удовлетворение всех бизнес-требований. Помимо Power BI, Power Platform состоит из следующих компонентов: Power Apps предоставляет окружение для разработки бизнес-приложений с минимальным количеством кода для использования на мобильных устройствах и в браузерах. Эти приложения могут помочь при вводе и редактировании данных; Power Automate позволяет автоматизировать обмен процессами между разными системами, службами и пользовательскими приложениями. К примеру, процессы могут запускаться в  момент поступления письма по электронной почте, при этом может запрашиваться подтверждение пользователя и обновляться модель данных Power BI и связанные отчеты; 34

ГлаВа 1.1

DAX в бизнес-аналитике

Power Virtual Agents предлагает платформу для интерактивного обмена информацией в  рамках Power Platform посредством интеллектуальных чатботов. С их помощью пользователь может вводить данные или инициировать действия в интерактивном режиме, без необходимости запоминать какие-то сложные команды интерфейса. Несмотря на то что описанные выше инструменты представляют собой отдельные приложения в рамках Power Platform, между ними существует тесная связь. К примеру, вы можете создать отчет в Power BI со встроенным приложением Power Apps, чтобы пользователи могли изменять данные в удобном виде прямо в  момент получения аналитических выводов. Также и  потоки Power Automate могут быть встроены в отчеты Power BI, что позволит пользователям предпринимать необходимые действия на основе полученных результатов.

Заключение В этой главе мы обсудили область применения бизнес-аналитики и центральную роль, отведенную в современных аналитических решениях моделям данных. Модели Power BI идеально подходят для использования в аналитических решениях, не в последнюю очередь благодаря мощи DAX. Вы узнали о двух возможностях, предоставляемых языком запросов DAX, которые позволяют ключевым образом влиять на концепцию и разработку решений: „ DAX может быть использован для создания сложных вычислений, связанных с агрегацией данных, для чего раньше приходилось долго и кропотливо подготавливать данные. Таким образом, DAX позволяет сместить фокус с данных и всех связанных с ними работ на логику получения аналитических выводов; „ с помощью DAX бизнес-пользователи, знакомые с Excel, могут самостоятельно работать с  аналитическим решением в  меру своей осведомленности и навыков. Это позволяет более правильно расставить бизнес-приоритеты и привлечь пользователей к работе. Поскольку модели данных Power BI и DAX представляют две стороны одной монеты, очень важно уметь балансировать между ними для достижения оптимальных результатов. Проще говоря, DAX необходимо использовать только там, где это уместно, чтобы не углубляться в работу с данными и не обращаться к нему при подготовке и генерировании данных. В следующих нескольких главах мы разовьем описанные здесь идеи. В главе  1.2 мы поговорим о том, что нужно и  что не  нужно делать при разработке моделей данных Power BI. Глава  1.3 будет посвящена использованию DAX для достижения наилучших результатов. А в главе 1.4 мы продолжим работать с  DAX и  обсудим очень важные концепции, применимые при написании запросов и  вычислений. Во  второй части книги будет содержаться множество примеров, по большей части из реальных проектов, с  помощью которых мы продемонстрируем всю мощь языка DAX и покажем, как нужно балансировать между выражениями DAX и моделированием данных в Power BI.

ГЛ А В А  1.2

Разработка модели Эффективное использование языка DAX начинается с  разработки хорошей во всех отношениях аналитической модели данных. В этой главе мы обсудим важные аспекты моделирования данных – ключевые для построения полноценной модели. Темы, которые будут рассмотрены в этой главе: „ хранение данных в движке Power BI; „ правильный выбор типов данных; „ связи; „ оптимальная структура модели данных. Чтобы модель данных отличалась своей эффективностью, вам необходимо особенным образом выстроить свое мышление. Такая смена образа мыслей требуется при переходе в Power BI из Excel или из реляционных баз данных. Разработчикам Excel бывает непросто понять концепцию связей между таблицами в модели данных Power BI. Но и для специалистов в области баз данных, где также есть связи, в Power BI найдется немало новых нюансов в этой области. Основной сложностью здесь является внешняя схожесть концепций, используемых в Power BI и базах данных, притом что они имеют фундаментальные отличия. Об этом, в частности, мы поговорим в данной главе.

Колоночное хранение данных Своей мощью Power BI в основном обязан очень эффективному способу хранения данных. По  своей сути модели данных Power BI представляют собой своеобразные базы данных по части организации и хранения информации. Но в то же время внутренне  система хранения кардинально отличается от традиционных баз данных и других технологий, с которыми вы сталкивались в работе ранее.

Реляционные базы данных Традиционно хранение информации в компаниях организуется при помощи реляционных систем управления базами данных (relational database management system – RDBMS), или РСУБД, например Microsoft SQL Server. В реляционных базах данных информация хранится в виде таблиц (table) с фиксированным количеством столбцов. При этом каждый столбец характеризуется определенным типом данных (data type), таким как, например, integer, text или decimal, и информация о типах столбцов служит основой для подсчета места на диске, 36

ГлаВа 1.2

Разработка модели

необходимого для хранения одной строки или записи (record) в таблице, и количества записей, которые могут быть сохранены в  файле данных на диске. Такая концепция хранения данных делает реляционные базы идеальным выбором для приложений, обрабатывающих транзакции, таких как система учета розничных продаж или финансовой отчетности. Концепция индексов (index) позволяет достаточно быстро осуществлять поиск нужной записи в таблице, что обусловливает эффективную обработку существующих записей при работе с транзакциями. При этом таблицы могут быть объединены посредством связей (relationship), откуда и  происходит название реляционной базы данных. Это позволяет обеспечить данным целостность. Например, вы не сможете добавить в таблицу с продажами запись с неизвестным покупателем. Концепция реляционных баз данных появилась достаточно давно и за это время была значительно улучшена и оптимизирована. Этим объясняется тот факт, что многие традиционные аналитические платформы до сих пор полагаются на такое хранение информации. В то же время стоит отметить, что нужды аналитических решений значительно отличаются от требований к транзакционным системам. При работе с аналитикой вам обычно не требуется извлекать данные по строкам. Вместо этого вам чаще необходимо оперировать данными в виде блоков из большого количества записей, содержащих один или несколько столбцов. Для получения исходных данных из колонки реляционной базы данных все равно так или иначе приходится проходить по целым строкам в хранилище. Этим объясняется низкая эффективность реляционных баз при агрегировании данных на основании большого количества записей. На рис. 1.2.1 схематически показан принцип хранения данных по строкам (помечены цифрами). Как видите, при таком хранении нет никакой возможности быстро выбрать сведения, относящиеся к нужной колонке (затемненные блоки).

Столбец для извлечения Рис. 1.2.1. Извлечение значений из столбца при построчном хранении данных

Колоночные базы данных В моделях Power BI концепция хранения данных в реляционных базах перевернута на 90 градусов, так что информация в них аккумулируется не по строкам, а по столбцам. Одна из причин этого в том, что при работе с аналитическими системами нам зачастую требуется извлекать данные всего из одной ГлаВа 1.2

Разработка модели

37

или нескольких колонок, но по всем существующим строкам. А  эффективно это можно сделать только в одном случае – если данные из одного столбца физически хранятся вместе. Еще одной причиной такого хранения данных является то, что в реальных системах значения в строках из одной колонки очень часто повторяются. К примеру, в таблице, содержащей миллионы строк, в одной из колонок может присутствовать всего несколько тысяч повторяющихся товаров. При колоночном хранении такие данные могут быть очень эффективно сжаты – по сути, храниться будут только уникальные значения и номера строк, в которых они присутствуют. Высокая степень сжатия данных в колоночном хранилище дает возможность легко удерживать всю базу в памяти компьютера, вместо того чтобы обращаться за нужными данными к диску. Это, в свою очередь, позволяет существенно повысить скорость извлечения данных. Кроме того, колоночная концепция хранения обеспечивает очень эффективное выполнение агрегации данных. К примеру, вместо того чтобы суммировать все значения из столбца построчно, движок может просто взять список уникальных значений из нужного столбца и  перемножить на количество их вхождений. Иными словами, в Power BI реализован собственный движок хранения и обработки данных, позволяющий удовлетворить все основные требования при работе с аналитическими решениями, включая обработку больших массивов данных с определенными характеристиками, а также агрегирование и вычисления на их основе. Есть, однако, у  колоночного типа хранения данных и  один недостаток. В конце концов, как вы понимаете, мы не можем полностью абстрагироваться от информации о том, какие значения из столбцов соседствуют друг с другом в одной и той же строке. Нам недостаточно знать, что товар с кодом 103 был продан, мы хотим также понимать, кому он был продан, когда именно и в каком количестве. Для предоставления этой информации движку необходимо хранить список указателей (pointer) с информацией о том, какие значения в каких строках размещаются. Очевидно, что накладные расходы, связанные с хранением этих указателей, растут с увеличением количества столбцов в таблице. Из этого следует, что «узкие» таблицы, состоящие из небольшого количества колонок, в моделях Power BI будут показывать лучшую эффективность по сравнению с «широкими».

Типы данных и кодирование В моделях Power BI вы можете работать с ограниченным набором типов данных (data type). Выбор правильного типа данных для столбца имеет огромное значение, поскольку именно этот тип будет определять, как данные будут храниться и кодироваться (encoding) и насколько эффективно модель будет их обрабатывать. Ниже приведен список всех типов данных, поддерживаемых в Power BI: „ Текст (Text): это самый общий тип данных. Практически любые данные можно сохранить в виде текста. При загрузке данных посредством Power Query общий тип Any преобразуется в модели Power BI в тип Текст. Это может приводить к  хранению числовых данных в  виде текста, если за38

ГлаВа 1.2

Разработка модели

быть указать тип в Power Query в явном виде. Конечно, вы можете изменить тип данных в модели, что приведет к созданию соответствующего шага в Power Query; „ Целое число (Whole Number): как несложно догадаться, этот тип используется для хранения целочисленных данных. Это один из самых эффективных типов данных в Power BI с точки зрения хранения и сжатия; „ Десятичное число (Decimal Number): это наиболее универсальный тип данных для хранения числовой информации. Он позволяет хранить как целые числа, так и дробные. Максимальное количество знаков – 15; „ Десятичное число с фиксированной запятой (Fixed Decimal Number): этот тип данных, иногда называемый Валюта (Currency), используется для хранения десятичных чисел с четырьмя знаками. Максимальное количество знаков, включая десятичные, составляет 19. Таким образом, этот тип данных имеет меньшую точность по сравнению с предыдущим. Обычно он используется для хранения финансовой информации, но может быть использован и для любых других числовых данных, не требующих большого количества знаков после запятой; „ Дата и время, Дата, Время (Date/Time, Date, Time): в Power BI календарные значения хранятся в структурах, подобных тем, которые используются в Excel. Таким образом, для их хранения применяются десятичные числа, в которых целая часть отвечает за дату, а дробная – за время. Разница с  Excel заключается в том, какая дата рассматривается в  качестве точки отсчета: в Power BI число 1 соответствует дате 31 декабря 1899 года, тогда как в Excel – 1 января 1900 года (в обоих случаях отсчет ведется с полуночи). Десятичные знаки добавляются в виде доли от суток. Например, значение 2,5 в Power BI будет представлять полдень 1 января 1900 года. Вы можете хранить календарные данные в трех видах: тип Дата и время (Date/Time) позволяет хранить одновременно дату и время, тогда как типы Дата (Date) и Время (Time) предназначены для хранения отдельных составляющих, при этом тип Дата (Date) аналогичен целочисленному типу, а Время (Time) – десятичному; „ Истина/ложь (True/False): этот тип данных предназначен для хранения только двух значений: Истина (True) или Ложь (False). Очевидно, что это очень эффективный в плане хранения тип данных из-за ограниченности используемых значений; „ Двоичный (Binary): этот тип данных используется для хранения данных, которые не могут быть представлены в виде текста, таких как изображения или документы. К таким данным неприменимы агрегации и вычисления, но этот тип данных может применяться для хранения изображений, использующихся в отчетах. Выбор наиболее подходящих типов данных для колонок – это ключевой момент, влияющий на эффективность будущей модели. Движок Power BI оптимизирован таким образом, чтобы уникальные значения в столбцах хранились максимально эффективно, насколько это возможно. Давно прошли те времена, когда мы сражались за каждый байт и даже бит при хранении информации, но при разработке моделей данных по-прежнему очень важно понимать, как именно в компьютерах хранится информация. Выбор типов данных поможет ГлаВа 1.2

Разработка модели

39

определить, сколько бит потребуется для хранения вашей модели. А поскольку модель размещается в оперативной памяти, борьба за каждый бит здесь попрежнему актуальна. Представьте, что в  вашей модели данных есть колонка с  целочисленными значениями в  интервале от 0 до 10. В  двоичном представлении число 10 выражается как 1010 и  занимает 4 бита в  памяти. Таким образом, значения в  этой колонке могут быть закодированы словами размером 4 бита с явным представлением значений. Такой тип кодирования называется кодированием на основе значений (value encoding). В современных компьютерах с  64-битными процессорами использование для хранения значений четырех бит вместо шестидесяти четырех уже сулит неплохую прибавку в отношении эффективности. Кодирование на основе значений может использоваться для типов данных, представляющих из себя «под капотом» целочисленные значения. К таким типам относятся Целое число, Дата, Истина/ложь и Десятичное число с фиксированной запятой. Последний из них по своей сути представляет целочисленный тип с делением на 10 000. Движок DAX способен выполнять базовые трансформации перед осуществлением кодирования на основе значений. Например, он может вычесть из всех значений одно и то же число. Остальные типы данных не могут быть напрямую представлены в целочисленном виде, но движку все же необходимо находить экономичный способ их хранения. В таких случаях он нумерует уникальные значения в столбце и хранит только их цифровые эквиваленты. Такой подход получил название кодирование на основе хеша (hash encoding). Этот тип кодирования менее эффективен по сравнению с кодированием на основе значений, поскольку движку приходится выполнять сопоставление кодов и  реальных значений при каждом обращении к столбцу. Важно помнить, что Power BI автоматически выбирает оптимальный тип кодирования данных на основании типа и значений в столбцах. Это означает, что даже при хранении в колонке целочисленных значений движок может остановиться на кодировании на основе хеша. К примеру, если интервал значений в столбце не ограничивается диапазоном от 0 до 10, а может включать в себя и 1 000 000, для хранения значений в явном виде может потребоваться довольно много места, в результате чего движок может сделать выбор в пользу хеширования. Мы достаточно регулярно наблюдаем подобную ситуацию в реальных проектах, особенно в  случаях с  хранением дат. Представьте, что у  вас в  модели есть таблица с сотрудниками, в которой учитываются не только даты приема сотрудников на работу, но и даты их увольнения. Для людей, которые работают в компании и пока никуда не планируют уходить, последняя дата, очевидно, должна оставаться незаполненной. Но  зачастую вместо пустых значений в этом случае принято использовать какую-нибудь дату из далекого будущего. Это вполне допустимый подход, но если, к примеру, выбрать для этих целей дату 31 декабря 9999 года, то можно попрощаться с возможностью использовать для этой колонки кодирование на основе значений. Вместо этого всегда лучше использовать не  столь далекую дату, например 31 декабря 2029 года, в зависимости от вашего конкретного сценария. 40

ГлаВа 1.2

Разработка модели

Связи Одной из наиболее недооцененных концепций применительно к  моделям данных Power BI является концепция связей (relationship) между таблицами. Независимо от того, пришли вы в Power BI из Excel или работали с реляционными базами данных, вам в любом случае придется пересмотреть свои взгляды на принципы связывания данных в таблицах.

Данные в Excel Давайте сначала вспомним, как организованы данные в Excel. Максимально приближенной к базам данных концепцией в Excel являются так называемые умные таблицы. Вы вполне можете рассматривать такую таблицу как плоскую базу данных (flat database). У такого способа хранения данных есть множество недостатков. На рис. 1.2.2 представлены данные с рабочего листа в Excel.

Рис. 1.2.2. Умная таблица в Excel

В этой таблице содержится информация о продажах с привязкой к сотруднику. Давайте перечислим проблемы, присущие таким плоским базам данных: „ вся информация о сотрудниках, совершивших продажу, такая как должность или дата рождения, дублируется во всех относящихся к сотруднику строках. Это приводит к  хранению огромного количества избыточных данных; „ хранение повторяющихся данных повышает риск возникновения ошибок в них; „ при изменении любого атрибута сотрудника (например, занимаемой им должности) правки должны быть внесены во все строки, относящиеся к этому сотруднику; „ все становится еще хуже, когда сотруднику должны соответствовать несколько значений одного и того же атрибута. В нашем примере с сотрудником по имени Джулиана (Giuliana) сопоставлены сразу две должности. При этом каждая ее продажа имеет привязку к одной из должностей, что может быть заложено в бизнес-логике проекта, а может и нет. И если мы будем агрегировать продажи по должностям, то в  результаты по должности консультанта (Consultant) пойдет только одна продажа Джулианы; ГлаВа 1.2 Разработка модели

41

„ большие проблемы могут возникать и  при поступлении данных из разных источников. Давайте представим, что у нас есть данные не только по фактическим продажам, но и по целевым показателям. В этом случае нам будет очень непросто совместить всю имеющуюся в  разных источниках информацию в одной плоской таблице. По сути, большую часть времени пользователи Excel тратят на подготовку данных в единой плоской таблице, которую в дальнейшем используют при работе со сводными таблицами. В Excel у  нас нет никаких обходных путей для решения перечисленных проблем – разве что использовать Power Pivot, представляющий собой аналог модели данных Power BI в  Excel. Прежде чем перейти к  подходу, принятому в Power BI, давайте посмотрим, как организована информация в реляционных базах данных.

Реляционные базы данных В реляционных системах управления базами данных (relational database management system – RDBMS), или РСУБД, все данные поделены между таблицами. Обычно эти таблицы ассоциированы с  сущностями, характерными для конкретной организации, и в них хранится, например, информация о сотрудниках, клиентах и товарах. Каждая строка в таблице содержит уникальный идентификатор, или ключ (key), с помощью которого можно однозначно ссылаться на данные из других таблиц. К примеру, в таблицу с заказами могут быть включены только ключевые идентификаторы покупателей или товаров, чтобы не дублировать все их атрибуты, как показано на рис. 1.2.3.

Рис. 1.2.3. Связи между таблицами в реляционной базе данных

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

ГлаВа 1.2 Разработка модели

связанных таблиц. Таким образом, пользователю не  удастся вставить новую запись или отредактировать существующую в попытке сохранить в ней несуществующий ключ. Иными словами, движок накладывает определенные ограничения (constraint) на хранящиеся данные для обеспечения ссылочной целостности (referential integrity).

Реляционная модель Power BI В модели данных Power BI таблицы объединяются при помощи связей. На первый взгляд, это то же самое, что и в реляционных базах данных, но на самом деле здесь есть фундаментальные отличия. В основе связи в модели данных Power BI лежит таблица с уникальным ключом. Другая таблица с теми же значениями ключа может быть связана с ней, причем в ней правило уникальности ключа может не соблюдаться. Образованная связь именуется связью типа «один ко многим» (one-to-many). Это значит, что в одной таблице конкретное значение ключа может присутствовать лишь раз, а в связанной с ней может встречаться многократно. Вы можете называть колонку с уникальными значениями первичным ключом (primary key), а колонку с неуникальными значениями (в другой таблице) – внешним ключом (foreign key), как в реляционных базах данных. Структуру модели данных Power BI с таблицами и связями между ними можно посмотреть в виде диаграммы в Power BI Desktop, как показано на рис. 1.2.4.

Рис. 1.2.4. Связь между двумя таблицами в модели данных Power BI

Существует два основных отличия между связями в модели Power BI и реляционной базе данных. Первое заключается в контроле обеспечения ссылочной целостности. Если в  реляционной базе данных целостность может быть гарантирована созданной связью между таблицами, связи в  модели данных Power BI не выполняют такую функцию. По сути, Power BI все равно, как согласуются ваши данные в таблицах. Связь между таблицами может быть создана, даже если в  колонке с  внешним ключом содержатся значения, которых нет в первичном ключе. В этом случае каждое значение с неизвестным внешним ключом будет связано с пустой строкой, как показано на рис. 1.2.5. Эта строка не видна в модели данных, но может быть выведена в отчетах. ГлаВа 1.2

Разработка модели

43

Рис. 1.2.5. Неизвестные значения сопоставляются с пустой строкой

Одним из преимуществ такого подхода является то, что вы можете не беспокоиться по поводу того, в  каком порядке таблицы будут загружаться или обновляться, тогда как в случае с реляционной базой данных порядок имеет значение. Конечно, нужно проявлять осторожность при создании связей, особенно если вы делаете это путем перетаскивания мышью полей на диаграмме модели. Power BI не помешает вам случайно создать связь не с тем полем, что может привести к неправильным расчетам в дальнейшем. Еще одно серьезное отличие между подходами, применяемыми в Power BI и  реляционных базах данных, связано с  распространением фильтров (filter propagation). Связи в модели Power BI активно фильтруют данные. Иначе говоря, при выборе определенных строк в одной таблице автоматически будут выбираться соответствующие строки из связанных таблиц (по направлению стрелки на связи). В этом состоит один из ключевых принципов моделирования данных в Power BI, оказывающий влияние на вычисления в DAX. В реляционных базах данных связи между таблицами не  обладают таким поведением. При выполнении запроса к базе разработчик должен явным образом указывать, какие таблицы необходимо объединить и  по каким полям. Это позволяет повысить гибкость запросов при обращении к  базам данных, но в то же время увеличивает объем работы, который необходимо выполнять движку БД при обработке каждого запроса. В результате операция извлечения данных сразу из нескольких таблиц со множеством связей между ними может серьезно затянуться.

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

44

ГлаВа 1.2

Разработка модели

Активные и неактивные связи Для распространения фильтра между таблицами должна быть установлена четкая и недвусмысленная связь. Предположим, для транзакции с продажей мы указали как дату документа, так и дату оплаты. При установке связи этой таблицы с календарем (таблица Calendar), в котором выбрана конкретная строка с датой, не ясно, какие строки должны быть выбраны в таблице с заказами: с датой документа, соответствующей выбранному дню, с датой оплаты или и те, и другие. Для разрешения подобных неопределенностей в  модели Power BI допускается наличие лишь одной активной связи (active relationship) между двумя таблицами. Это правило применимо и  при создании связи между таблицами через промежуточные таблицы – активный путь от одной таблицы до другой всегда должен быть только один. На рис. 1.2.6 показана активная связь между столбцами fSales[Order Date] и Calendar[Date]. При определении связей между другими столбцами этой таблицы они автоматически становятся неактивными (inactive relationship) и отображаются на схеме пунктиром. На рис. 1.2.6 это связи между полями fSales[Delivery date] и Calendar[Date], а также между fSales[Payment date] и Calendar[Date].

Рис. 1.2.6. Одна активная связь и две неактивные

ГлаВа 1.2 Разработка модели

45

При необходимости произвести какое-то вычисление неактивные связи можно активировать с помощью функции DAX USERELATIONSHIP. Предупреждение. Активирование связей с использованием функции USERELATIONSHIP будет приводить к ошибкам при установленной безопасности на уровне строк (row-level security – RLS) для таблицы, содержащей первичный ключ. Причина этого в том, что фильтры безопасности, как и все остальные фильтры, распространяются по установленным связям между таблицами. Деактивирование связи, по которой распространяется фильтр безопасности, и активирование другой связи приводит к появлению неоднозначности относительно того, что именно должно быть выбрано. В связи с этим необходимо очень тщательно подходить к процессу проектирования модели данных и учитывать возможные в будущем требования к безопасности. Не злоупотребляйте неактивными связями. О безопасности мы будем подробно говорить в главе 2.1.

Направление фильтрации В обычных условиях фильтры по связям распространяются от первичной таблицы к внешней. На схеме модели, показанной на рис. 1.2.7, направление распространения фильтра, также называемого кросс-фильтрацией, или перекрестной фильтрацией (cross filtering), показано при помощи стрелки на связи.

Рис. 1.2.7. Направление распространения фильтра по связи

При необходимости направление перекрестной фильтрации можно изменить, чтобы фильтр распространялся по связи в обе стороны. Для этого нужно открыть диалоговое окно Изменение связи (Edit relationship) и выбрать в выпадающем списке Направление кросс-фильтрации (Cross filter direction) вариант Двунаправленная (Both). Вам может показаться, что можно просто для всех связей установить этот вариант по умолчанию, но не делайте этого! Двунаправленная фильтрация должна использоваться только в крайних случаях, 46

ГлаВа 1.2

Разработка модели

когда это действительно необходимо. Во всех остальных ситуациях старайтесь избегать этой опции, иначе ваши отчеты будут вести себя довольно странно, в модели появится множество неактивных связей, а вычисления DAX значительно усложнятся. Одним из сценариев допустимого использования двунаправленных связей является реализация связи типа «многие ко многим» (many-to-many relationship). Представьте, что у  вас есть таблица с  клиентами (Customers) и таблица с  филиалами (Branch office), как показано на рис.  1.2.8. При этом один клиент может обслуживаться в разных филиалах, и в одном филиале могут обслуживаться разные клиенты.

Рис. 1.2.8. Клиенты и филиалы

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

Рис. 1.2.9. Промежуточная связующая таблица

ГлаВа 1.2 Разработка модели

47

Пока наши связи не настроены для корректной кросс-фильтрации от таблицы Customer к таблице Branch office и наоборот. При выборе строки в таблице с клиентами фильтр распространится по связи только на промежуточную таблицу, но не пройдет дальше – к таблице с филиалами. Решение состоит в  установке двунаправленной перекрестной фильтрации для обеих созданных связей, как показано на рис. 1.2.10. Теперь при выборе клиента фильтр будет распространяться на промежуточную таблицу, а от нее перейдет к таблице филиалов. И наоборот, при выборе филиала фильтр пройдет через вспомогательную таблицу и приземлится в таблице с клиентами.

Рис. 1.2.10. Реализация связи типа «многие ко многим» посредством промежуточной таблицы

Примечание. Проблемы возникнут тогда, когда вы захотите одну из таблиц (например, Customer) связать с таблицей продаж, как показано на рис. 1.2.11. Очевидно, что для каждой продажи в таблице указывается клиент. Таким образом, вы всегда можете выбрать одного клиента (допустим, MegaBike) и посмотреть продажи по нему. При этом у вас не получится посмотреть продажи по клиенту MegaBike только в разрезе выбранного филиала. При выборе филиала вы увидите все продажи по клиенту MegaBike, если этот клиент обслуживался в данном филиале. При просмотре отчета по общим продажам по филиалу продажи по клиенту MegaBike будут включены в цифры тех филиалов, где они производились. В данном сценарии почти всегда лучше будет связать таблицы Customer и Branch office напрямую с таблицей продаж без использования промежуточной таблицы. 48

ГлаВа 1.2

Разработка модели

Рис. 1.2.11. Использование кросс-фильтрации между таблицами с клиентами и филиалами

Кратность По умолчанию между таблицами создаются связи типа «один ко многим», когда с  одной стороны находится таблица с  первичным ключом (уникальными значениями в столбце), а с другой – таблица с внешним ключом (неуникальным столбцом с теми же значениями). Тип создаваемой связи иначе называется ее кратностью (cardinality). Но одной только кратностью «один ко многим» выбор не  ограничивается. Сразу напрашивается кратность «многие к одному» (many-to-one), но по своей сути это то же самое с обратным порядком таблиц. Также есть кратность связи «один к одному» (one-to-one), при которой в обеих таблицах ключевое поле содержит уникальные значения. Такой тип связи по умолчанию имеет двунаправленную кросс-фильтрацию. В  результате две связанные таблицы начинают работать как одна почти во всех обстоятельствах. Вам следует избегать использования такой кратности в своих моделях. Если у вас нет веских причин для разделения таблиц (а о них мы подробно поговорим в главе 2.4), лучше будет объединить их в одну физически. Последним видом кратности является «многие ко многим» (many-to-many). В  этом случае ни в  одной из связанных таблиц ключевой столбец не  будет иметь ограничения на уникальность значений. Опять же, у вас должны быть серьезные основания для использования такого типа связи. Во  всех остальных случаях мы настоятельно рекомендуем не использовать связи «многие ко многим», поскольку это может значительно усложнить модель данных. Позже в данной главе мы еще коснемся этого вопроса. ГлаВа 1.2

Разработка модели

49

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

Схемы «звезда» и «снежинка» При использовании в  качестве хранилища для аналитических задач реляционной базы данных лучше всего подходит схема, именуемая «звездой» (star schema), которая показана на рис.  1.2.12. Применительно к  моделям данных Power BI эта схема также отлично себя показывает.

Рис. 1.2.12. Структура схемы «звезда»

Центральное место в  такой схеме занимают таблицы фактов (fact tables). В этих таблицах хранятся данные о событиях или транзакциях – совершенных или планируемых. Это могут быть заказы товаров, продажи, финансовые операции, счета клиентов, информация о зачисленных студентах и т. д. Посредством столбцов с внешними ключами таблицы фактов объединяются с таблицами, в которых хранится детальная информация об используемых сущностях, таких как клиенты, товары, студенты, даты и  т. д. В  концепции схемы «звезда» такие таблицы именуются измерениями (dimensions), однако в терминологии Power BI мы предпочитаем называть их фильтрующими таблицами (filter tables) по причинам, описанным ниже. Столбцы в таких таблицах используются для осуществления фильтрации результатов в отчетах, располагаясь либо в строках табличных/матричных визуальных элементов, либо на осях диаграмм, либо в срезах отчетов. В свою очередь, в таблицах фактов содержится информация, агрегируемая в отчетах. Каждое значение ключа может встречаться в таблице фактов множество раз, что отражает многократность выполняемых операций в один день, по одному клиенту и т. д. 50

ГлаВа 1.2

Разработка модели

В канонической схеме «звезда» фильтрующие таблицы между собой не объединяются при помощи связей. Если необходимо реализовать связь между ними, схема преобразуется в «снежинку» (snowflake), показанную на рис. 1.2.13.

Рис. 1.2.13. Схема «снежинка»

Проблемы, присущие схеме «звезда» В среде специалистов по реляционным базам данных схема «снежинка» традиционно считается немного ущербной, и они много сил расходуют на то, чтобы прийти к канонической «звезде». В то же время многие консультанты и разработчики моделей данных Power BI пришли в эту область именно из реляционных баз данных и принесли с собой знания, накопленные в той вселенной. Этим отчасти объясняется то, что вы нередко при обсуждении моделирования данных в Power BI будете слышать советы о том, что всегда нужно стремиться к схеме «звезда». Несколько провокационный заголовок этого раздела как бы призывает к  дискуссии о  том, что хорошо и  что плохо для модели данных Power BI. Факт же состоит в том, что модель Power BI – это не реляционная база данных. И очень странно, что большинство советов по моделированию в Power BI прорастают корнями в реляционных базах. В результате, как это ни удивительно, мы получаем далеко не оптимальные модели данных. Очень важно понимать, что концепция схемы «звезда» была разработана тогда, когда еще даже не существовало колоночных баз данных. В реляционной модели данных реализация этой схемы призвана уменьшить количество объединений таблиц в  запросах, поскольку большое количество связей приводит к  значительному снижению эффективности запросов в  целом. Объединение таблиц  – это типичная нагрузка для хранилищ данных, которые традиционно используются в качестве источников для реляционных отчетов. Под традициями мы здесь подразумеваем обращение ко времени, когда еще не существовали модели данных Power BI. Сейчас же хранилища данных, если они используются, представляют собой просто источник данных для моделей Power BI, а при импорте информации в модель вряд ли вам понадобятся какието объединения таблиц. На вопрос «а почему звезда?» обычно мы слышим что-то вроде «в противовес традиционной транзакционной схеме». В  действительности нужды бизГлаВа 1.2

Разработка модели

51

нес-аналитики с  ее склонностью агрегировать данные на основе огромного количества строк серьезно отличаются в  плане  нагрузки от типичной транзакционной обработки, где важную роль играют вставка и редактирование отдельных строк с  сохранением исходной целостности данных. Для аналитики модель данных, спроектированная на основе схемы «звезда», – это абсолютная необходимость. Многие, однако, транслируют эту простую истину в виде следующей канонической фразы: «Не используйте схему снежинка!» Иначе говоря, делайте так, чтобы каждое измерение было напрямую связано с таблицей фактов. И хотя в этом есть свои резоны при организации хранилища данных, используемого исключительно для отчетов, в  целом для всех моделей данных Power BI это неприменимо. Технология, используемая в  моделях данных Power BI, гораздо лучше работает со связями, а вкупе с эффективным сжатием данных применение схемы «снежинка» не должно представлять никаких проблем. Можно сказать, что схема «звезда» является идеальной отправной точкой при проектировании модели данных в Power BI, но не стоит тратить слишком много сил на то, чтобы любыми способами избегать образования схемы «снежинка». К чему было это долгое вступление, касающееся противостояния схем «звезда» и  «снежинка»? Мы  просто хотели предостеречь вас заранее от бессмысленных споров относительно внедрения традиционных идей проектирования хранилищ данных в моделях Power BI. В нашей работе мы регулярно сталкиваемся с тем, что отделы информационной поддержки в  компаниях именно этим и  занимаются, и  у нас уходит немало времени на объяснение того, что модели данных Power BI – это немного про другое. И мы не случайно используем другую терминологию для некоторых элементов применительно к моделям данных Power BI – тем самым мы хотим подчеркнуть различия в сравнении с  концепцией реляционных моделей и  облегчить понимание этих идей для бизнес-пользователей. В следующем разделе мы поговорим о  том, как можно навредить модели данных Power BI, если применительно к ней использовать принципы проектирования реляционных баз данных и хранилищ данных.

Концепции реляционных моделей, которых нужно избегать при моделировании в Power BI В предыдущем разделе мы уже предупредили вас о  бездумном применении концепций из реляционных баз данных при моделировании в Power BI. Ниже мы подробнее коснемся отдельных аспектов этих концепций.

Независимые измерения Что такое измерение (dimension)? В  терминах хранилищ данных измерение представляет собой таблицу, содержащую описательные атрибуты фактов, хранящихся в таблицах фактов. Само слово измерение было унаследовано из области математики и физики, где с помощью него характеризуются независимые параметры, описывающие объект, такие как высота или ширина. Этот термин вкупе с термином многомерное моделирование (multidimensional modeling), применяемым в прежнюю эпоху аналитики (до появления колоноч52

ГлаВа 1.2

Разработка модели

ных баз данных), предполагает, что измерения в  хранилище данных должны представлять собой независимые сущности. В то же время мы регулярно сталкиваемся со случаями, когда измерения бывают очень тесно связаны между собой. К  примеру, у  вас может быть измерение Customer с  клиентами, а также измерение Market Segment с сегментами рынка. Если один клиент может представлять несколько сегментов рынка, эти измерения можно назвать независимыми. Но зачастую бывает, что один клиент принадлежит только одному сегменту. Это не проблема для хранилища данных, чего не скажешь о модели данных Power BI. Представьте, что у  вас есть отчет в  Power BI со срезами по сегментам рынка и  клиентам. Пользователь будет вправе ожидать, что при выборе сегмента в срезе с клиентами останутся только компании, относящиеся к выбранному сегменту рынка. Иными словами, в  вашей модели данных должно осуществляться распространение фильтра с таблицы Market Segment на таблицу Customer и наоборот. Но с односторонним направлением кросс-фильтрации, устанавливаемой для связей между таблицами по умолчанию, этого добиться невозможно. Придется реализовывать двунаправленные связи между таблицами, что приведет к модели, показанной на рис. 1.2.14.

Рис. 1.2.14. Независимые измерения требуют двунаправленной перекрестной фильтрации

Но это одна из худших практик моделирования данных в Power BI, и причину вы поймете сами, если попытаетесь добавить в эту модель еще одну таблицу фактов, – вам просто не удастся сделать это, сохранив активность всех построенных связей. Гораздо лучше будет объединить в кластеры фильтрующие таблицы из одной области и оставить связь с таблицей фактов только для одной из них, при этом связь должна быть однонаправленной. При необходимости связи между таблицами внутри кластера могут быть реализованы как двунаправленные, что показано на рис. 1.2.15. Важным преимуществом такого подхода является возможность избавиться от множества ключевых столбцов в таблице фактов. ГлаВа 1.2

Разработка модели

53

Рис. 1.2.15. Фильтрующие таблицы, объединенные в кластер

Примечание. В этой книге мы в основном используем термин фильтрующая таблица (filter table), а не измерение, по трем основным причинам. Во-первых, чтобы дать понять, что речь идет не о реляционном моделировании данных. Во-вторых, несколько фильтрующих таблиц запросто могут объединяться в кластеры, что не позволяет им называться независимыми. Ну и, наконец, мы хотим использовать термин, который будет понятен бизнес-пользователю и лучше соотноситься с общей концепцией фильтрации данных, применяемой в Power BI.

Конечно, вы можете поспорить, заявив, что фильтрующие таблицы, объединенные в кластер, можно заменить одной большой таблицей, что вернет модель данных к столь желанной схеме «звезда». Разумеется, можно, но совсем не обязательно это делать. Более того, существуют веские причины для того, чтобы как раз не делать этого. Во-первых, вы можете с гораздо большей пользой для дела потратить это время. Кроме того, в вашей модели данных могут присутствовать другие таблицы фактов с иной гранулярностью, которые могут быть связаны только с отдельными фильтрующими таблицами в кластере (например, с целевыми показателями по сегментам рынка). Ну и в целом пользователям гораздо удобнее оперировать с данными, разделенными на таблицы с понятными для них названиями, а не объединенными в единую огромную таблицу с разнородными данными.

Только одна таблица фактов Здесь все просто: иногда вам встречаются люди, которые требования схемы «звезда» интерпретируют как необходимость присутствия в  модели данных всего одной таблицы фактов. И хотя это решило бы все проблемы с наличием 54

ГлаВа 1.2

Разработка модели

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

Хранилище данных как единый источник истины Из дискуссии, которую мы описали выше, должно быть понятно, что у Power BI нет никакой технической необходимости в наличии (реляционного) хранилища данных с тщательно спроектированной схемой базы данных. Поскольку хранилище в  этом случае будет только обслуживать таблицы с данными для моделей Power BI, вам не будет необходимости использовать архитектуру на основе схемы (schema-first architecture) в реляционном хранилище. Вместо этого вам достаточно будет работать с более простой архитектурой на основе данных (data-first architecture), подобной озеру данных (data lake). Но зачастую в вашем распоряжении будет хранилище данных. Обычно это предполагает обязательное выполнение требования к тому, чтобы вся бизнеслогика была реализована в  хранилище данных. С точки зрения Power BI это далеко не лучший подход. Основным его недостатком является то, что хранилище данных располагает лишь одним способом коммуникации с внешним миром – посредством данных. В отчетах обычно содержатся агрегированные данные – либо с помощью простой агрегации, либо с использованием более сложных методик (которым будет посвящена вся вторая часть книги). Важно то, что зачастую вы не сможете удовлетворить все требования отчетов при помощи стандартных агрегаций вроде суммирования или извлечения среднего значения по столбцу. Таким образом, у вас не будет никакой возможности реализовать всю необходимую вам бизнес-логику в рамках хранилища данных. Многие хранилища пытаются сделать все, чтобы по максимуму вмещать в себя возможности по реализации сложной бизнес-логики. Но здесь мы возвращаемся к  главному ограничению хранилищ, состоящему в  том, что они могут коммуницировать с  внешним миром только посредством данных. Это приводит к  появлению таблиц фактов с  огромным количеством столбцов, в  каждом из которых реализуется особенное бизнес-правило или агрегация. Однако мы начали с того, что не хотим, чтобы в наших моделях данных Power BI присутствовали таблицы фактов с большим количеством столбцов! Примечание. Любопытно, что существует технология на основе базы данных, способная взаимодействовать как при помощи данных, так и с помощью логики агрегаций, и это модель Power BI! Поскольку у вас есть возможность посредством режима DirectQuery подключаться к наборам данных Power BI, вы можете использовать модели Power BI в качестве основного концентратора для данных и агрегаций, наследуя от них новые модели. Иными словами, если и может быть единый источник истины (single source of truth), то, возможно, им может стать именно модель Power BI.

ГлаВа 1.2 Разработка модели

55

Использование связей типа «многие ко многим» Чего вам действительно стоит избегать любыми возможными способами, это создания непосредственных связей между таблицами фактов. Как вы понимаете, поскольку в таблицах фактов редко содержатся поля с  уникальными значениями, эти связи будут иметь кратность «многие ко многим». Если же в вашей таблице фактов присутствует столбец с уникальными или почти уникальными значениями, вам непременно стоит задуматься, нужен ли он вам в модели данных. Наличие связей между двумя таблицами фактов может не только привести к появлению неожиданных результатов в вычислениях по причине сложности отслеживания направлений распространения всех фильтров, но и  негативно сказаться на производительности модели в целом из-за образования слишком большого количества связанных строк. Эффективность связи напрямую связана с количеством уникальных значений в первичном ключе, т. е. на стороне один. Не позволяйте этому показателю стать слишком большим – в качестве ориентира можно взять предельное значение в 100 000 строк. Чуть более приемлемым способом использования связей типа «многие ко многим» является объединение таблиц фактов с фильтрующими таблицами на другом уровне гранулярности. К примеру, если в вашей модели присутствует таблица Product, в которой есть столбец Category, группирующий разные товары по категориям, вероятно, вы будете учитывать данные о продажах на уровне гранулярности отдельных товаров. В то же время целевые показатели или прогнозы могут рассчитываться с детализацией до категории. В  Power BI вы можете создать связь типа «многие ко многим» между таблицей фактов с целевыми метриками и таблицей Product по столбцу Category, как показано на рис. 1.2.16.

Рис. 1.2.16. Использование связи типа «многие ко многим»

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

ГлаВа 1.2 Разработка модели

Рис. 1.2.17. Использование промежуточной таблицы

Используя вспомогательную таблицу, мы приводим модель к обычному виду, в котором все связи имеют кратность «один ко многим» – идеальную для движка DAX. Кроме того, при наличии связи «многие ко многим» в фильтрующей таблице не  создаются пустые строки в  случае появления неизвестного значения, что может приводить к неожиданным результатам. В конце концов, отсутствие целостности в данных обычно проявляется в модели данных Power BI именно в результате появления пустых меток, образующихся из-за наличия этих пустых строк. Без этих пустот отсутствие целостности остается незамеченным. Объединение фильтрующих таблиц в  кластеры, о  чем мы говорили ранее, помогает естественным образом решить вопросы с различиями в отношении уровней гранулярности в таблицах фактов с использованием обычных связей.

Производительность модели и использование памяти Структура модели Power BI напрямую влияет на занимаемый ею объем памяти, а это оказывает воздействие на производительность. В этом разделе мы дадим несколько полезных советов по оптимизации модели данных и тем самым подведем итоги данной главы. Обычно модели меньшего размера работают более быстро. В качестве ориентира вы можете использовать размер файла Power BI. Более подробные сведения о размере модели и ее производительности вы можете почерпнуть в специализированных инструментах, таких как DAX Studio. Ниже мы перечислим основные идеи, следование которым позволит вам повысить эффективность своих моделей данных: „ чем меньше столбцов, тем лучше. Модели данных Power BI очень эффективно сжимаются благодаря использованию концепции колоночного хранения данных. При этом в модели также хранится информация о том, какие значения из колонок принадлежат одной строке. И  чем больше в таблице столбцов, тем больше ресурсов понадобится модели для учета этих привязок. Так что старайтесь без необходимости не увеличивать коГлаВа 1.2

Разработка модели

57

личество колонок в таблицах. Мы часто видим, что разработчики бездумно загружают в модель данных все столбцы из источника, потому что им так удобно (а скорее всего, просто из лени). Учтите, что добавить колонку в модель при необходимости всегда проще, чем проводить полный анализ в дальнейшем и удалять неиспользуемые столбцы. Модель никогда не уменьшается в размерах сама собой; „ используйте подходящие типы данных. Внутренне в моделях Power BI данные оптимизируются на уровне битов. Все оптимизации в колоночных базах данных построены именно на этом. Это означает, что все типы данных, за исключением целочисленных, должны кодироваться при помощи словарей значений. Однако это не говорит о том, что только целочисленные значения могут храниться эффективно. Мы уже упоминали, что и другие типы данных внутренне воспринимаются как целочисленные, в  том числе даты и  десятичные числа с  фиксированной запятой. Выбор типов данных для столбцов также влияет на организацию связей между таблицами, так что всегда, когда это возможно, используйте для ключевых столбцов целочисленные типы; „ много строк – не проблема, много значений – проблема. Используемая в моделях данных Power BI концепция колоночного хранилища позволяет очень эффективно оперировать большим количеством строк. Для каждого столбца в любом случае будет найден оптимальный способ хранения, что не исключает того, что большое количество уникальных значений в столбце потребует больше места. Таким образом, одним из наиболее влиятельных факторов на объем требуемых для хранения данных ресурсов является именно количество уникальных значений в столбцах. Зачастую можно эффективно уменьшить размер модели данных, просто избавившись от уникальных ключей в таблицах фактов. Во многих транзакционных системах каждая операция имеет свой уникальный идентификатор, и  при загрузке таких данных в  модель Power BI мы получим очень дорогой с точки зрения эффективности хранения столбец. На практике мы сталкивались с тем, что удаление одного ключевого столбца из самой большой таблицы фактов приводило к уменьшению ее объема более чем на 90 %! Кроме того, количество уникальных значений в столбце оказывает влияние и на создаваемые связи. Число значений в первичном ключе должно быть относительно небольшим. Если первичные ключи в ваших связях содержат сотни тысяч или даже миллионы записей, значит, пришло время пересмотреть структуру хранилища; „ избегайте появления выбросов. Во многих транзакционных системах разработчики предпочитают использовать особые значения для обозначения отсутствия данных или иных особых обстоятельств. При этом подобные значения часто выбираются так, чтобы они не  могли случайно пересечься с реальными значениями в таблице. Например, для столбца с датами можно выбрать дату вроде 31 декабря 9999 года. Такие выбросы в данных могут привести к тому, что Power BI будет хранить их при помощи словарей, что крайне неэффективно, несмотря на то что даты можно представить в  виде целых чисел. Причина в  том, что при выборе типа кодирования движок учитывает разницу между минимальным и макси58

ГлаВа 1.2

Разработка модели

мальным значениями в столбце. А при наличии выбросов более эффективным может оказаться кодирование при помощи словаря. Чтобы избежать подобных ситуаций, оставляйте пропущенные значения в столбцах пустыми или выбирайте для них особые значения, но не  слишком экстремальные; „ а вам нужна вся эта история? Самым очевидным способом снизить объем модели данных является загрузка в нее только части информации. Зачастую в источнике данных хранится информация за долгие годы. Конечно, вы можете загрузить в модель все продажи начиная с 2000 года, но прежде задайтесь вопросом: кому может понадобиться анализировать продажи до 2010 или 2015 года? Для любителей археологии можно создать отдельную модель, в которой будут храниться данные за прошлые периоды, тогда как в актуальной модели должна присутствовать только та информация, которую вам необходимо анализировать на ежедневной основе; „ иногда можно прибегнуть к разделению данных в столбцах. Бывают ситуации, когда можно эффективно разделить один столбец на два, если в результате в этих новых столбцах окажется существенно меньше уникальных значений. Так часто бывает с  составными ключами,  например с кодами товаров, состоящими из кода категории и номера последовательности: «A82.019». Если разделить такие коды на два столбца, в каждом из них окажется не так много уникальных значений, что позволит осуществить более эффективное хранение. В сложных случаях этот подход может быть сопряжен с  определенными проблемами. Кроме того, составные ключи могут вам понадобиться для образования связей между таблицами. Так что используйте этот метод только в подходящих для этого ситуациях. Многие из перечисленных советов обретают бóльшую важность при росте размера модели данных. Но  придерживаться их стоит даже тогда, когда у вас в распоряжении не так много данных. В любом случае проблемы легче предупредить, чем устранять.

Заключение В этой главе мы поговорили о ключевых концепциях моделирования данных в Power BI. Вы узнали, чем эти модели принципиально отличаются от всех других способов хранения данных и какие методы оптимизации лучше для них подходят. В основе модели данных Power BI лежит очень эффективная структура, состоящая из таблиц фактов, фильтрующих таблиц и связей между ними. Выбор оптимальной структуры данных и их типов при проектировании модели так же, как и правильное восприятие своих данных с точки зрения гранулярности, уникальных значений и распределения информации, поможет вам создать максимально эффективную модель. Кроме того, что не менее важно, такая модель послужит основой для создания богатых вычислений при помощи языка запросов DAX. В следующей главе мы как раз и  посмотрим, как при помощи DAX можно расширить модель данных Power BI.

ГЛ А В А 1.3

Применение DAX Истинная мощь моделей данных Power BI кроется в  возможности создавать сложные вычисления при помощи языка запросов DAX. И хотя многие пользователи Power BI стараются обходиться одними только средствами моделирования – без использования DAX, любые вычисления сложнее простых базовых агрегаций требуют участия этого языка запросов. Понятно, что при работе с  моделями данных Power BI вам рано или поздно понадобится применить свои навыки работы с этим языком на практике. Зачастую первый же хорошо спроектированный отчет в Power BI дает не только ответы, но и порождает новые вопросы, связанные с вашими данными. Во второй части книги мы углубимся в тему того, каких высот в вычислениях можно достичь при помощи DAX. Но прежде быстро пробежимся по основам DAX применительно к моделям Power BI. Некоторые из тем, которые будут рассмотрены в этой главе: „ вычисляемые столбцы; „ вычисляемые таблицы; „ меры; „ фильтры безопасности; „ запросы DAX. Мы также поговорим о создании таблиц данных в DAX, а в конце главы дадим несколько практических советов по использованию этого языка. Примечание. В сопроводительных материалах к этой книге содержится файл PBIX с примерами, который можно также загрузить по адресу https://github.com/PacktPublishing/ExtremeDAX/tree/main/Chapter1.3.

Вычисляемые столбцы Вычисляемым (calculated column) называется столбец, добавленный в  модель данных при помощи выражения языка запросов DAX. Например, мы могли бы вычислить сумму продажи, умножив количество проданного товара на его цену (обратите внимание, что в DAX имена столбцов заключаются в квадратные скобки): Amount = [Quantity] * [Price]

60

ГлаВа 1.3

Применение DAX

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

Рис. 1.3.1. Вычисляемый столбец

Создание вычисляемого столбца представляет собой простейший способ добавления бизнес-логики в  модель данных Power BI. Если вы раньше работали в Excel, вам знакома эта концепция по добавлению формул в столбцах – это первое, с чем знакомятся все начинающие пользователи Excel. В то же время мы НЕ советуем вам использовать вычисляемые столбцы в Power BI без крайней на то необходимости. Ниже мы перечислим основные доводы в пользу отказа от них: „ вычисляемые столбцы участвуют в создании новых данных, которым физически требуется место для хранения. А как мы уже говорили ранее, чем больше столбцов будет в  модели, тем объемнее и  медленнее она будет становиться; „ если по какой-то причине вам понадобится удалить таблицу из модели данных и добавить ее вновь в измененном виде (а иногда вы будете сталкиваться с такой необходимостью), вы потеряете все вычисляемые столбцы и будете вынуждены создавать их заново; „ колонки, участвующие в  формуле вычисляемых столбцов, такие как [Quantity] и [Price] в нашем примере, обязаны оставаться в модели данных, даже если больше нигде не используются. Спросите себя, для чего вам могла бы понадобиться колонка [Price]. Для вычисления средней цены по товарам? Вряд ли – средняя цена должна быть взвешена по количеству проданных товаров, так что простое вычисление среднего по этому столбцу не имеет никакого смысла. Вместо это можно было бы поделить общую сумму продажи на общее количество проданных товаров, а для этого вам без надобности столбец [Price]; „ результаты расчетов в вычисляемых столбцах остаются статичными. Эти вычисления производятся только в момент определения столбца и при обновлении модели Power BI. Это противоречит динамической природе DAX и отчетов Power BI в целом. ГлаВа 1.3

Применение DAX 61

Проблема с вычисляемыми столбцами состоит в том, что зачастую их создание относится к  слою подготовки данных в  нашей пятислойной модели, которую мы обсуждали в главе 1.1. В то же время существуют более подходящие инструменты для подготовки данных по сравнению с Power BI, например Power Query. Нормальная оптимизированная модель данных будет содержать столбцы [Quantity] и  [Amount] в  таблице с  транзакциями продаж, но никак не [Price]. Далее мы приведем пару исключений, когда без вычисляемых столбцов вам будет обойтись очень проблематично: „ иногда при создании сложных выражений на DAX вы будете обнаруживать, что некоторые их составляющие фактически являются статичными, так что их логичнее всего реализовывать при помощи вычисляемых столбцов. Если речь идет о  достаточно сложных вычислениях, которые должны выполняться из раза в  раз, вы можете существенно повысить эффективность, воплотив их в виде вычисляемых столбцов. Но первым делом вы должны задуматься о том, не стоит ли попытаться перенести эти расчеты в слой подготовки данных; „ некоторые сложные вычисления, которые должны быть реализованы в виде столбцов в модели, просто невозможно выполнить на этапе подготовки данных (например, в Power Query) без использования специфических функций языка DAX. В этих случаях вы сможете сэкономить как на разработке, так и на обновлении данных, выбрав в качестве средства реализации вычислений именно вычисляемые столбцы. Это часто бывает, когда результатом вычисления в столбце должна быть какая-то сложная агрегация. Но и здесь вам сначала необходимо задаться вопросом о том, можно ли обойтись без вычисляемых столбцов. В общем, как мы уже сказали в самом начале, не используйте вычисляемые столбцы без крайней необходимости.

Вычисляемые таблицы Вычисляемые таблицы (calculated table) можно сравнить с  вычисляемыми столбцами – с помощью них также добавляются данные в модель Power BI, но на этот раз в  табличном виде. Для создания вычисляемых таблиц вам чаще всего потребуется вызвать одну из табличных функций DAX. В  главе 1.4 мы пройдемся по основным табличным функциям, а во второй части книги познакомимся с ними ближе. Для создания простейшей вычисляемой таблицы в модели данных Power BI можно прибегнуть к  помощи табличного конструктора. В  выражении, показанном ниже, список значений заключен в фигурные скобки, в результате чего будет создана таблица с одним столбцом: Example = {1, 2, 3}

В результате запуска этого выражения будет создана таблица с  именем Example и единственным столбцом [Value], как показано на рис. 1.3.2. 62

ГлаВа 1.3 Применение DAX

Рис. 1.3.2. Вычисляемая таблица, созданная при помощи конструктора

Обратите внимание, что конструктор дает не так много контроля над создаваемой таблицей. Столбец по умолчанию называется Value, а  тип данных в нем выводится автоматически, исходя из введенных значений (хотя в большинстве случаев тип выбирается правильно). Если в  колонке представлены значения разных типов, будет выбран тип данных, способный вместить все указанные значения: Example2 = {1, 2, "3"}

При выполнении этого выражения тип данных в столбце Value будет установлен в Текст (Text). Табличный конструктор также позволяет создавать таблицы с несколькими столбцами. Для этого необходимо каждую строку в списке обрамлять круглыми скобками, как показано ниже: Example3 = { (1, "Red"), (2, "Green"), (3, "Blue") }

Результатом запуска этого выражения будет таблица, показанная на рис. 1.3.3.

Рис. 1.3.3. Табличный конструктор с двумя столбцами

Если вы хотите создать в DAX таблицу с заданными именами столбцов и типами данных, вам необходимо воспользоваться функцией DATATABLE. В качестве аргументов эта функция принимает пары, указывающие имена и  типы столбцов, а также список со значениями для строк. С функцией DATATABLE свяГлаВа 1.3

Применение DAX 63

заны две особенности. Во-первых, имена типов данных в ней отличаются от тех, которые используются в  модели данных Power BI (INTEGER для целочисленных значений, STRING – для текстовых и т. д.), а  во-вторых, значения для одной строки в  передаваемом списке объединяются не  круглыми скобками, как в  конструкторе, а  фигурными. Таким образом, таблицу, показанную на рис. 1.3.3, но с более понятными именами столбцов можно создать при помощи функции DATATABLE, как показано ниже: Example4 = DATATABLE( "Number", INTEGER, "Color", STRING, { {1, "Red"}, {2, "Green"}, {3, "Blue"} } )

Вычисляемые таблицы зачастую используются в Power BI для создания календаря. Об этом мы более подробно поговорим далее в данной главе. Некоторые из перечисленных выше недостатков вычисляемых столбцов характерны и  для вычисляемых таблиц. В  частности, они увеличивают размер модели данных, и, кроме того, велик риск того, что выполняемые при создании таблицы действия можно было осуществить на этапе подготовки данных. Однако в противовес вычисляемым столбцам вычисляемые таблицы не так тесно связаны с другими элементами в модели. При удалении столбцов и таблиц, которые используются при расчете значений в  вычисляемой таблице, вы получите ошибку, но все нормализуется, когда эти элементы будут восстановлены. Как и в случае с вычисляемыми столбцами, здесь мы дадим ту же рекомендацию, касающуюся того, что не нужно создавать вычисляемые таблицы, если выполняемые в  ней действия можно провести на этапе подготовки данных. Если же у  вас нет доступа к  слою подготовки данных,  например при работе с  хранилищем, управляемым централизованно, вы можете создать вычисляемые таблицы с информацией, не представленной в хранилище или вовсе не предназначенной для хранения в нем.

Меры Меры (measures), или вычисляемые поля (calculated fields), как их называли в более ранних версиях моделей, представляют собой, без сомнения, мощнейший элемент моделей данных Power BI. Фактически большая часть работы, производимой с моделями Power BI, сводится к разработке и реализации мер на языке DAX. При использовании числовых колонок из таблицы фактов в отчете Power BI значения из них будут автоматически агрегироваться. Набор базовых агрегаций (basic aggregations) включает в себя сумму, среднее, минимум, максимум, количество, количество уникальных, а также несколько статистических функ64

ГлаВа 1.3

Применение DAX

ций, таких как стандартное отклонение, дисперсия и  медиана. Перечень доступных базовых агрегаций зависит от типа данных столбца. К примеру, для столбца с датами вы можете выбрать между самым ранним значением, самым поздним, количеством элементов и  количеством уникальных значений. При таком использовании колонок Power BI автоматически создает неявные меры (implicit measures), представляющие собой функции, возвращающие выбранную агрегацию значений в столбце. В реальности же многие аналитические выводы не  могут быть получены с  использованием базовых агрегаций. Пользователи, имеющие опыт работы с Excel или хранилищами данных, приходят и начинают создавать (вычисляемые) столбцы с нужными им вычислениями в случаях, когда легко можно было обойтись базовыми агрегациями. Например, в моделях Excel и в хранилищах данных вы можете встретить индикатор, говорящий о том, входит ли строка с данными в период нарастающего итога с начала года. Но это статическое решение, которое не  позволит вам, скажем, извлечь результаты нарастающим итогом относительно периода двухмесячной давности. С помощью DAX вы можете объявлять собственные пользовательские агрегации (custom aggregations) в виде явных мер (explicit measures). В качестве примера можно привести средневзвешенную цену, которую мы упоминали в предыдущем разделе. В DAX вычислить ее можно следующим образом: Average Price = SUM(fSales[Amount]) / SUM(fSales[Quantity])

В этой формуле мы исходим из предположения о том, что таблица с продажами у нас называется fSales. Примечание. Оператор деления / обычно заменяется в DAX функцией DIVIDE, как показано ниже: Average Price = DIVIDE( SUM(fSales[Amount]), SUM(fSales[Quantity]) )

Преимущество функции DIVIDE заключается в ее элегантном подходе к возникновению ошибки деления на ноль. Это один из примеров пользы от использования явных мер с формулами DAX по сравнению с базовыми агрегациями в столбцах.

Создание мер с  использованием языка DAX должно стать вашим приоритетным способом реализации бизнес-логики в моделях данных Power BI. Мера не добавляет никаких данных в модель, а значит, не влияет на ее размер и быстродействие. В то же время, поскольку вычисления в мерах выполняются по запросу пользователя (при открытии им отчета), очень важно приложить усилия к тому, чтобы само выражение было написано оптимально. В связи с этим во ГлаВа 1.3

Применение DAX 65

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

Фильтры безопасности Язык запросов DAX также можно использовать для реализации механизмов безопасности в моделях данных Power BI. При формировании отчета в обычных условиях пользователь видит все данные из модели, к которым обращается отчет. Но зачастую нам необходимо ограничивать доступ к информации для пользователей на основании их роли или учетных сведений. Давайте для примера рассмотрим выражение безопасности (security expression) на DAX, приведенное ниже: Customer[Region] = "Europe"

Будучи примененным к  конкретной роли безопасности (security role), этот фильтр безопасности DAX ограничит видимость для пользователей с указанной ролью одним лишь европейским регионом покупателей и прочей связанной с ними информацией. Подробно о механизмах безопасности в DAX мы будем говорить в главе 2.1.

Запросы DAX DAX можно использовать и  исключительно в  качестве языка запросов. При работе с визуальными отчетами в Power BI вам это не понадобится, но классические инструменты отчетности, ориентированные на реляционные базы данных, по большей части полагаются на извлечение пользовательских наборов данных из баз и дальнейшее формирование отчетов. Традиционными источниками для них являются хранилища или базы данных. Кроме того, в этой роли могут выступать и модели данных Power BI в виде опубликованных наборов данных. На момент написания книги последняя возможность доступна только при наличии лицензии Power BI Premium на емкость или на пользователя (Power BI Premium Per User). Также запросы DAX могут использоваться в  отчетах Power BI с  разбивкой на страницы (paginated reports). Их разработкой можно заниматься в Power BI Report Builder (в противовес Power BI Desktop, который используется для всех остальных задач) с подключением к моделям данных Power BI. При подключении к данным вам необходимо предоставить запрос DAX для извлечения данных из модели Power BI, как показано на рис. 1.3.4. 66

ГлаВа 1.3

Применение DAX

Рис. 1.3.4. Запрос DAX в Power BI Report Builder

Если вы используете модели данных Power Pivot в Excel, то можете использовать запросы DAX для извлечения данных из модели в качестве альтернативы их вывода в сводной таблице. Как и вычисляемые таблицы, запросы DAX используют в своей основе табличные выражения (table expression). При этом для запуска выражения и возвращения результата применяется функция EVALUATE. Например, выражение, показанное ниже, вернет полную таблицу Customer: EVALUATE( Customer )

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

ГлаВа 1.3

Применение DAX 67

Таблицы дат Большинство, если не все модели Power BI включают в свой состав таблицы на основе дат. Таким образом, таблицу дат (date table), или календарную таблицу, можно назвать одним из основных ингредиентов любой модели данных Power BI. Таблице дат отведено отдельное место в модели по причине использования в ней специфических функций DAX, связанных с логикой операций со временем (time intelligence functions). В таблице дат должны располагаться строки с  датами из непрерывного временного интервала. Обычно этот интервал бывает достаточно протяженным, чтобы вместить в себя все упоминания в таблицах фактов. Рекомендуется начинать и  заканчивать таблицу дат первой и  последней датами года соответственно. В таблице дат должен присутствовать столбец с датами, который может рассматриваться в  качестве первичного ключа. Имя для этого столбца вы можете выбрать по своему усмотрению. Остальные колонки в таблице должны хранить атрибуты дат, такие как номер дня, года, месяца, квартала, дня недели и т. д. Примечание. В параметрах Power BI присутствует настройка Автоматические дата и время (Auto date/time), которая позволяет создавать скрытые таблицы дат для каждого столбца с календарным типом данных в модели с поддержкой иерархии годов и месяцев. Если вы еще этого не сделали, отключите эту настройку! Наличие таких таблиц сильно увеличивает объемы моделей данных и негативно сказывается на быстродействии модели. Помочь автоматические таблицы дат могут разве что совсем неопытному пользователю, который не хочет разбираться в тонкостях моделирования данных. Для мало-мальски опытных разработчиков эти таблицы не представляют никакой ценности. Любому отчету Power BI, предоставляющему информацию в разрезе одного выбранного периода – скажем, одного года, – так или иначе потребуется таблица дат. Как бы то ни было, прямо сейчас отправляйтесь в настройки Power BI Desktop и отключите флажок Автоматические дата и время (Auto date/time). Вы можете особым образом выделить свой календарь при помощи опции Отметить как таблицу дат (Mark as date table) в меню. После этого выбранный в диалоговом окне, показанном на рис. 1.3.5, столбец будет отмечен в модели как колонка с датами. В главе 1.4 вы узнаете обо всех преимуществах такой особой пометки таблицы с датами, когда мы будем изучать функции логики операций со временем. 68

ГлаВа 1.3

Применение DAX

Рис. 1.3.5. Специальная пометка таблицы как календарной

Создание таблицы дат Чисто технически таблицы дат ничем не отличаются от остальных таблиц в модели. Источник календарных данных у вас может находиться где угодно, и вы можете просто импортировать данные из него в модель Power BI. Можно заполнить таблицу и при помощи входных параметров (например, указав, какие годы она должна охватывать) в Power Query, но этой темы в данной книге мы касаться не будем. Здесь мы сконцентрируемся на создании таблицы дат в виде вычисляемой таблицы на основе формулы DAX. В DAX присутствуют две функции, подходящие для создания календаря: CALENDAR и CALENDARAUTO. Обе функции возвращают таблицу с единственным столбцом, содержащую даты. Функция CALENDARAUTO в автоматическом режиме просматривает всю модель данных и ищет минимальную и максимальную даты в столбцах с календарными типами, при этом вычисляемые столбцы и колонки в вычисляемых таблицах не учитываются. В результате календарь заполняется начиная с года минимальной даты по год максимальной, включая все даты в этих годах. И хотя это звучит весьма логично, вы должны понимать, что если модель содержит дни рождения сотрудников или какие-нибудь значения, представляющие собой выбросы вроде 31 декабря 2199 года, созданная таблица дат может включать в себя несколько десятилетий, если не веков. Гораздо удобнее использовать для создания календаря ручную функцию CALENDAR. Она принимает в качестве аргументов начальную и конечную даты создаваемого календаря, как показано ниже: CALENDAR( DATE(2021, 1, 1), DATE(2023, 12, 31) )

ГлаВа 1.3

Применение DAX 69

Выполнение приведенных выше функций приведет к  созданию таблицы с  единственным столбцом с  типом Дата (Date). Чтобы календарь получился полноценным и  готовым к  работе, вам необходимо снабдить его дополнительными столбцами, представляющими составляющие части дат. Это можно сделать при помощи отдельного добавления вычисляемых столбцов, но можно воспользоваться функцией ADDCOLUMNS, которая добавит несколько колонок сразу, как показано ниже: Date = ADDCOLUMNS( CALENDAR( DATE(2021, 1, 1), DATE(2023, 12, 31) ), "Year", YEAR([Date]), "Month", FORMAT([Date], "mmmm", "En-US"), "MonthNr", MONTH([Date]), "Year/Month", FORMAT([Date], "yyyy-mm") )

Здесь функция ADDCOLUMNS принимает на вход результат выполнения функции CALENDAR и добавляет к нему новые колонки. При этом вы можете передать как имя нового столбца, так и выражение для его вычисления. В этом примере видно, как можно использовать функцию FORMAT для приведения даты к нужному виду с применением локали. Результат выполнения этой формулы показан на рис. 1.3.6.

Рис. 1.3.6. Таблица дат, созданная при помощи формулы DAX

В реальности начальная и конечная даты в календаре должны быть динамическими, чтобы учитывать возможность добавления новых данных. К примеру, вы могли бы извлекать последнюю дату из таблицы фактов fSales с помощью формулы MAX(fSales[OrderDate]) и использовать ее для задания конечной даты в календаре. Также можно определить последнюю дату года последнего заказа. Вариантов много, мы не будем далее распространяться о них. 70

ГлаВа 1.3 Применение DAX

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

Первым делом меры! Если вы еще не  поняли из предыдущего раздела, то усвойте сейчас: вашим главным инструментом в  DAX должны быть меры! Он  очень динамичный, не увеличивает без необходимости размер модели данных и позволяет выполнять любые вычисления, которые вы только захотите. Всегда помните: если вы можете реализовать свою идею при помощи мер, даже не смотрите в сторону вычисляемых столбцов и таблиц!

Создавайте явные меры Мы рекомендуем вам отдавать предпочтение созданию явных мер в  DAX, а  не  полагаться на использование в  отчетах значений из числовых столбцов в таблицах фактов напрямую. На то есть несколько причин: „ при обращении к столбцу Power BI все равно создаст меру автоматически, так что лучше сделать это самостоятельно явным образом; „ явным мерам можно дать понятные имена, например Total sales вместо Amount или Sum of Amount, как в Power Pivot для Excel; „ для меры можно задать свой формат вывода, который будет применяться везде, где она будет использоваться. К примеру, вы можете выводить меру Total sales без символа валюты, без десятичных знаков, но с разделителями разрядов. Формат, унаследованный по умолчанию от типа данных, может предписывать совершенно иной вывод; „ явные меры могут быть использованы в  качестве строительных блоков для создания более сложных вычислений (смотрите ниже). Неявные меры в  этом контексте могут быть недоступны, или их будет неудобно использовать, поскольку они неизменяемые. Кроме того, использование явных мер предотвратит риск от применения неправильных агрегаций. Как вы уже видели на примере вычисления средней цены, очень просто можно добавить в отчет колонку, которая будет выдавать некорректные результаты при агрегации.

Используйте базовые меры в качестве строительных блоков В формулах DAX вычисления могут строиться на основе результатов вычислений мер. Построение блоков мер на основе базовых мер, т. е. простейших ГлаВа 1.3

Применение DAX 71

агрегаций числовых столбцов в таблицах фактов, позволит постепенно увеличивать сложность создаваемых вычислений. Использование базовых мер убережет вас от мыслей о том, какие агрегации использовать при их вычислении. А думать об этом придется постоянно. Многие разработчики допускают эту ошибку. Кроме того, применение базовых мер позволяет легко адаптировать бизнес-логику под меняющиеся требования. Часто бывает, что какой-то блок бизнес-логики становится доступен только на поздних стадиях разработки решения на базе Power BI. К примеру, сначала вам говорили, что продажи складываются из сумм всех счетов. Однако при выходе в свет первой модели данных может поступить важная корректировка, говорящая о том, что счета с типом X не должны включаться в эти расчеты. Если вы используете базовые меры, вам достаточно будет просто поправить формулу в одной из них, исключив тип счетов X. В противном случае вам придется проходить по всем мерам, где вы используете продажи, и менять в них логику.

Прячьте от пользователей ненужные им элементы модели При разработке модели Power BI вы можете рассчитывать на то, что только вы будете пользоваться отчетами на ее основе. Но на практике к вам могут присоединиться и другие пользователи. И чтобы не вводить их в заблуждение, рекомендуется скрывать некоторые вспомогательные элементы модели: „ столбцы, выступающие в  качестве внешних ключей в  связях, должны быть скрыты: те же значения представлены в первичном ключе с другой стороны, и по ним можно выполнять фильтрацию данных; „ технические (ключевые) столбцы, не требующиеся для вывода в отчетах, также не стоит показывать пользователю; „ мы также рекомендуем скрывать таблицы фактов: все внешние ключи в  них не  стоит показывать, а  числовые столбцы нет нужды использовать напрямую – для этого достаточно явных мер. Другие столбцы, присутствующие в  таблицах фактов, можно перенести в  соответствующие фильтрующие таблицы (измерения) или вовсе удалить. Некоторые колонки в таблице фактов могут использоваться для фильтрации без отображения пользователю, они могут остаться в ней; „ также необходимо скрыть меры, использующиеся исключительно с  целью произведения промежуточных вычислений для более сложных мер в DAX. Тактическое скрытие элементов в  Power BI позволит избежать путаницы и снизить количество вопросов к вам как к проектировщику модели.

Не смешивайте данные и меры – создайте для мер отдельную таблицу У всех мер DAX есть своя начальная таблица (home table), в которой они отображаются при просмотре модели данных разработчиком. Но, что более важно, именно в этой таблице на панели Поля (Fields) нужно будет искать их и пользователю отчетов. Мы  часто видим, что меры размещают прямо в  таблицах фактов, содержащих агрегируемые в них столбцы. И хотя это приемлемо для 72

ГлаВа 1.3

Применение DAX

простых мер с базовыми агрегациями, мы все же категорически рекомендуем отказаться от такой практики по следующим причинам: „ в более сложных мерах могут использоваться агрегации столбцов сразу из нескольких таблиц, что может привести к неопределенности относительно расположения меры; „ более важно, что, как и в случае с вычисляемыми столбцами, если вам по какой-то причине  придется удалять таблицу и  создавать ее заново, вы потеряете все размещенные в ней меры. Во избежание этих неудобств мы рекомендуем хранить меры в специально отведенных для этого таблицах, которые можно так и назвать – таблицы мер (measure tables). В  этих таблицах не  должно быть данных, здесь будут располагаться только меры. При этом в  отношении таблиц мер допустимо исключение из правила о том, что не стоит создавать вычисляемые таблицы. Простейшим способом создать таблицу мер является выполнение следующего выражения: Results = ROW("ZZ", "OK")

В итоге мы получим вычисляемую таблицу с именем Results, содержащую один столбец ZZ с единственной строкой данных в виде текста «OK». Колонка все равно нужна, поскольку без колонок не  бывает таблиц. Но  при скрытии ее Power BI понимает, что в этой таблице будут храниться меры, и помещает ее в  верхнюю часть списка таблиц на панели Поля (Fields). Это значительно облегчает поиск мер. А  колонку мы назвали ZZ, чтобы она отображалась последней в списке. Она и строки в ней никогда не будут использоваться, так что вы можете заменить значение «OK» на любое другое по вашему усмотрению. На рис. 1.3.7 показан результат создания таблицы мер.

Рис. 1.3.7. Таблица мер в Power BI Desktop: слева на вкладке Данные (Data), справа – на вкладке Отчет (Report)

Вы также можете создать таблицу мер в Power Query, например при помощи инструмента ввода данных. Там это работает так же: просто скрываете столбец с данными и добавляете меру, чтобы таблица переместилась в верхнюю часть списка на панели Поля (Fields). ГлаВа 1.3

Применение DAX 73

На момент написания книги от способа создания таблицы мер зависело, как будет выглядеть ее иконка на панели. При импорте из Power Query у этой таблицы будет специальная иконка, а  при создании вручную – обычная, что видно на рис. 1.3.8.

Рис. 1.3.8. Вычисляемая (вверху) и импортированная (внизу) таблица мер

В сложных моделях данных для группировки мер вы можете использовать папки отображения. У  вас также может быть сразу несколько таблиц мер. К примеру, мы иногда выделяем базовые меры, служащие для создания более сложных мер, в отдельную таблицу. Это позволяет при необходимости одновременно скрыть или отобразить все базовые меры (вручную вам нужно скрывать только столбец ZZ).

Типы таблиц Рекомендуется делать четкие различия между разными типами таблиц, которые мы обсудили в этой и предыдущей главах. В дополнение к трем уже рассмотренным типам таблиц можно добавить еще один – вспомогательную таблицу (helper table): „ таблицы фактов содержат основные данные для агрегации, но столбцы в них скрыты и не используются в отчетах; „ фильтрующие таблицы (или, если хотите, измерения) включают в себя все атрибуты, на основе которых фильтруются результаты из модели; „ таблицы мер не содержат данных, а служат контейнерами для мер, размещаясь при этом на вершине списка таблиц на панели Поля; „ вспомогательные таблицы представляют собой небольшие дополнительные таблицы, определяющие специфическое поведение отчетов, например возможность выбора временного периода. Больше об этом типе таблиц вы узнаете в главе 2.2. 74

ГлаВа 1.3

Применение DAX

Совсем не обязательно подчеркивать различия между перечисленными типами таблиц с помощью имен. К примеру, мы обычно не советуем добавлять, скажем, префикс dim к именам фильтрующих таблиц по примеру dimCustomer, dimCalendar и  т. д. Это не  самый дружественный по отношению к  пользователю способ разделять таблицы, ведь он может не  догадываться, что значит приставка dim. Вместо этого вы должны стремиться к тому, чтобы организация элементов Power BI говорила сама за себя. Скройте таблицы фактов, используйте таблицы мер и дайте фильтрующим таблицам простые и понятные описательные имена – в результате на панели Поля допустимые вычисляемые результаты разместятся в верхней части, а атрибуты для их фильтрации – внизу, при этом все элементы будут эстетично объединены в логические группы (которые вы как разработчик можете называть таблицами).

Заключение В этой главе мы познакомились с разными способами применения языка DAX в Power BI, в том числе для создания вычисляемых столбцов и таблиц, мер, механизмов безопасности и запросов. Главное, что вы должны были уяснить, – это то, что именно меры должны рассматриваться в качестве приоритетного способа извлечения аналитических выводов из данных, и в оставшейся части книги мы главным образом сосредоточимся на логике выражений DAX в мерах. Мы уже дали вам несколько простых советов по использованию DAX, и вы знаете, что необходимо стараться избегать создания вычисляемых столбцов, использовать явные меры, создавать базовые вспомогательные меры, которые можно будет использовать при построении логики более сложных мер, организовывать меры с помощью специальных таблиц и прятать элементы модели, которые могут ввести пользователя в заблуждение (даже если этим пользователем являетесь вы сами). В следующей главе мы рассмотрим одну из важнейших концепций при работе с  языком DAX, касающуюся контекстов и  фильтрации. После этого, во второй части книги, мы будем готовы к  обсуждению сложных приемов использования DAX в реальных бизнес-кейсах.

ГЛ А В А 1.4

Контексты и фильтры Контекст (context) представляет собой наиболее важную концепцию для понимания при осуществлении вычислений с помощью языка DAX. Именно тонкости реализации контекстов позволяют отделить DAX как динамический язык аналитики от функций Excel, выражений SQL или скриптов Power Query. Используя все эти инструменты для извлечения информации, мы увидим различия в ней только в случае изменения самих исходных данных (за несколькими исключениями, такими как применение параметров). В то же время одна и та же формула DAX может возвращать совершенно разные результаты в зависимости от того, где и как вы ее используете. В этом и состоит суть контекстов. Также без понимания контекстов DAX вы не сможете писать по-настоящему сложные выражения на этом языке. После того как вы преодолеете барьеры первых неудач в DAX, связанные с тем, что вы не будете знать, какую функцию применить, как построить синтаксис или какие скобки использовать, вы столкнетесь со «взрослыми» проблемами, базирующимися на нюансах построения контекстов. Мы можем пойти дальше и сделать следующее громкое заявление: Все проблемы в DAX связаны с контекстами, а все их решения находятся в результате тщательного изучения контекстов. И с этим утверждением не поспоришь! В этой главе мы рассмотрим все аспекты контекстов в  DAX, необходимые вам для понимания более сложных примеров, о которых пойдет речь во второй части книги. Темы, которые будут рассмотрены в этой главе: „ введение в контексты DAX; „ фильтрация в DAX: использование функции CALCULATE; „ функции логики операций со временем; „ изменение поведения связей; „ табличные функции в DAX; „ фильтрация при помощи табличных функций; „ переменные в DAX. В этой главе мы будем демонстрировать возможности DAX на относительно простых примерах, а во второй части книги погрузимся в сложные детали работы с контекстами. Здесь мы будем работать с моделью данных, представленной на рис. 1.4.1. В модели присутствует одна таблица фактов fSales и целый ряд фильтрующих таблиц. 76

ГлаВа 1.4

Контексты и фильтры

Рис. 1.4.1. Демонстрационная модель данных Power BI

Примечание. Файл с названием 1.4 Context and filtering. pbix, на примере которого мы будем изучать контексты и фильтрацию в DAX, находится в сопроводительных материалах. Также вы можете загрузить его по адресу https:// github.com/PacktPublishing/Extreme-DAX/tree/main/Chapter1.4.

Введение в контексты DAX Обобщенным термином в отношении контекстов DAX является контекст вычисления (evaluation context). Под ним подразумевается контекст, в  котором вычисляется формула DAX и благодаря которому мы можем получать разные результаты. Можно выделить три различных контекста: „ контекст строки; „ контекст запроса; „ контекст фильтра. В большинстве публикаций и документаций, относящихся к Power BI, речь обычно ведется о двух типах контекста: контексте строки и контексте фильтра. Термин контекст запроса использовался в Power Pivot в Excel, когда Power BI еще не появился (да, мы такие старые!), и мы продолжаем его применять. По нашему опыту преподавания языка DAX, понимание различий между контекстом запроса и  контекстом фильтра позволяет людям затем лучше разобраться в сложных сценариях. Давайте рассмотрим каждый тип контекста отдельно.

Контекст строки Контекст строки (row context) представляет собой контекст, с которым вы работаете при создании вычисляемых столбцов. Формула DAX, определенная для вычисГлаВа 1.4

Контексты и фильтры

77

ляемого столбца, рассчитывается для каждой строки в таблице. Соответственно, и результат вычисления будет специфичным для каждой отдельной строки. Причина этого в том, что в процессе расчета могут использоваться значения текущей строки из других столбцов, а они, как правило, отличаются от значений в других строках. К примеру, мы можем вычислить прибыль в таблице фактов fSales как разницу между суммой продаж и затратами при помощи следующей формулы: Margin = fSales[SalesAmount] – fSales[Costs]

Легко понять, что эта формула применяется в  вычисляемом столбце, поскольку в ней используются прямые ссылки на колонки. Это допустимо делать только в контексте строки, и именно это отличает данный контекст от других. В упрощенном виде префикс fSales часто не используется. Обратите внимание, что при прямом обращении к  столбцу извлекается значение из него только для текущей строки, в которой производится вычисление. Для получения значений из других столбцов вам придется использовать совершенно иной подход. Это существенно отличается от того же Excel, где обычным делом является извлечение значения, например, из предыдущей строки. И отличие это легко объясняется тем, что в модели данных не предусмотрен четкий порядок следования строк в таблицах. Для работы исключительно в контексте строки в DAX предусмотрено не так много функций. Если в  формуле вычисляемого столбца вам необходимо обратиться к значениям в связанной таблице, вы можете сделать это, воспользовавшись функцией RELATED. В формуле, приведенной ниже, используется связь между таблицами fSales и Date по столбцам OrderDate и Date соответственно для извлечения года для каждой строки: Year = RELATED('Date'[Year])

На рис.  1.4.2 показана появившаяся в  результате колонка Year в  таблице fSales.

Рис. 1.4.2. Добавление в таблицу вычисляемого столбца Year (некоторые колонки были перемещены для лучшего восприятия)

78

ГлаВа 1.4

Контексты и фильтры

На функцию RELATED налагается только одно обязательное требование, состоящее в  том, что на другом конце используемой связи должны быть уникальные значения, – в нашем случае в таблице Date. Это обусловлено тем, что формула должна вернуть единственное значение. При наличии между таблицами связи обратной кратности вы можете воспользоваться родственной RELATED функцией под названием RELATEDTABLE. К примеру, если вам необходимо добавить в таблицу Date вычисляемый столбец с суммарным количеством продаж по каждому дню, вы можете использовать следующую формулу: Number of Transactions = COUNTROWS(RELATEDTABLE(fSales))

Здесь функция RELATEDTABLE для каждой строки в  таблице Date будет возвращать набор соответствующих ей строк из таблицы fSales. Как вы понимаете, раз это таблица, значит, она содержит не одно значение, а следовательно, нам необходимо применить какую-то агрегацию для извлечения нужного нам числа. В данном случае мы применили функцию COUNTROWS для подсчета количества строк в возвращаемом наборе. Хотя функция RELATEDTABLE и предназначается для использования в контексте строки, она принципиально отличается от функции RELATED тем, что внутренне она применяет совсем другой тип контекста. Примечание. В главе 1.3 мы всячески предостерегали вас от создания в модели данных вычисляемых столбцов. Но это не значит, что вам не придется работать с контекстом строки. Этот тип контекста играет важную роль при выполнении табличных функций DAX. Подробнее об этом мы поговорим далее в данной главе. Теперь давайте рассмотрим другие типы контекста. Некоторые любопытные особенности работы контекста строки откроются вам только тогда, когда вы узнаете, как действуют контекст запроса и контекст фильтра.

Контекст запроса С контекстом запроса (query context) мы сталкиваемся при использовании мер DAX. Подобно контексту строки, контекст запроса способствует тому, чтобы мера возвращала специфический для конкретного запуска результат. Разница состоит в том, что в данном случае мы не работаем в рамках одной таблицы. Иными словами, контекст запроса оперирует набором выбранных строк в модели Power BI, поверх которых вычисляется формула DAX. Это позволяет сделать различие между разными, хоть и тесно связанными, элементами в контексте запроса: „ выборка (selection) относится к  набору строк в  каждой таблице модели данных, выбранных в указанном контексте; „ фильтры (filters) подразумевают причину выбора строк. ГлаВа 1.4

Контексты и фильтры

79

В контексте запроса фильтры проистекают из элементов в отчетах Power BI. Это могут быть и срезы (slicers), и фильтры на соответствующей панели, и метки на визуальных элементах, и даже выбранные объекты в других элементах визуализации. Каждый из этих элементов формирует особое правило, относящееся к  столбцу. Например, на рис. 1.4.3 срез инициирует создание следующего фильтра в столбце Year: значение равно 2019. При этом одновременно могут быть отфильтрованы разные столбцы, и  даже на один столбец может быть наложено несколько фильтров. Вместе эти фильтры определяют, какие строки будут выбраны в каждой таблице: а это строки, удовлетворяющие всем созданным правилам фильтрации. Примечание. В главе 2.4 мы подробно поговорим о визуальных фильтрах.

Рис. 1.4.3. Простой отчет в Power BI

В контексте запроса важную роль играют связи между таблицами, влияющие на распространение фильтров (filter propagation). Под этим подразумевается, что фильтр, установленный на одном столбце в одной таблице, распространяется на другую таблицу в соответствии с направлением кросс-фильтрации, как показано на рис. 1.4.4. Если говорить строго, то по связям распространяются не  сами фильтры, а эффект от их применения: в связанной таблице выбираются только строки, соответствующие записям, удовлетворяющим примененному правилу фильтрации. А нам именно такое поведение и требуется. Если в срезе по годам выбран 2019 год, как на рис. 1.4.3, мы хотим видеть результаты именно за этот год, а значит, все вычисления должны производиться в таблице фактов только со строками, соответствующими 2019 году. 80

ГлаВа 1.4

Контексты и фильтры

Рис. 1.4.4. Распространение фильтров по связям происходит в направлении стрелки

Природа контекста запроса такова, что вы не можете напрямую обращаться к столбцам в формулах, как в случае с контекстом строки. К примеру, формула, показанная ниже, в виде меры не будет принята редактором запросов: Report Year = 'Date'[Year]

При попытке ее выполнить вы получите сообщение об ошибке, говорящее о  том, что не  получилось извлечь одно значение из столбца Year в  таблице Date. Причина в том, что теоретически выборка может содержать и множество строк. Ошибка возникнет, даже если в  столбце содержится только одно уникальное значение или в таблице присутствует лишь одна строка.

Контекст фильтра Контекст фильтра (filter context) похож на контекст запроса, за одним важным исключением, состоящим в том, что он представляет контекст, меняющийся при помощи кода DAX, например путем добавления или изменения фильтров в  контексте запроса. В  основном с  этой целью в  DAX используется функция CALCULATE (или ее табличная версия CALCULATETABLE). Подробно о том, как именно происходит изменение контекста с помощью функции CALCULATE, мы поговорим далее в этой главе. Трудности при работе с  контекстом фильтра заключаются в  том, что вы не  можете определять фильтры в  контексте из визуальных элементов, как в случае с контекстом запроса. Работа с контекстом фильтра требует присутствия абстрактного мышления и  максимального внимания к  мелочам относительно того, какие фильтры активны в той или иной ситуации. По этой же причине меры, в которых создается и меняется контекст фильтра, могут вводить пользователей в заблуждение, в связи с чем их необходимо использовать с осторожностью и давать им понятные имена. Способность менять контекст открывает перед нами богатые возможности, недоступные при использовании контекста строки или запроса. С  ней мы можем извлекать результаты, не  соотносящиеся с  контекстом запроса, а это позволяет добывать необычные аналитические выводы, такие как доли продаж конкретного товара, сравнение двух разных лет, прогнозирование ГлаВа 1.4

Контексты и фильтры

81

показателей и  т. д. Фактически ни один сценарий, который мы будем обсуждать во второй части книги, невозможно реализовать без использования контекста фильтра. Схематически алгоритм создания сложных аналитических вычислений при помощи языка DAX можно описать следующим образом. Изучаем (возможные) контексты запроса, в  которых будет выполняться наше вычисление. 1. Решаем, какой контекст фильтра нам нужен для расчета нужного результата. 2. Определяем способ перехода от контекста запроса к контексту фильтра. Чтобы добиться значимых высот в DAX, вам необходимо научиться выстраивать мысли именно так, несмотря на то что это кардинально отличается от подходов, применяемых для извлечения данных с помощью SQL, языков программирования или вычислений в Excel.

Обнаружение фильтров Действующие фильтры в контексте вычисления оказывают влияние на выбор строк в таблицах модели данных. Рассматривая этот эффект применительно к  одному столбцу, можно выделить сразу несколько видов фильтрации. Значения в столбце могут быть не выделены вовсе – в этом случае все они будут входить в контекст. Также могут быть выделены несколько значений в столбце. Это может произойти в  результате наложения фильтра на эту колонку – и тогда мы говорим о прямой фильтрации (directly filtered), – или посредством применения фильтра к другому столбцу в той же таблице или даже в другой, если между таблицами установлена соответствующая связь с распространением фильтра. В  этом случае вне  зависимости от того, откуда именно пришел фильтр, мы говорим, что столбец отфильтрован косвенно (indirectly filtered), или к нему применена перекрестная или кросс-фильтрация. В языке DAX есть ряд функций для обнаружения присутствующих в контексте фильтров. Каждая из перечисленных ниже функций принимает на вход ссылку на столбец (скажем, столбец A): ISFILTERED: служит для установки факта прямой фильтрации столбца A; ISCROSSFILTERED: определяет, присутствуют ли в  модели данных фильтры, влияющие на выбор значений в столбце A; HASONEFILTER: проверяет наличие выбора единственного значения в  столбце A в результате наложения прямого фильтра; HASONEVALUE: определяет, присутствуют ли в  модели данных фильтры, в  результате которых выбирается единственное значение в столбце A; ISINSCOPE: может использоваться для определения того, выделено ли в столбце A  единственное значение в  результате наложения фильтра, исходящего из визуального элемента. Эта функция предназначена для определения текущего уровня детализации в визуальных элементах, поддерживающих иерархии. Все перечисленные функции могут быть использованы при исследовании контекстов. Также их можно применять с  целью задания определенного поведения для мер DAX, хотя на этом пути вас могут ждать ловушки. В главе 2.1 мы рассмотрим несколько примеров на эту тему. 82

ГлаВа 1.4 Контексты и фильтры

Сравнение контекста запроса и фильтра с контекстом строки Теперь, когда мы узнали о контексте запроса и фильтра, мы можем взглянуть на контекст строки под другим углом. В качестве примера предположим, что вы создали вычисляемый столбец в таблице fSales при помощью следующей формулы: TotalTax = SUM(fSales[Tax])

В итоге будет создан столбец TotalTax с одинаковыми значениями во всех строках. Функция SUM сложила значения во всех строках в таблице, несмотря на наличие контекста строки. Новичкам в  DAX бывает непросто это понять. Теперь давайте взглянем на еще один вычисляемый столбец, на этот раз в таблице Date: TotalShipping = SUM(fSales[ShippingCosts])

И снова вы увидите, что во всех строках будет присутствовать одинаковое значение, несмотря на наличие связи между таблицами fSales и Date. Почему же эта связь не позволила рассчитать затраты на перевозку для каждой даты отдельно? Представленные примеры помогают нам лучше понять природу контекста строки. В случае со столбцом TotalShipping мы видим, что в контексте строки выбор не распространяется по связям. Это полезно иметь в виду при работе, но в реальности все еще чуть более запутано. В контексте строки DAX позволяет использовать значения столбцов из той же таблицы, но в остальном все фильтры и выборки остаются незадействованными. Таким образом, в вычисляемом столбце нет никаких фильтров на столбцы из этой таблицы. Как следствие распространять по связям тоже нечего, и  при обращении к другой таблице, как в случае с вычисляемым столбцом TotalShipping, вы будете работать со всей таблицей в  целом. И  даже если вы будете обращаться к  колонкам из той же таблицы, как в примере с вычислением TotalTax, вы также будете работать со всеми без исключения строками. Соответственно, если вы находитесь в контексте строки, но вам необходимо использовать связь для вычисления, вам придется найти способ преобразовать контекст строки в контекст фильтра. И для этого вы можете использовать функцию CALCULATE.

Фильтрация в DAX: использование функции CALCULATE Изменение контекста с помощью кода является одной из мощнейших возможностей языка DAX. И главная роль в этом процессе отводится функции CALCULATE, которая и способствует выполнению преобразования контекста (context ГлаВа 1.4 Контексты и фильтры

83

transformation). Передавая этой функции фильтрующие выражения, вы можете легко и  просто управлять наборами строк, к  которым будет применена формула. При этом допустимо как добавлять и  заменять фильтры на новые в действующем контексте, так и удалять существующие. Поскольку при формировании контекстов важнейшую роль играют связи, участвующие в распространении фильтров, их активацию, деактивацию и  изменение направления распространения фильтров также можно считать разновидностью преобразования контекста. Давайте начнем с простейшей меры DAX: SalesLargeUnitAmount = CALCULATE( SUM(fSales[SalesAmount]), fSales[UnitAmount] > 25 )

Эта мера возвращает сумму продаж по транзакциям, в  которых количество проданных товаров (UnitAmount) превышает 25 штук. Первым аргументом в  функцию CALCULATE передается выражение, которое должно быть вычислено,  – в  нашем случае это сумма по столбцу SalesAmount в  таблице fSales. Остальные аргументы, а их может быть несколько, отвечают за фильтры. Примечание. Язык DAX позволяет писать формулы без явного указания функции CALCULATE, когда вычисление производится в отдельной мере. Допустим, у нас есть базовая мера Sales, выраженная следующей формулой: Sales = SUM(fSales[SalesAmount])

Тогда нашу предыдущую меру SalesLargeUnitAmount мы бы могли переписать таким образом: SalesLargeUnitAmount = [Sales] (fSales[UnitAmount] > 25)

Мы не советуем вам использовать этот синтаксис, поскольку явное упоминание функции CALCULATE в мере делает ее гораздо более легкой для восприятия, особенно в случае со сложными формулами.

Чтобы лучше понимать, как работает функция CALCULATE, необходимо усвоить четыре этапа ее выполнения. 1. Существующий контекст (контекст строки/запроса или другой контекст фильтра) преобразуется в контекст фильтра. 2. Удаляются действующие фильтры (если они установлены) на столбцы (или целые таблицы), на которые ссылаются фильтрующие аргументы. 84

ГлаВа 1.4

Контексты и фильтры

3. Добавляются новые фильтры. 4. Выражение, переданное в  первом аргументе, вычисляется в  обновленном контексте фильтра. В аргументах фильтра могут быть использованы различные функции DAX, позволяющие менять поведение вычисления. Но сначала давайте подробно пройдем по перечисленным выше шагам на примере меры SalesLargeUnitAmount.

Шаг 1. Настройка контекста фильтра Первое, что необходимо сделать функции CALCULATE, – это создать окружение, в котором могут быть изменены нужные фильтры. Если у нас уже есть контекст запроса или фильтра, значит, есть и окружение, так что для его создания ничего делать не придется. Таким образом, для нашей меры SalesLargeUnitAmount этот шаг будет тривиальным. Но когда у нас в наличии есть лишь контекст строки, все становится несколько сложнее. Преобразование контекста строки в  контекст фильтра достигается путем создания фильтра для каждой колонки в таблице, чтобы в ней осталось только значение из текущей строки (помните, что контекст строки всегда связан с одной строкой). В результате мы получим контекст фильтра с выбранными значениями из текущей строки. Если наша таблица связана с другими таблицами, в этом случае фильтры будут распространяться по связям, что приведет к выбору соответствующих наборов строк в этих связанных таблицах. К примеру, мы можем изменить формулу для вычисляемого столбца TotalTax, который создали ранее, просто обернув ее в функцию CALCULATE. Заметьте, что в этом случае функция CALCULATE используется без аргументов фильтра: TotalTax2 = CALCULATE(SUM(fSales[Tax]))

Для каждой строки в  таблице контекст строки будет преобразован в  контекст фильтра. В результате функция SUM будет применяться только к выбранным строкам – в нашем случае к одной текущей строке. Иными словами, мы получим просто значения из колонки Tax, как показано на рис. 1.4.5.

Рис. 1.4.5. Эффект от применения функции CALCULATE в вычисляемом столбце

ГлаВа 1.4

Контексты и фильтры

85

С вычисляемым столбцом TotalShipping, созданным в таблице Date, сделаем то же самое: TotalShipping = CALCULATE(SUM(fSales[ShippingCosts]))

Контекст строки в таблице Date преобразуется в контекст фильтра, включающий в себя фильтры по всем колонкам. На этот раз связь между таблицами fSales и Date может помочь распространить эти фильтры, что приведет к выбору строк в таблице fSales, соответствующих текущей строке в таблице Date, как показано на рис. 1.4.6.

Рис. 1.4.6. Использование функции CALCULATE привело к распространению фильтров по связям

В результате применения функции CALCULATE мы получили актуальные цифры в вычисляемом столбце TotalShipping, отражающие затраты на перевозку по каждому дню. Примечание. Вам может показаться нереалистичным использование функции CALCULATE без аргументов фильтра, но на самом деле вы применяете ее в таком виде гораздо чаще, чем вам кажется. Каждый раз, когда вы вызываете меру внутри формулы, она неявным образом заключается в функцию CALCULATE. Рассмотрим следующий пример: SalesByCustomer = DIVIDE([Sales], [Number of Customers])

Здесь оба вычисления – для Sales и Number of Customers – выполняются в контексте фильтра. Вызов меры – распространенный способ инициировать преобразование контекста строки в контекст фильтра, что требуется достаточно часто.

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

ГлаВа 1.4

Контексты и фильтры

Шаг 2. Удаление существующих фильтров На втором шаге происходит удаление фильтров из нового контекста фильтра. Этот процесс очень прост и заключается в проверке каждого столбца, упомянутого в аргументах фильтра, на наличие установленных ранее фильтров. Если фильтры есть, то они удаляются. В нашем примере с мерой SalesLargeUnitAmount аргумент фильтра был лишь один: fSales[UnitAmount] > 25

Так что функции CALCULATE достаточно будет удалить все наложенные фильтры со столбца fSales[UnitAmount]. О чем часто забывают пользователи и  разработчики, так это о  том, что столбцы, не упомянутые в аргументах фильтра, сохраняют наложенные ранее фильтры. И  если у  вас нет полного контроля за исходным контекстом фильтра, вы должны с большой осторожностью настраивать вычисление, учитывая различные сценарии, в которых может быть использована ваша мера. Вам может понадобиться удалить больше фильтров, чем вы планировали изначально. После описания всех четырех шагов мы продемонстрируем пример, в котором будет показано влияние других фильтров на вычисление. Поведение функции CALCULATE на втором шаге можно скорректировать при помощи функции KEEPFILTERS. Эта функция позволяет пропустить второй шаг для аргумента фильтра, к которому она применена. Например: SalesLargeUnitAmount KeepFilters = CALCULATE( [Sales], KEEPFILTERS(fSales[UnitAmount] > 25) )

В этом случае если на столбец UnitAmount ранее уже был наложен фильтр, он будет сохранен, а на третьем шаге к нему добавится новый фильтр.

Шаг 3. Применение новых фильтров На третьем шаге выполнения функции CALCULATE происходит применение новых фильтров. Как и  на втором шаге, мы проходим по аргументам фильтра и применяем эти инструкции для создания новых фильтров. Снова обратимся к нашему простому примеру: fSales[UnitAmount] > 25

В результате на основании этой инструкции будет создан фильтр на столбец UnitAmount, отбирающий только строки со значением, превышающим 25. Для лучшего понимания работы функции CALCULATE необходимо иметь в виду, что шаги 2 и 3 выполняются именно в таком порядке. При этом порядок следования аргументов фильтра не  имеет значения. Рассмотрим следующий простой пример: ГлаВа 1.4 Контексты и фильтры

87

Sales373_374 = CALCULATE( [Sales], Products[ProductID] = 373, Products[ProductID] = 374 )

Если вы плохо знакомы с  языком DAX, то могли бы подумать, что в данном случае вернется сумма продаж по товару с идентификатором 374, и аргументировали бы это тем, что сначала был бы установлен фильтр на идентификатор 373, а затем – на 374. В действительности же эта мера всегда будет возвращать пустое значение по причине  объединения перечисленных фильтров, противоречащих друг другу. В таблице Products не  могут присутствовать элементы, содержащие несколько идентификаторов сразу, в результате чего всегда будет возвращаться пустое значение (если мы предполагаем, что между таблицами Products и fSales установлена соответствующая связь с возможностью распространения фильтров). Вам может эта мера показаться излишне простой, однако ее немного измененный аналог способен ввести в заблуждение многих: Sales373OrWhat = CALCULATE( [Sales], Products[ProductID] = 373, ALL(Products) )

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

Шаг 4. Вычисление выражения в новом контексте Последний шаг выполнения функции CALCULATE очень прост и  заключается в том, чтобы после настройки нового контекста фильтра, удаления прежних фильтров и  добавления новых вычислить выражение из первого аргумента в создавшемся окружении. И это кульминационный момент, поскольку именно на этом этапе мы наблюдаем эффект от преобразования контекста. В качестве доказательства того, как сложно бывает разобраться в  манипуляциях с фильтрами, давайте рассмотрим матрицу, показанную на рис. 1.4.7. В представленной матрице информация о товарах выводится с использованием столбцов Group и  ProductID в  виде меток. Мы  сосредоточим внимание на товаре с  идентификатором 373 в  товарной группе Rear wheel. Название интересующего нас товара, содержащееся в колонке Product, звучит так: REAR WHEEL STEEL #525. Мы хотим иметь возможность сравнивать суммы продаж по другим товарам с товаром с идентификатором 373. Допустим, этот товар для нашей компании является неким эталоном с точки зрения стратегии продаж, и мы хотим выразить суммы продаж по другим товарам в процентах от установленного эталона. 88

ГлаВа 1.4 Контексты и фильтры

Рис. 1.4.7. Вывод двух мер в матрице

Для выполнения сравнения нам необходимо иметь колонку с суммой продаж по товару с идентификатором 373 в каждой строке. Попробуем добиться этого с помощью двух следующих мер: Sales373 = CALCULATE( [Sales], Products[ProductID] = 373 )

И вторая: SalesRearWheel525 = CALCULATE( [Sales], Products[Product] = "REAR WHEEL STEEL #525" )

Мера Sales также выведена в  матрицу, чтобы вам было легче ориентироваться. В большинстве строк этого визуального элемента в контексте запроса присутствуют два фильтра: один на столбце Group, а второй – на ProductID. Исключения составляют строки с  подытогами (в  которых отфильтрован только столбец Group) и с общими итогами (в ней фильтров нет). Как видите, две наши меры, использующие функцию CALCULATE, возвращают разные результаты. В чем причина такой разницы? Поскольку в мере Sales373 мы ссылаемся в аргументе фильтра на столбец ProductID, все наложенные на него ранее фильтры будут удалены (шаг 2) перед установкой новых фильтров (шаг 3). К примеру, в строке с товаром 239 будет удален фильтр «ProductID = 239», а вместо него появится «ProductID = 373». В результате, как мы и видим на рис. 1.4.7, будет выведена сумма продаж по товару с идентификатором 373. В мере SalesRearWheel525 все несколько иначе. Здесь в аргументе фильтра мы ссылаемся на столбец Product, так что существующие фильтры будут удалены именно с него, после чего будут наложены новые. Давайте снова посмотрим на строку с товаром 239. Здесь контекст запроса, как мы уже говорили, включает ГлаВа 1.4

Контексты и фильтры

89

в  себя два фильтра: по столбцам Group и  ProductID. В  нашей мере фильтры из этих колонок не удаляются, а лишь добавляется фильтр по столбцу Product. Таким образом, образовавшийся контекст фильтра включает строки, удовлетворяющие всем трем условиям. Иными словами, для всех товаров, которые не будут отвечать всем трем критериям, будут выводиться пустые значения. Как вы понимаете, это условие будет соблюдаться только для товара с идентификатором 373, и именно поэтому в матрице столбец SalesRearWheel525 заполнен лишь для него. По той же причине  мера Sales373 возвращает пустые значения для всех групп, кроме Rear wheel: когда в фильтре по столбцу Group установлена другая группа, комбинация с  фильтром на равенство 373 по столбцу ProductID, добавленным на шаге 3, приведет к отсутствующему выбору в таблице Product.

Удаление фильтров при помощи функций группы ALL У обеих мер из предыдущего раздела была одна и та же проблема – обе они зависели от текущего контекста. Чтобы написать меру, всегда возвращающую сумму продаж по товару с идентификатором 373, вне зависимости от того, какие товары выбраны в текущем контексте запроса, необходимо обладать возможностью избавляться от любых фильтров, которые могут встать на пути. Очень важно иметь полный контроль над тем, какие именно фильтры удаляются. Для этого в DAX предусмотрен целый ряд функций группы ALL. Разница между ними состоит в том, какие именно фильтры удаляются. ALL: эта функция может принимать ссылку на один или несколько столбцов, а также на целую таблицу. В результате ее выполнения будут удалены фильтры со всех указанных колонок или со всех колонок в переданной таблице. При желании вы можете использовать функцию ALL и без аргументов – в этом случае будут сброшены все существующие фильтры в модели данных Power BI. Примеры: ALL(Cities[Country]) ALL(Cities[Country], Cities[State]) ALL(Cities) ALL()

Примечание. При использовании функции ALL применительно ко всей таблице в целом фильтры также будут удалены со столбцов в связанных таблицах. К примеру, выражение ALL(fSales) приведет также к снятию фильтров в таблице Cities при наличии связи типа «многие к одному» между таблицами fSales и Cities. Смотрите также функцию ALLCROSSFILTERED. ALLEXCEPT: эта функция может быть использована в  качестве альтернативы функции ALL со множеством аргументов. Вместо того чтобы указывать все столбцы, с  которых вы хотите снять фильтры, вы можете задать таблицу и лишь те столбцы, для которых необходимо сохранить фильтры. В результате со всех остальных колонок в таблице фильтры будут удалены. Пример: 90

ГлаВа 1.4

Контексты и фильтры

ALLEXCEPT(Cities, Cities[Country])

ALLNOBLANKROW: при использовании функции ALL в результирующий контекст будут включены все значения из указанных столбцов, в том числе и значения из пустых строк, которые добавляются в таблицы при наличии неполных связей (как мы говорили в главе 1.2, такие значения всегда будут пустыми). Если же вы не хотите, чтобы в новый контекст были включены пустые значения, то можете использовать функцию ALLNOBLANKROW вместо ALL. Эта функция принимает в качестве аргумента либо столбец, либо таблицу: ALLNOBLANKROW(Cities[Country])

ALLSELECTED: это особая функция группы ALL, поскольку она умеет оперировать источниками фильтров. Функция ALLSELECTED удаляет только фильтры, проистекающие из меток в рамках визуального элемента, в котором используется мера. При этом внешние фильтры, исходящие от срезов, панелей фильтров или других визуальных элементов, остаются нетронутыми. Эта функция используется с целью создания мер для агрегирования выбранных элементов в рамках визуального элемента,  например чтобы при расчете процентов итог всегда составлял 100  %. Функция принимает в  качестве аргументов таблицу или одну либо несколько столбцов. Также она может быть вызвана без аргументов. Пример: ALLSELECTED(Cities[Country])

ALLCROSSFILTERED: эта функция была введена в использование для составных моделей Power BI, моделей, включающих в себя как импортированные таблицы, так и таблицы в режиме DirectQuery, а также моделей с несколькими подключениями DirectQuery. Связи между таблицами различного происхождения в таких моделях являются слабыми (weak relationships). В стандартной модели в режиме импорта с использованием функции ALLCROSSFILTERED нет никакой необходимости. Пример: ALLCROSSFILTERED(fSales)

Примечание. Существует еще одна функция в DAX, которая также может применяться для удаления фильтров внутри CALCULATE, и это REMOVEFILTERS. В качестве аргументов она может принимать таблицу или один либо несколько столбцов, например: CALCULATE( [Sales], REMOVEFILTERS(Cities) )

Эта функция была представлена в качестве более понятной альтернативы функции ALL. Мы предпочитаем использовать более короткую функцию ALL и никогда не применяем REMOVEFILTERS.

ГлаВа 1.4

Контексты и фильтры

91

Тщательно выбирая одну функцию группы ALL или их комбинацию, вы можете заставить функцию CALCULATE делать именно то, что вы хотите. Если помните, мы хотели создать меру для расчета суммы продаж по товару с идентификатором 373. Таким образом, мы точно знаем, каким мы хотим видеть наш контекст фильтра. Мы не можем контролировать то, какие фильтры изначально будут присутствовать в контексте запроса, но нам вполне по силам удалить все ненужное. Вот как будет выглядеть наша мера: SalesRearWheel525_ALL = CALCULATE( [Sales], Products[Product] = "REAR WHEEL STEEL #525", ALL(Products) )

В результате выполнения этой формулы все присутствующие в  таблице Products фильтры будут удалены, после чего будет наложен фильтр на столбец Product. Разница очевидна, и она показана на рис. 1.4.8.

Рис. 1.4.8. Использование функции ALL

Заметьте, что в данном случае сумма продаж по нашему эталонному товару выводится не только для товаров в той же группе и итогов, но также и для всех товаров и групп в справочнике. Это позволяет нам очень легко и просто выразить продажи по другим товарам в виде процента от товара с идентификатором 373, как показано ниже: Sales% = DIVIDE([Sales], [SalesRearWheel525_ALL]

Комбинируя аргументы фильтра с  функциями группы ALL, вы можете испытать всю мощь языка DAX при создании мер. При этом некоторые фильтры бывает очень непросто реализовать стандартными средствами, например это касается фильтров, связанных с логикой операций со временем. Именно поэтому в DAX предусмотрен целый ряд отдельных функций для работы с временными рядами, о которых мы поговорим в следующем разделе. 92

ГлаВа 1.4 Контексты и фильтры

Функции логики операций со временем Вряд ли вам удастся встретить хотя бы одну модель данных Power BI, в которой не будет ни в каком виде производиться анализ временных рядов. К примеру, нам всегда приходится сравнивать текущие показатели с их прошлогодними значениями. Но этим дело обычно не ограничивается. К операциям логики со временем относятся и накопительные показатели с начала года, и скользящие итоги, и  метрики прироста по сравнению с любым другим периодом в  прошлом. Сложность здесь заключается в том, что григорианский календарь, хоть и привычен всем нам, структурирован не самым логичным образом – в большинстве лет 365 дней, но иногда и 366, да и дней в месяцах бывает от 28 до 31. Несмотря на все эти сложности с  календарем, анализ временных рядов сводится к  простой фильтрации с  целью изменения контекста. Взгляните на рис. 1.4.9, на котором изображены суммы продаж нарастающим итогом с начала года.

Рис. 1.4.9. Диаграмма нарастающего итога с начала года

В столбике августа мы видим сумму продаж, накопленную с  начала января этого года по конец августа. В то же время контекст запроса для этого столбика содержит фильтры по году и месяцу, что по умолчанию приведет к выбору всех дат из одного только августа. Очевидно, что нам необходимо изменить контекст, чтобы правильно посчитать сумму продаж нарастающим итогом с начала года. На первый взгляд кажется, что для произведения нужного нам вычисления следует воспользоваться уже известной нам функцией CALCULATE с какими-то аргументами фильтра, как показано ниже: SalesYTD = CALCULATE( [Sales], ... (некоторые аргументы фильтра) )

ГлаВа 1.4

Контексты и фильтры

93

И действительно, в языке DAX предусмотрено сразу несколько аргументов фильтра для функции CALCULATE, способных справляться с  перечисленными выше сложностями календаря. К  примеру, фильтр для вычисления нарастающего итога с  начала года можно задать с  помощью специальной функции DATESYTD следующим образом: SalesYTD = CALCULATE( [Sales], DATESYTD('Date'[Date]) )

Функция DATESYTD выполняется на основе контекста запроса в таблице дат. Алгоритм ее действия следующий. 1. Извлекается последняя дата в контексте. 2. Определяется, какому году принадлежит эта дата, и находится первый день этого года. 3. Создается фильтр на столбце Date[Date], выбирающий все даты начиная с первого дня года до последнего дня в контексте. В рамках образовавшегося контекста может производиться вычисление при помощи функции CALCULATE – в нашем примере речь идет о вычислении меры Sales. Но постойте! В аргументе фильтра DATESYTD происходит ссылка на столбец Date, а  на рис.  1.4.9 мы видим, что фильтры установлены на столбцы Year и Month. Это весьма распространенная ситуация, для разрешения которой функция DATESYTD выполняет еще один дополнительный шаг: 4. Неявным образом добавляется аргумент фильтра ALL('Date'). Последний шаг является общим для всех функций, реализующих логику операций со временем. Он помогает избежать явного включения всех необходимых функций ALL в аргументы фильтра. Примечание. Условие ALL('Date'[Date]) добавляется в формулу только в том случае, если вы явным образом пометили календарь в Power BI как таблицу дат или связь между таблицей фактов и календарем создана по столбцу с типом Дата. И хотя функции логики операций со временем могут корректно работать и без явного определения таблицы дат, мы настоятельно рекомендуем такую таблицу иметь. Как уже упоминалось, анализ временных рядов – это одна из самых распространенных задач в аналитике данных. Именно поэтому в DAX предусмотрены и  более короткие версии функций логики операций со временем. К  примеру, рассмотренная выше комбинация функций DATESYTD и CALCULATE может быть заменена более лаконичной функцией TOTALYTD, как показано ниже: 94

ГлаВа 1.4

Контексты и фильтры

SalesYTD_short = TOTALYTD( [Sales], 'Date'[Date] )

Эта формула приведет к  точно таким же результатам, хотя синтаксически она сильно отличается от предыдущего варианта. Несмотря на все преимущества сокращенной записи, к ее недостаткам можно отнести то, что новички в DAX часто ошибочно считают, что данная функция вычисляет именно итог (total) по показателю с начала года. На самом же деле все, что делает функция TOTALYTD, – это изменяет контекст. Таким образом, она может вычислять не только нарастающий итог, но и нарастающее среднее и любую другую агрегацию – все зависит от меры, передаваемой в качестве первого аргумента. Примечание. Также функции DATESYTD и TOTALYTD могут быть использованы для анализа показателей в финансовых календарях, где начало финансового года может быть любой датой. С этой целью функция DATESYTD предусматривает возможность передачи второго аргумента в виде даты, например "8/31" или "2020/9/30", которую функция будет воспринимать в качестве последней даты года. По неведомой нам причине в функции TOTALYTD этот аргумент стоит в списке аргументов лишь четвертым, а это означает, что вам необходимо включить и третий аргумент, представляющий собой дополнительный фильтр. Здесь вы можете безопасно использовать выражение ALL('Date'[Date]), поскольку оно все равно добавляется неявным образом. Также, помимо DATESYTD, в DAX часто используются следующие функции логики операций со временем. SAMEPERIODLASTYEAR: как ясно из названия, эта функция берет текущий контекст и  переносит его во времени ровно на год назад. Это необходимо при сравнении показателей с  прошлогодними значениями. Интересно, что для этой функции не предусмотрена сокращенная версия. Сумму продаж прошлого года можно вычислить с помощью следующей формулы: SalesLY = CALCULATE( [Sales], SAMEPERIODLASTYEAR('Date'[Date]) )

DATESINPERIOD: эта функция может быть использована для получения периода, начинающегося (или заканчивающегося) с  указанной даты и  продолжающегося заданное количество дней, месяцев, кварталов или лет. Функция DATESINPERIOD бывает полезна при расчете скользящих показателей. К примеГлаВа 1.4

Контексты и фильтры

95

ру, скользящую сумму продаж за 12 месяцев можно вычислить с помощью показанной ниже формулы. Здесь мы используем выражение MAX('Date'[Date]) для определения последней даты в контексте в качестве базовой: SalesRollingTotal = CALCULATE( [Sales], DATESINPERIOD( 'Date'[Date], MAX('Date'[Date]), -12, MONTH ) )

Работая с  функциями логики операций со временем, важно помнить, что каждая такая функция всего лишь изменяет контекст в таблице дат, ничего более. Таким образом, если какая-то таблица в вашей модели данных не связана с таблицей дат, на нее это изменение контекста никак не повлияет. Кроме того, стоит учитывать, что вы можете получить непредсказуемые результаты при использовании сокращенной таблицы дат. К примеру, если даты в вашем календаре начинаются с 2020 года и вы используете функцию SAMEPERIODLASTYEAR в  контексте с  выбранными датами из февраля этого года, результирующий смещенный контекст окажется пустым. Это приведет к возвращению мерами пустых результатов даже в том случае, если в таблице фактов, данные из которой вы анализируете, присутствуют транзакции за 2019 год и ранее.

Изменение поведения связей В главе 1.2 мы узнали, что между двумя таблицами может быть установлено сразу несколько связей, лишь одна из которых в любой момент времени должна быть активна. Это касается и последовательностей из связей между таблицами: в Power BI допускается только один активный маршрут, по которому от одной таблицы можно прийти к другой. Разумеется, такая концепция может быть приемлема только при наличии возможности сделать любую связь активной или неактивной по требованию. И такую возможность предоставляет функция USERELATIONSHIP. Функция USERELATIONSHIP используется путем включения ее в  аргументы фильтра функции CALCULATE. Вас это может удивить, но в  действительности в этом есть свой резон. Связи между таблицами фактов и фильтрующими таблицами обеспечивают распространение выбора элементов между ними в направлении от измерений к фактам. Активирование другой связи будет означать распространение выбора уже из другой фильтрующей таблицы, в результате чего в таблице фактов окажутся выбраны уже другие строки. Иными словами, активирование связи приводит к изменению контекста для вычисления, а для этого функция CALCULATE в конечном счете и используется. Функция USERELATIONSHIP принимает два аргумента, указывающих на столбцы, связь между которыми должна быть активирована. Например, если между 96

ГлаВа 1.4 Контексты и фильтры

таблицами fSales и  Date активная связь установлена по столбцу fSales[OrderDate], а  вы хотите использовать связь по столбцу fSales[InvoiceDate], то можете сделать это при помощи следующей формулы: TotalInvoiced = CALCULATE( [Sales], USERELATIONSHIP(fSales[InvoiceDate], 'Date'[Date]) )

Очевидно, что при использовании другой активной связи между этими таблицами результаты вычисления изменятся. Если мера Sales возвращает общую сумму заказов, то мера TotalInvoiced будет вычислять сумму, выставленную по счетам. Первую можно использовать при анализе доходности, а  вторую – для учета денежных потоков (в этом случае важную роль играет расчет фактических платежей). Разумеется, тонкости расчетов зависят от того, как в конкретной организации выполняется анализ. Еще одним способом изменить поведение связи является корректировка направления распространения фильтров по ней. В  DAX с  этой целью используется функция CROSSFILTER, которая также передается функции CALCULATE в качестве аргумента фильтра. Подобно USERELATIONSHIP, функция CROSSFILTER первыми двумя аргументами принимает столбцы, участвующие в связи. Третьим аргументом устанавливается направление распространения фильтров (filter propagation direction) или тип кросс-фильтрации (cross filter type) связи. Существует пять типов кроссфильтрации, которые вы можете использовать в функции CROSSFILTER: „ OneWay: фильтры распространяются в направлении, установленном по умолчанию: от таблицы с  первичным (уникальным) ключом к  таблице с внешним (неуникальным) ключом; „ Both: фильтры распространяются по связи в обоих направлениях; „ None: фильтры по связи не распространяются; „ OneWay_LeftFiltersRight: фильтры распространяются в одном направлении, от столбца из первого аргумента к столбцу из второго аргумента; „ OneWay_RightFiltersLeft: фильтры распространяются в одном направлении, от столбца из второго аргумента к столбцу из первого аргумента. Для демонстрации работы функции CROSSFILTER давайте представим, что вам понадобилось узнать, в скольких штатах продавались ваши товары. В модели данных, представленной на рис. 1.4.10, присутствуют связи между таблицами fSales, Cities и Date. Обратите внимание на диаграмме, что мы можем выбрать конкретный месяц, и все транзакции за этот месяц будут выделены, следуя активной связи между таблицами. Мы  не  имеем возможности посчитать количество штатов напрямую, поскольку по действующей связи между таблицами Cities и fSales фильтры распространяются только в направлении таблицы фактов, тогда как нам необходимо отфильтровать данные в  обратном направлении, чтобы выбранные строки в таблице fSales могли подсветить соответствующие им строки в таблице Cities, и тогда мы сможем посчитать количество задействованных штатов. ГлаВа 1.4

Контексты и фильтры

97

Рис. 1.4.10. Модель данных для анализа продаж товаров по городам

Очевидно, что для этого нам нужно повернуть реку связи между этими таблицами вспять. В DAX вы можете очень легко это сделать при помощи показанной ниже формулы: StatesSoldTo = CALCULATE( DISTINCTCOUNT(Cities[State]), CROSSFILTER(fSales[CityID], Cities[CityID], Both) )

Функция DISTINCTCOUNT позволяет посчитать количество неповторяющихся значений в столбце State. Но перед этим мы используем функцию CROSSFILTER, чтобы выбор строк в таблице fSales повлиял на фильтр, наложенный на таблицу Cities.

Табличные функции в DAX В DAX можно много всего сделать с использованием базовых агрегатных функций вроде SUM и AVERAGE, особенно в комбинации с богатыми возможностями в области фильтрации, предлагаемыми функцией CALCULATE. Но язык DAX этим не  ограничивается. В  данном разделе мы поговорим о  табличных функциях (table functions), открывающих новый взгляд на вычисления в DAX. Во второй части книги вы узнаете, что многие реальные сценарии включают активное использование табличных функций.

Табличные агрегации Давайте начнем с простейшей агрегации в DAX, показанной ниже: Sales1 = SUM(fSales[SalesAmount])

98

ГлаВа 1.4

Контексты и фильтры

Функция SUM здесь проходит по таблице fSales и в каждой строке извлекает значения из столбца SalesAmount. Затем все эти значения складываются, и получается итоговый результат. Примечание. В Power BI данные кодируются и хранятся специфическим образом, так что чисто технически этот процесс происходит иначе. Но смысл операции суммирования от этого не меняется, а нас здесь интересует именно это. Предположим, что SalesAmount  – это вычисляемый столбец со следующей формулой: SalesAmount = fSales[UnitAmount] * fSales[SalesPrice]

Поскольку столбцы UnitAmount и SalesPrice также располагаются в таблице fSales, возникает резонный вопрос: можно ли посчитать сумму продаж без использования столбца SalesAmount? Ведь ранее мы настоятельно не  рекомендовали создавать в таблицах вычисляемые столбцы. Выражение, которое нам нужно, должно проходить по таблице fSales, но вместо того чтобы извлекать значения из столбца SalesAmount – брать числа из колонок UnitAmount и SalesPrice и перемножать их построчно. В DAX с этой целью можно использовать функцию SUMX, как показано ниже: Sales2 = SUMX( fSales, fSales[UnitAmount] * fSales[SalesPrice] )

Тогда как функция SUM принимает на вход единственный аргумент в  виде ссылки на агрегируемый столбец, функция SUMX оперирует двумя входными аргументами. В первом ей необходимо передать таблицу для выполнения итераций (в  нашем примере fSales), а  во втором – выражение для вычисления в каждой строке. Мы называем функции, подобные SUMX, табличными агрегатными функциями (table aggregation functions), но вы также можете встретить название итератор (iterator), поскольку эти функции проходят по переданной таблице подобно циклу и выполняют агрегацию. Большинство агрегатных функций имеют свои аналоги из числа табличных агрегатных функций, такие как SUMX, AVERAGEX, MINX, MAXX, COUNTX, COUNTAX, PRODUCTX и CONCATENATEX. Как ясно из представленного списка, имена этих функций образуются путем добавления буквы X к их базовым аналогам. К числу менее популярных табличных агрегатных функций относятся статистические функции MEDIANX, PERCENTILEX и STDEVX. Последние две функции имеют по две разновидности, о чем мы не будем подробно распространяться на страницах этой книги. Простейшей табличной агрегатной функцией является COUNTROWS, возвращающая количество строк в таблице и не имеющая базового эквивалента. В то же время функция RANKX является табличным аналогом функции RANK.EQ. ГлаВа 1.4

Контексты и фильтры

99

Пример, показанный выше, хорошо подходит, если вы хотите избавиться от использования вычисляемых столбцов. Но истинная мощь табличных агрегатных функций кроется в том, что вы можете использовать в качестве их первого аргумента любую таблицу. Представим, что нам необходимо создать меру, вычисляющую среднюю сумму продаж по городам. Здесь нам нет никакой необходимости проходить по таблице fSales, содержащей отдельные транзакции. Вместо этого будет достаточно пройти по таблице Cities с городами, как показано ниже: SalesPerCity = AVERAGEX( Cities, [Sales] )

Чуть позже мы узнаем, как именно в данном случае происходит вычисление, но для начала давайте поговорим о том, какие таблицы могут использоваться для итераций. Как вы уже поняли, мы можем передавать в качестве первого аргумента табличным агрегатным функциям не только любые таблицы из модели данных, но и любые созданные таблицы, именуемые виртуальными (поскольку термин вычисляемые таблицы в моделях данных Power BI уже занят).

Использование виртуальных таблиц В предыдущем разделе мы написали формулу для расчета средней суммы продаж по городам. Теперь давайте представим, что нам понадобилось вычислить средние суммы продаж по штатам, которым эти города принадлежат. Если применять тот же подход, что и в мере SalesPerCity, нам понадобится таблица со штатами. Но такой таблицы в модели данных у нас нет, поскольку указание на штат (столбец State) присутствует только в  таблице городов (Cities). Таким образом, нам придется создать такую таблицу самостоятельно. Конечно, в этом простейшем случае можно добавить в модель таблицу State (допустим, в  виде вычисляемой таблицы), но гораздо лучше будет создать виртуальную таблицу (virtual table). Эта таблица будет существовать только во время вычисления меры. В DAX есть большое количество функций для создания виртуальных таблиц. Основная сложность при работе с этими функциями заключается как раз в  том, что на выходе они дают таблицу. А  значит, у  нас нет никакого стандартного механизма для просмотра полученных результатов, как в  случае с обычными мерами в Power BI. В какой-то степени может помочь создание вычисляемой таблицы в Power BI с использованием той же функции, но в целом работа с табличными функциями в DAX требует наличия определенного абстрактного мышления. Чтобы получить список штатов, мы можем воспользоваться функцией VALUES, принимающей в качестве единственного аргумента ссылку на столбец и возвращающей таблицу с уникальными значениями из него. Пример: VALUES(Cities[State])

100

ГлаВа 1.4

Контексты и фильтры

Это выражение вернет таблицу с  уникальными значениями из столбца State. Эквивалентом данной функции является функция DISTINCT, которая также возвращает неповторяющиеся значения из столбца. Разница между ними состоит в том, что функция DISTINCT не включает в вывод пустое значение из пустой строки, появляющейся в результате наличия неполной связи (мы говорили об этом в главе 1.2 и демонстрировали на рис. 1.2.5). Вам решать, нужны ли в результирующей таблице пустые значения. Формула для расчета средних сумм продаж по штатам будет следующей: SalesPerState = AVERAGEX( VALUES(Cities[State]), [Sales] )

В языке DAX есть целый ряд функций, возвращающих таблицу, и мы не будем приводить все из них. Перечислим лишь наиболее распространенные. SUMMARIZE: хотя сама по себе эта функция довольно универсальна и способна генерировать полноценные сводные таблицы, в мерах DAX она используется несколько иначе. В  частности, она интересна как расширение функции VALUES: если VALUES возвращает уникальные значения только из одного столбца, то SUMMARIZE умеет определять уникальные комбинации значений сразу из нескольких столбцов. К  примеру, список уникальных комбинаций столбцов CityID и ProductID из таблицы fSales может быть получен с помощью приведенной ниже формулы (заметьте, что первым аргументом функция принимает ссылку на таблицу-источник): SUMMARIZE(fSales, fSales[CityID], fSales[ProductID])

FILTER: эта функция принимает на вход два аргумента: первый представляет собой таблицу (либо существующую в  модели, либо результат другой табличной функции), а второй – условное выражение, которое будет применено к  каждой строке в  этой таблице. Выражение должно возвращать истину или ложь, и в результирующий вывод будут включаться только строки, в которых условие выполняется. К  примеру, формула, приведенная ниже, возвращает список городов, располагающихся в Германии: FILTER(Cities, Cities[Country] = "Germany")

TOPN: как и  FILTER, функция TOPN возвращает поднабор строк из исходной таблицы. В  результирующий набор включаются первые или последние несколько строк в  соответствии с  определенным критерием. На  вход функции вы передаете количество элементов, таблицу-источник, выражение для сортировки строк и порядок сортировки. К примеру, следующая формула поможет вам вывести 15 клиентов с лучшими продажами: TOPN(15, Customers, [Sales], DESC)

ГлаВа 1.4 Контексты и фильтры

101

CROSSJOIN: эта функция создает одну таблицу из двух входящих. Фактически функция CROSSJOIN возвращает таблицу со всеми комбинациями строк из переданных таблиц-источников. Пример ниже демонстрирует вывод всех комбинаций товаров и городов, при этом в результирующем наборе будут присутствовать все столбцы из таблиц Cities и Products: CROSSJOIN(Cities, Products)

GENERATE: как и CROSSJOIN, функция GENERATE возвращает таблицу, являющуюся комбинацией входных таблиц. Однако в этом случае табличное выражение из второго аргумента применяется к каждой строке таблицы из первого аргумента. Если в результате для какой-то из строк возвращается пустая таблица, эта строка не включается в итоговый набор. Если вам необходимо, вы можете использовать функцию GENERATEALL, которая не  отбрасывает такие строки, а  в столбцах из табличного выражения выводит пустые значения. К примеру, формула, приведенная ниже, возвращает таблицу с городами и товарами, которые были проданы клиентам из этих городов. В вывод также будут включаться все столбцы из таблиц Cities и Products: GENERATE(Cities, FILTER(Products, [Sales] > 0))

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

Контекст в табличных функциях Пример с использованием функции GENERATE из предыдущего раздела кажется интуитивно понятным, но при ближайшем рассмотрении оказывается, что с ним не все так просто. Для лучшего понимания того, какие именно действия выполняют подобные выражения, важно хорошо осознавать, как в табличных функциях работают контексты. Давайте рассмотрим меру, использующую в формуле функцию GENERATE: AvgUnitAmount1 = AVERAGEX( GENERATE( Cities, FILTER( Products, [Sales] > 10000 ) ), AVERAGE(fSales[UnitAmount]) )

Назначение этой меры состоит в вычислении среднего количества проданных товаров на транзакцию (столбец UnitAmount) для всех транзакций по товарам и городам, где эти товары были проданы на сумму больше 1000. Вы видите ошибку в этой формуле? 102

ГлаВа 1.4

Контексты и фильтры

При использовании данной меры в визуальном элементе Power BI она вычисляется в контексте запроса. И этот контекст может быть любым – в нем могут содержаться один или несколько фильтров по столбцам или модели данных. Функция AVERAGEX принимает два аргумента, и они вычисляются в разных контекстах: „ первый аргумент, являющийся табличным выражением, вычисляется в том же контексте, что и сама функция AVERAGEX; „ второй аргумент в виде скалярного выражения вычисляется в контексте строки для каждой строки в таблице из первого аргумента. Вы могли это видеть в мере Sales2, о которой мы говорили ранее, – в ней мы использовали прямые ссылки на столбцы во втором аргументе функции SUMX. Контекст строки здесь позволяет напрямую обращаться к  колонкам во время вычисления. Фактически контекст строки накладывается на контекст запроса, и если в контексте строки фильтров нет, то фильтры из контекста запроса сохраняются. Примечание. Ошибки при использовании виртуальных таблиц часто бывают связаны с контекстом строки в табличных агрегатных функциях. Посмотрите на этот пример: ThisDoesntWork = SUMX( VALUES(fSales[UnitAmount]), fSales[Tax] )

Хотя в этой формуле используется два столбца из таблицы fSales, к столбцу Tax нельзя обращаться напрямую, поскольку контекст строки действует не в таблице fSales, а в табличном выражении, возвращаемом функцией VALUES. А в нем присутствует только колонка fSales[UnitAmount], и только к ней можно обращаться напрямую.

Табличные функции FILTER, TOPN и GENERATE, о которых мы говорили выше, работают по одинаковому принципу. Табличный аргумент вычисляется в контексте, в  котором функция была вызвана, а  следующий аргумент оперирует уже контекстом строки. В случае с функцией GENERATE это означает, что в контексте строки выполняется табличное выражение. В результате мера AvgUnitAmount1, показанная выше, содержит в себе целый набор разных контекстов. Давайте пройдем по ним шаг за шагом. 1. AVERAGEX: вычисляется в контексте запроса. 2. GENERATE: вычисляется в том же контексте, что и AVERAGEX. 3. Cities: контекст остается прежним. 4. FILTER: вычисляется в контексте строки в таблице Cities. 5. Products: вычисляется в том же контексте, что и FILTER. ГлаВа 1.4 Контексты и фильтры

103

6. [Sales]: поскольку здесь мы обращаемся к другой мере, неявный вызов функции CALCULATE приводит к созданию контекста фильтра. Каждое вычисление меры производится с одной строкой из таблицы Cities и одной из таблицы Products. В контексте фильтра добавляются фильтры на колонки Cities и Products. Результатом вычисления будет сумма продаж по текущему товару в текущем городе. 7. AVERAGE: вычисляется в контексте строки в таблице, возвращенной функцией GENERATE, в  которой представлен поднабор комбинаций городов и товаров. Так где же ошибка в этой формуле? Она кроется на последнем шаге! Хотя этот шаг и выполняется в таблице с правильными комбинациями городов и товаров, вычисление здесь происходит в контексте строки. Это означает, что на вычисление функции AVERAGE будут оказывать влияние только фильтры, присутствующие в контексте запроса. Текущие город и  товар не  будут влиять на вычисление, поскольку в таблицах Cities и Products не установлены (дополнительные) фильтры для выбора конкретного города и товара. Для решения этой проблемы достаточно преобразовать контекст строки в контекст фильтра, как на шестом шаге. Это можно сделать с  помощью явного использования функции CALCULATE, как показано ниже: AvgUnitAmount2 = AVERAGEX( GENERATE( Cities, FILTER( Products, [Sales] > 10000 ) ), CALCULATE(AVERAGE(fSales[UnitAmount])) )

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

ГлаВа 1.4

Контексты и фильтры

акциям. Чтобы исправить этот недочет, необходимо применить иной подход. Но сначала давайте поговорим о производительности формул.

Производительность при использовании табличных функций Есть пара вещей, о которых необходимо думать при использовании виртуальных таблиц в DAX. Нашей общей главной целью является извлечение корректных результатов за минимальное время, так что о производительности никогда не стоит забывать. Первое, о  чем необходимо помнить всегда, – это то, что виртуальные таблицы создаются движком DAX в памяти компьютера. Это значит, что чем больше виртуальная таблица, тем больше памяти вам понадобится и тем выше риск возникновения проблем с производительностью. Более того, вы можете столкнуться с появлением ошибок, связанных с недостатком памяти для выполнения вычислений. Поэтому вам всегда стоит думать о том, как можно уменьшить объем таблиц, с которыми вы работаете здесь и сейчас. В частности, вы должны задаваться вопросом о целесообразности использования всех столбцов или их подмножества. В мере AvgUnitAmount2, созданной нами ранее, явно нет необходимости в использовании всех столбцов. В виртуальной таблице, созданной функцией GENERATE, содержатся все колонки из обеих таблиц: Cities и Products. Но для вычисления нам, по сути, нужны только ключевые столбцы с  уникальными значениями – именно они определяют, какие строки будут выделены в таблице fSales, а значит, влияют на значение меры Sales. Оптимизированная версия формулы может выглядеть так: AvgUnitAmount3 = AVERAGEX( GENERATE( VALUES(Cities[CityID]), FILTER( VALUES(Products[ProductID]), [Sales] > 10000 ) ), CALCULATE(AVERAGE(fSales[UnitAmount])) )

Второе, о чем необходимо подумать, – это о количестве строк в виртуальной таблице. Это не только влияет на общий объем таблицы, но и определяет количество итераций в таблице агрегаций. Например, если цена товаров хранится в таблице Products, общий объем закупок в таблице fSales можно вычислить следующим образом: TotalPurchased1 = SUMX( fSales, fSales[UnitAmount] * RELATED(Products[PurchasePrice]) )

ГлаВа 1.4 Контексты и фильтры

105

Но вы можете ограничить количество итераций, заставив функцию SUMX проходить по таблице Products вместо fSales. А  еще лучше перебирать уникальные значения из столбца PurchasePrice, как показано ниже: TotalPurchased2 = SUMX( VALUES(Products[PurchasePrice]), Products[PurchasePrice] * CALCULATE(SUM(fSales[UnitAmount])) )

В этой версии меры выполняется агрегирование результатов для уникальных значений в  столбце PurchasePrice. Обратите внимание, что в  функции CALCULATE здесь производится преобразование контекста строки в  контекст фильтра, что позволяет выбрать нужные транзакции. Примечание. Использование памяти и количество задействованных итераций – две главные причины не применять в работе функцию CROSSJOIN при написании мер DAX. Количество строк, возвращаемых этой функцией, может быть огромным, что может привести к излишнему использованию памяти. Рассмотрим пример ниже (A и B здесь – произвольные табличные выражения): SUMX( CROSSJOIN(A, B), [Sales] )

Часто более эффективно будет отказаться от использования функции CROSSJOIN в пользу следующего выражения: SUMX( A, SUMX( B, [Sales] ) )

Еще одним способом оптимизации при написании мер является избавление от итераций и переход на использование виртуальных таблиц с целью фильтрации данных. Об этом мы поговорим в следующем разделе.

Фильтрация при помощи табличных функций Одним из важнейших открытий при работе с языком DAX должно стать осознание тесной связи между таблицами и фильтрацией. В данном разделе вы 106

ГлаВа 1.4

Контексты и фильтры

узнаете, как связаны эти сущности, и научитесь использовать приобретенные знания в своих вычислениях.

Использование функции CALCULATETABLE Как мы уже говорили ранее в  этой главе, табличные выражения, используемые в табличных агрегатных функциях наподобие SUMX, вычисляются в том же контексте, что и сама функция. Но вам не всегда нужно именно это – иногда вам необходимо использовать другой контекст. И в DAX существует функция, позволяющая это сделать. Она называется CALCULATETABLE. Как и родственная ей функция CALCULATE, CALCULATETABLE изменяет контекст перед вычислением выражения. В случае с функцией CALCULATE в качестве результата возвращается скалярное выражение, тогда как CALCULATETABLE возвращает таблицу. В остальном функция CALCULATETABLE следует тому же алгоритму, что и CALCULATE. 1. Установка контекста фильтра. 2. Удаление всех фильтров из столбцов и таблиц, на которые ссылаются аргументы фильтра. 3. Добавление новых фильтров из переданных аргументов. 4. Вычисление табличного выражения, переданного в качестве первого аргумента. Зачастую функции CALCULATE и CALCULATETABLE могут быть взаимозаменяемы. Рассмотрим следующий пример: AveragePerCity_Canada1 = AVERAGEX( CALCULATETABLE( VALUES(Cities[CityID]), Cities[Country] = "Canada" ), [Sales] )

Вы можете переписать эту меру с использованием функции CALCULATE таким образом: AveragePerCity_Canada2 = CALCULATE( AVERAGEX( VALUES(Cities[CityID]), [Sales] ), Cities[Country] = "Canada" )

Обе формулы вернут средние продажи по городам Канады (в зависимости от контекста запроса, конечно). В то же время чисто технически между этими формулами есть серьезное различие. В  первой формуле фильтр Cities[Country] = "Canada" применяется к вычислению в таблице Cities, а во второй – как к таблице ГлаВа 1.4

Контексты и фильтры

107

Cities, так и к вычислению меры Sales. Хотя в данном случае фильтр не оказывает влияния на результат вычисления меры, вы можете использовать гораздо более сложные меры по сравнению с Sales, и тогда это может стать серьезным фактором. Примечание. Как и CALCULATE, функция CALCULATETABLE создает контекст фильтра. Таким образом, при использовании в вычисляемом столбце для каждой строки таблицы будет добавляться новый фильтр с выбором значений из нее. При наличии связанной таблицы в новом контексте будет происходить распространение фильтров по связи, в результате чего данные в связанной таблице сократятся до строк, относящихся к значениям в текущей строке. Именно поэтому функция RELATEDTABLE, использующаяся для извлечения связанной части данных в другой таблице, есть не что иное, как функция CALCULATETABLE без аргументов фильтра. Используя функцию CALCULATETABLE, вы можете добавлять фильтры к  табличным вычислениям. Любопытно, что для добавления фильтров вы также можете прибегать к помощи таблиц.

Фильтры и таблицы Теперь, когда мы познакомились с  табличными функциями, пришло время сделать шаг назад и новым взглядом посмотреть на фильтры. Раньше мы говорили о фильтрах в контексте запроса и контексте фильтра как о неких правилах, применяемых к столбцам в модели Power BI, например: «Значение в столбце Cities[Country] должно быть равно "France" или "Germany"». Это правило можно рассматривать как список значений, которому должна удовлетворять колонка Country, а можно представить и как таблицу с одним столбцом и двумя строками, содержащими значения "France" и "Germany". Фактически именно так работают фильтры и  выполняется функция CALCULATE  – путем добавления таблиц, определяющих, какие значения в  колонке должны быть выбраны. При этом новые фильтры могут заменять уже существующие. Основной принцип звучит так: Любой фильтр  – это таблица, и  любая таблица может быть использована в качестве фильтра. Это означает, что любой простой аргумент фильтра в функции CALCULATE может быть представлен в  виде таблицы. Рассмотрим для примера следующую формулу: SalesFranceGermany = CALCULATE( [Sales], Cities[Country] IN {"France", "Germany"} )

108

ГлаВа 1.4

Контексты и фильтры

В данном случае представленный аргумент фильтра является полным аналогом такого табличного выражения: FILTER( ALL(Cities[Country]), Cities[Country] IN {"France", "Germany"} )

Таким образом, словами значение этого фильтра можно выразить так: из всех значений в таблице Country выбери только "France" и "Germany". Примечание. Этим объясняется тот факт, почему, к примеру, работает приведенная ниже формула: CALCULATE( [Sales], Cities[Country] = "France" || Cities[Country] = "Germany" )

И хотя это не самый простой аргумент фильтра, он полностью эквивалентен следующему выражению: FILTER( ALL(Cities[Country]), Cities[Country] = "France" || Cities[Country] = "Germany" )

Большинство функций DAX, которые мы представляли как функции фильтра, в действительности являются табличными функциями, что видно по выражению FILTER выше, в котором ALL используется как табличная функция. На самом деле все функции группы ALL являются табличными: ALL(Cities[Country]) – это таблица с  единственным столбцом, содержащая все уникальные страны, а ALL(Cities[Country], Cities[State]) – таблица с двумя столбцами, которая содержит все уникальные комбинации стран и штатов из таблицы Cities. Примечание. Не все функции, использующиеся в аргументах фильтра, являются табличными: функции USERELATIONSHIP и CROSSFILTER влияют на поведение связей и при этом не создают таблиц. Функция KEEPFILTERS меняет поведение функции CALCULATE, но не может быть использована для создания таблиц. Функция REMOVEFILTERS, действующая как ALL в аргументе фильтра, также не может применяться для создания таблиц. Даже функции логики операций со временем, о которых мы говорили в одном из предыдущих разделов, за исключением сокращенных версий вроде ГлаВа 1.4 Контексты и фильтры

109

TOTALYTD, являются табличными. Каждая из них создает таблицу с единственной колонкой, содержащую даты из указанного периода. Это означает, что данные функции можно использовать в табличных агрегатных функциях, например для расчета средних продаж по дням нарастающим итогом с начала года, как показано ниже: AverageSalesPerDay_YTD = AVERAGEX( DATESYTD('Date'[Date]), [Sales] )

Еще более любопытным примером реализации принципа фильтр как таблица является возможность использования любой (виртуальной) таблицы в качестве фильтра в рамках функции CALCULATE. В качестве простого примера представим, что нам необходимо написать меру, возвращающую общую сумму продаж по стране или странам, которые выбраны в таблице Cities. Если вы уверены, что в контексте запроса для этого вычисления столбец Country подвергается фильтрации, формула будет очень простой: SalesWholeCountry1 = CALCULATE( [Sales], ALLEXCEPT(Cities, Cities[Country]) )

Здесь мы удаляем все фильтры из таблицы Cities, за исключением столбца Country. Таким образом, если в контексте запроса присутствуют фильтры "Город равно Atlanta" и "Страна равно United States", в результирующем контексте останется только фильтр "Страна равно United States". В то же время если в контексте запроса будет присутствовать лишь фильтр "Город равно Atlanta", у  нас будут проблемы, поскольку в  текущем выборе окажется весь мир. Для исправления ситуации нам потребуется вновь вводить в контекст наш фильтр "Страна равно United States", и он должен быть выведен из фильтра по City. Это можно сделать с использованием следующего табличного фильтра: SalesWholeCountry2 = CALCULATE( [Sales], ALL(Cities), VALUES(Cities[Country]) )

Чтобы лучше понять, что здесь происходит, необходимо полностью осознавать, что аргументы фильтра вычисляются в том же контексте, что и функция CALCULATE. В контексте запроса с выделением только одного города выражение VALUES(Cities[Country]) вернет таблицу с  одним столбцом, содержащую страну, которой принадлежит выбранный город. Это именно тот фильтр, который нам нужно применить для получения требуемого результата. 110

ГлаВа 1.4

Контексты и фильтры

В качестве другого примера рассмотрим формулу, вычисляющую сумму продаж по 10 000 крупнейших клиентов: SalesLargestCustomers1 = CALCULATE( [Sales], TOPN(10000, ALL(Customers), [Sales], DESC) )

Обратите внимание, что здесь мы используем выражение ALL(Customers), чтобы быть уверенными в отсутствии других фильтров, ограничивающих список клиентов. Функция TOPN возвращает 10 000 крупнейших клиентов в виде поднабора таблицы Customers, включающего все столбцы, – при необходимости вы можете удалить из этого списка ненужные колонки. В дальнейшем созданная таблица используется в качестве фильтра. Эта формула показывает, почему использование табличных фильтров является более предпочтительным вариантом в сравнении с табличными агрегациями. Давайте рассмотрим аналогичную формулу, но с использованием табличной агрегатной функции: SalesLargestCustomers2 = SUMX( TOPN(10000, ALL(Customers), [Sales], DESC), [Sales] )

Теперь спросите себя: сколько раз в  этом выражении будет вычисляться мера Sales? Конечно, это зависит от количества клиентов в таблице Customers. Давайте исходить из того, что у нас 60 000 контрагентов. Функция TOPN должна вызвать меру Sales для каждого из них с целью определения того, кто входит в  10 000 крупнейших. После этого мере SalesLargestCustomers1 понадобится один дополнительный вызов меры Sales: на этот раз табличное выражение TOPN будет использоваться в  качестве фильтра, создавая контекст для крупнейших клиентов. В то же время мера SalesLargestCustomers2 вынуждена проходить по таблице TOPN и вызывать для ее строк меру Sales. Иными словами, в этом выражении мера Sales вызывается 70 000 раз, тогда как в случае с мерой SalesLargestCustomers1 – лишь 60 001. Движок DAX может выполнить некоторую оптимизацию, но разница будет существенной. Использование таблиц в качестве фильтров может стать еще более мощным инструментом, когда вы поймете, что, в отличие от фильтров в контексте запроса, табличные фильтры могут содержать несколько столбцов. Это означает, что у нас появляется повод исправить ошибку в мере AvgUnitAmount3, созданной ранее в этой главе: AvgUnitAmount4 = CALCULATE( AVERAGE(fSales[UnitAmount]), GENERATE( VALUES(Cities[CityID]),

ГлаВа 1.4 Контексты и фильтры

111

FILTER( VALUES(Products[ProductID]), [Sales] > 10000 ) ) )

Вместо расчета среднего по средним с табличным агрегированием по выражению GENERATE мы используем эту функцию в качестве фильтра для выбора комбинаций городов и товаров, по которым сумма продаж превышает 10 000. Затем мы можем вычислить среднее количество проданных товаров для всех транзакций, соответствующих выбранным комбинациям город/товар. В  результате мы не только произведем правильный с математической точки зрения расчет, но также исключим перебор таблицы, возвращаемой функцией GENERATE, что положительно скажется на быстродействии нашей меры.

Использование функции TREATAS Существует еще одно серьезное ограничение при использовании табличных фильтров: они действительно должны фильтровать таблицы, на основе которых выполняется вычисление. Если просто добавить таблицу в качестве фильтра, никак не связанную с остальной моделью данных, ничего не получится. К примеру, формула, приведенная ниже, не будет возвращать сумму продаж для Великобритании: UKSales_wrong = CALCULATE( [Sales], ROW("Country", "United Kingdom") )

Почему это не работает? Модель Power BI не может самостоятельно определить, должна ли произвольная таблица, созданная при помощи функции ROW и  имеющая в  своем составе столбец с  именем Country, фильтровать таблицу Cities с таким же столбцом. Вы  могли бы подумать: раз имя столбца совпадает, движок DAX мог бы и сам понять, что имеется в виду одна и та же информация. Но такой образ мыслей, если бы он был присущ движку DAX, мог бы привести к появлению непредсказуемых результатов в модели данных при наличии столбцов с одинаковыми именами в разных таблицах. Чтобы движок DAX мог использовать переданную информацию в  качестве фильтра, он должен для начала идентифицировать связь между виртуальной таблицей и таблицей, или набором столбцов, в модели данных. Такая связь именуется привязкой данных, или происхождением данных (lineage), и, если коротко, ее наличие означает, что при создании виртуальной таблицы движок DAX продолжит поддерживать соответствие между созданными столбцами в виртуальной таблице с их источниками в модели. Давайте снова взглянем на выражение GENERATE: GENERATE( VALUES(Cities[CityID]),

112

ГлаВа 1.4

Контексты и фильтры

FILTER( VALUES(Products[ProductID]), [Sales] > 10000 )

Здесь нам явно дают понять, что столбец Cities[CityID] находится в модели данных, а значит, у выражения VALUES(Cities[CityID]) возникает привязка к нему. По тому же принципу у выражения VALUES(Products[ProductID]) возникает привязка к столбцу ProductID. При помощи функции GENERATE создается таблица с комбинациями значений из двух выражений VALUES, так что все колонки в  результирующей таблице будут обладать явной привязкой к  соответствующим столбцам в модели. Большинство табличных функций сохраняют привязку к исходным данным. Некоторые из них, однако, позволяют формировать таблицы в довольно причудливом виде, и  это становится проблемой, когда речь заходит о  привязке. К примеру, функция UNION допускает объединение таблиц по строкам из двух разных источников. В результате в итоговой таблице столбцы могут не быть явным образом привязаны ни к одному из столбцов в модели. Также иногда возникают ситуации, когда вам необходимо самостоятельно изменить привязку данных в созданной виртуальной таблице. Язык DAX позволяет сделать это, воспользовавшись функцией TREATAS, которая дает возможность явным образом указать привязку данных в столбцах. TREATAS – еще один пример функции, которая может быть использована в качестве аргумента фильтра в функциях CALCULATE и CALCULATETABLE. В формуле, приведенной ниже, производится корректное вычисление суммы продаж для Великобритании (хотя, конечно, есть способы сделать это проще): UKSales_correct = CALCULATE( [Sales], TREATAS( ROW("Country", "United Kingdom"), Cities[Country] ) )

Обратите внимание, что функция TREATAS не  требует, чтобы имена столбцов полностью совпадали. Мы  могли бы назвать столбцы в  функции ROW как угодно. Функция TREATAS также может работать и с таблицами с несколькими колонками. В этом случае вы должны явным образом сопоставить все столбцы в таблице со столбцами в модели данных. Полноценный пример использования функции TREATAS вы найдете в главе 2.5.

Переменные в DAX Табличные функции и фильтры позволяют выполнять очень сложные вычисления в DAX. Обратной стороной этого преимущества является то, что формулы ГлаВа 1.4 Контексты и фильтры

113

при этом могут становиться довольно объемными. Ну а наличие множества контекстов затрудняет возможность получения корректных результатов. Переменные (variables) DAX способны значительно облегчить жизнь разработчику при написании составных формул. Несмотря на название, цель переменных состоит в том, чтобы вычислить какое-то выражение лишь раз, а затем использовать полученный результат в разных обстоятельствах (и зачастую в разных контекстах) без необходимости производить повторные вычисления. Иными словами, в DAX переменные фактически используются как константы! Переменные в DAX объявляются при помощи ключевого слова VAR. При этом в формуле может быть объявлено несколько переменных, и одной переменной может быть присвоено значение другой, объявленной ранее. Область объявления переменных завершается ключевым словом RETURN, как показано ниже: VAR ThisValue = 5 RETURN

... Переменные в DAX могут быть использованы в любых выражениях внутри формул. Кроме того, в  переменной может храниться как скалярное выражение, так и табличное. Довольно причудливая формула, приведенная ниже, является вполне приемлемой в языке DAX: VariableTest = VAR Variable1 = 3 VAR Variable2 = Variable1 + 5 RETURN CALCULATE( VAR Variable3 = MAX(fSales[UnitAmount]) RETURN SUM(fSales[Tax]) + Variable2 + Variable3, VAR Variable4 = 4 VAR TableVariable = FILTER( ALL(fSales[UnitAmount]), fSales[UnitAmount] = Variable4 ) RETURN TableVariable )

Расположение объявления переменной в формуле определяет контекст, в котором эта переменная будет вычислена. К примеру, переменная Variable3 в приведенной выше формуле вычисляется в контексте фильтра, образованном после применения табличного фильтра TableVariable к исходному контексту запроса для этой меры. Если бы переменная Variable3 была объявлена сразу за переменной Variable2, она была бы вычислена в контексте запроса. Обратите внимание, что переменные Variable4 и  TableVariable используются в  аргументе фильтра функции CALCULATE, и обе они вычисляются в исходном контексте запроса. Каждая переменная характеризуется своей областью видимости (scope) и не может быть использована за пределами выражения, в рамках которого она 114

ГлаВа 1.4

Контексты и фильтры

объявлена. Применительно к формуле выше переменная Variable3 не может быть использована при определении значения переменной TableVariable – она объявлена в качестве первого аргумента функции CALCULATE и за его пределами применяться не может. И наоборот, переменные Variable4 и TableVariable не  могут быть использованы в  первом аргументе функции CALCULATE. Переменные Variable1 и Variable2 присутствуют в глобальной области видимости формулы, так что могут быть использованы повсеместно. Переменные DAX позволяют упростить процедуру расчета и при этом способны сделать ваши формулы более легкими для восприятия, если, конечно, вы даете им подходящие имена. Давайте еще раз вспомним нашу меру с именем AvgUnitAmount4: AvgUnitAmount4 = CALCULATE( AVERAGE(fSales[UnitAmount]), GENERATE( VALUES(Cities[CityID]), FILTER( VALUES(Products[ProductID]), [Sales] > 10000 ) ) )

С использованием переменных эта формула может быть упрощена и представлена следующим образом: AvgUnitAmount5 = VAR LargeCityProductCombinations = GENERATE( VALUES(Cities[CityID]), FILTER( VALUES(Products[ProductID]), [Sales] > 10000 ) ) RETURN CALCULATE( AVERAGE(fSales[UnitAmount]), LargeCityProductCombinations )

Всегда помните о том, что переменные в DAX на самом деле являются константами! Таким образом, вариант формулы, приведенный ниже, является некорректным: AvgUnitAmount_wrong = VAR LargeProducts = FILTER(

ГлаВа 1.4 Контексты и фильтры

115

VALUES(Products[ProductID]), [Sales] > 10000 ) VAR LargeCityProductCombinations = GENERATE( VALUES(Cities[CityID]), LargeProducts ) RETURN CALCULATE( AVERAGE(fSales[UnitAmount]), LargeCityProductCombinations )

Что здесь не так? Поместив функцию FILTER в переменную, мы создали постоянную таблицу. В то же время внутри функции GENERATE мы хотим, чтобы список товаров генерировался для каждого отдельного города.

Заключение В этой главе мы познакомились с контекстом строки, контекстом запроса и контекстом фильтра, а также узнали о том, какую роль они играют в вычислении формул DAX. Мы поговорили о том, как при помощи функции CALCULATE можно изменять контексты путем удаления существующих фильтров и добавления новых. В дополнение мы бросили беглый взгляд на функции логики операций со временем, помогающие при создании фильтров для календарных вычислений. После этого мы обратили взор на табличные функции в DAX, дающие возможность выполнять агрегацию в табличном виде, а также рассмотрели способы использования пользовательских виртуальных таблиц в формулах DAX. Виртуальные таблицы значительно расширяют и без того богатейший арсенал инструментов, доступный в виде стандартных функций и средств фильтрации языка DAX. Мы поговорили о тесной связи между таблицами и фильтрами, позволяющей использовать любые таблицы в качестве фильтров. Наконец, мы познакомились с  переменными в  языке DAX, которые помогают значительно упростить логику сложных формул и облегчают понимание витиеватых выражений. Все это относится к  фундаментальным концепциям, о  которых вам стоит знать, приступая к  более глубокому изучению возможностей языка DAX. Во  второй части книги мы узнаем, как применять все перечисленные выше приемы и концепции в реальных сценариях. Наша цель состоит в том, чтобы вы, пройдя обучение на примерах из жизни, осознали всю мощь, присущую языку DAX, и смогли применять его в своих вычислениях. Следующая глава будет посвящена вопросам безопасности в моделях Power BI. Вы  увидите, как можно применить все полученные знания о  контекстах и фильтрации при разработке принципов безопасности.

Ч АС Т Ь I I

Применение DAX на реальных примерах

ГЛ А В А 2.1

Безопасность в DAX При работе с  данными то и  дело возникает необходимость в  обеспечении безопасности конфиденциальной информации. Даже в  рамках организации одним сотрудникам всегда дозволено видеть больше, чем другим. В моделях Power BI вы можете реализовывать достаточно сложные механизмы обеспечения безопасности данных. И в этой главе мы этим и займемся. Заметьте, что мы будем говорить не  о  безопасности, связанной с  распространением отчетов и  дашбордов, а  о безопасности внутри модели данных. Типичным сценарием в этом случае является наличие двух или более пользователей, каждый из которых видит свою часть содержимого в одних и тех же отчетах в зависимости от настроек в области безопасности. Темы, которые будут рассмотрены в этой главе: „ защита данных в  моделях Power BI при помощи безопасности на уровне строк; „ настройка безопасности применительно к иерархическим данным; „ защита атрибутов или отдельных столбцов в таблицах; „ защита уровней агрегации для вычисляемых мер. Примечание. В этой главе мы будем использовать несколько моделей данных, которые вы можете загрузить со страницы книги и из репозитория GitHub по адресу https://github. com/PacktPublishing/Extreme-DAX/tree/main/Chapter2.1.

Знакомство с безопасностью на уровне строк (RLS) С помощью механизма безопасности на уровне строк (row-level security – RLS) можно ограничивать доступ к информации в модели данных Power BI для разных пользователей. Это основной инструмент обеспечения безопасности данных в  моделях Power BI. Свое название (безопасность на уровне  строк) этот механизм получил благодаря принципу своей работы – фактически он заключается в том, что вы сами определяете, какие строки из каких таблиц в модели будет видеть тот или иной пользователь. Обратите внимание, что поскольку механизм RLS применяется на уровне  всей модели, все отчеты в  ней будут удовлетворять заданным требованиям доступа к данным. Перед тем как двигаться дальше, давайте кое-что проясним. Если вы хотите реализовать определенные принципы безопасности в своей модели данных 118

ГлаВа 2.1

Безопасность в DAX

Power BI, вы просто вынуждены использовать механизм безопасности на уровне строк (или объектов, о чем мы поговорим далее в этой главе). Обходного пути нет. Не пытайтесь внедрять безопасность посредством совместного использования отчетов. Вы не можете заранее знать, как ваша модель будет использоваться в  будущем: пользователи могут начать обращаться к  ней из собственных подсистем самообслуживания, случайным образом добавляться в группы безопасности, да мало ли что еще. По  той же причине  не  следует пытаться защитить возвращаемые мерами данные путем их ограничения по каким-то условиям – пользователи, наделенные полномочиями строить отчеты поверх модели, легко смогут обойти это ограничение. Главное и единственное правило здесь звучит так: каждый пользователь может иметь доступ к модели данных и не должен видеть информацию, которая для него не предназначена.

Роли безопасности Механизм безопасности на уровне  строк базируется на ролях безопасности (security role). Роли можно рассматривать как отдельно реализованные политики безопасности. Вы  можете, к  примеру, создать отдельные роли безопасности для продавцов, руководителей высшего звена и менеджеров по кадрам. Роли безопасности являются составной частью модели данных Power BI. Но  этого нельзя сказать о  членстве в  роли (role membership), поскольку роли можно назначать пользователям только после публикации модели. Вы можете создать столько ролей безопасности, сколько вам нужно, но стоит придерживаться определенных правил, о которых мы порассуждаем далее в этом разделе. Определение ролей безопасности выполняется в  специальном разделе Power BI с названием Управление ролями (Manage roles), окно которого показано на рис. 2.1.1.

Рис. 2.1.1. Окно управления ролями

ГлаВа 2.1 Безопасность в DAX 119

После публикации модели данных с определенными в ней ролями безопасности никто из пользователей не сможет получить доступ к информации, пока не будут назначены соответствующие роли, за исключением администратора, члена или автора рабочей области, в которой была опубликована модель. В службе Power BI можно определить, были ли для набора данных определены роли безопасности, по наличию пункта Безопасность (Security) в  контекстном меню набора, что видно на рис. 2.1.2.

Рис. 2.1.2. Опция настройки безопасности для набора данных

Назначать пользователям роли безопасности можно как по отдельности, используя их электронный адрес, так и целыми группами безопасности (security group). Обратите внимание, что добавление пользователя к  роли безопасности не обеспечивает ему полного доступа к набору данных, и вам нужно дать ему доступ на двух уровнях: „ доступ к  набору данных (посредством разделяемых отчетов, членства в рабочей области и/или разрешений в самом наборе данных); „ включение в роль безопасности. 120

ГлаВа 2.1

Безопасность в DAX

Фильтры безопасности в DAX После создания роли безопасности необходимо определить сами политики безопасности, которым должна отвечать роль. Это делается путем объявления фильтров безопасности (security filters) в DAX применительно к одной или нескольким таблицам в модели. Обратите внимание, что фильтры безопасности привязываются к  роли и таблице. Таким образом, для одной таблицы может быть определено несколько фильтров безопасности, если они находятся в разных ролях безопасности. Посредством фильтров безопасности в  DAX вы можете определить, какие строки будут видеть пользователи конкретной роли. Эти фильтры можно сравнить с вычисляемыми столбцами, добавляемыми в таблицы, в которых хранятся значения TRUE или FALSE. Рассмотрим следующий простой фильтр: Product[Category] = "Components"

Будучи определенным в  таблице Product, этот фильтр будет возвращать значение TRUE для каждой строки со значением столбца Category, равным "Components", и FALSE для всех остальных строк. Этот фильтр будет добавляться к  каждому запуску мер, в  результате чего пользователь будет видеть только информацию о товарах категории Components. Вам нет необходимости устанавливать фильтры безопасности для каждой таблицы в модели, поскольку они сами, как и другие фильтры, будут распространяться по связям между таблицами. Это означает, что вы, по сути, можете реализовать нужные вам правила безопасности в модели данных при помощи всего нескольких фильтров. Но помните о том, что изменения в модели могут привести к нарушению установленной политики безопасности. На рис. 2.1.3 вы видите простой пример со связью (1) и двумя таблицами (2). С  примененной ролью безопасности SelectCanada, в  которой установлен фильтр на значение Canada в столбце Country, данные в таблице fSales оказались отфильтрованы по связи (3). При работе с двунаправленными связями требуется повышенное внимание к настройке механизма безопасности на уровне строк. По умолчанию фильтры безопасности по таким связям распространяются только в  одну сторону, а  именно от таблицы «один» к таблице «многие», как было бы при наличии однонаправленной связи. Явно указать, что фильтры безопасности должны распространяться в  обе стороны, можно в  окне  Изменение связи (Edit relationship), показанном на рис.  2.1.4. Нужный флажок называется Применить фильтр безопасности в обоих направлениях (Apply security filter in both directions).

ГлаВа 2.1

Безопасность в DAX 121

Рис. 2.1.3. Распространение фильтров безопасности по связям

Динамическая безопасность на уровне строк Статические фильтры безопасности, такие как Product[Category] = «Furniture», не  слишком полезны в  реальных сценариях. В  действительности механизм безопасности на уровне строк бывает гораздо более эффективным в его динамическом виде (dynamic), когда конкретные фильтры выводятся из того, какой именно пользователь работает с системой. Примечание. В данном разделе мы будем использовать в качестве примера модель, которую вы можете загрузить в виде файла 2.1 Row-level security.pbix.

122

ГлаВа 2.1

Безопасность в DAX

Рис. 2.1.4. Окно изменения связи

Установить личность пользователя можно с  помощью пары полезных функций, наиболее надежной из которых является функция USERPRINCIPALNAME. Эта функция возвращает электронный адрес пользователя, который впоследствии можно использовать для установки правильной логики безопасности. Примечание. Более старая функция USERNAME при использовании в службе Power BI возвращает электронный адрес пользователя, а в Power BI Desktop и Analysis Services – имя пользователя. Для облегчения процесса идентификации пользователя и была представлена новая универсальная функция с именем USERPRINCIPALNAME. При использовании Power BI Embedded вы можете настроить безопасность на уровне приложения, в которое встроен отчет Power BI (это называется App Owns Data). В этом случае не пользователь, а само приложение будет передавать секретный идентификатор Power BI при обращении к отчету. Это может быть идентификатор отдельного пользователя или целого отдела либо организации, который после извлечения при помощи функции USERPRINCIPALNAME может быть использован для установки фильтров безопасности. ГлаВа 2.1 Безопасность в DAX 123

Обычно вы используете в  качестве идентификатора пользователя в  модели данных не  его электронный адрес, а  какое-то числовое значение, будь то номер сотрудника из учетной системы отдела кадров или специально сгенерированный ключ. Так или иначе, вам придется хранить информацию о соответствии между идентификаторами и электронными адресами пользователей в отдельной таблице. В простых моделях данных вы можете настроить связь между этой таблицей  – назовем ее UserSecurity  – и таблицей, содержащей данные о  пользователях. Также вы можете напрямую фильтровать таблицу Employee при помощи электронного адреса.

Рис. 2.1.5. Таблицы UserSecurity и Employee

В более масштабных и  сложных моделях данных единственной таблицей Employee, возможно, будет не обойтись. В этом случае имеет смысл держать отдельную таблицу UserSecurity, не связанную с другими таблицами в модели. Причина этого в том, что иначе у вас в модели появится множество путей от таблицы UserSecurity к другим таблицам, что приведет к необходимости деактивировать какие-то связи. Работая с обособленной таблицей UserSecurity, вам нужно будет извлекать идентификатор пользователя из таблицы как часть фильтра безопасности. Например, для защиты таблицы Employee фильтр безопасности может выглядеть следующим образом: VAR ThisUser = LOOKUPVALUE( UserSecurity[EmpNr], UserSecurity[Email], USERPRINCIPALNAME() ) RETURN [EmpNr] = ThisUser

Обратите внимание, что вы можете использовать переменные DAX в фильтрах безопасности. Переменная ThisUser извлекает значение из столбца EmpNr таблицы UserSecurity, используя для поиска результат функции USERPRINCIPALNAME. После инструкции RETURN фильтр проверяет, совпадает ли значение в  текущей строке таблицы Employee в  столбце EmpNr со значением 124

ГлаВа 2.1

Безопасность в DAX

переменной ThisUser, фактически оставляя в таблице только одну строку с текущим пользователем. Помните, что в таблице UserSecurity содержатся конфиденциальные данные, так что ей и  самой может потребоваться защита. Для этой таблицы вы можете создать следующий простой фильтр безопасности: FALSE()

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

Особенности моделирования данных с RLS Использование механизма безопасности на уровне  строк связано с  определенными ограничениями в отношении моделирования данных. В особенности вы увидите, что при применении RLS вы не сможете воспользоваться некоторыми неактивными связями. Давайте рассмотрим модель данных, приведенную на рис. 2.1.6. Эта модель содержит таблицу фактов fHours, в которой хранится информация о часах, отработанных сотрудниками. Если сотрудники работают над определенным проектом, мы называем потраченные часы прямыми затратами времени (direct hours). Часы, ушедшие на вспомогательные события, такие как собрания, отпуска, болезни и т. д., относятся к косвенным затратам времени (indirect hours). У каждого проекта также есть руководитель (Project Manager) из общего списка сотрудников.

ГлаВа 2.1 Безопасность в DAX 125

Рис. 2.1.6. Таблицы fHours, Project и Employee

Примечание. Этот пример вы найдете в файле InactiveRelationship.pbix в сопроводительных материалах. В данной модели прямые затраты времени могут учитываться по проектам и их руководителям, а косвенные – напрямую по сотрудникам. Таким образом, между таблицами Employee и fHours у нас наблюдается две связи, а значит, одна из них должна быть неактивной. В нашем случае мы сделаем неактивной прямую связь между таблицами fHours и Employee. Какие меры можно создать для извлечения информации о прямых затратах времени из этой модели? Базовая формула будет простой: Direct Hours = SUM(fHours[Hours])

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

126

ГлаВа 2.1

Безопасность в DAX

Direct Hours = CALCULATE( SUM(fHours[Hours]), NOT(ISBLANK(fHours[ProjectNr])) )

Вычисление косвенных затрат работает так же, но на этот раз нас интересуют только строки с пустым значением в столбце ProjectNr. Также нам необходимо активировать связь между таблицами fHours и Employee: Indirect Hours = CALCULATE( SUM(fHours[Hours]), ISBLANK(fHours[ProjectNr]), USERELATIONSHIP(fHours[EmpNr], Employee[EmpNr]) )

Приведенные выше меры позволяют вычислить прямые и косвенные затраты времени по сотрудникам (в  случае с  прямыми затратами речь идет о  сотруднике, руководящем проектом), как показано на рис. 2.1.7.

Рис. 2.1.7. Прямые и косвенные затраты времени по сотрудникам

Теперь предположим, что нам необходимо реализовать механизм безопасности на уровне строк в этой модели данных. Давайте создадим простую роль безопасности, возвращающую результаты для сотрудника по имени Даг Адамс (Doug Adams). Для этого нужно определить следующий фильтр безопасности для таблицы Employee: [FullName] = "Adams, Doug"

ГлаВа 2.1

Безопасность в DAX 127

Когда эта роль будет активна, вместо заполненного данными визуального элемента вы увидите сообщение об ошибке, показанное на рис. 2.1.8.

Рис. 2.1.8. Ошибка, говорящая о невозможности загрузки данных для визуального элемента

Как-то странно, да? Между таблицами fHours и Employee существует два пути, но ведь идея использования функции USERELATIONSHIP заключается в том, чтобы воспользоваться альтернативным путем, так? Это правда, но только когда дело касается вычисления мер, а не фильтров безопасности. В действительности вы должны быть благодарны тому, что возникла такая ошибка, поскольку мы здесь фактически пытаемся изменить или снять ограничения безопасности с  таблицы fHours. В  обычной ситуации мы можем видеть в таблице fHours информацию только о проектах, которыми руководит Даг. Однако, используя функцию USERELATIONSHIP, мы фактически указываем движку игнорировать это правило и  открыть нам другие строки в таблице. Модель не позволяет это сделать, оставляя строки скрытыми. В конце концов, если бы такой защитный механизм не был реализован, это бы означало, что любой пользователь, имеющий доступ к написанию своих мер, мог бы легко обойти правила безопасности и поставить под угрозу конфиденциальность данных. Такое поведение заложено в  основу модели данных, и  оно демонстрирует вам, с какими проблемами можно столкнуться, если заранее не позаботиться о безопасности. Главная идея здесь состоит не в том, чтобы не использовать неактивные связи вовсе, а  в том, чтобы понимать возможные последствия, когда придет время защитить модель данных от посторонних глаз. Единственного способа избежать подобных ситуаций не существует. Все зависит от ваших аналитических потребностей. К примеру, вы можете разбить таблицу фактов надвое, чтобы в одной хранилась информация о прямых затратах времени, а в другой – о косвенных, как показано на рис. 2.1.9.

128

ГлаВа 2.1

Безопасность в DAX

Рис. 2.1.9. Разделение единой таблицы фактов на таблицы fHoursDirect и fHoursIndirect

Это решение, однако, не позволит вывести в отчете прямые затраты времени по сотрудникам, не являющимся руководителями проектов. Еще один вариант состоит в  дублировании таблицы Employee под именем Project Manager, как показано на рис. 2.1.10.

ГлаВа 2.1

Безопасность в DAX 129

Рис. 2.1.10. Добавление таблицы Project Manager

Это решение потребует установки фильтров безопасности на обе таблицы – Project Manager и Employee.

Проверка ролей безопасности При реализации и  внедрении политик безопасности вам понадобится возможность тестировать полученные результаты. Для этого в  Power BI Desktop и службе Power BI реализован схожий механизм под названием Просмотреть как роли (View as roles), позволяющий отображать информацию так, как если бы вы принадлежали определенной роли безопасности. Также этот инструмент дает возможность выдать себя за конкретного пользователя и  взглянуть на модель данных его глазами. В  Power BI Desktop это сделать очень просто  – достаточно в  окне  Просмотреть как роли выбрать одновременно и  роль, которую вы хотите протестировать, и  опцию Другой пользователь (Other user), как показано на рис. 2.1.11. В появившемся поле вы можете ввести электронный адрес пользователя, за которого хотите себя выдать.

130

ГлаВа 2.1 Безопасность в DAX

Рис. 2.1.11. Проверка ролей в Power BI Desktop

В службе Power BI этот механизм работает почти так же, за исключением того, что опция Другой пользователь (Other user) здесь скрыта. При выборе пункта Безопасность (Security) для набора данных вам необходимо щелкнуть по кнопке с тремя точками справа от роли безопасности и выбрать пункт Проверить в качестве роли (Test as role), как показано на рис. 2.1.12.

Рис. 2.1.12. Проверка ролей в службе Power BI

При просмотре отчета от имени выбранной роли щелкните на окрашенной в  синий цвет области со словами Содержимое просматривается в  качестве (Now viewing as). В появившемся окне вы можете ввести электронный адрес пользователя, в  которого хотите «перевоплотиться», как показано на рис. 2.1.13. ГлаВа 2.1

Безопасность в DAX 131

Рис. 2.1.13. Выбор пользователя

При тестировании механизма безопасности на уровне строк есть одно ограничение, состоящее в том, что вы не можете так же запросто проверять отчеты с динамическим подключением (live connection) к опубликованной модели Power BI, они находятся не в том же файле, где модель. Это действительно проблема, поскольку режим динамического подключения очень распространен при публикации отчетов. В следующем разделе мы рассмотрим один подход для тестирования RLS с такими отчетами, позволяющий разработчику имитировать любого пользователя.

Тестирование отчетов с динамическим подключением В службах поддержки организаций, развертывающих свои отчеты Power BI, зачастую поступают жалобы от пользователей приблизительно такого содержания: «Я не вижу никаких данных в своем отчете!» или «Я должен видеть X и Y, а вижу только X! А, и еще какой-то Z…», а бывает и еще хуже: «Джон должен видеть X, а он видит все». Часто подобные проблемы возникают в связи с неправильным присвоением пользователю роли безопасности или случайным изменением политик безопасности в  Power BI и  являются поводом для пересмотра установленных политик безопасности. Так или иначе, бывает очень полезно иметь возможность притвориться другим пользователем и взглянуть на мир его глазами. А с  учетом того, что вы должны уметь мимикрировать непосредственно в  рабочей среде, необходимо обеспечить безопасность самого этого перевоплощения. Иными словами, такая возможность должна присутствовать только у конкретных служб (к примеру, у службы поддержки) и быть недоступной для остальных пользователей. Кроме того, вы должны легко переключаться между пользователями в рабочей среде, а это означает, что это должно работать как с отчетами, так и с используемыми моделями данных Power BI. 132

ГлаВа 2.1

Безопасность в DAX

Решение предполагает наличие следующих специфических элементов: „ параметра запроса, используемого для имперсонализации: pImpersonation; „ специальной тестовой учетной записи PBITestUser – это должен быть аккаунт с электронным адресом в вашей организации и лицензией Power BI; „ рабочей области Power BI: PBITest. Давайте рассмотрим этот прием подробно.

Модель для имперсонализации Начнем мы с очень простой модели данных. Единственным элементом в ней будет параметр запроса (query parameter) с именем pImpersonation. 1. Создайте новую модель в Power BI Desktop. 2. Нажмите на кнопку Преобразование данных (Transform Data), чтобы открылся редактор Power Query (Power Query Editor). 3. Нажмите на выпадающую кнопку Управление параметрами (Manage Parameters) и выберите пункт Создать параметр (New Parameter). 4. Назовите параметр pImpersonation и  убедитесь, что флажок Требуется (Required) не установлен. В выпадающем списке Тип (Type) выберите вариант Текст (Text), заполните поле Описание (Description) и оставьте поле Текущее значение (Current Value) пустым, как показано на рис. 2.1.14.

Рис. 2.1.14. Создание параметра в окне Управление параметрами

ГлаВа 2.1 Безопасность в DAX 133

5. Нажмите на кнопку OK, чтобы подтвердить создание параметра. В результате параметр запроса появится на панели Запросы (Queries) и будет написан курсивом, поскольку параметры по умолчанию не загружаются в модель данных Power BI. Но в нашем случае мы хотим, чтобы он загружался! Для этого щелкните на параметре правой кнопкой мыши и выберите пункт Включить загрузку (Enable Load). Теперь имя параметра будет написано обычным шрифтом. 6. Нажмите на кнопку Закрыть и применить (Close & Apply). Power BI загрузит параметр в модель, в результате чего в ней появится одна пустая таблица с одной колонкой, что видно на рис. 2.1.15.

Рис. 2.1.15. Таблица pImpersonation

7. Сохраните модель, дав ей имя Impersonation, и опубликуйте ее в рабочей области PBITest. Примечание. Вы можете загрузить уже существующую модель данных из хранилища GitHub, файл называется 2.1 Impersonation.pbix.

Добавление таблицы pImpersonation в модель данных Теперь в исходной модели данных, которую нужно протестировать, вы можете добавить подключение DirectQuery к  таблице pImpersonation, следуя приведенным ниже шагам. Модель при этом получит составной тип. 1. Откройте модель данных в Power BI Desktop и щелкните на панели инструментов по кнопке Набор данных Power BI (Power BI Datasets). 2. На данном этапе вы можете выбрать модель для подключения. Выберите набор данных Impersonation из рабочей области PBITest. 3. В результате в модель будет добавлена таблица pImpersonation, как показано на рис. 2.1.16. Вы не можете просмотреть содержимое этой таблицы, но можете создать визуальный элемент с колонкой pImpersonation, что позволит увидеть, что в ней находится (в настоящий момент таблица пустая).

Добавление тестовой роли безопасности Теперь мы можем создать новую роль безопасности с именем UserTest, которая будет учитывать значение параметра pImpersonation. Если в  нем содержится допустимый электронный адрес, фильтры безопасности примут его для имперсонализации пользователя. Если значение пустое, никакие фильтры безопасности применяться не будут. 134

ГлаВа 2.1

Безопасность в DAX

Рис. 2.1.16. Таблица pImpersonation добавлена в модель данных

В качестве примера ниже приведен адаптированный фильтр безопасности для таблицы Employee: VAR Impersonation = SELECTEDVALUE(pImpersonation[pImpersonation]) VAR User = LOOKUPVALUE( UserSecurity[EmpNr], UserSecurity[Email], Impersonation ) RETURN ISBLANK(User) || [EmpNr] = User

В переменной Impersonation используется функция SELECTEDVALUE для извлечения значения параметра pImpersonation (представленного в  модели в  качестве столбца в таблице). Эта функция обычно применяется для получения значения из колонки только в том случае, если в  ней находится только одно (уникальное) значение. В нашем случае это именно так, поскольку в таблице pImpersonation у нас содержится лишь одна строка. В переменной User производится попытка извлечения значения столбца EmpNr из таблицы UserSecurity при помощи функции LOOKUPVALUE. Обратите внимание, что таблица UserSecurity используется для сопоставления электронных адресов и идентификаторов пользователей. Если значение EmpNr найдено, фильтр безопасности дальше работает с ним. Обратите внимание, что при отсутствии значения pImpersonation или неправильном электронном адресе функция LOOKUPVALUE вернет значение BLANK. Это означает, что никакую фильтрацию выполнять не нужно. Последняя строка кода (ISBLANK(User) || [EmpNr] = User) может быть интерпретирована следующим образом. Если в переменной User находится пустое значение, функция ГлаВа 2.1 Безопасность в DAX 135

ISBLANK(User) вернет истину для каждой строки в таблице. В противном случае формула вернет истину только для строки, в которой значение в столбце EmpNr совпадает со значением переменной User.

Собираем все вместе Теперь, когда мы создали новую роль безопасности, можно опубликовать нашу модель данных. Но  нам еще нужно кое-что сделать, чтобы можно было воспользоваться плодами своего труда. 1. Добавьте аккаунт PBITestUser к роли безопасности UserTest. 2. Добавьте аккаунт PBITestUser в рабочую область PBITest, в которой опубликован набор данных Impersonation. 3. Поделитесь отчетом с  динамическим подключением (live connection), который должен быть протестирован под пользователем PBITestUser. 4. Не давайте полного доступа к модели данных пользователю PBITestUser! Для проверки отчета необходимо, чтобы пользователь PBITestUser вошел в Power BI, открыл набор данных Impersonation и изменил значение параметра pImpersonation в  настройках набора данных на электронный адрес пользователя, которого нужно проверить. Не  забудьте обновить набор данных Impersonation после изменения значения параметра! Поскольку, кроме параметра, в этой модели ничего нет, ее обновление займет всего секунду. Теперь необходимо открыть нужный нам отчет. По причине того, что в модели с отчетом таблица Impersonation добавлена в  режиме DirectQuery, значение измененного параметра будет доступно напрямую. Роль безопасности подхватит электронный адрес и соответствующим образом отфильтрует отчет. Обратите внимание, что фильтры безопасности корректно применяются при первом подключении пользователя к  модели в течение сессии. При изменении значения параметра в момент просмотра отчета новое значение не будет автоматически подхватываться. Придется выйти из отчета и немного подождать (в наших тестах достаточно было 10 минут). Показанный здесь метод будет работать до тех пор, пока аккаунт PBITestUser будет оставаться единственным в  роли UserTest. Поскольку никакие другие роли не  используют значение pImpersonation, набор данных Impersonation не нуждается в обеспечении безопасности – в конце концов, это только электронный адрес и ничего больше. Для проверки таким образом других ролей безопасности вы можете создать отдельные тестовые роли для каждой роли безопасности в модели. В этом случае вы сможете видеть, что происходит, даже если пользователь входит в несколько ролей, путем добавления PBITestUser в соответствующие тестовые роли. Примечание. Мы использовали отдельную рабочую область для хранения набора данных Impersonation, но вы можете все организовать иначе. Здесь важно, чтобы тестовый пользователь обладал доступом к этому набору данных и не имел полного доступа к модели, которая тестируется (т. е. не был администратором рабочей области, содержащей модель). Причина в том, что для администраторов, членов или участников рабочей области роли безопасности не применяются.

136

ГлаВа 2.1

Безопасность в DAX

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

Иерархическая безопасность с применением функций PATH В большинстве организаций данные не  относятся напрямую к  конкретному пользователю, у которого есть к ним доступ. Вместо этого пользователи объединены в  группы доступа к  различным наборам данных. Например, управляющие могут видеть информацию, относящуюся к подотчетным им сотрудникам. В  DAX присутствует целая группа функций для работы с  подобными иерархиями типа родитель–потомок (parent-child hierarchies). Все эти функции начинаются со слова PATH.

Иерархические таблицы Для начала давайте взглянем на типичную организационную структуру на примере вымышленной компании QuantoBikes. В  компании присутствует несколько подразделений на разных континентах, и  каждое подразделение включает в себя команды. Организационная схема компании представлена на рис. 2.1.17.

Рис. 2.1.17. Организационная схема QuantoBikes

В таблице Employee эта иерархическая структура реализована при помощи вспомогательного столбца MngrNr, в котором указан идентификационный номер управляющего (Manager Number) данного сотрудника, как показано на рис.  2.1.18. В  этом столбце содержится код прямого начальника для каждого работника, за исключением главы компании, который никому не подчиняется. ГлаВа 2.1

Безопасность в DAX 137

Рис. 2.1.18. Таблица Employee компании QuantoBikes

Примечание. Мы делаем предположение о том, что у одного сотрудника в нашей компании не может быть двух непосредственных начальников, что довольно логично. При необходимости вы можете реализовать модель с несколькими родительскими элементами для точек данных (например, семейное древо), но сделать это при помощи одних функций группы PATH будет проблематично. В данной книге мы не будем касаться такой схемы организации данных. Также иерархия может содержать несколько деревьев, когда в нескольких строках таблицы будет отсутствовать родительский элемент.

Знакомство с функциями группы PATH При наличии двух столбцов в таблице для хранения информации об иерархии вы можете перейти от сотрудника к  его непосредственному начальнику, затем подняться до руководителя этого начальника и так до главы компании, что позволит вам собрать всю необходимую информацию о служебной иерархии. В DAX предусмотрен ряд функций для работы с иерархиями, которые мы подробно рассмотрим в этом разделе.

PATH Выражение PATH(Employee[EmpNr], Employee[MngrNr]) должно вычисляться в контексте строки в таблице Employee. Функция PATH принимает в виде аргументов два столбца с  информацией об иерархии и  возвращает полный путь от верхнего уровня иерархии до самого элемента. Результат функции  – это строка, в которой все идентификаторы элементов из столбца EmpNr разделены вертикальной чертой. К примеру, для сотрудника с именем Лео Джонсон (Leo Johnson) и кодом 10106 путь, возвращенный показанной выше формулой, будет следующим: 10001|10010|10101|10106

138

ГлаВа 2.1

Безопасность в DAX

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

PATHCONTAINS Функция PATHCONTAINS принимает иерархический путь и  значение для поиска в  качестве аргументов и  возвращает TRUE, если переданное значение входит в этот путь. На примере Лео Джонсона это будет выглядеть так: PATHCONTAINS( , 10010 )

Результатом этого выражения будет TRUE.

PATHLENGTH Функция PATHLENGTH возвращает количество элементов в  иерархическом пути элемента. Иными словами, она показывает уровень иерархии, на котором находится переданный элемент, что видно на примере ниже: PATHLENGTH()

Результатом этого выражения будет 4.

PATHITEM Функция PATHITEM принимает на вход иерархический путь и индекс и возвращает элемент, располагающийся в  переданном пути по указанному индексу, начиная с вершины иерархии: PATHITEM(, 3)

Это выражение вернет 10101.

PATHITEMREVERSE Функция PATHITEMREVERSE делает то же самое, что и PATHITEM, но поиск начинает с конца иерархии, то есть с ее самого вложенного уровня, как показано ниже: PATHITEMREVERSE(, 3)

На этот раз результатом будет 10010 – это руководитель непосредственного начальника Лео. Заметьте, что функции PATHITEM и PATHITEMREVERSE всегда возвращают текстовые значения, даже если передаваемые иерархические пути были построены на основе числовых идентификаторов.

ГлаВа 2.1

Безопасность в DAX 139

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

Использование функций PATH совместно с RLS Функции группы PATH можно использовать с целью реализации более продвинутой логики безопасности при наличии иерархических данных. Предположим, вам необходимо внедрить политику безопасности, при которой руководитель любого звена мог бы иметь доступ ко всем данным своих подчиненных (прямых и косвенных). Начнем с создания колонки в таблице Employee, содержащей иерархический путь подчинения для каждого сотрудника: Path = PATH([EmpNr], [MngrNr])

При написании фильтра безопасности мы снова начнем с извлечения кода сотрудника для текущего пользователя: VAR ThisUser = LOOKUPVALUE( UserSecurity[EmpNr], UserSecurity[Email], USERPRINCIPALNAME() )

На следующем шаге мы проверим, находится ли текущий пользователь в иерархическом пути сотрудника: RETURN PATHCONTAINS([Path], ThisUser)

Функция PATHCONTAINS возвращает TRUE только в том случае, если текущий пользователь присутствует на одном из уровней иерархического пути сотрудника, и FALSE во всех остальных случаях. В результате применения этого фильтра в таблице останутся видны, помимо самого текущего пользователя, только сотрудники, находящиеся в его иерархии подчинения.

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

ГлаВа 2.1

Безопасность в DAX

и допустим, что управляющий должен видеть данные не только по своим подчиненным, но и  по подчиненным своих линейных коллег, т. е. сотрудников, непосредственно подчиняющихся тому же начальнику, что и он сам. Сформулируем наши требования более детально: „ если Джон является управляющим, он может видеть свои данные и данные всех своих подчиненных (прямых и косвенных); „ если Джон является управляющим, он может видеть данные сотрудников, подчиняющихся его коллегам по отделу (т. е. тем, кто находится в непосредственном подчинении у начальника Джона); „ Джон не может видеть данные своего начальника или его коллег; „ если Джон не является управляющим, он может видеть только свои данные, даже если у кого-то из его отдела есть подчиненные. Нам придется написать немного кода, чтобы реализовать задуманную политику безопасности, и  в этом нам помогут переменные DAX. Общий алгоритм действий будет такой. 1. Определить, является ли Джон управляющим. 2. Определить, какие сотрудники непосредственно подчиняются начальнику Джона. 3. Удалить линейных коллег Джона из выборки. 4. Сделать видимой для Джона результирующую группу сотрудников. Для начала давайте поймем, как определить, является ли Джон управляющим. Это не так просто, поскольку иерархические пути направлены вверх и не отображают факта наличия подчиненных элементов. Но мы можем пройти по таблице Employee и посчитать, сколько раз код Джона встречается в иерархических путях сотрудников. Если сотрудник не является управляющим, его код будет присутствовать только в  его собственном пути и  больше ни в  каких. Также перед использованием функций группы PATH нам необходимо извлечь код сотрудника Джона из таблицы UserSecurity. Сделаем это при помощи следующего кода DAX: VAR ThisUser = LOOKUPVALUE( UserSecurity[EmpNr], UserSecurity[Email], USERPRINCIPALNAME() ) VAR IsManager = IF( COUNTROWS( FILTER(Employee, PATHCONTAINS(Employee[Path], ThisUser) ) ) > 1, TRUE(), FALSE() )

Переменная IsManager будет возвращать TRUE только в  том случае, если в подчинении у Джона кто-то есть. Мы могли бы найти всех сотрудников, находящихся в  подчинении у  Джона, но, согласно нашей политике безопасноГлаВа 2.1

Безопасность в DAX 141

сти, все сотрудники, данные которых Джон может просматривать, подконтрольны  – прямо или косвенно  – его непосредственному начальнику. Таким образом, логично будет начать с проверки, присутствует ли в пути сотрудника непосредственный начальник Джона. Используем функцию LOOKUPVALUE для извлечения значения из столбца MngrNr текущего пользователя (переменная ThisUser), после чего воспользуемся функцией PATHCONTAINS для проверки того, встречается ли начальник Джона в иерархическом пути сотрудника: VAR ThisUserMngr = LOOKUPVALUE( Employee[MngrNr], Employee[EmpNr], ThisUser ) VAR ReportsToManager = PATHCONTAINS([Path], ThisUserMngr)

Если бы мы использовали для фильтра полученную переменную ReportsToManager как она есть, мы бы выбрали слишком много сотрудников для просмотра. По  условию нашей задачи, мы должны исключить из этого списка начальника Джона и всех его линейных коллег. Чтобы сделать это, давайте поработаем с  уровнями в  иерархии Джона и  его начальника. По  идее, Джон должен видеть информацию сотрудников, находящихся как минимум на два уровня ниже его начальника. В этом нам поможет функция PATHLENGTH: VAR ThisUserPath = LOOKUPVALUE( Employee[Path], Employee[EmpNr], ThisUser ) VAR MngrLevel = PATHLENGTH(ThisUserPath) - 1

Обратите внимание, что фильтр безопасности выполняется для каждой строки в таблице Employee, но при этом мы каждый раз обращаемся к уровню непосредственного начальника Джона. Теперь у нас есть вся необходимая информация для получения списка сотрудников, которые должны быть видны: VAR ShouldBeVisible = ReportsToManager && PATHLENGTH([Path]) >= MngrLevel + 2

Но подождите, есть ведь еще один сотрудник, для которого наша формула вернет FALSE, но при этом данные по нему должны быть видны, и это сам Джон. Давайте финально подправим выражение: VAR ShouldBeVisible = (ReportsToManager && PATHLENGTH([Path]) >= MngrLevel + 2) || [EmpNr] = ThisUser

142

ГлаВа 2.1

Безопасность в DAX

Теперь мы можем оформить наш фильтр безопасности, собрав все воедино: RETURN IF( IsManager, ShouldBeVisible, [EmpNr] = ThisUser )

Существует только один случай, когда наш фильтр сработает неправильно, и его стоит обсудить. Дело в том, что у главы компании нет начальников, а значит, он будет видеть только свои данные, что не совсем правильно. Причина этого в том, что переменная ThisUserMngr для него будет возвращать пустое значение, а  значит, ни один сотрудник не  будет воспринят как его подчиненный: в конце концов, ни один иерархический путь не содержит пустых значений, а значит, переменная ReportsToManager будет возвращать FALSE для всех сотрудников. К счастью, у нас уже есть переменная, которая поможет отследить эту ситуацию, – это MngrLevel. Сравним ее с нулем и перепишем наш фильтр безопасности следующим образом: RETURN SWITCH(TRUE(), MngrLevel = 0, TRUE(), IsManager, ShouldBeVisible, [EmpNr] = ThisUser )

Функция SWITCH сначала проверяет, имеем ли мы дело с  главой компании, и если да, то делает всех сотрудников видимыми, возвращая значение TRUE(). Затем выполняется проверка того, является ли наш пользователь управляющим, и применяется соответствующий фильтр безопасности. Если пользователь не является ни главой компании, ни управляющим, показываем ему только его собственные данные. Теперь, когда вы увидели работу механизма безопасности на уровне строк в действии, обсудим, как можно применять его для реализации более сложных политик безопасности, а именно для защиты атрибутов и уровней агрегации.

Безопасность атрибутов В этом разделе мы посмотрим на безопасность в  моделях данных Power BI под совершенно иным углом. В  предыдущих разделах мы главным образом концентрировались на ограничении видимости строк в таблицах модели. Это наиболее распространенное применение политик безопасности, но иногда этих ограничений бывает недостаточно. Если применить к  безопасности на уровне строк термин горизонтальная, то должна быть и безопасность вертикальная, в которой защите подлежат отдельные столбцы или атрибуты. ГлаВа 2.1 Безопасность в DAX 143

Применение безопасности атрибутов Защита модели данных Power BI при помощи механизма безопасности на уровне  строк может потребоваться в  случае расширения аудитории, взаимодействующей с ней. Если вашей моделью пользуется только руководство высшего звена, вам, скорее всего, не понадобится использовать RLS вовсе, поскольку на этом уровне всем сотрудникам может быть доступна вся информация. И только при увеличении аудитории может появиться необходимость в сегментировании данных по географическому принципу, категориям клиентов или организационной структуре, о чем мы говорили в предыдущих разделах. В таком же ключе можно рассуждать и о вертикальной безопасности. Пока ваша модель данных охватывает единственный бизнес-процесс, будь то продажи или управление сделками, вам вряд ли понадобится защищать конкретные атрибуты. В такой модели, к примеру, могут содержаться имена продавцов и все, что касается их зон ответственности, но, вероятно, не будет присутствовать информация об их зарплатах, днях рождения или паспортных данных. Когда же данные в  модели охватывают сразу несколько сфер деятельности компании, например продажи и управление кадрами, вам потребуется включать в нее дополнительные атрибуты, которые не должны быть доступны всем пользователям.

Безопасность на уровне объектов и ее ограничения Одним из способов защиты атрибутов является применение механизма безопасности на уровне объектов (object-level security). Этот механизм бывает двух видов: „ безопасность на уровне  таблиц (table-level security) позволяет скрывать для ролей безопасности таблицы целиком; „ безопасность на уровне столбцов (column-level security) отвечает за видимость отдельных столбцов в таблицах. К примеру, если к  таблице Product применить механизм безопасности на уровне таблиц, модель будет вести себя так, будто этой таблицы не существует вовсе. То же самое касается и безопасности на уровне столбцов. В этой книге мы не будем подробно описывать все нюансы безопасности на уровне объектов, частично из-за того, что она реализуется без помощи языка DAX, которому посвящена книга, но главным образом потому, что в следующих разделах мы освоим более интересные способы защиты атрибутов. Конечно, можно придумать немало областей применения безопасности на уровне объектов. В официальной документации сказано, что этот механизм служит для ограничения доступа к «конфиденциальным» именам таблиц и столбцов, в которых могут храниться настолько секретные данные, что даже само знание об их существовании должно быть доступно только избранным (напрашивается фильтрующая таблица с именем UFO Type (Тип НЛО), но наверняка могут быть и более серьезные примеры). В то же время скрытие таблиц и колонок представляет определенную проблему для моделей данных и отчетов Power BI. При наличии связей между защищенными таблицами и обычными вам может понадобиться создать другие пути между ними для пользователей, не  имеющих доступа к  защищенным 144

ГлаВа 2.1

Безопасность в DAX

таблицам. Но  более важно, что отчеты Power BI, ссылающиеся на скрытые столбцы или столбцы из защищенных таблиц, начнут выдавать ошибки при обращении к ним пользователей с ограниченным доступом. Иными словами, при использовании безопасности на уровне  объектов вы будете вынуждены создавать и  поддерживать разные версии одних и тех же отчетов для пользователей с ограничением доступа и остальных. С учетом этих обстоятельств вы можете прийти к мысли о том, чтобы создать отдельную модель для защищенных данных – в конце концов, к ней будут обращаться только особые пользовательские отчеты. Примечание. Хотя механизм безопасности на уровне объектов и поддерживается моделями данных Power BI, в настоящее время он не может быть настроен непосредственно в Power BI Desktop.

Динамическая защита атрибутов: безопасность на уровне значений В этом разделе мы познакомимся с еще одной формой защиты атрибутов, позволяющей поддерживать как пользователей с  ограниченным доступом, так и  остальных, при работе с  одними и теми же отчетами. Мы  используем термин безопасность на уровне  значений (value-level security  – VLS), поскольку этот механизм сочетает в  себе признаки безопасности на уровне  строк и  на уровне столбцов. Использование безопасности на уровне значений позволяет давать доступ пользователям к значениям столбца в одних строках и ограничивать – в других. Перечисленные далее политики безопасности могут быть легко реализованы с помощью этого механизма. К примеру, управляющие могут видеть зарплаты своих подчиненных, но не тех, кто находится в подчинении у их коллег по отделу. При этом доступ к подчиненным коллег у них остается, и они могут видеть их продажи. Второй пример может базироваться на следующей логике: учителя могут видеть имена учеников, их оценки и т. д., но только преподаватели-консультанты или тьюторы имеют доступ к их адресам. В то же время учителя, являющиеся тьюторами для определенных учеников, могут видеть их адреса, но не адреса их одноклассников. Вам наверняка захотелось реализовать подобные сценарии с помощью мер DAX. И хотя в качестве первых шагов это будет полезно сделать, как основное решение это не годится. Пользователи, имеющие доступ к созданию своих мер в  Power BI для формирования собственных отчетов (а  это распространенная практика в Power BI), смогут воспользоваться любыми сведениями из модели данных. Так что любую безопасность, реализованную посредством мер, можно обойти с помощью других мер. Еще более важно то, что в  крупных производственных моделях данных зачастую могут присутствовать десятки и сотни мер. И если в каждую из них придется встраивать определенную логику, связанную с  безопасностью, на ГлаВа 2.1 Безопасность в DAX 145

выходе можно получить настоящего монстра, склонного к ошибкам и неповоротливого в плане поддержки. Вместо этого хотелось бы найти решение, которое будет работать с любыми мерами, созданными в модели. Реализация безопасности на уровне значений предполагает сочетание моделирования данных и применения фильтров безопасности DAX. В следующих разделах мы подробно коснемся этой темы. Примечание. Пример для данного раздела вы найдете в файле 2.1 Value-level security.pbix в сопроводительных материалах.

Безопасность на уровне значений: моделирование данных Первое, что необходимо сделать при разработке решения с применением механизма безопасности на уровне значений, – это решить, как должен выглядеть защищенный отчет. Вам наверняка не захочется видеть в нем сообщения об ошибках, гораздо лучше будет оставлять защищенные значения пустыми. На  рис.  2.1.19 для некоторых сотрудников номера социального страхования (SSN) отображаются, а для других – нет.

Рис. 2.1.19. Защищенный при помощи механизма безопасности на уровне значений отчет

Здесь важно отметить, что защищенные значения не отображаются в отчете. Но в нашем примере колонка SSN представляет метку, а не результат вычисления меры, поэтому в модели должно присутствовать какое-то значение для вывода в визуальном элементе. Это может быть пустой текст, значение BLANK или что-нибудь еще, и это значение должно находиться в строке таблицы. Теперь, когда мы понимаем, что для некоторых пользователей значения должны отображаться, а  для других  – оставаться скрытыми, пришло время 146

ГлаВа 2.1

Безопасность в DAX

разделить таблицу, которую мы будем скрывать (в нашем случае это таблица Employee), на две: в одной будут присутствовать общедоступные столбцы, а в другой – защищенные, как показано на рис. 2.1.20.

Рис. 2.1.20. Разделение таблицы Employee на общую и защищенную

При этом нам необходимо сохранять соответствие между строками в  созданных таблицах. Сделать это будет очень просто, поскольку у нас есть столбец EmpNr в обеих таблицах. Вы могли бы создать связь типа «один к одному» между этими таблицами, но это нам не поможет и не позволит получить пустые значения для защищенных колонок. Вместо этого данный вариант фактически отбросит нас к ситуации с единой таблицей. Решением здесь может быть добавление строк в защищенной таблице. В итоге таблица Employee (private) должна содержать вдвое больше строк по сравнению с таблицей Employee, разбитых на два набора. Один набор строк будет содержать все значения для ключа EmpNr, включая защищенные данные. Назовем эти строки положительными (positive rows). Во втором наборе строк также будут присутствовать все значения для ключа EmpNr, но с пустотами в защищенных столбцах. Такие строки мы назовем отрицательными (negative rows). А дополнительный столбец Private поможет отличать строки одного набора от строк другого. На рис. 2.1.21 схематически показана структура этих таблиц. Таблицы Employee (private) и  Employee должны быть объединены связью типа «многие к  одному», при этом таблица Employee (private) должна располагаться на стороне «многие». К тому же для связи должна быть активирована двунаправленная кросс-фильтрация. При выборе сотрудника по полному имени (FullName), например, нам бы хотелось, чтобы для него выделялись его защищенные данные в таблице Employee (private). И наоборот: при выборе зарплаты (Pay level) нам необходимо, чтобы были выделены все сотрудники с такими зарплатами. Таким образом, фильтры между таблицами Employee и Employee (private) должны распространяться по связи в обе стороны. ГлаВа 2.1

Безопасность в DAX 147

Положительные строки (защищенные данные)

Отрицательные строки (пустые значения)

Рис. 2.1.21. Добавление строк в таблицу Employee (private)

Примечание. Промежуточная таблица может быть легко создана при помощи приведенного ниже скрипта на языке M. Здесь мы предполагаем, что у вас есть запрос sEmployee, являющийся источником базовых данных для таблицы Employee, а сам не загружается в модель: let Source = sEmployee, PositiveRows = Table.SelectColumns(Source, {"EmpNr", "SSN", "DateOfBirth", "PayLevel"}), AddPrivate = Table.AddColumn(PositiveRows, "Private", each 1, Int64. Type), NegativeRows = Table.SelectColumns(Source, {"EmpNr"}), AddPrivate2 = Table.AddColumn(NegativeRows, "Private", each 0, Int64. Type), Combine = Table.Combine({AddPrivate, AddPrivate2}) in Combine

В этом скрипте создаются две копии таблицы sEmployee: одна с защищенными столбцами, а вторая – с единственным столбцом EmpNr. Далее в обе таблицы добавляется столбец Private со значениями 1 и 0 соответственно, после чего таблицы объединяются. Также может оказаться полезным добавить в таблицу дополнительные столбцы. Например, если вы хотите использовать организационную иерархию в политиках безопасности для защищенных атрибутов, имеет смысл вставить в обе таблицы столбец MngrNr.

148

ГлаВа 2.1

Безопасность в DAX

Убедитесь, что вы не  включили флажок Применить фильтр безопасности в обоих направлениях (Apply security filter in both directions) для установленной связи. Как вы увидите в следующем разделе, мы собираемся защищать частные атрибуты с помощью фильтра безопасности на таблице Employee (private) в добавление к фильтру безопасности на таблице Employee. В этом случае модель не позволяет фильтру распространяться против направления, заданного по умолчанию.

Безопасность на уровне значений: фильтры безопасности Включая в вывод колонки из таблиц Employee и Employee (private), вы заметите, что для каждого сотрудника у вас будет по две строки: одна с актуальными значениями частных атрибутов (положительные строки), а вторая – с пустыми значениями (отрицательные строки), что видно на рис. 2.1.22. Причина этого в том, как мы проектировали таблицу Employee (private).

Рис. 2.1.22. Две строки для каждого сотрудника

Это означает, что теперь вы можете использовать механизм безопасности на уровне строк для выбора того, какую копию показывать. Для каждого сотрудника, для которого необходимо отображать значения защищенных атрибутов, нужно сделать видимыми соответствующие положительные строки в таблице Employee (private) и скрыть отрицательные. А для тех, чью информацию необходимо сделать недоступной, достаточно сделать обратное, т. е. показать отрицательные строки и скрыть положительные. Таким образом, фильтр безопасности для таблицы Employee (private) может выглядеть следующим образом: (

&& [Private] = 1 ) || (

ГлаВа 2.1 Безопасность в DAX 149

NOT() && [Private] = 0 )

По аналогии назовем первое условие положительным, а  второе – отрицательным. К  примеру, чтобы отобразить защищенные атрибуты только для сотрудника с кодом 10203, нужно написать такой фильтр: ( [EmpNr] = 10203 && [Private] = 1 ) || ( [EmpNr] 10203 && [Private] = 0 )

Это выражение вернет истину для строки с EmpNr = 10203 и защищенными данными, а также для других значений EmpNr с пустыми значениями. Для всех остальных строк в таблице Employee (private) выражение вернет ложь, и строки будут скрыты. Как видите, для каждого сотрудника отображается только одна строка из таблицы Employee (private). Сотрудники, которые не должны выводиться вовсе, могут быть скрыты при помощи обычного фильтра безопасности на таблице Employee. Если вы установите фильтр [MngrNr] = 10201 в таблице Employee, то получите результат, показанный на рис. 2.1.23.

Рис. 2.1.23. Отображение личных данных пользователей

Безопасность на уровне значений: сложные сценарии В фильтрах безопасности, наложенных на защищенные таблицы, вы можете использовать любые возможности, предоставляемые языком DAX. Например, для реализации политики безопасности, позволяющей управляющему видеть частные атрибуты только его непосредственных подчиненных, можно начать 150

ГлаВа 2.1

Безопасность в DAX

с  добавления в  таблицу Employee (private) столбцов MngrNr и  Path. Первым делом извлечем в фильтре безопасности нашего пользователя: VAR ThisUser = LOOKUPVALUE( UserSecurity[EmpNr], UserSecurity[Email], USERPRINCIPALNAME() )

Теперь определим иерархический путь для пользователя и его уровень в организационной иерархии: VAR ThisUserPath = LOOKUPVALUE( 'Employee (private)'[Path], 'Employee (private)'[EmpNr], ThisUser ) VAR ThisUserLevel = PATHLENGTH(ThisUserPath)

Ограничение относительно только непосредственных подчиненных сводится к  поиску сотрудников, подотчетных нашему пользователю (т. е. тех, у  кого в иерархическом пути присутствует его идентификатор) и обладающих организационным уровнем, на единицу меньшим по сравнению с проверяемым пользователем. В DAX это требование может быть выражено следующим образом: PATHCONTAINS([Path], ThisUser) && PATHLENGTH([Path])