MDX Query to find the last ever non empty value in icCube - performance

Following the post from Chris Web I am looking for a fast way to find the last buy from a customer.
I use an MDX statement like the following:
WITH FUNCTION previous_buys() AS tail( nonempty({NULL:[Time].[Time].currentmember.prevmember} ,[measures].[sales amt]),1)
MEMBER [last buy] as previous_buys().(0).key
select [measures].[last buy] on 0
, [Customers].[Customers].[name].members on 1
from [Store Sales]
where [Time].[Time].[day].&[2015-12-20T00:00:00.000]
This gives as expected, but it is taking a very long time. Is there an easy way to speed this query up somehow. As icCube is somewhat different then the Microsoft MDX I can not just copy Chris Web's solution.
Any idea's?

The main problem we're going to have with this solution is the scalability as we're evaluating {NULL:[Time].[Time].currentmember.prevmember} count members.
I thought that using a Reverse with a Head function would not evaluate the whole set, but the current implementation of the Empty function 'materializes' the set. This means we're evaluating all members. Not yet a valid solution.
Another solution and more elegant is using a recursive function. This will should reduce drastically the number of members evaluated.
WITH
FUNCTION previous_buys(t_) AS IIF( (t_,[Measures].[Amount]) = NULL, previous_buys(t_.prevMember), t_ )
MEMBER [last buy] as previous_buys( [Time].[Calendar].current).name
SELECT
[measures].[last buy] on 0,
[Customers].[Geography].[Region] on 1
FROM [Sales]
WHERE [Time].[Calendar].[Year].[2006].[Q1 2006].[Jan 2006].[8 Jan 2006]
If you've a lot of empty dates you could complicate a bit the algorithm going down to a month level for checking emptiness. This will evaluate a whole month in one iteration instead of the 30/31 we'll have in the day version.
The last and fastest by an order of magnitude is relying of the aggregation engine of icCube. What we want here is a measure that returns the last existing day.
The idea would be to add a measure with a date as input value and max as aggregation method. Then we would use eval - important as we're caching the subcube - on the set with this new measure.

This is relatively quick using SSAS against AdvWrks. I amalgamated you two custom structures (& needed to change from FUNCTION as I don't think this is part of MS's implementation of mdx):
WITH
MEMBER [Measures].[previous_buys] AS
Tail
(
NonEmpty
(
{NULL : [Date].[Calendar].CurrentMember.PrevMember}
,[Measures].[Internet Sales Amount]
)
).Item(0).Item(0).Member_Key
SELECT
NON EMPTY
[Measures].[previous_buys] ON 0
,NON EMPTY
[Product].[Product Categories].[Product] ON 1
FROM [Adventure Works]
WHERE
[Date].[Calendar].[Date].&[20071015];
It results in the following:

Related

Tableau - Aggregate and non aggregate error for divide forumla

I used COUNT (CUST_ID) as measure value to come up [Total No of Customer]. When I created new measure for [Average Profit per customer] by formula - [Total Profit] / [Total No of Customer], the error of Aggregate and non aggregate error prompted.
DB level:
Cust ID_____Profit
123_______100
234_______500
345_______350
567_______505
You must be looking for avg aggregate function.
Select cust_id, avg(profit)
From your_table
Group by cust_id;
Cheers!!
In your database table, you appear to have one data row per customer. Customer ID is serving as a unique primary key. The level of detail (or granularity) of the database table is the customer.
Given that, the simplest solution to your question is to display AVG([Profit]) -- without having [Cust ID] in the view (i.e. not on any shelf)
If the assumptions mentioned above are not correct, then you may need to employ other methods depending on how you define your question. I suggest making sure you understand what COUNT() actually does compared to COUNTD(). The behavior is not what people tend to assume. LOD calculations may prove useful. All described in the online help.
Put the calculations directly in the calculated field as:
SUM([Profit])/COUNT([CUST_ID])
This will give you aggregate and aggregate calculation.
If you want to show Average profit using a key like [CUST_ID], you can use LOD expression:
{FIXED [CUST_ID]: AVG[Profit]}

How to get MDX openingperiod() to work as calculated measure in ssas cube

