Cross join multiple measure groups in MDX - ssas

I have two dimensions, and one measure group: [Time].[Day], [Scenario].[Scenario] and the measures group containing two measures, [Measures].[CleanPrice] and [Measures].[DirtyPrice]
I start out with the following result which works fine:
01-01-2011 01-01-2012
Base CleanPrice 100 100
Base DirtyPrice 100 100
Up1% CleanPrice 101 101
Up1% DirtyPrice 101 101
What I really want to produce is a calculated member to show the measure, and the delta between the current scenario and the Base scenario.
The desired result should look like the following:
01-01-2011 01-01-2012
Base CleanPrice Value 100 100
Base CleanPrice Delta 0 0
Base DirtyPrice Value 100 100
Base DirtyPrice Delta 0 0
Up1% CleanPrice Value 101 101
Up1% CleanPrice Delta 1 1
Up1% DirtyPrice Value 101 101
Up1% DirtyPrice Delta 1 1
I attempted something like this, but it is not possible to cross join measure groups:
WITH MEMBER [Value] as [Measures].CurrentMember
MEMBER [Delta] as [Measures].CurrentMember - ([Scenario].[Scenario].&[0], [Measures].CurrentMember)
SELECT NON EMPTY
{ [Time].[Day].&[2011-01-01T00:00:00], [Time].[Day].&[2012-01-01T00:00:00] }
ON COLUMNS,
{ [Scenario].[Scenario].&[0], [Scenario].[Scenario].&[1] } *
{ [Measures].[CleanPrice], [Measures].[DirtyPrice] } *
{ [Value], [Delta] }
ON ROWS
FROM Results
I create two calculated members, [Value] to simply be the current measure, and [Delta] to take the current measure, and get the difference for that measure against the base scenario.
I would like to cross join the measure group that has the clean price and dirty price, with a measure group of the two calculated members, but this is not possible.
Is there any way to produce the desired result? I would like to avoid creating the measures [CleanPrice Delta] and [DirtyPrice Delta] because I actually have a lot of them.

One approach would be to create a new dimension, say, [Metric] with two members, [Value] and [Delta]. Then write MDX into the calculation scripts along the following lines:
SCOPE ([Metric].[Delta], [Scenario].[Scenario].[Scenario]);
this = ([Metric].[Value], [Scenario].[Scenario].currentmember)
- ([Metric].[Value], [Scenario].[Scenario].&[0]);
END SCOPE;
This will then automatically work across all measures that are linked to the [Scenario] dimension.

Related

How to multiply two measures prior to aggregation

I have two measures. The first is amount, and the second consist of values -1,0 and 1, so table looks like this:
Amount Sign
--------------
400 -1
200 1
300 0
Result I want to get is 400*(-1) + 200*1 + 300*0 = -200, but I am getting (400+200+300)*(-1+1+0) = 0
This is my calculated member:
WITH
MEMBER [Measures].[Result]
AS
[Measures].[Sign]*[Measures].[Amount]
select
[Measures].[Result] on 0,
[Time].[Time].members on 1
from [MyCube]
In you SSAS project, go to the datasource view, for the underlying fact table add a NamedCalculation. In that do the multiplication that you explained. Now in the Cube add that as a measure. It will behave exactly like you want it to behave.

Counting latest instance of multiple only based on filter context

