MDX: Linkmember > Descendants offsets with currentmember context - mdx

I'm self taught in MDX and am struggling with a problem.
I need to be able to isolate a set of products according to a window around their release date and then calculate their sales by the current rows date context. The 'window' is a set of streetdates where member is between or equal to currentmember -27 and currentmember + 7 (as we sometimes see early sales/preshipping).
Final output should look something like (forward days blank):
NR Sales
2018-03-25 117.12
2018-03-26 130.01
2018-03-27 2,827.63
2018-03-28
2018-03-29
2018-03-30
2018-03-31
My cube has a street/release date hierarchy which is a mirror of my fiscal date hierarchy. It pulls correct values with a slightly altered query looking at a single day in the where clause.
The below query returns values, but is slightly incorrect as i think the descendants call isn't working properly.
WITH
SET [StreetDateSet] AS
LinkMember
(
Descendants
(
[Date].[Yr-Qtr-Mo-Wk-Day].CurrentMember
,[Date].[Yr-Qtr-Mo-Wk-Day].[Day]
).Item(0)
,[Street Date].[StreetDate-Yr-Mo-Wk-Dy]
).Lag(27)
:
LinkMember
(
Descendants
(
[Date].[Yr-Qtr-Mo-Wk-Day].CurrentMember
,[Date].[Yr-Qtr-Mo-Wk-Day].[Day]
).Item(0)
,[Street Date].[StreetDate-Yr-Mo-Wk-Dy]
).Lag(-7)
MEMBER [NR Sales] AS
Sum
(
[StreetDateSet]
,[Measures].[Sales Value]
)
SELECT
NON EMPTY
[NR Sales] ON 0
,[Date].[Date].Children ON 1
FROM [CUBE]
WHERE
(
[Date].[Yr-Qtr-Mo-Wk-Day].[Week].&[2018]&[2]&[4]
,[Item].[New Used].[New]
,{
[Item].[Category Code].[220]
,[Item].[Category Code].[230]
,[Item].[Category Code].[210]
}
);
I suspect there's a few problems here but am not sure how to address them. Descendants is likely impacted by the date hierarchy in the where clause. I've tried to get it into the 1 axis, but i get 'The Yr-Qtr-Mo-Wk-Day hierarchy already appears in the Axis1 axis.' and am unsure how to address.
Thanks for any advice/guidance on where i'm going wrong.

Turns out that linkmember wasn't required and ended up finding a solution via MSDN that uses a filter/Properties function:
Set iteration in calculation
Solution involves a member which iterates a NULL * CurrentMember defined set and I slightly adopted the statement to generate a dynamic set which matches off the complementary hierarchies.
WITH
MEMBER [Measures].[SameDay] AS
NULL * Count([Date].[Yr-Mo-Wk-Day].CurrentMember AS MySet)
+
Sum
(
{
Filter
(
[Street Date].[StreetDate-Yr-Mo-Wk-Dy].MEMBERS AS MySet2
,
MySet2.Current.Properties("Key") = MySet.Item(0).Properties("Key")
).Item(0).Lag(28)
:
Filter
(
[Street Date].[StreetDate-Yr-Mo-Wk-Dy].MEMBERS AS MySet2
,
MySet2.Current.Properties("Key") = MySet.Item(0).Properties("Key")
).Item(0).Lead(7)
}
,[Measures].[Sales Value]
)

Related

Number or Results and WITH MEMBER Calculation interacting weird

