Persisting user-defined groupings of members in SSAS cube - ssas

I would like to allow users to specify their own dynamic grouping of members. In the AdventureWorks tutorial, that would mean a user could define "Product Group 1" as Accessories and Clothing, and "Product Group 2" as Accessories and Bikes." I could have an
I can get results like that by defining members as part of the MDX query:
with
member [Product].[Category].[Product Group 1] as
aggregate({[Product].[Category].[Accessories], [Product].[Category].[Clothing]})
member [Product].[Category].[Product Group 2] as
aggregate({[Product].[Category].[Accessories], [Product].[Category].[Bikes]})
select [Measures].[Internet Sales-Sales Amount] on 0,
{[Product].[Category].[Product Group 1], [Product].[Category].[Product Group 2] } on 1
from [Analysis Services Tutorial]
My question is: is there any way I could save these product groups as part of the cube, so you could just reference {[Product].[CustomGroups]} rather than having to include with member group1 as ... member group2 as .. on the query?
Sure, I could incorporate them into the dimensional model itself, which is almost what I want, but I'd like changes to take effect without refreshing the cube.

As mentioned in the comments. Inside the cube-script you should be able to:
1.
CREATE the custom members that you mentioned:
create member [product group 1] as ...
create member [product group 2] as ...
2.
CREATE the custom set that you mentioned:
create set [productSet] as ...{[product group 1],[product group 2]}
Exact syntax for the above will be on MSDN

Related

MDX calculated member dimension context

I have the following calculated member which represents the quantity of "overstocked" products:
WITH
MEMBER [Measures].[Overstocked Items Count] AS
FILTER(
[Items].[Item No].CHILDREN,
[Measures].[Overstocked Qty] > 0
).COUNT
It works just fine for any linked to the measure group dimension except for the Items dimension itself and the reasons are obvious. Is there a way to create a calculated member that would respect the context it is evaluated in? So basically if this member is evaluated against an item group code I need items count by those groups, not the entire items set.
EXISTING is a useful keyword that can add the current context to your measure:
WITH
MEMBER [Measures].[Overstocked Items Count] AS
FILTER(
EXISTING([Items].[Item No].CHILDREN),
[Measures].[Overstocked Qty] > 0
).COUNT
EXISTING is very good when you want to know the members present from a different hierarchy within the same dimension. e.g. say you have U.S.A selected from the country hierarchy (in geography dimension) and you need to count state/county members from a stateCounty hierarchy that is also part of the geography dimension then EXISTING is the correct choice.
If you want to go across dimensions so say you have U.S.A selected and you'd like to count customer, from the customer dimension who are associated with the U.S.A then I don't think EXISTING will work - you'll need to explore either EXISTS or NONEMPTY.

Calculated SSAS Member based on multiple dimension attributes

