How to mimic SQL subtraction of results from two different queries in mdx - mdx

I wanted to do the trend analysis between the dates. For an instance current date- 30 days
30-60 days and so on.Below is the snippet of comparable sql query but same I wanted to do in MDX.
SQL
SELECT
ROUND
(
(
(
(
SELECT
SUM(del_pri_impr)
FROM
reporting.so_sli_calc_val a,
reporting.user_group_tenant b,
reporting.salesorder c
WHERE
created_on BETWEEN DATE(now()-30) AND DATE(now())
)
-
(
SELECT
SUM(del_pri_impr)
FROM
reporting.so_sli_calc_val a,
reporting.user_group_tenant b,
reporting.salesorder c
WHERE
created_on BETWEEN DATE(now()-60) AND DATE(now()-30)
)
)
/
(
SELECT
SUM(del_pri_impr)
FROM
reporting.so_sli_calc_val a,
reporting.user_group_tenant b,
reporting.salesorder c
WHERE
created_on BETWEEN DATE(now()-60) AND DATE(now()-30)
) *100
)
,
0
) AS trend
MDX:
WITH
SET [~FILTER] AS
{[Created_Date.Created_Hir].[Created_On].[2014-04-01]:[Created_Date.Created_Hir].[Created_On].[2014-04-30]}
SET [~ROWS] AS
{[Sales Order Attributes SO.Sales_order].[Sales Order ID].Members}
SELECT
NON EMPTY {[Measures].[CONT_AMT_GROSS], [Measures].[CONT_AMT_NET]} ON COLUMNS,
NON EMPTY [~ROWS] ON ROWS
FROM [SALES_ORDER]
WHERE [~FILTER]
As of now I have hard coded the dates, that will come from parameters.
I am facing difficulty in creating the second set and how to do subtraction between two sets in MDX.

You already have the logic on how to obtain sets of date corresponding to "last 30 days from now" and "last 60 to last 30 days from now". So, I am going to skip that part.
NOTE - You would have to use the parameter values while building these sets.
What you want to do here is first find the values corresponding to these sets of dates and then perform operations on them.
You can proceed like this -
WITH
SET [~FILTER] AS
{[Created_Date.Created_Hir].[Created_On].[2014-04-01]:[Created_Date.Created_Hir].[Created_On].[2014-04-30]}
SET [~ROWS] AS
{[Sales Order Attributes SO.Sales_order].[Sales Order ID].Members}
SET [Last30Days] AS
...
SET [Last60ToLast30Days] AS
...
MEMBER [~Last30Days - Now] AS
Aggregate
(
[Last30Days],
[Measures].[SomeMeasure]
)
MEMBER [~Last60Days - Last30Days] AS
Aggregate
(
[Last60ToLast30Days],
[Measures].[SomeMeasure]
)
MEMBER [~Measure] AS
([~Last30Days - Now]-[~Last60Days - Last30Days] )/([~Last60Days - Last30Days] * 100), format_string = '#,##0'
SELECT
NON EMPTY {
[Measures].[CONT_AMT_GROSS],
[Measures].[CONT_AMT_NET],
[~Measure]
} ON COLUMNS,
NON EMPTY [~ROWS] ON ROWS
FROM [SALES_ORDER]
Format_String takes care of rounding.