I've got a large table of events that have occurred in an inventory of vehicles, which affect whether they are in service or out of service. I would like to create a measure that would be able to count the number of vehicles in the various inventories at any point in time, based on the events in this table.
This table is pulled from a SQL database into an Excel 2016 sheet, and I'm using PowerPivot to try to come up with the DAX measure.
Here is some example data event_list:
vehicle_id event_date event event_sequence inventory
100 2018-01-01 purchase 1 in-service
101 2018-01-01 purchase 1 in-service
102 2018-02-04 purchase 1 in-service
100 2018-02-07 maintenance 2 out-of-service
101 2018-02-14 damage 2 out-of-service
101 2018-02-18 repaired 3 in-service
100 2018-03-15 repaired 3 in-service
102 2018-05-01 damage 2 out-of-service
103 2018-06-03 purchase 1 in-service
I'd like to be able to create a pivot table in Excel (or use CUBE functions, etc) to get an output table like this:
date in-service out-of-service
2018-02-04 3 0
2018-02-14 1 2
2018-03-15 3 0
2018-06-03 3 1
Essentially, I want to be able to calculate the inventory based on any date in time. The example only has a few dates, but hopefully provides enough of a picture.
I've basically come up with this so far, but it counts more vehicles than desired - I can't figure out how to only take the latest event_sequence or event_date and use that to count the inventory.
cumulative_vehicles_at_date:=CALCULATE(
COUNTA([vehicle_id]),
IF(IF(HASONEVALUE (event_list[event_date]), VALUES (event_list[event_date]))>=event_list[event_date],event_list[event_date])
)
I tried using MAX() and EARLIER() functions, but they don't seem to work.
Edit: Added the PowerBI tag as I'm now using that software to attempt to solve this as well. See comments on Alexis Olson's answer.
I think I've found a much cleaner method than I gave previously.
Let's add two columns onto the event_list table. One which counts vehicles "in-service" on that date and one which counts vehicles "out-of-service" on that date.
InService =
VAR Summary = SUMMARIZE(
FILTER(event_list,
event_list[event_date] <= EARLIER(event_list[event_date])),
event_list[vehicle_id],
"MaxSeq", MAX(event_list[event_sequence]))
VAR Filtered = FILTER(event_list,
event_list[event_sequence] =
MAXX(
FILTER(Summary,
event_list[vehicle_id] = EARLIER(event_list[vehicle_id])),
[MaxSeq]))
RETURN SUMX(Filtered, 1 * (event_list[inventory] = "in-service"))
You can create an analogous calculated column for OutOfService or you can just take the total minus the InService count.
OutOfService =
CALCULATE(
DISTINCTCOUNT(event_list[vehicle_id]),
FILTER(event_list,
event_list[event_date] <= EARLIER(event_list[event_date])))
- event_list[InService]
Now all you have to do is put event_date on the matrix visual rows section and add the InService and OutOfService columns to the values section (use Maximum or Minimum for the aggregation option rather than Sum).
Here's the logic behind the calculated column InService:
We first create a Summary table which calculates the maximal event_sequence value for each vehicle. (We filter the event_date to only consider dates up to the current one we are working with.)
Now that we know what the last event_sequence value is for each vehicle, we use that to filter the entire table down to just the rows that correspond to those vehicles and sequence values. The filter goes through the table row by row and checks to see if the sequence value matches the one we calculated in the Summary table. Note that when we filter the Summary table to just the vehicle we are currently working with, we only get a single row. I'm just using MAXX to extract the [MaxSeq] value. (It's kind of like using LOOKUPVALUE, but you can't use that on a variable.)
Now that we've filtered the table just to the most recent events for each vehicle, all we need to do is count how many of them are "in-service". I used a SUMX here where the 1*(True/False) coerces the boolean value to return 1 or 0.
This is pretty difficult. I don't have a great answer, but here's something that kind of works.
You'll create a new calculated table where you'll calculate the status for each vehicle on each date. Start with the base cross join for each vehicle and each date:
= CROSSJOIN(VALUES(event_list[vehicle_id]), VALUES(event_list[event_date]))
Then add a calculated column to find the max sequence number for each vehicle on that date.
Sequence = MAXX(
FILTER(event_list,
event_list[event_date] <= Cross[event_date] &&
event_list[vehicle_id] = Cross[vehicle_id]),
event_list[event_sequence])
Now you can lookup the inventory value for each vehicle/sequence pair with another calculated column:
Inventory = LOOKUPVALUE(
event_list[inventory],
event_list[vehicle_id], Cross[vehicle_id],
event_list[event_sequence], Cross[Sequence])
The result should look something like this:
Once you have this, you can create a matrix using this calculated table. Put the event_date on the rows and Inventory on the columns. Filter out blank inventory values in the visual level filter and put the vehicle_id in the values field, using a count or distinct count as the aggregation method (instead of the default sum).
It should look like this:

MDX Using multiple date dimensions

What I want to do is generate a report containing an overview of gains and losses (of contracts). For example:
Year | contract_gains | contract_losses
_______________________________________
2015 | 10 | 2
2016 | 15 | 4
Showing the gains is quite easy because I can just count distinct contracts (which is the aggregator for the measure value) with a start period.
SELECT
{[contract_start_date].[year].MEMBERS}
ON ROWS,
{[Measures].[value]}
ON COLUMNS
FROM Cube
Showing the losses seperately is also easy because I can do the same with the second date dimension.
SELECT
{[contract_end_date].[year].MEMBERS}
ON ROWS,
{[Measures].[value]}
ON COLUMNS
FROM Cube
But what I want to do is generate a report containing both of the values in a single report. Sadly I have no idea how I can do this.
A little slow method, going cell by cell. Does this work for you -
WITH MEMBER [Measures].contract_losses AS
(
GENERATE
(
[contract_start_date].[year].CURRENTMEMBER,
FILTER
(
[contract_end_date].[year].MEMBERS,
CStr([contract_start_date].[year].CURRENTMEMBER.MEMBER_VALUE) = CStr([contract_end_date].[year].CURRENTMEMBER.MEMBER_VALUE)
)
).ITEM(0)
,
[Measures].[value]
)
MEMBER [Measures].contract_gains AS
[Measures].[value]
SELECT
NON EMPTY {[contract_start_date].[year].MEMBERS}
ON ROWS,
{[Measures].contract_gains, [Measures].contract_losses}
ON COLUMNS
FROM [Cube]

MDX Query Can't connect Fiscal Month/Quarter to my Measures