I'm attempting to create a new Calculated Measure that is based on 2 different attributes. I can query the data directly to see that the values are there, but when I create the Calculated Member, it always returns null.
Here is what I have so far:
CREATE MEMBER CURRENTCUBE.[Measures].[Absorption]
AS sum
(
Filter([Expense].MEMBERS, [Expense].[Amount Category] = "OS"
AND ([Expense].[Account Number] >= 51000
AND [Expense].[Account Number] < 52000))
,
[Measures].[Amount - Expense]
),
VISIBLE = 1 , ASSOCIATED_MEASURE_GROUP = 'Expense';
Ultimately, I need to repeat this same pattern many times. A particular accounting "type" (Absorption, Selling & Marketing, Adminstrative, R&D, etc.) is based on a combination of the Category and a range of Account Numbers.
I've tried several combinations of Sum, Aggregate, Filter, IIF, etc. with no luck, the value is always null.
However, if I don't use Filter and just create a Tuple with 2 values, it does give me the data I'd expect, like this:
CREATE MEMBER CURRENTCUBE.[Measures].[Absorption]
AS sum
(
{( [Expense].[Amount Category].&[OS], [Expense].[Account Number].&[51400] )}
,
[Measures].[Amount - Expense]
),
VISIBLE = 1 , ASSOCIATED_MEASURE_GROUP = 'Expense';
But, I need to specify multiple account numbers, not just one.
In general, you should only use the FILTER function when you need to filter your fact table based on the value of some measure (for instance, all Sales Orders where Sales Amount > 10.000). It is not intended to filter members based on dimension properties (although it could probably work, but the performance would likely suffer).
If you want to filter by members of one or more dimension attributes, use tuples and sets to express the filtering:
CREATE MEMBER CURRENTCUBE.[Measures].[Absorption]
AS
Sum(
{[Expense].[Account Number].&[51000]:[Expense].[Account Number].&[52000].lag(1)} *
[Expense].[Amount Category].&[OS],
[Measures].[Amount - Expense]
),
VISIBLE = 1 , ASSOCIATED_MEASURE_GROUP = 'Expense';
Here, I've used the range operator : to construct a set consisting of all [Account Number] members greater than or equal to 51000 and less than 52000. I then cross-join * this set with the relevant [Amount Category] attribute, to get the relevant set of members that I want to sum my measure over.
Note that this only works if you actually have a member with the account number 51000 and 52000 in your Expense dimension (see comments).
An entirely different approach, would be to perform this logic in your ETL process. For example you could have a table of account-number ranges that map to a particular accounting type (Absorption, Selling & Marketing, etc.). You could then add a new attribute to your Expense-dimension, holding the accounting type for each account, and populate it using dynamic SQL and the aforementioned mapping table.
I don't go near cube scripts but do you not need to create some context via the currentmember function and also return some values for correct evaluation against the inequality operators (e.g.>) via the use of say the membervalue function ?
CREATE MEMBER CURRENTCUBE.[Measures].[Absorption]
AS sum
(
[Expense].[Amount Category].&[OS]
*
Filter(
[Expense].[Account Number].MEMBERS,
[Expense].[Account Number].currentmember.membervalue >= 51000
AND
[Expense].[Account Number].currentmember.membervalue < 52000
)
,
[Measures].[Amount - Expense]
),
VISIBLE = 1 , ASSOCIATED_MEASURE_GROUP = 'Expense';
EDIT
Dan has used the range operator :. Please make sure your hierarchy is ordered correctly and that the members you use with this operator actually exist. If they do not exist then they will be evaluated as null:
Against the AdvWks cube:
SELECT
{} ON 0
,{
[Date].[Calendar].[Month].&[2008]&[4]
:
[Date].[Calendar].[Month].&[2009]&[2]
} ON 1
FROM [Adventure Works];
Returns the following:
If the left hand member does not exist in the cube then it is evaluated as null and therefore open ended on that side:
SELECT
{} ON 0
,{
[Date].[Calendar].[Month].&[2008]&[4]
:
[Date].[Calendar].[Month].&[1066]&[2] //<<year 1066 obviously not in our cube
} ON 1
FROM [Adventure Works];
Returns:

MDX - Count of Filtered CROSSJOIN - Performance Issues