Not sure if I totally agree with Sourav's answer as I think some form of aggregation will be needed; creating tuples with sets in them may raise an exception.
Here is a simple model, against AdvWrks, that is tested and will do a subtraction for you:
WITH
SET [Set1] AS
[Date].[Calendar].[Date].&[20060301]
:
[Date].[Calendar].[Date].&[20070308]
SET [Set2] AS
[Date].[Calendar].[Date].&[20070308]
:
[Date].[Calendar].[Date].&[20080315]
MEMBER [Date].[Calendar].[All].[Set1Agg] AS
aggregate([Set1])
MEMBER [Date].[Calendar].[All].[Set2Agg] AS
aggregate([Set2])
MEMBER [Date].[Calendar].[All].[x] AS
(
[Date].[Calendar].[All].[Set1Agg]
,[Measures].[Internet Sales Amount]
)
MEMBER [Date].[Calendar].[All].[y] AS
(
[Date].[Calendar].[All].[Set2Agg]
,[Measures].[Internet Sales Amount]
)
MEMBER [Date].[Calendar].[All].[x-y] AS
[Date].[Calendar].[All].[x] - [Date].[Calendar].[All].[y]
SELECT
{
[Date].[Calendar].[All].[x]
,[Date].[Calendar].[All].[y]
,[Date].[Calendar].[All].[x-y]
} ON 0
,[Product].[Category].[Category] ON 1
FROM [Adventure Works];
Reflecting against your code maybe something like the following:
WITH
SET [Set1] AS
[Created_Date.Created_Hir].[Created_On].[2014-04-01]
:
[Created_Date.Created_Hir].[Created_On].[2014-04-30]
SET [Set2] AS
[Created_Date.Created_Hir].[Created_On].[2014-03-01]
:
[Created_Date.Created_Hir].[Created_On].[2014-03-31]
MEMBER [Created_Date.Created_Hir].[All].[Set1Agg] AS
Aggregate([Set1])
MEMBER [Created_Date.Created_Hir].[All].[Set2Agg] AS
Aggregate([Set2])
MEMBER [Measures].[~Last30Days - Now] AS
(
[Created_Date.Created_Hir].[All].[Set1Agg]
,[Measures].[SomeMeasure]
)
MEMBER [Measures].[~Last60Days - Last30Days] AS
(
[Created_Date.Created_Hir].[All].[Set2Agg]
,[Measures].[SomeMeasure]
)
MEMBER [Measures].[~Measure] AS
([Measures].[~Last30Days - Now] - [Measures].[~Last60Days - Last30Days])
/
[Measures].[~Last60Days - Last30Days]
* 100
,format_string = '#,##0'
SET [~ROWS] AS
{
[Sales Order Attributes SO.Sales_order].[Sales Order ID].MEMBERS
}
SELECT
NON EMPTY
{
[Measures].[CONT_AMT_GROSS]
,[Measures].[CONT_AMT_NET]
,[Measures].[~Measure]
} ON COLUMNS
,NON EMPTY
[~ROWS] ON ROWS
FROM [SALES_ORDER]
WHERE
[~FILTER];

Related

MDX query runs for more than an hour

