WITH
MEMBER MEASURES.DEMO AS [Measures].[Internet Sales Amount] * 2SELECT {[Measures].[Internet Sales Amount], MEASURES.DEMO} ON 0,
NON EMPTY
[Product].[Category].[Category].MEMBERS
*
[Product].[Subcategory].[Subcategory].MEMBERS
*
[Product].[Product].[Product].MEMBERS
*
[Customer].[Postal Code].[Postal Code].MEMBERS
ON 1
FROM [Adventure Works]
WHERE([Date].[Calendar Year].&[2004])
Simply by adding the NON_EMPTY_BEHAVIOR property to the calculated member, so:
WITH MEMBER MEASURES.DEMO AS [Measures].[Internet Sales Amount] * 2
,NON_EMPTY_BEHAVIOR=[Measures].[Internet Sales Amount]
SELECT {[Measures].[Internet Sales Amount], MEASURES.DEMO} ON 0,
NON EMPTY
[Product].[Category].[Category].MEMBERS
*
[Product].[Subcategory].[Subcategory].MEMBERS
*
[Product].[Product].[Product].MEMBERS
*
[Customer].[Postal Code].[Postal Code].MEMBERS
ON 1
FROM [Adventure Works]
WHERE([Date].[Calendar Year].&[2004])
The query returns the same results in just 6 seconds. Analysis Services can do non-empty filtering very efficiently on regular measures, but has to use a more time-consuming algorithm on calculated measures which don’t have NON_EMPTY_BEHAVIOR set; this property just tells AS that it can use the more efficient algorithm when doing non-empty filtering on the calculated measure and treat it eaxactly the same as the given regular measure. Clearly, we can say that whenever [Measures].[Internet Sales Amount] is null then [Measures].[Internet Sales Amount]*2 will be null.
But what about calculated measures where we can’t set NON_EMPTY_BEHAVIOR? Take the following query, similar to the one we’ve just been looking at, which again executes in 1 minute 20 seconds on a cold cache:
WITH MEMBER MEASURES.DEMO AS ([Measures].[Internet Sales Amount], [Date].[Calendar Year].PREVMEMBER)SELECT {[Measures].[Internet Sales Amount], MEASURES.DEMO} ON 0,
NON EMPTY
[Product].[Category].[Category].MEMBERS
*
[Product].[Subcategory].[Subcategory].MEMBERS
*
[Product].[Product].[Product].MEMBERS
*
[Customer].[Postal Code].[Postal Code].MEMBERS
ON 1
FROM
[Adventure Works]
WHERE([Date].[Calendar Year].&[2004])
The first thing to note is that if you can do away with the calculated measure altogether, then do so. The following query returns the same values but by crossjoining the years we’re interested in with [Measures].[Internet Sales Amount]:
SELECT
{[Measures].[Internet Sales Amount]}
*
{[Date].[Calendar Year].&[2004], [Date].[Calendar Year].&[2003]}
ON 0,
NON EMPTY
[Product].[Category].[Category].MEMBERS
*
[Product].[Subcategory].[Subcategory].MEMBERS
*
[Product].[Product].[Product].MEMBERS
*
[Customer].[Postal Code].[Postal Code].MEMBERS
ON 1
FROM
[Adventure Works]
If you’re generating the entire query dynamically, then this might be more work but is the cleanest option. But often we can’t get rid of the calculated measure, or don’t want to for reasons of simplicity. We can’t set NON_EMPTY_BEHAVIOR on the calculated measure on our calc because we can’t say that it will be null whenever [Measures].[Internet Sales Amount] or any other regular measure will be null but we can do something similar. The following query returns the same results as the preceding query but again brings the performance back down to 6 seconds
WITH
MEMBER MEASURES.DEMO AS
([Measures].[Internet Sales Amount], [Date].[Calendar Year].PREVMEMBER)
SELECT {[Measures].[Internet Sales Amount], MEASURES.DEMO} ON 0,
NON EMPTY
NONEMPTY(
[Product].[Category].[Category].MEMBERS
*
[Product].[Subcategory].[Subcategory].MEMBERS
*
[Product].[Product].[Product].MEMBERS
*
[Customer].[Postal Code].[Postal Code].MEMBERS
, ([Measures].[Internet Sales Amount],[Date].[Calendar Year].[All Periods]) )
ON 1
FROM
[Adventure Works]
WHERE([Date].[Calendar Year].&[2004])
What we’re doing here is a two-step filtering operation: first remove the tuples on rows which would be empty for all years using the NONEMPTY function (this allows us to use the more efficient algorithm again, because we’re filtering by a regular measure) and then getting rid of the empty rows that are still left using NON EMPTY.