BACKGROUND: I've been using MDX for a bit but I am by no means an expert at it - looking for some performance help. I'm working on a set of "Number of Stores Authorized / In-Stock / Selling / Etc" calculated measures (MDX) in a SQL Server Analysis Services 2012 Cube. I had these calculations performing well originally, but discovered that they weren't aggregating across my product hierarchy the way I needed them to. The two hierarchies predominantly used in this report are Business -> Item and Division -> Store.
For example, in the original MDX calcs the Stores In-Stock measure would perform correctly at the "Item" level but wouldn't roll up a proper sum to the "Business" level above it. At the business level, we want to see the total number of store/product combinations in-stock, not a distinct or MAX value as it appeared to do originally.
ORIGINAL QUERY RESULTS: Here's an example of it NOT working correctly (imagine this is an Excel Pivot Table):
[FILTER: CURRENT WEEK DAYS]
[BUSINESS] [AUTH. STORES] [STORES IN-STOCK] [% OF STORES IN STOCK]
[+] Business One 2,416 2,392 99.01%
[-] Business Two 2,377 2,108 93.39%
-Item 1 2,242 2,094 99.43%
-Item 2 2,234 1,878 84.06%
-Item 3 2,377 2,108 88.68%
-Item N ... ... ...
FIXED QUERY RESULTS: After much trial and error, I switched to using a filtered count of a CROSSJOIN() of the two hierarchies using the DESCENDANTS() function, which yielded the correct numbers (below):
[FILTER: CURRENT WEEK DAYS]
[BUSINESS] [AUTH. STORES] [STORES IN-STOCK] [% OF STORES IN STOCK]
[+] Business One 215,644 149,301 93.90%
[-] Business Two 86,898 55,532 83.02%
-Item 1 2,242 2,094 99.43%
-Item 2 2,234 1,878 99.31%
-Item 3 2,377 2,108 99.11%
-Item N ... ... ...
QUERY THAT NEEDS HELP: Here is the "new" query that yields the results above:
CREATE MEMBER CURRENTCUBE.[Measures].[Num Stores In-Stock]
AS COUNT(
FILTER(
CROSSJOIN(
DESCENDANTS(
[Product].[Item].CURRENTMEMBER,
[Product].[Item].[UPC]
),
DESCENDANTS(
[Division].[Store].CURRENTMEMBER,
[Division].[Store].[Store ID]
)
),
[Measures].[Inventory Qty] > 0
)
),
FORMAT_STRING = "#,#",
NON_EMPTY_BEHAVIOR = { [Inventory Qty] },
This query syntax is used in a bunch of other "Number of Stores Selling / Out of Stock / Etc."-type calculated measures in the cube, with only a variation to the [Inventory Qty] condition at the bottom or by chaining additional conditions.
In its current condition, this query can take 2-3 minutes to run which is way too long for the audience of this reporting. Can anyone think of a way to reduce the query load or help me rewrite this to be more efficient?
Thank you!
UPDATE 2/24/2014: We solved this issue by bypassing a lot of the MDX involved and adding flag values to our named query in the DSV.
For example, instead of doing a filter command in the MDX code for "number of stores selling" - we simply added this to the fact table named query...
CASE WHEN [Sales Qty] > 0
THEN 1
ELSE NULL
END AS [Flag_Selling]
...then we simply aggregated these measures as LastNonEmpty in the cube. They roll up much faster than the full-on MDX queries.
It should be much faster to model your conditions into the cube, avoiding the slow Filter function:
If there are just a handful of conditions, add an attribute for each of them with two values, one for condition fulfilled, say "cond: yes", and one for condition not fulfilled, say "cond: no". You can define this in a view on the physical fact table, or in the DSV, or you can model it physically. These attributes can be added to the fact table directly, defining a dimension on the same table, or more cleanly as a separate dimension table referenced from the fact table. Then define your measure as
CREATE MEMBER CURRENTCUBE.[Measures].[Num Stores In-Stock]
AS COUNT(
CROSSJOIN(
DESCENDANTS(
[Product].[Item].CURRENTMEMBER,
[Product].[Item].[UPC]
),
DESCENDANTS(
[Division].[Store].CURRENTMEMBER,
[Division].[Store].[Store ID]
),
{ [Flag dim].[cond].[cond: yes] }
)
)
Possibly, you even could define the measure as a standard count measure of the fact table.
In case there are many conditions, it might make sense to add just a single attribute with one value for each condition as a many-to-many relationship. This will be slightly slower, but still faster than the Filter call.
I believe you can avoid the cross join as well as filter completely. Try using this:
CREATE MEMBER CURRENTCUBE.[Measures].[Num Stores In-Stock]
AS
CASE WHEN [Product].[Item Name].CURRENTMEMBER IS [Product].[Item Name].[All]
THEN
SUM(EXISTS([Product].[Item Name].[Item Name].MEMBERS,[Business].[Business Name].CURRENTMEMBER),
COUNT(
EXISTS(
[Division].[Store].[Store].MEMBERS,
(
[Business].[Business Name].CURRENTMEMBER,
[Product].[Item Name].CURRENTMEMBER
),
"Measure Group Name"
)
))
ELSE
COUNT(
EXISTS(
[Division].[Store].[Store].MEMBERS,
(
[Business].[Business Name].CURRENTMEMBER,
[Product].[Item Name].CURRENTMEMBER
),
"Measure Group Name"
)
)
END
I tried it using a dimension in my cube and using Area-Subsidiary hierarchy.
The case statement handles the situation of viewing data at Business level. Basically, the SUM() across all members of Item Names used in CASE statement calculates values for individual Item Names and then sums up all the values. I believe this is what you needed.

Distinctly counting measure results that match a certain criteria