I've been building an MDX query using excel's powerpivot. I connect to my cube, drag and drop Measures /Dimensions and my query has been working just fine. Up until I try to pull different dimensions.
A simple version of my query:
SELECT
NON EMPTY { [Measures].[EP Projected Impressions] } ON COLUMNS,
NON EMPTY { ([EP Hierarchy].[EP Tactic ID].[EP Tactic ID].ALLMEMBERS ) } ON ROWS
FROM [MI_Cube]
This will return:
(EP Tactic ID) (EP Projected Impressions)
1 10
2 20
3 30
4 40
5 50
Now when I try to pull in date information for each tactic from the Time dimension it just gives me a copy of the above results with each time dimension member.
Example query:
SELECT
NON EMPTY { [Measures].[EP Projected Impressions] } ON COLUMNS,
NON EMPTY { ([EP Hierarchy].[EP Tactic ID].[EP Tactic ID].ALLMEMBERS * [Time].[Fiscal Year].[Fiscal Year].ALLMEMBERS ) } ON ROWS
FROM [MI_Cube]
Results:
(EP Tactic ID) (EP Projected Impressions) (Fiscal Year)
1 10 FY2015
1 10 FY2014
1 10 FY2013
1 10 FY2012
1 10 FY2011
2 20 FY2015
2 20 FY2014
2 20 FY2013
2 20 FY2012
2 20 FY2011
etc....
Does this mean that I cannot pull the Time.FiscalYear dimension for each TacticID? Or do I need to restructure my query? EP Hierarchy has lots of dimension members I can pull successfully, but when I try to pull anything from EP Hierarchy and Time my results get multiplied instead of combined.
Thanks for any advice, trying to wrap my head around cubes and mdx queries.
It seems that you are simply missing a relation between the fact table holding the [EP Projected Impressions] member, and the dimension table holding your [Time] dimension.
By adding a relation between a foreign key on the fact table and the primary key on the dimension table, your measures should get correctly filtered by any attributes you slice on the dimension.
Thank you for the responses, it turns out the measure I was using was not connected to the time dimension. Apparently that was an expected behavior, after trying different measures I am getting the results I was expecting.

Outer table reference in sub-select

I have two tables, one that represents stock trades:
Blotter
TradeDate Symbol Shares Price
2014-09-02 ABC 100 157.79
2014-09-10 ABC 200 72.50
2014-09-16 ABC 100 36.82
and one that stores a history of stock splits for all symbols:
Splits
SplitDate Symbol Factor
2014-09-08 ABC 2
2014-09-15 ABC 2
2014-09-20 DEF 2
I am trying to write a report that reflects trades and includes what their current split adjustment factor should be. For these table values, I would expect the report to look like:
TradeDate Symbol Shares Price Factor
2014-09-02 ABC 100 157.79 4
2014-09-10 ABC 200 72.50 2
2014-09-16 ABC 100 36.82 1
The first columns are taken straight from Blotter - the Factor should represent the split adjustments that have taken place since the trade occurred (the Price is not split-adjusted).
Complicating matters is that each symbol could have multiple splits, which means I can't just OUTER JOIN the Splits table or I will start duplicating rows.
I have a subquery that I adapted from https://stackoverflow.com/a/3912258/3063706 to allow me to calculate the product of rows, grouped by symbol, but how do I only return the product of all Splits records with SplitDates occurring after the TradeDate?
A query like the following
SELECT tb.TradeDate, tb.Symbol, tb.Shares, tb.Price, ISNULL(s.Factor, 1) AS Factor
FROM Blotter tb
LEFT OUTER JOIN (
SELECT Symbol, EXP(Factor) AS Factor
FROM
(SELECT Symbol, SUM(LOG(ABS(NULLIF(Factor, 0)))) AS Factor
FROM Splits s
WHERE s.SplitDate > tb.TradeDate -- tb is unknown here
GROUP BY Symbol
) splits) s
ON s.Symbol = tb.Symbol
returns the error "Msg 4104, Level 16, State 1, Line 1 The multi-part identifier "tb.TradeDate" could not be bound."
Without the inner WHERE clause I get results like:
TradeDate Symbol Shares Price Factor
2014-09-02 ABC 100 157.79 4
2014-09-10 ABC 200 72.50 4
2014-09-16 ABC 100 36.82 4
Update The trade rows in Blotter are not guaranteed to be unique, so I think that rules out one suggested solution using a GROUP BY.
One way without changing the logic too much is to put the factor calculation into a table valued function:
create function dbo.FactorForDate(
#Symbol char(4), #TradeDate datetime
) returns table as
return (
select
exp(Factor) as Factor
from (
select
sum(log(abs(nullif(Factor, 0)))) as Factor
from
Splits s
where
s.SplitDate > #TradeDate and
s.Symbold = #Symbol
) splits
);
select
tb.TradeDate,
tb.Symbol,
tb.Shares,
tb.Price,
isnull(s.Factor, 1) as Factor
from
Blotter tb
outer apply
dbo.FactorForDate(tb.Symbol, tb.TradeDate) s;
To do it in a single statement is going to be something like:
select
tb.TradeDate,
tb.Symbol,
tb.Shares,
tb.Price,
isnull(exp(sum(log(abs(nullif(factor, 0))))), 1) as Factor
from
Blotter tb
left outer join
Symbol s
on s.Symbol = tb.Symbol and s.SplitDate > tb.TradeDate
group by
tb.TradeDate,
tb.Symbol,
tb.Shares,
tb.Price;
This will probably perform better if you can get it to work.
Apologies for any syntax errors, don't have access to SQL at the moment.