What does MDX Aggregate() do with a single argument? - ssas

I understand how to use the MDX Aggregate() and Sum() functions, and the differences between them.
(One interesting one is that the Sum of a measure defined at a higher level in a hierarchy over that level's Children multiplies the measure by the number of children - whereas Aggregate "correctly" returns just the value defined at the higher level).
The documented syntax on MSDN is:
Aggregate(Set_Expression [ ,Numeric_Expression ])
I've always used it with both arguments. But what does Aggregate do when only the set_expression argument is provided? The documentation (again from MSDN) is pretty obscure:
If a numeric expression is not provided, this function aggregates each measure within the current query context by using the default aggregation operator that is specified for each measure.
I tried it in an MDX query like this:
WITH MEMBER WeekSummedTotal AS
Aggregate([Days].[WeeksAndDays].CurrentMember.Children)
SELECT
{Measures.ThingoCount,Measures.WeekTotal,Measures.WeekSummedTotal} ON 0,
[Days].[WeeksAndDays].[WeekName] ON 1
FROM DateGRoupingTest
What would this do? Would Aggregate aggregate the cube's default measure over the set? Or the set Measures.Members? Or the set of other measures specified on the 0 axis?
None of these! The query runs and returns results, but the calcd measure WeekSumTotal shows #Error, with a completely nonsensical error:
Aggregate functions cannot be used on calculated members in the measures dimension
Now this is true, but completely irrelevant. None of the other measures in the query is calculated, and in fact the cube doesn't have any calculated members. So what is Aggregate() actually trying to do here? Is this error message (again, in MDX!) completely misleading?
ADDITION: #whytheq in the answer below suggested creating the calculated measure using Aggregate, but creating it on a spare dimension hierarchy rather than in the Measures dimension. This works, but only if the cross-join with the [All] member of the selected "any old..." dimension is included.
Creating the measure there also makes it impossible to put the two (base) measures and the calculated measure on the same axis. If I try to do this:
{Measures.ThingoCount,Measures.WeekTotal,[Ages].[Age Key].WeekSummedTotal} ON 0,
I get the deeply-unhelpful error message:
Members, tuples or sets must use the same hierarchies in the function.
which, I think, translates to "I can't make a set using the , (UNION) function between members of Measures and members of [Ages].[Age Key] because they're members of different dimensions".
My conclusion, thanks to your informative answers, is that Aggregate() with a single argument is a tricky beast; I wonder why it was designed with the second argument optional?
I've also noted that trying to create my calculated member on my Ages dimension (only one hierarchy, only one attribute) gives me the misleading error message:
The 'Ages' dimension contains more than one hierarchy, therefore
the hierarchy must be explicitly specified.
unless I explicitly specify the hierarchy. MDX has so much potential, but the learning curve would be that much gentler if MS had put more effort into making it feed back errors properly.

What would this do? Would Aggregate aggregate the cube's default
measure over the set? Or the set Measures.Members? Or the set of other
measures specified on the 0 axis?
Aggregate function aggregates the set over the current measure for Measures dimension. And a measure is "current" if it is in scope. If a measure is not in scope, the default member from measures dimension is considered for aggregation.
A measure can be added to scope in many ways like
Having the measure on axes
with member [Customer].[Customer].abc as
aggregate([Customer].[Customer].members)
select [Customer].[Customer].abc on 0,
{[Measures].[Internet Sales Amount],[Measures].[Reseller Sales Amount]} on 1
from [Adventure Works]
In the above example the member abc was calcualted twice, once for each measure.
Using Subcube
with member [Customer].[Customer].abc as
aggregate([Customer].[Customer].members)
select [Customer].[Customer].abc on 0
from (select {[Measures].[Internet Sales Amount] } on 0 from [Adventure Works])
Having the measure in definition
with member [Customer].[Customer].abc as
aggregate([Customer].[Customer].members, [Measures].[Internet Sales Amount])
select [Customer].[Customer].abc on 0
from [Adventure Works]
In Where clause
with member [Customer].[Customer].abc as
aggregate([Customer].[Customer].members)
select [Customer].[Customer].abc on 0
from [Adventure Works]
where [Measures].[Internet Sales Amount]
As suggested by whytheq, have the member on some other dimension-hierarchy combo. Otherwise, the aggregate function would probably lead to the calculated member self-referencing itself.

Taking this section of the MSDN definition:
...this function aggregates each measure within the current query
context ...
each measure in the context of your script is the following:
{Measures.ThingoCount,Measures.WeekTotal,Measures.WeekSummedTotal}
Now Measures.WeekSummedTotal is a calculated members in the measures dimension - hence the error.
I'd imagine something like the following would function ok, where you use Aggregate to create a member in a dimension other than Measures?:
WITH
MEMBER [SomeSpareDim].[SomeSpareHier].WeekSummedTotal AS
Aggregate
(
[Days].[WeeksAndDays].CurrentMember.Children * [SomeDim].[SomeHier].[All]
)
SELECT
[SomeSpareDim].[SomeSpareHier].WeekSummedTotal ON 0
,[Days].[WeeksAndDays].[WeekName] ON 1
FROM DateGRoupingTest;
The above can be changed to show Aggregate being very useful:
WITH
MEMBER [Days].[WeeksAndDays].[Last3Weeks] AS
Aggregate
(
{
[Days].[WeeksAndDays].[Weekx]
,[Days].[WeeksAndDays].[Weeky]
,[Days].[WeeksAndDays].[Weekz]
}
)
SELECT
{Measures.ThingoCount,Measures.WeekTotal} ON 0
,{
//<< the following custom aggregated member will work for any measure, that is ON 0, via Aggregate
//it can be mixed up with the normal members of the same hierarchy like the following
[Days].[WeeksAndDays].[Last3Weeks]
,[Days].[WeeksAndDays].[WeekName].members
} ON 1
FROM DateGRoupingTest;

Related

MDX to show Measure property

I'm familiar with how to get member properties into an MDX result-set: create a calculated member using WITH.
The problem is when the member whose properties I want is a measure, not a dimension member. Because the calculated member is created on the Measures hierarchy, I get the dreaded "The Measures hierarchy already appears in the Axis0 axis" error. Here's the query I'm running:
WITH MEMBER Measures.MeasureType AS
Measures.CurrentMember.Properties('MEMBER_TYPE')
SELECT
MeasureType ON 0,
Measures.Members on 1
FROM TheCube
What I'm after is simply a list of all the measures ON 1 (this works, in itself); but with the measure's MEMBER_TYPE showing as the one column ON 0
You can't have members from the same hierarchy on both axes. There are two ways to get rid of this error.
1. Create the calculated member on some other dimension
WITH MEMBER [SomeDimension].[SomeHierarchy].MeasureType AS
Measures.CurrentMember.Properties('MEMBER_TYPE')
SELECT
[SomeDimension].[SomeHierarchy].MeasureType ON 0,
Measures.Members on 1
FROM [TheCube]
2. Have them in a set and not on different axes.
WITH MEMBER Measures.MeasureType AS
Measures.CurrentMember.Properties('MEMBER_TYPE')
SELECT
{Measures.MeasureType, Measures.Members} ON 0
FROM [TheCube]
Discarding the second method as it gives a static value. What's really needed is a cross-tab value. So sticking with first method.

Set 0 for specific value MDX query

I've been looking around for the answer but I didn't find anything. Sorry if the answer has been given elsewhere.
Here is my problem :
I have a calculated member which is the number of items (of the current member) divided by the total number of items (sumitem).
with
member
sumitem
as
SUM ([FailureReason].[FailureReason].[All],[Measures].[Items])
member
Impact
as
[Measures].[Items]/[Measures].[SumItem]
But for a specific member of my dimension FailureReason, the result of Impact has to be 0. So I tried to add this :
member
ImpactFinal
as
iif ([FailureReason].CurrentMember = [FailureReason].[FailureReason].&[127],
0,
Impact
)
and I select my data like this :
select
{[Measures].[Items],
ImpactFinal
} on columns,
[FailureReason].members on rows
from
NoOTAR
But instead of getting 0 only for this specific member, every members of this dimension have their ImpactFinal equals to 0. What is strange is if I replace 0 by any other value, the result is good.
Just use
[FailureReason].CurrentMember IS [FailureReason].[FailureReason].&[127]
instead of
[FailureReason].CurrentMember = [FailureReason].[FailureReason].&[127]
and it will work.
Update: Several tips:
There is also not necessary to use SUM function, since you can define only tuple, this will be enough for server: ([FailureReason].[FailureReason].[All],[Measures].[Count])
It's quite reasonable to check sumitem measure for dividing by zero in ImpactFinal calculation. Because once some filters are applied, this may cause zeroing this measure and errors in reports.
If you have an opportunity not only to query, but update cube, SCOPE ([FailureReason].[FailureReason].&[127],[Measures].[Impact]) with THIS = 0 is better than additional member because of performance.
Best of luck!
UPDATE to fix totals:
If total should be w/o FailureReason 127, you can substitute your measures with:
member Impact
as
iif ([FailureReason].[FailureReason].CurrentMember is [FailureReason].[FailureReason].&[127],
0,
[Measures].[Items]
)
member ImpactFinal
as
iif ([FailureReason].[FailureReason].CurrentMember is [FailureReason].[FailureReason].[All]
,[Measures].[Items]-([FailureReason].[FailureReason].&[127],[Measures].[Items])
,[Measures].[Impact])/[Measures].[SumItem]
But I have another solution, which is more readable:
member v2_ImpactUncountableFailure
as
iif ([FailureReason].[FailureReason].CurrentMember.Level.Ordinal=0
or
[FailureReason].[FailureReason].CurrentMember is [FailureReason].[FailureReason].&[127]
,([FailureReason].[FailureReason].&[127],[Measures].[Items])
,null)
member v2_ImpactFinal
as
([Measures].[Items]-[Measures].[v2_ImpactUncountableFailure])
/
([FailureReason].[FailureReason].[All],[Measures].[Items])
Use only this two measures instead of set of measures sumitem,Impact,ImpactFinal. First one will show result on failure-127 and total. Second subtracts it from clean unfiltered measure, so in the end we have clean members, zeroed failure-127 and corrected total.
Please let me know if it isn't work, I've tested on my DB and everything is OK.
A simple CASE statement would solve your problem: Try this
With
Member ImpactFinal As
CASE
WHEN [FailureReason].[FailureReason].CurrentMember IS [FailureReason].[FailureReason].&[127] THEN 0
ELSE
[Measures].[Items]
/
([FailureReason].[FailureReason].[All], [Measures].[Items])
END
SELECT
{ [Measures].[Items], ImpactFinal } On Columns
[FailureReason].[FailureReason].Members On Rows
From NoOTAR
Try
with
member sumitem
as
SUM ([FailureReason].[FailureReason].[All],[Measures].[Items])
member LeaveOut
as
[FailureReason].[FailureReason].CurrentMember.Properties("Key")
member Impact
as
IIf([Measures].[LeaveOut]= "127", 0, [Measures].[Items]/[Measures].[SumItem])

Arbitrarily picking a dimension to add members to

The following script gives exactly the result I want.
It feels like a hack as I've added the custom members VALUE and VALUE_MTD onto the hierarchy [Customer].[Country]. I've chosen this hierarchy arbitrarily - just not used [Measures] or [Date].[Calendar] as they are already in use.
Is there a more standard approach to returning exactly the same set of cells?
WITH
MEMBER [Customer].[Country].[VALUE] AS
Aggregate([Customer].[Country].[(All)].MEMBERS)
MEMBER [Customer].[Country].[VALUE_MTD] AS
Aggregate
(
PeriodsToDate
(
[Date].[Calendar].[Month]
,[Date].[Calendar].CurrentMember
)
,[Customer].[Country].[VALUE]
)
SELECT
{
[Customer].[Country].[VALUE]
,[Customer].[Country].[VALUE_MTD]
} ON 0
,NON EMPTY
{
[Measures].[Internet Sales Amount]
,[Measures].[Internet Order Quantity]
}
*
Descendants
(
{
[Date].[Calendar].[Month].&[2007]&[12]
:
[Date].[Calendar].[Month].&[2008]&[01]
}
,[Date].[Calendar].[Date]
) ON 1
FROM [Adventure Works];
The standard approach is called utility dimension. If you Google this term, you will find several descriptions of this approach. A "utility dimension" is one which does not reference any data, but is just added to the cube for the purpose of being able to cross join them with all other dimensions for calculations. You can have one or more of them.
Thus, in most cases, physically there is nothing in the dimension. It is just used for calculated members. (Depending on the implementation, you may have the attribute members defined physically, if you want to have some properties for them. But then, only the default member is referenced in the star schema from the fact tables. The attribute member values are then overwritten in the calculation script.)
Typical applications for this are time calculations like YTD, MTD, MAT (Moving Annual Total, i. e. a full year of data ending in the selected date), or comparisons like growth vs. a previous period.

Ever a need for CurrentMember.Item(0)

The custom measure in the following is taken from the book MDX Cookbook (Tomislav Piasevoli):
WITH
MEMBER [Internet Sales PP] AS
Sum
(
Generate
(
{
[Date].[Calendar].[Date].&[20080105]
:
[Date].[Calendar].[Date].&[20080125]
}
,{
ParallelPeriod
(
[Date].[Calendar].[Calendar Year]
,1
,[Date].[Calendar].CurrentMember.Item(0)
)
}
)
,[Measures].[Internet Sales Amount]
)
SELECT
{
[Measures].[Internet Sales Amount]
,[Internet Sales PP]
} ON 0
,[Product].[Color].MEMBERS ON 1
FROM [Adventure Works];
What purpose does the item(0) serve?
My understanding, which is probably wrong is
<set>.item(0) gives us first tuple in set
<tuple>.item(0) gives us first member in tuple
So what is the point of <member>.item(0)?
Refer this excellent article on the topic.
To sum it up, when we are doing a .ITEM(0) on a member, that member is implicitly converted to a tuple. So, .ITEM(0) does not really serve any purpose other than returning the member itself.
I would assume this is a typo or a copy paste error. At least in the official Microsoft MDX reference, there are only the two Item() versions that you mention.
And this does not cause an error, as there are some implicit type conversions:
If you have a member and need a tuple for the current expression, AS implicitly builds a one-member tuple from the member. Which is what takes place here presumably, when applying Item(0) to a member.
If you have a one member tuple and need a member for the current expression, AS implicitly applies Item(0).
There are similar implicit conversions from tuple and level to set, from tuple to scalar value, from dimension to hierarchy, and from hierarchy to member.

MDX: using distinct count in YTD for calculated member

I have created new measure that counts distinct policies (this measure is called FK Policy Distinct Count).
Then I created new calculated member called CountPolicyEndorsesNull which counts all policies from FK Policy Distinct Count using a filter:
(([Policy].[Endorses].&[0],[FK Policy Distinct Count]).
Than I did new calculated member called CountPolicy:
SUM(EXCEPT([Policy].[Policy Status].[Policy Status],[Policy].[Policy Status].&[Void]), [Measures].[CountPolicyEndorsesNull])
Next, I created a new member CountNewBound
SUM(
{
[Submission].[Tran Type].&[New], [Submission].[Tran Type].&[Developed]
},
[Measures].[CountPolicy]
)
And finally, YTDCountNewBound
SUM(YTD([Invoice Date].[Date Hierarchy].CurrentMember), [Measures].[CountNewBound])
Obviously, SUM function doesn't work in this case. Any idea how to make proper YTD count for calculated member?
Distinct count is a special measure which should be managed with a little more care. The rational behind this is that the when evaluating the measure a set of previous values is kept in memory. In order to improve performance, this structure is not passed and it's quickly converted to a scalar value.
Going back to your problem :
Distinct count can be evaluated over a tuple without problem, but you'll get in problems once you try to evaluate over a set of tuples. A possible, but costly and not always possible, is to create a hierarchy of values so you can convert your set in a member of a dimension.
In your case instead of using YTD([Invoice Date].[Date Hierarchy].CurrentMember) function using another hierarchy -> [Invoice Date].[YTD Date Hierarchy].
All this depends on the specific OLAP implementation you're using, but I guess holds true for mainly all OLAP vendors.