We have a measure for feedback scores which I am trying to report on. I need to calculate how many have reached a score >5 to calculate a performance %age. The issue I have is where there is more than one score available for my member which is aggregated in my results.
Here is what I have so far:
with
MEMBER [Client Sat Score] AS ([Measures].[Avg_Score], Linkmember([Bill Period].[Fiscal].[Fiscal Period].&[201409],[Date].[Fiscal]),[Bill Period].[Fiscal].[All],[Time Slice].[KeyTimeSlice].[12M])
MEMBER [Sum Scores] AS([Measures].[Sum of Scores], Linkmember([Bill Period].[Fiscal].[Fiscal Period].&[201409],[Date].[Fiscal]),[Bill Period].[Fiscal].[All],[Time Slice].[KeyTimeSlice].[12M])
MEMBER [Number Scores] AS([Measures].[Number of Scores], Linkmember([Bill Period].[Fiscal].[Fiscal Period].&[201409],[Date].[Fiscal]),[Bill Period].[Fiscal].[All],[Time Slice].[KeyTimeSlice].[12M])
MEMBER [Over 5] As IIF([Client Sat Score]>5,1,NULL)
MEMBER [Scores Over 5] As SUM([Matter].[KeyMatterNumber].[KeyMatterNumber].members,[Over 5])
MEMBER [Percent 6 or 7s] As IIF([Number Scores]=0,NULL,[Scores Over 5] / [Number Scores])
select {[Client Sat Score],[Sum Scores],[Number Scores],[Over 5],[Scores Over 5],[Percent 6 or 7s] }
on columns,
non empty ({[Client].[KeyClientRelatedID].&[XXX] })
on rows
from [Cube]
This returns 4 for "Scores over 5" but there are actually 2 scores of 7 on one of the members. These scores are on different date keys but I am unable to stop them aggregating within the SUM.
Any suggestions/advice please?
EDIT:
I've found that if I run the following I do get the 5 separate results each with a score above 5:
select {[Measures].[Avg_Score] }
on columns,
non empty ({[Matter].[KeyMatterNumber].[KeyMatterNumber].members })*
{[Date].[KeyDate].[KeyDate].&[20130206]:[Date].[KeyDate].[KeyDate].&[20140206]}
on rows
from [Cube]
where (
[Client].[KeyClientRelatedID].&[XXXX]
)
Does this help at all with the amendment of my first query?
It is difficult to answer without a cube schema available. However, it sounds like you are trying to do a row level calculation post-aggregation which results in your attempts to find the combination of dimensions that will cause leaf level data to be used in the calculation. Even if possible these invariably perform badly.
I would suggest pushing this calculation into the underlying database and generating an additional dimension with banded values { [1-5], [6-7] }.
Also, you didn't mention if this Analysis Services (or another vendor) Multidimensional or Tabular - if Tabular just create a row calc for "Over 5" in the cube and use that as your new dimension members.

MDX: How To Aggregate Hierarchy Level Members With Same Name

Greetings,
I am new to MDX, and am having trouble understanding how to perform an aggregation on a hierarchy level with members that have the same names. This query is particular to Microsoft Analysis Services 2000 cubes.
I have a given hierarchy dimension with levels defined as follows:
[Segment].[Flow].[Segment Week]
Within the [Segment Week] level, I have the following members:
[Week- 1]
[Week- 2]
[Week- 3]
...
[Week- 1]
[Week- 2]
[Week- 3]
The members have the same names, but are aligned with a different [Flow] in the parent level. So, the first occurrence of the [Week- 1] member aligns with [Flow].[A] while the second occurrence of [Week- 1] aligns with [Flow].[B]. What I am trying to do is aggregate all the members within the [Segment Week] level that have the same name. In SQL terms, I want to GROUP BY the member names within the [Segment Week] level. I am unsure how to do this. Thank you.
Dave
I think your cube design is flawed.
The best way to design a dimension is so all its members are unique, and the aggregation path follows the natural hierarchy of the dimension.
You could remove the WEEK level from SEGMENT and create another dimension (if I remember correctly, you can't have more than one hierarchy in AS 2000), say WEEK, with the following structure:
[Week].[Week- 1]
[Week].[Week- 2]
[Week].[Week- 3]
etc
Then you just have to filter by SEGMENT and by Week.
For example, to get all the Week1 of Flow A you'd do:
SELECT {[Measures].members} on 0
FROM MYCUBE
WHERE ([Week].[Week- 1],[Segment].[A])
Could you use the key of the member?
[Week].&[1] ([Week].[Week - 1] (flow1))
[Week].&[5] ([Week].[Week - 1] (flow2))
For reference, in the 'Adventure Works DW Standard Ed' Cube for 2008, the key for Customer Aaron A. Allen is [Customer].[Customer].&[20075]
UPDATE:
Sorry, just reread your question, it looks like you're not trying to get the specific week to a flow, you want to aggreagate them. What about a CASE Statement like this:
CASE
WHEN [Week].CURRENTMEMBER.NAME='Week - 1'
THEN [Week].CURRENTMEMBER
ELSE 0
END
Not very generic or flexible, but it might be a start...