In our MDX Queries, we use a template like the following WITH MEMBER . SELECT SUBSET . FROM (SELECT . FROM .) a lot.
What this particular query should do (and does!) is list all product variants matching a filter, and give the sales amount for it for a month in time. On the same row, we have the sales total of all variants of this product.
We need only a subset of the result (for paging), and the results need to ordered to allow paging of course. Then there is a non empty, so only product variants that were sold in the time period qualify.
This works just fine. Paging also works: putting 30, 60, 90 for Start in SUBSET is gives the correct results.
However, if I put a 1 as Count (SUBSET(..., x, 1)) the result of the "Price all" goes to (null) for each and any row I select with x. If I return two or more rows it works just fine, and all rows have their correct values.
Two results and the correct value in the rightmost column (in case you are wondering how the total of all variants, 67, can be lower than the sales of one particular variant (280): I picked a bad example - there was a return (so a negative sale) for another variant of this product, for 213. The 67 is correct and desired!)
One Row and a (null) result
I do not understand how or why the SUBSET count would interfere with the Tuple I select in the WITH MEMBER. If it does it for 30 rows at a time, why not for one?
*edit
I just tried, what would happen if I filter in a way that only one result matches (and SUBSET paging is set to "working values" of 0, 30): The same as in the 2nd screenshot, for one result row the WITH MEMBER evaluates to (null).
So it is not that SUBSET is the problem, but more general: When there is only one result row!
Could this be a bug in our SQL Server Version? It is SQL 2012 with SP4 and all latest security patches.
WITH
MEMBER [DIM Date].[HI Year_Month_Day].[CURRENT_MONTH] AS
[DIM Date].[HI Year_Month_Day].&[20201215].Parent /* Select Month of December */
MEMBER [MEASURES].[Price all] AS (
(
[DIM Products].[HI Products].Currentmember.Parent
, [DIM Products].[HI Subproduct].[Total]
)
, [MEASURES].[Price]
), FORMAT_STRING = '#,0' /* Get the parent product total next to every subproduct */
SELECT {
([DIM Datum].[HI Year_Month_Day].[CURRENT_MONTH], [MEASURES].[Price])
, ([DIM Datum].[HI Year_Month_Day].[CURRENT_MONTH], [MEASURES].[Price All])
} ON COLUMNS
, {
SUBSET(
ORDER(
NONEMPTY(
([DIM Products].[HI Produt Subproduct].[Subproduct])
, {([DIM Date].[HI Year_Month_Day].[CURRENT_MONTH], [MEASURES].[Price])}
)
, ([DIM Date].[HI Year_Month_Day].[CURRENT_MONTH], [MEASURES].[Price])
, BDESC
)
, 0
, 30
)
} ON ROWS
FROM (
SELECT
{[DIM StoreLocation].[HI Country].[Country].&[US]}
* {[DIM Productgroup].[HI Productgroup].[Productgroup].&[Bikes]}
ON COLUMNS
FROM [Cube]
)

How To Get All Items Created or Still Open For A Given Time

I am working with a system were items are created (postDate dimension) and closed (endDate dimension). The endDate column is always populated with the last time the item was seen. An item is considered closed in a certain time if its last seen date is before the date you are querying. Each row in the fact table has the item, postDate, endDate, locationID, and some other dimensions used for aggregations. What I am trying to accomplish is getting all items still active for a given time frame. For example I want to know all items posted in November 2008 or before November 2008 that has not yet closed. In SQL it would look something like:
SELECT C.geoCountyArea,TM.CalendarYear,COUNT(DISTINCT a.itemid)
FROM [dbo].[factTable] a
JOIN dbo.dimDate AS TM
ON TM.DateKey BETWEEN postDate AND endDate
JOIN [dbo].[dim_geography] C
ON A.geographyID=C.geographyID
WHERE C.geoCountyArea = '1204000057'
AND TM.CalendarYear = 2008 AND TM.MonthNumberOfYear = 11
GROUP BY C.geoCountyArea,TM.CalendarYear
ORDER BY C.geoCountyArea,TM.CalendarYear
This returns 27,715 which is expected. Now, in MDX this looks like:
WITH MEMBER Measures.[itemCount] AS
AGGREGATE(
{NULL:[PostDate].[Month Name].&[2008]&[11]} * {[EndDate].[Month Name].&[2008]&[11]:NULL},
[Measures].[Fact_itemCount]
)
SELECT NON EMPTY (
Measures.[itemCount]
) ON 0,
NON EMPTY (
{[PostDate].[Month Name].&[2008]&[11]},
{[Geography].[Geo County Area].&[1204000057]}
)ON 1
FROM [Cube];
This returns 27,717 - which is 2 more than the SQL version that could be due to items with no end Date posted. Now, the complication comes when I want to get more than one explicit time - for example item count for all months in 2008 or item count for all years. I looked up methods to link a given param to another one via roll playing dimensions and came across this link. I altered my script so it looks like:
WITH MEMBER Measures.[itemCount] AS
AGGREGATE(
{NULL:LINKMEMBER([DATE].[Calendar].CURRENTMEMBER
,[PostDate].[Calendar])}
* {LINKMEMBER([DATE].[Calendar].CURRENTMEMBER
, [EndDate].[Calendar]):NULL}
, [Measures].[Fact_itemCount]
)
SELECT {Measures.[jobCount]} ON 0,
NON EMPTY (
{[DATE].[Month Name].&[2008]&[11]},
{[Geography].[Geo County Area].&[1204000057]}
)ON 1
FROM [Cube];
This, however, returns only the items created in November 2008 - value of 14,884. If I add in other months I do get individual counts for each month but, again, these are just the items created in those months.
How do I get the "active" item count for a given month/year/quarter without having do explicitly declare the time values in the AGGREGATE?
Can you use NonEmpty?
WITH MEMBER Measures.[itemCount] AS
AGGREGATE(
{NULL:
NONEMPTY(
[PostDate].[Month Name].MEMBERS //<<AMEND TO EXACT STRUCTURE USED IN YOUR CUBE
,[DATE].[Calendar].CURRENTMEMBER
).ITEM(0).ITEM(0)}
* {NONEMPTY(
[EndDate].[Month Name].MEMBERS //<<AMEND TO EXACT STRUCTURE USED IN YOUR CUBE
,[DATE].[Calendar].CURRENTMEMBER
).ITEM(0).ITEM(0): NULL}
, [Measures].[Fact_itemCount]
)
...
This ended up being the solution that provided valid results (tested against SQL calls against the warehouse tables):
WITH MEMBER Measures.[itemCount] AS
AGGREGATE(
{NULL:LINKMEMBER([Post Date].[Calendar],
[Post Date].[Calendar])}
* {LINKMEMBER([Post Date].[Calendar],
[End Date].[Calendar]):NULL},
[Measures].[Fact_itemCount]
)
SELECT {Measures.[itemCount]} ON 0,
NON EMPTY (
{[Post Date].[Month Name].Children},
{[Geography].[Geo County Area].&[1204000057]}
)
FROM [Cube]
Not that I am doing LINKMEMBER against the post and end dates - not against the global Date measure.