I am new to MDX queries. I have written a query which uses lead function to get values for (Current Week plus 1) as a new column for each of the metrics. Basically comparing current week value with last week's value. Without the new members the query runs within seconds. After adding the new members it runs forever. Pls suggest ways to optimize this query
Thanks for help.
WITH
SET [Range] as strtomember
(" [Time].[Week].&["+ Format(DateAdd('d', - DatePart('w', Now(), 2), Now()+7), "yyyy-MM-ddT00:00:00")
+"]")
:strtomember
(" [Time].[Week].&["+ Format(DateAdd('d', - DatePart('w', Now(), 2), Now()+14), "yyyy-MM-ddT00:00:00")
+"]")
MEMBER [Measures].[SalesNew] as
CASE WHEN [Time].[Year].CURRENTMEMBER IS [Time].[Year].&[2019] THEN
sum([Time].[Day].CURRENTMEMBER.LEAD(7),[Measures].[Sales Prev])
ELSE null
END,FORMAT_STRING = "$#,###.00"
MEMBER [Measures].[Order UnitsNew] as
CASE WHEN [Time].[Year].CURRENTMEMBER IS [Time].[Year].&[2019] THEN
sum([Time].[Day].CURRENTMEMBER.LEAD(7),[Measures].[Order Units Prev])
ELSE null
END,FORMAT_STRING = "#,##0"
MEMBER [Measures].[Count of OrdersNew] as
CASE WHEN [Time].[Year].CURRENTMEMBER IS [Time].[Year].&[2019] THEN
sum([Time].[Day].CURRENTMEMBER.LEAD(7),[Measures].[Count of Orders Prev])
ELSE null
END,FORMAT_STRING = "#,##0"
SELECT
{ [Measures].[Sales],[Measures].[Sales Prev],[Measures].[SalesNew],[Measures].[Order Units],[Measures].[Order Units Prev],
[Measures].[Order UnitsNew], [Measures].[Count of Orders],[Measures].[Count of Orders Prev], [Measures].[Count of OrdersNew]} ON COLUMNS ,
[Range] *
[Time].[Day].[Day].ALLMEMBERS * -- 4
[Time].[Hour].ALLMEMBERS *
[Product].[Merch Dept].ALLMEMBERS *
[Product].[Class].ALLMEMBERS ON ROWS
FROM [Cube]
Your MDX looks pretty cool.
Optimizing MDX is a little bit black magic - you need to just try alternatives.
Initially I'd try:
1.Swapping in strToSet rather than the two strToMember functions.
2.Do you need the condition [Time].[Year].CURRENTMEMBER IS [Time].[Year].&[2019] ? Reason I ask is that [Range] seems to
already be 2019 and you cross-join to that set so do you need to
worry about 2019?
3.You can use a simple Tuple instead of the aggregate SUM
4.Maybe while testing it might be worth removing the format_strings - shouldn't make a difference but if so you can add back.
I've not tested the following so please excuse typos:
WITH
SET [DateSet] AS
strtoSet
(
"[Time].[Week].&["
+ Format(DateAdd('d', - DatePart('w', Now(), 2), Now()+7), "yyyy-MM-ddT00:00:00")
+ "]:[Time].[Week].&["
+ Format(DateAdd('d', - DatePart('w', Now(), 2), Now()+14), "yyyy-MM-ddT00:00:00")
+"]"
)
MEMBER [Measures].[SalesNew] AS
(
[Time].[Day].CURRENTMEMBER.LEAD(7)
,[Measures].[Sales Prev]
)
MEMBER [Measures].[Order UnitsNew] AS
(
[Time].[Day].CURRENTMEMBER.LEAD(7)
,[Measures].[Order Units Prev]
)
MEMBER [Measures].[Count of OrdersNew] AS
(
[Time].[Day].CURRENTMEMBER.LEAD(7)
,[Measures].[Count of Orders Prev]
)
SELECT
{
[Measures].[Sales]
,[Measures].[Sales Prev]
,[Measures].[SalesNew]
,[Measures].[Order Units]
,[Measures].[Order Units Prev]
,[Measures].[Order UnitsNew]
,[Measures].[Count of Orders]
,[Measures].[Count of Orders Prev]
,[Measures].[Count of OrdersNew]
} ON COLUMNS ,
[DateSet] *
[Time].[Day].[Day].ALLMEMBERS *
[Time].[Hour].ALLMEMBERS *
[Product].[Merch Dept].ALLMEMBERS *
[Product].[Class].ALLMEMBERS ON ROWS
FROM [Cube];

How can i use a subquery or (WITH)statment in MDX Query?