I want to use the MDX functions openingperiod() and closingperiod() in our cube, but cannot get it to work properly.
Is this actually possible, or are these functions only applicable in a MDX-query?
The main two articles I used to learn about openingperiod() are:
https://www.sqlservercentral.com/steps/stairway-to-mdx-level-12-mdx-timedate-series-functions-openingperiod-and-closingperiod-functions
https://learn.microsoft.com/en-us/sql/mdx/openingperiod-mdx?view=sql-server-2017
In the above documentation the two functions openingperiod() and closingperiod() are only used in a query, but I want to integrate them in the cube.
The code I used as a regular query is:
sum(
openingperiod
(
[Date Booking].[Year Month],
[Date Booking].[Year Month].currentmember
),
[Measures].[Goods Amount])
The result should be the amount of goods at the start of the month, but the result is NULL.
I used sum to get a valid syntax that points to the right member.
I will try to give you the idea from Adventure Works sample database.
This should be the syntax to create your calculated member in the cube ([Adventure Works] is the cube name in this example), so you can call it later:
create member [Adventure Works].Measures.MyMeasure4
as
(OpeningPeriod([Date].[Calendar].[Date],[Date].[Calendar].Currentmember.Parent), [Measures].[Internet Sales Amount])
And then you should call it like this to see the result:
select {Measures.MyMeasure4, [Measures].[Internet Sales Amount]} on 0,
[Date].[Calendar].members on 1
from [Adventure Works]
So for January, MyMeasure4 will have the value of January the 1st member (value for opening period), for February it will have the value from Feb the 1st, if that was your intention.

Cognos 11 Crosstab - need a value that doesn't have a reference to the column values

Crosstab report works 99%.
About 20 rows, all but one are ok.
5 columns - Company Division.
The rows are things like cost, revenue, revenue 2, etc.
All the rows that work have three attributes I'm using to select them:
Fiscal Year
Period
Solution.
The problem is there is table that lists an YTD rate for each period. This table is not Division Specific; it's company wide.
All the tables are linked to the accounting period table that has fiscal year and period. So the overall query limits data to fiscal year (?pFiscalYear?) and period <= ?pPeriod?, based on prompt page results.
The source table has this:
FY_CD PD_NO ACT_CURR_RT ACT_YTD_RT
2018 1 0.36121715 0.36121715
2018 2 0.32471476 0.34255512
2018 3 0.25240906 0.31210183
2018 4 0.33154745 0.31925874
Note the YTD rate is not an average of any of the other numbers.
When I select the ACT_YTD_RT, as a row, I want the ACT_YTD_RT that matches the selected period.
What I get is the average if I set the aggregation to average or the lowest if I set it to other aggregations. So sometimes, it looks right (if I run for period 1,2,3, as the rate kept falling), and sometimes it's wrong (period 4
returns .3121 instead of .3192).
I've tried a number of different methods and can generate garbage data (totals, min, max, average) and crossjoins but can't figure out how to get the value I'm looking for.
I want YTD_RT where fiscal year =?pFiscal? and period = ?pPeriod?.
I tried a straight if then clause:
if (sourcetable.fiscalYear = ?pFiscalYear?) and (sourcetable.Period = ?pPeriod?) then (ACT_YTD_RT)
but I get an error like this:
'ACT_YTD_RT' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause. (SQLSTATE=42000, SQLERRORCODE=8120)
If I create another query that generates the right response and try to include it, I get a crossjoin error that the query I'm referencing is trying to crossjoin several other items in the crosstab query.
A union doesn't work (different number of columns).
Not sure how a join would work since the division doesn't exist in the rate table.
I maybe could create a view in the database that did a crossjoin of the division table and the rate table, add that to the framework and then I wouldn't have a crossjoin since the solution would be in the rate "table" (really view), but that seems wrong somehow.
If I could just write a freaking parameterized query direct to the database I'd be done. But in Cognos 11 crosstabs I can't find a place for a SQL query object. And that shouldn't be necessary.
I've spent hours and hours chasing this in circles.
Anybody have any ideas?
Thanks
Paul
So the earlier problem was that this:
if (sourcetable.fiscalYear = ?pFiscalYear?) and (sourcetable.Period = ?pPeriod?) then (ACT_YTD_RT)
Generated an error like this:
'ACT_YTD_RT' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause. (SQLSTATE=42000, SQLERRORCODE=8120)
To fix the above, I had to add a cross join of the division table and the rate table as a view in the database. Then add that to the framework. Then build the data item this way:
total (
if (sourcetable.fiscalYear = ?pFiscalYear?) and (sourcetable.Period = ?pPeriod?) then (ACT_YTD_RT)
)
And now the "total" provides the missing group by. And the crossjoin in the database provides the division information so the crosstab is happy.
I still think there should have been an easier way to do this, but I have a functioning hammer at the moment.