MDX where clause in subquery does not slice cube - how to understand?

This query gives me sales of one store:
select
[measures].[sales] on 0
from [MyCube]
where [store].[store].[042]
However, if I move the slicer to inside of the subquery, it gives me sales of all stores.
select
[measures].[sales] on 0
from (select
from [MyCube]
where [store].[store].[042]
)
How to understand the mechanisms behind this difference?
This is also noted in this article, but without much explanation.
----EDIT----:
I tried various things and read around for a while. I'd like to add a question: is there a scenario in which the where clause in sub-select does filter the result?
This query gives me sales of all stores in state MI (store [042] belongs to MI):
select
[measures].[sales] on 0
from (select
[store].[state].[MI] on 0
from [myCube]
where [store].[store].[042]
)
Thinking of 'inner query only filters if the filtered dimension is returned on an axis', the theory is proved wrong if I do this:
select
[measures].[sales] on 0
from (select
[store].[state].members on 0
from [myCube]
where [store].[store].[042]
)
The sub-select still returns one state MI, but the outer query returns sales of all stores (of all states).
----EDIT 4/13----:
Re-phrasing the question in AdventureWorks cube with screenshot.
Query 1: sales of one store
Query 2: it returns sales of all stores if where clause is in the sub-select.
Query 3: the two answers I got suggested that we select the dimension in an axis - here is the result - we get all cities.
select
[measures].[sales] on 0
from (select
from [MyCube]
where [store].[store].[042]
)
The above query reduces the scope of stores just to the member [042]. Make note that sub-select is executed before the actual select. So, when it comes to the select, the engine just sees a cube which has all the members in all the dimensions; but only the member [store].[store].[042] in the store dimension. It's as if the cube has been kept intact every where else but sliced off on the Store dimension.
If you go a step ahead and add the store on to one of the axes, like
select
[measures].[sales] on 0,
[store].[store].members on 1
from (select
from [MyCube]
where [store].[store].[042]
)
you would see that although the member [All] appears in the output, it actually is just comprised of only one store.
In essence, the [All] is a special member which is calculated with respect to scope of the cube. It reflects the combined effect of all the members in the cube.
In SQL terms, it is similar to:
select sales, store as [All] from
(select sales, store from tbl where store = '042') tbl
Even though you see Sales----All, it is but a reflection of sales for store [042]
Here are some other good references concerning sub-select and slicer debate:
http://bisherryli.com/2013/02/08/mdx-25-slicer-or-sub-cube/
https://cwebbbi.wordpress.com/2014/04/07/free-video-on-subselects-in-mdx/
Chris Webb's video being located here:
https://projectbotticelli.com/knowledge/what-is-a-subselect-mdx-video-tutorial?pk_campaign=tt2014cwb
This should still leave an All member:
SELECT
[measures].[sales] ON 0
FROM
(
SELECT
FROM [MyCube]
WHERE
[store].[store].[042]
);
...but the member [All] of the Store hierarchy will only now be made up of [store].[store].[042].
You can see this by adding the Store hierarchy onto ROWS:
SELECT
[measures].[sales] ON 0,
[store].MEMBERS ON 1
FROM
(
SELECT
FROM [MyCube]
WHERE
[store].[store].[042]
);
This is the AdvWorks version similar to the reference in your question:
SELECT
{[Measures].[Order Count]} ON 0
,[Subcategory].MEMBERS ON 1
FROM
(
SELECT
{
[Subcategory].[Subcategory].&[22]
} ON 0
FROM [Adventure Works]
);
It returns the member from the sub-select and the All member adjusted to take account of the subselect:
In the references article why is the [All] less than the sum of the other two - this is not down to the subselect but is in connection with the measure that he has chosen [Measures].[Order Count] which is a distinct count. If you take away the subselect you see exactly the same behaviour of the All member being less than the sum of the other subcategory members (I've marked the point at which the total of the parts becomes higher than the All member):
SELECT
{[Measures].[Order Count]} ON 0
,Order
(
[Subcategory].MEMBERS
,[Measures].[Order Count]
,bdesc
) ON 1
FROM [Adventure Works];
Order Count: on 1 order there might be several Product Subcategories - hence this behaviour.
Edit
This query of yours:
select
[measures].[sales] on 0
from (select
[store].[state].members on 0
from TestCube //<< added this!
where [store].[store].[042]
)
This inner script is not valid? Using the same dimension on an axes and the WHERE clause is not valid:
select
[store].[state].members on 0
from TestCube
where [store].[store].[042]
Edit2
An mdx script returns a cube, which may be sliced or not sliced, but nevertheless it returns a cube. The WHERE clause is used to slice the cube that is returned. If we were using a third party tool then the dimension added to the WHERE clause would go into a combobox - with say Cliffside selected. BUT the user could effectively select Ballard from that combobox - it is just a slicer. The WHERE clause is not changing the cube that is returned by the mdx script, it is just affecting what is displayed in the cellset.
WHERE is valid within a subselect. It is part of the definition:
https://msdn.microsoft.com/en-us/library/ff487138.aspx
I've never found a use case for a subselect's WHERE clause.
Edit3
This link will explain things:
https://social.msdn.microsoft.com/Forums/sqlserver/en-US/ccb66ac3-0f9a-4261-8ccc-b6ecc51b6f07/is-where-clause-pointless-inside-a-subselect?forum=sqlanalysisservices
As Darren gosbell says in the answer to this question:
https://msdn.microsoft.com/en-us/library/ff487138.aspx it says that:
The WHERE clause does not filter the subspace.

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:

Non-empty previous value - MDX

I am using Performance Point Dashboard Designer 2013 and SharePoint Server 2013 for building dashboards. I am using SSAS2012 for Cube.
I have a scenario similar to the one illustrated by figure below. I am required to find Previous Non-Empty value for purpose of finding Trends.
Measure: [Quota]
Dimension: [Date].[Calendar Date].[Date]
The script ([Measures].[Quota], [Date].[Calendar Date].PrevMember) gives you a previous date. Lets say for date 27-Jan-13 whose Quota value is 87, it returns 26-Jan-13 which has null value. I want it to return 21-Jan-13 that has some Quota value. And for date 21-Jan-13, I want to return 15-Jan-13.
I wonder if this is possible.
Thanks,
Merin
After long searches and hits & trials and so on, I think I invented a solution of my own for myself.
Following is the script for my Calculated Member.
(
[Quota],
Tail
(
Nonempty
( LastPeriods(15, [Date].[Calendar Date].PrevMember)
,[Quota]
)
).Item(0)
)
Explanation
The number 15 means it will look for non-empty measures up to 15 siblings.
Now we know up to how many siblings to traverse back, in this case 15.
Lets find 15 previous siblings (both empty and non-empty) excluding current member.
(LastPeriods(15, [Date].[Calendar Date].PrevMember)
Since it will yield both empty and non-empty members, lets filter out empty members in terms of measure [Quota]. If we don't specify measure here, it will use default measure whatever it is and we may not get desired result.
Nonempty(LastPeriods(15, [Date].[Calendar Date].PrevMember),[Quota])
We may have several members in the output. And we will choose the last one.
Tail
(
Nonempty
( LastPeriods(15, [Date].[Calendar Date].PrevMember)
,[Quota]
)
)
So far, the script above gives previous non-empty member. Now we want to implement this member for our measure [Quota].
Hence we get the script below ready to create a Calculated Member.
(
[Quota],
Tail
(
Nonempty
( LastPeriods(15, [Date].[Calendar Date].PrevMember)
,[Quota]
)
).Item(0)
)
You can use recursion to define this.
The following query delivers something similar for the Adventure Works cube:
WITH member [Measures].[Prev non empty] AS
IIf(IsEmpty(([Date].[Calendar].CurrentMember.PrevMember, [Measures].[Internet Sales Amount])),
([Date].[Calendar].CurrentMember.PrevMember, [Measures].[Prev non empty]),
([Date].[Calendar].CurrentMember.PrevMember, [Measures].[Internet Sales Amount])
), format_String = '$#,##0.00'
SELECT {[Measures].[Internet Sales Amount], [Measures].[Prev non empty]}
ON COLUMNS,
non empty
Descendants([Date].[Calendar].[Month].&[2007]&[12], [Date].[Calendar].[Date])
ON ROWS
FROM [Adventure Works]
WHERE [Customer].[Customer].&[12650]
You would have to replace the name of the date hierarchy, as well as the measure name from Internet Sales Amount to Quota in the recursive definition of the measure Prev non empty.