I have a CustomerToFactor as a Measure and Customer as a Dimension. Now I want to create a MDX code like this SQL code but I can't. because (WITH) statements has another meaning in MDX.
with Total_Customer(
select cus_id
,sum(ctf_price) cus_total_price
from dbo.Customer
join dbo.CustomertoFactor on cus_id = ctf_cus_id
group by cus_id
)
select cus_id
,cus_name
,ctf_date
,ctf_price
,(cus_total_price / 100 * ctf_price) as Price_pro_customer
from dbo.Customer
join dbo.CustomertoFactor on cus_id = ctf_cus_id
join Total_Customer on Total_customer.cus_id = dbo.Customer.cus_id
SELECT NON EMPTY { [Measures].[ctf_date]
,[Measures].[ctf_price]
, (?) Price_pro_customer
} ON COLUMNS
,NON EMPTY {[Customer].[Customer - cus_name].[Customer - cus_name].ALLMEMBERS}
FROM [CustomerToFactor]
Thanks for your Answers. but it doesn't work. Actually I want it to be grouped for every name you name. for Example: for the name Alex only the sum would have to be calculated for Alex(100+300 = 400) as well as Group by.
I do not really understand the point of the calculation :)
But anyway, in MDX you can have your own measures calculated like this:
WITH MEMBER [Measures].[Price_pro_customer] AS
(SUM([Measures].[ctf_price]) / 100 * [Measures].[ctf_price])
SELECT NON EMPTY { [Measures].[ctf_date]
,[Measures].[ctf_price]
,[Measures].[Price_pro_customer]
} ON COLUMNS
,NON EMPTY {[Customer].[Customer - cus_name].[Customer - cus_name].ALLMEMBERS}
FROM [CustomerToFactor]
I am not sure you'll get the same result as the SQL query though, since you have [Customer].[Customer - cus_name].[Customer - cus_name].ALLMEMBERS on the rows which basically does a GROUP BY on the customer name.
So if in the table you had several rows for the same customer the output of MDX query should be 1 row for each customer. The SUM([Measures].[ctf_price]) is also different since it sums over all customers
I think you should create a date dimension reference to ctf_date.
Then your mdx should be as below:
WITH MEMBER [Measures].[Price_pro_customer] AS
SUM([DimDate].[ctf_date].[All], [Measures].[ctf_price]) / 100 * [Measures].[ctf_price]
SELECT NON EMPTY {
[Measures].[ctf_price] ,
[Measures].[Price_pro_customer]
} ON COLUMNS ,
NON EMPTY {[Customer].[Customer - cus_name].[Customer - cus_name].ALLMEMBERS *
[DimDate].[ctf_date].[ctf_date].ALLMEMBERS} ON ROWS
FROM [CustomerToFactor]

MDX: sum over time with range in rows

I have a day level in my time dimension.
I want to output the sum/avg for a selected range of days like in the following statement:
WITH
MEMBER measures.[sum1] AS
Sum([Measures].[Menge_Artikel_Stk])
MEMBER measures.[sum2] AS
Sum
(
[D_Datum].[Datum].[Tag]
,[Measures].[Menge_Artikel_Stk]
)
SELECT
{
[Measures].[stock]
,[Measures].[sum1]
,[Measures].[sum2]
} ON 0
,NON EMPTY
CrossJoin
(
{[D_item].[itemno].[itemno].MEMBERS}
,{
[D_Date].[Date].[day].[30.01.2017] : [D_Date].[Date].[Day].[05.02.2017]
}
) ON 1
FROM [Cube];
My goal is to show the sum/avg along the date-dimension filtered on rows.
Instead of 40/1653 i want sum to display 250 for the sum or 35,7 for the average. (1653 is obviously the sum of the entire day level). I want to add this calculated member to an Excel-Sheet. Thus the time range set is variable.
To get the sum/avg rows across the specific range you can do something like this:
WITH
MEMBER measures.[sum1] AS
Sum([Measures].[Menge_Artikel_Stk])
MEMBER measures.[sum2] AS
Sum
(
[D_Datum].[Datum].[Tag]
,[Measures].[Menge_Artikel_Stk]
)
MEMBER [D_Date].[Date].[All].[DaySum] AS
Sum
(
[D_Date].[Date].[day].[30.01.2017] : [D_Date].[Date].[Day].[05.02.2017]
)
MEMBER [D_Date].[Date].[All].[DayAvg] AS
Avg
(
[D_Date].[Date].[day].[30.01.2017] : [D_Date].[Date].[Day].[05.02.2017]
)
SELECT
{
[Measures].[stock]
,[Measures].[sum1]
,[Measures].[sum2]
} ON 0
,NON EMPTY
CrossJoin
(
{[D_item].[itemno].[itemno].MEMBERS}
,{
{
[D_Date].[Date].[day].[30.01.2017] : [D_Date].[Date].[Day].[05.02.2017]
}
,[D_Date].[Date].[All].[DaySum]
,[D_Date].[Date].[All].[DayAvg]
}
) ON 1
FROM [Cube];
You can use the Axis function to define the current set on your axis:
WITH
MEMBER measures.[sum] AS
Sum(
Axis(1),
[Measures].[Menge_Artikel_Stk]
)
MEMBER measures.[avg] AS
AVG(
Axis(1),
[Measures].[Menge_Artikel_Stk]
)
SELECT
{[Measures].[Menge_Artikel_Stk],[Measures].[sum],[Measures].[avg]} ON 0,
NON EMPTY {[D_Date].[Date].[day].[30.01.2017]:[D_Date].[Date].[Day].[05.02.2017]} ON 1
FROM [Cube];