Add multiple calculating filters in MDX

I am really new to MDX but I have spent the past two days looking for an answer but failed. So I greatly appreciate your help and patience.
I am trying to query a Cube with filters on multiple dimensions, and I realize that there are many similar questions already there, like this or this.
The thing is, instead of specifying a particular content I am looking for, I am trying to set up filters that picks up all records that begins with a specific string. This requires left function in the filters (i.e. calculating filters?) but I cannot blend them nicely into the code.
My failed code is like this (the two filters should be in ANDrelation)
Select Non Empty ([Measures].[Sales]) ON 0
FROM [Cube_Name]
WHERE
(
FILTER
(
[Customer].[CustomerID].Members, Left([Customer].[CustomerID].CurrentMember.Name,4)="ABCD"),
[Product].[ProductID].Members, Left([Product].[ProductID].CurrentMember.Name,3)="EFG")
)
)
(My trial is based on the last answer here.)
I also read that there are some workarounds like CROSSJOIN WITH AGGREGATE or Sub-SELECT, but I just do not have any clue on 1)how to incorporate the conditions inside; 2) performance (I heard that CROSSJOIN can be slow).
I am not sure if I should mention it here, but I am actually implementing the MDX from Excel VBA by using the ADOMB.Cellset object. It only gives me the Grand total of the query I implemented under Cellset.Items(0) (there are no more items).
Thank you!
You need to split two sets into two filters:
Select
Non Empty [Measures].[Sales] on 0
From [Cube_Name]
Where
(
Filter(
[Customer].[CustomerID].[CustomerID].Members,
Left(
[Customer].[CustomerID].CurrentMember.Name,
4
) = "ABCD"
),
Filter(
[Product].[ProductID].[ProductID].Members,
Left(
[Product].[ProductID].CurrentMember.Name,
3
) = "EFG"
)
)

Subselecting with MDX

Greetings stack overflow community.
I've recently started building an OLAP cube in SSAS2008 and have gotten stuck. I would be grateful if someone could at least point me towards the right direction.
Situation: Two fact tables, same cube. FactCalls holds information about calls made by subscribers, FactTopups holds topup data. Both tables have numerous common dimensions one of them being the Subscriber dimension.
FactCalls FactTopups
SubscriberKey SubscriberKey
CallDuration DateKey
CallCost Topup Value
...
What I am trying to achieve is to be able to build FactCalls reports based on distinct subscribers that have topped up their accounts within the last 7 days.
What I am basically looking for an MDX equivalent to SQL's:
select *
from FactCalls
where SubscriberKey in
( select distinct SubscriberKey from FactTopups where ... );
I've tried creating a degenerate dimension for both tables containing SubscriberKey and doing:
Exist(
[Calls Degenerate].[Subscriber Key].Children,
[Topups Degenerate].[Subscriber Key].Children
)
Without success.
Kind regards,
Vince
You would probably find something like the following would perform better. The filter approach will be forced to iterate through each subscriber, while the NonEmpty() function can take advantage of optimizations in the storage engine.
select non empty{
[Measures].[Count],
[Measures].[Cost],
[Measures].[Topup Value]
} on columns,
{
NonEmtpy( [Subscriber].[Subscriber Key].Children,
( [Measures].[Topups Count],
[Topup Date].[Calendar].[Month Name].&[2010]&[3] ) )
} on rows
from [Calls] ;
You know how sometimes it's the simplest and most obvious solutions that somehow elude you? Well, this is apparently one of them. They say "MDX is not SQL" and I now know what they mean. I've been working at this from an entirely SQL point of view, completely overlooking the obvious use of the filter command.
with set [OnlyThoseWithTopupsInMarch2010] as
filter(
[Subscriber].[Subscriber Key].Children,
( [Measures].[Topups Count],
[Topup Date].[Calendar].[Month Name].&[2010]&[3] ) > 0
)
select non empty{
[Measures].[Count],
[Measures].[Cost],
[Measures].[Topup Value]
} on columns,
non empty{ [Test] } on rows
from [Calls] ;
Embarrassingly simple.

Resources