Расчет процентного вклада позволяет увидеть, какую часть от общего целого составляет конкретный элемент. Это может быть процент от общего итога (например, доля продукта в общих продажах компании) или процент от родителя (доля подкатегории в продажах своей категории).
Рецепт 1.1: Процент от Общего Итога (Percentage of Grand Total)Это самый распространенный сценарий: вы хотите увидеть, какой процент от общей суммы меры составляет каждый элемент. Для этого нам нужно разделить значение текущего элемента на общее значение меры, игнорируя все фильтры по измерениям, кроме тех, которые вы хотите сохранить.
Ключевая техника: Использование функции ALL или ALLMEMBERS для снятия фильтров. ALL снимает все фильтры с указанного измерения или иерархии, позволяя получить общий итог.
Пример 1: Процент продаж каждого продукта AdventureWorks от общего объема продаж компании за 2007 год
WITH MEMBER [Measures].[Sales % of Grand Total] AS
IIF(
IsEmpty([Measures].[Sales Amount]),
NULL,
[Measures].[Sales Amount] / ([Measures].[Sales Amount], ALL([Product].[Product]))
)
, FORMAT_STRING = "Percent"
SELECT
{[Measures].[Sales Amount], [Measures].[Sales % of Grand Total]} ON COLUMNS,
NON EMPTY [Product].[Product].[Product Name].Members ON ROWS
FROM
[Adventure Works]
WHERE
[Date].[Calendar Year].&[2007]
ORDER BY
[Measures].[Sales Amount] DESC
Разбор:
● [Measures].[Sales % of Grand Total]: Мы определяем новую расчетную меру.
● [Measures].[Sales Amount]: Это числитель – сумма продаж для текущего продукта (в контексте 2007 года).
● ([Measures].[Sales Amount], ALL([Product].[Product])): Это знаменатель. ALL([Product].[Product]) снимает все фильтры с иерархии [Product].[Product], позволяя получить общую сумму продаж [Sales Amount] для всех продуктов в контексте 2007 года. Это дает нам общий итог, относительно которого вычисляется процент.
● IIF(IsEmpty([Measures].[Sales Amount]), NULL, ...): Важная проверка на деление на ноль. Если продажи для продукта пусты, то и процент будет пустым.
● FORMAT_STRING = "Percent": Форматирует результат как процент.
Рецепт 1.2: Процент от Родителя (Percentage of Parent)Этот сценарий более специфичен: вы хотите увидеть, какую долю составляет элемент от суммы своего непосредственного родителя в иерархии. Например, доля подкатегории в продажах своей категории, или доля города в продажах своего региона.
Ключевая техника: Использование .Parent или Ancestor() для ссылки на родительский элемент в иерархии.
Пример 2: Процент продаж каждой подкатегории AdventureWorks от общей суммы продаж ее родительской категории за 2007 год
WITH MEMBER [Measures].[Sales % of Parent Category] AS
IIF(
IsEmpty(([Measures].[Sales Amount], [Product].[Category].CurrentMember.Parent)),
NULL,
[Measures].[Sales Amount] / ([Measures].[Sales Amount], [Product].[Category].CurrentMember.Parent)
)
, FORMAT_STRING = "Percent"
SELECT
{[Measures].[Sales Amount], [Measures].[Sales % of Parent Category]} ON COLUMNS,
NON EMPTY [Product].[Subcategory].Members ON ROWS
FROM
[Adventure Works]
WHERE
[Date].[Calendar Year].&[2007]
ORDER BY
[Product].[Category].CurrentMember.Parent.Name, [Measures].[Sales Amount] DESC
Разбор:
● [Product].[Category].CurrentMember.Parent: Это ключевой элемент. CurrentMember ссылается на текущую подкатегорию на оси ROWS. .Parent возвращает ее родительский член, то есть соответствующую категорию (например, для 'Road Bikes' родителем будет 'Bikes').
● ([Measures].[Sales Amount], [Product].[Category].CurrentMember.Parent): Этот кортеж вычисляет сумму продаж [Sales Amount] для родительской категории текущей подкатегории, тем самым предоставляя знаменатель для процентного расчета.
● ORDER BY [Product].[Category].CurrentMember.Parent.Name, [Measures].[Sales Amount] DESC: Мы сортируем результат сначала по имени родительской категории, а затем по продажам по убыванию, чтобы сгруппировать подкатегории под их родителями.
Рецепт 1.3: Обработка Деления на Ноль в Процентных Расчетах
Как вы могли заметить в предыдущих примерах, обработка деления на ноль является критически важной для надежности и стабильности ваших MDX-расчетов. Если знаменатель в процентном расчете равен нулю или пуст, MDX выдаст ошибку.
Ключевая техника: Использование функции IIF() (Immediate IF) в сочетании с IsEmpty() или прямой проверкой на 0.
Синтаксис IIF():
IIF(Logical_Expression, True_Value, False_Value)
● Logical_Expression: Условие, которое проверяется.
● True_Value: Значение, возвращаемое, если условие истинно.
● False_Value: Значение, возвращаемое, если условие ложно.
Пример (из предыдущих):
IIF(
IsEmpty([Measures].[Sales Amount]), -- Проверяем, пуст ли числитель
NULL,
[Measures].[Sales Amount] / ([Measures].[Sales Amount], ALL([Product].[Product]))
)
Или, если вы уверены, что знаменатель может быть равен 0:
IIF(
([Measures].[Sales Amount], ALL([Product].[Product])) = 0, -- Проверяем, равен ли знаменатель 0
NULL,
[Measures].[Sales Amount] / ([Measures].[Sales Amount], ALL([Product].[Product]))
)
Почему IsEmpty() лучше?
IsEmpty() проверяет, является ли ячейка пустой (NULL), что часто бывает в разреженных кубах. Проверка на = 0 не сработает, если значение просто отсутствует. Поэтому IsEmpty() является более надежным способом обработки потенциальных ошибок деления на ноль в MDX. Это соответствует рекомендациям экспертов, которые призывают к "оборонительному программированию" в MDX, чтобы обеспечить устойчивость отчетов к неполным данным.