With MDX hos to select first row?

I am using sharepoint report builder.
In my data (se picture below) - I would like to get all the first row - the ones with yellow.
How do i do that.
Here is the MDX code. What should I write to get the yellow rows?
SELECT
NON EMPTY
{[Measures].[Antal unikke brugere - Visiteret Tid]} ON COLUMNS
,NON EMPTY
{
[Borger].[Anonym Borgernøgle DPR].[Anonym Borgernøgle DPR].ALLMEMBERS*
[SundhedOgOmsorg - Ydelse].[Ydelse].[Ydelse].ALLMEMBERS*
[Kalender].[År].[År].ALLMEMBERS*
[Kalender].[Måned].[Måned].ALLMEMBERS
}
DIMENSION PROPERTIES
MEMBER_CAPTION
,MEMBER_UNIQUE_NAME
ON ROWS
FROM
(
SELECT
{
[SundhedOgOmsorg - Ydelse].[Ydelse].&[12.1 Hjemmetrænerforløb]
,[SundhedOgOmsorg - Ydelse].[Ydelse].&[12.2 Komb. Hjemmetræner & Terapeutforløb]
,[SundhedOgOmsorg - Ydelse].[Ydelse].&[12.3 Komplekse Rehabiliteringsforløb]
,[SundhedOgOmsorg - Ydelse].[Ydelse].&[12.7. hverdagsrehab. revis.gr. m. tp.]
} ON COLUMNS
FROM
(
SELECT
{[SundhedOgOmsorg - Modul].[Modul].&[Hjemmehjælp]} ON COLUMNS
FROM [FrbLis]
)
)
WHERE
[SundhedOgOmsorg - Modul].[Modul].&[Hjemmehjælp]
CELL PROPERTIES
VALUE
,BACK_COLOR
,FORE_COLOR
,FORMATTED_VALUE
,FORMAT_STRING
,FONT_NAME
,FONT_SIZE
,FONT_FLAGS;
You could try using one of the following iterative mdx functions:
Generate - https://msdn.microsoft.com/en-us/library/ms145526.aspx
Filter - https://msdn.microsoft.com/en-us/library/ms146037.aspx
To use filter is a little more convoluted as you need to specify specific tuples based on their relative positions. Generate is a little more straightforward. I will try to provide an example against the MS AdvWrks cube.
I think this is relatively similar to your situation:
SELECT
{[Measures].[Internet Sales Amount]} ON 0
,NON EMPTY
[Product].[Product Categories].[Product]
*
{
[Date].[Calendar].[Date].&[20050801]
:
[Date].[Calendar].[Date].&[20050807]
} ON 1
FROM [Adventure Works];
You would like the rows I've marked in red (there are lots of products with only 1 row)
So if we now apply the following multi-dimensional expression to the cube:
WITH
SET [prodSet] AS
[Product].[Product Categories].[Product]
SET [prodDtSet] AS
Generate
(
[prodSet] AS P
,Head
(
P.Current
*
NonEmpty
(
{
[Date].[Calendar].[Date].&[20050801]
:
[Date].[Calendar].[Date].&[20050807]
}
,(
P.Current
,[Measures].[Internet Sales Amount]
)
)
)
)
SELECT
{[Measures].[Internet Sales Amount]} ON 0
,[prodDtSet] ON 1
FROM [Adventure Works];
We get the results required:
Are you able to adapt my example to your context?
I will have a further play and supply a solution using Filter (it'll be, in a way, a little more complicated than the Generate approach)
Here is an approach using Filter:
WITH
SET [prodSet] AS
[Product].[Product Categories].[Product]
SET [prodDtSet] AS
NonEmpty
(
[prodSet]
*
{
[Date].[Calendar].[Date].&[20050801]
:
[Date].[Calendar].[Date].&[20050807]
}
,[Measures].[Internet Sales Amount]
)
SET [prodDtSetFiltered] AS
Filter
(
[prodDtSet]
,
(NOT
[prodDtSet].Item(
[prodDtSet].CurrentOrdinal - 1).Item(0)
IS
[prodDtSet].Item(
[prodDtSet].CurrentOrdinal - 2).Item(0))
OR
[prodDtSet].CurrentOrdinal = 1
)
SELECT
{[Measures].[Internet Sales Amount]} ON 0
,[prodDtSetFiltered] ON 1
FROM [Adventure Works];
Results are as following:

MDX query - best salesmen who sold all of given products

Let's say I have two simple dimensions:
Products - with id and name
Salesmen - with id and name
My fact table is named SALES and contains the ids of the abovementioned.
I need to produce a query that will show the names of salesmen who sold all of the given products.
This code solves the problem for two items X and Y:
SELECT
{} on 0,
EXISTS(
EXISTS(
{[Salesmen].[Name].MEMBERS},
{[Products].[Name].&[X]}
)
,{[Products].[Name].&[Y]}
)
ON 1
FROM [Test];
The other version is:
SELECT
{} on 0,
INTERSECT(
NONEMPTY(
{[Salesmen].[Name].MEMBERS}
,([Products].[Name].&[X])
)
,NONEMPTY(
{[Salesmen].[Name].MEMBERS}
,([Products].[Name].&[Y])
)
)
ON 1
FROM [Test];
However, this method becomes troublesome if the list of given products is large, for example - 100 random products..
Do you have a property member_key for the hierarchy [Products].[Name] ? We can test like this:
WITH
MEMBER [Measures].[Meas1] AS
[Products].[Name].CurrentMember.PROPERTIES("KEY ID")
MEMBER [Measures].[Meas2] AS
[Products].[Name].CurrentMember.MEMBER_Key
MEMBER [Measures].[Meas3] AS
[Products].[Name].CurrentMember.MEMBERvalue
select
{
[Measures].[Meas1]
,[Measures].[Meas2]
,[Measures].[Meas3]
} on COLUMNS,
[Products].[Name].MEMBERS on ROWS
FROM [Test];
Hopefully one of the custom measures gives you a value? I'll assume Meas2 is working (swap to a different one if Meas1 or Meas3 is returning numbers)
WITH
MEMBER [Measures].[Meas2] AS
[Products].[Name].CurrentMember.MEMBER_Key
SET [ProdsetA] AS
FILTER(
[Products].[Name].MEMBERS
,[Measures].[Meas2] <100
)
SET [ProdsetB] AS
FILTER(
[Products].[Name].MEMBERS
,[Measures].[Meas2] >500
)
SELECT
{} on 0,
INTERSECT(
NONEMPTY(
{[Salesmen].[Name].MEMBERS}
,[ProdsetA]
)
,NONEMPTY(
{[Salesmen].[Name].MEMBERS}
,[ProdsetB]
)
)
ON 1
FROM [Test];
... the >100 and <500 are important. These are the criteria for the filter function to use. The custom set [ProdsetA] will only contain Products that have MEMBER_Key that are <100 whereas the custom set [ProdsetB] will only contain Products that have MEMBER_Key that are >500. You need to use the member values presented to you by the first script to decide what values 100 and 500 should be in your cube context (...I don't know the key values in your cube so just used 100 and 500 as placeholders)