MDX Query, how to select first and last year of an event's period - mdx

I have a publications datamart from teachers of my university, I would like to select the list of teachers with how much books they wrote and in period while them publicated the books.
Example
Teacher - Last Year of pubblication - Number of Books
Teacher A - 2014 - 200 books
I tried to do something like:
WITH MEMBER [Measures].[LastYear] AS
'(ClosingPeriod([Anno].[Anno])
,[Autore].[Nome].CurrentMember)'
SELECT
{[Measures].[Unita (Libri)],[Measures].[LastYear]} ON COLUMNS,
NON EMPTY
[Autore].[Nome].Members ON ROWS
FROM [Pubblicazioni]
but the field last year is alway void, if i try to change the second part of ClosingPeriod with [Measures].[Unita (libri)] it is empty too... How should i implement that query? I'm a bit confused about
EDIT
select [Measures].[Libri] ON COLUMNS,
crossjoin([Autore.default].[Nome].[Fazzinga, Bettina], tail(NonEmptyCrossJoin([Autore.default].[Nome].[Fazzinga, Bettina], [Anno.default].[Anno].Members), 1).Item(0).Item(1)) ON ROWS
from [Pubblicazioni]
This one is working great for the teacher [Fazzinga, Bettina], but the query stops to work while i doing
select [Measures].[Libri] ON COLUMNS,
crossjoin([Autore.default].[Nome].Members, tail(NonEmptyCrossJoin([Autore.default].[Nome].CurrentMember, [Anno.default].[Anno].Members), 1).Item(0).Item(1)) ON ROWS
from [Pubblicazioni]

Measures needs some form of value or string to return, I think that is why your measure is returning void as ClosingPeriod is returning a member rather than value.
The following is not tested:
WITH MEMBER [Measures].[LastYear] AS
'(Tail(
nonempty(
[Anno].[Anno]
,([Autore].[Nome].CurrentMember, [Measures].[Unita (Libri)])
)
).item(0).item(0).MEMBERVALUE)'
SELECT
{[Measures].[Unita (Libri)],[Measures].[LastYear]} ON COLUMNS,
NON EMPTY
[Autore].[Nome].Members ON ROWS
FROM [Pubblicazioni]
Try this one instead:
SELECT
[Measures].[Libri] ON COLUMNS
,Generate
(
[Autore.default].[Nome].MEMBERS
,Tail
(
//NonEmptyCrossJoin <<hopefully nonempty is enough
NonEmpty
(
[Autore.default].[Nome].CurrentMember * [Anno.default].[Anno].MEMBERS
)
,1
) //.Item(0).Item(1) //<<don't believe this is required
) ON ROWS
FROM [Pubblicazioni];

I finally found a way to get it... Natively, every calculated member in the clause "WITH MEMBER" are treated as information of cube. In my case, if i do max("xpression of years when author worked") it will give the number of books he wrote in the relative max year. To avoid this, there is a construct called SetToStr() that will print the year as a String (something like [Hyerarchie].[Level].[Value]). The string can simple cutted with left() and right() condition. Here is the result:
WITH MEMBER [Measures].[Anno prima pubblicazione] AS 'left(right(settostr(head(extract(nonemptycrossjoin([Anno].[Anno].Members, nonemptycrossjoin([Libro].[Titolo].Members,[Autore].[Nome].CurrentMember)),[Anno]),1)),6),4)'
MEMBER [Measures].[Anno ultima pubblicazione] AS 'left(right(settostr(tail(extract(nonemptycrossjoin([Anno].[Anno].Members, nonemptycrossjoin([Libro].[Titolo].Members,[Autore].[Nome].CurrentMember)),[Anno]),1)),6),4)'
SELECT {[Measures].[Anno prima pubblicazione],[Measures].[Anno ultima pubblicazione]} ON COLUMNS,
[Autore].[Nome].Members ON ROWS
FROM [Pubblicazioni]
This query work in this way:
1) extract books for the current author with nonemptycrossjoin([Libro].[Titolo].Members,[Autore].[Nome].CurrentMember) so i get the tuple ;
2) I let the year join the previous tuple with nonemptycrossjoin([Anno].[Anno].Members,);
3) In my case the DM is ordered yet for year, in other case we should use even the function order;
4) we don'n need all that things, so we trash what we doesn't need using extract(,[Anno]), this mean i will maintain only the values of hierarchy [Anno];
5) we just need the first entry of the ordered list of years and we use head() function;
6) we can't use the value yet or MDX will count entries in the fact table for the value of [Anno] we selected, so we transform it as string with setToStr();
7) the value is still dirty, cause the string has hiearchy too, so we clean it using left() and right() function (a year is always composed by 4 chars).
It is a little big function, but this is the only one worked for me :)

Related

MDX - Use Aggregate on a CROSSJOINed multi-dimensional set to return a grand total row

I raised a ticket on grand totals when filtering ages ago and I used the proposed solution yesterday to get one dimension working:
WITH
SET [FilteredSet] AS
{
FILTER([CDR].[CDR].Members,([CDR].[CDR].CURRENTMEMBER.MEMBERVALUE="70648") OR ([CDR].[CDR].CURRENTMEMBER.MEMBERVALUE="70646"))
}
MEMBER [CDR].[CDR].[Test] AS Aggregate([FilteredSet])
SELECT NON EMPTY {[Measures].[CS01.TenorTotal] } ON COLUMNS, NON EMPTY {[FilteredSet], [CDR].[CDR].[Test]} ON ROWS FROM [TraderCube] WHERE ([Date].[Date].[2022-04-01],[Epoch].[Epoch].[Branch].[master].[17])
(Other ticket with useful info)
MDX - Grand total missing when filtering
And as I expand the test query (to get it all working and I then expand the C# code to add it) I note that this works:
WITH
SET [FilteredSet] AS
{
FILTER(CROSSJOIN(HIERARCHIZE(DRILLDOWNLEVEL([CDR].[CDR].[ALL].[AllMember]),POST),HIERARCHIZE(DRILLDOWNLEVEL([Book].[Book].[ALL].[AllMember]),POST)),(LEFT([CDR].[CDR].CURRENTMEMBER.MEMBERVALUE,2)="66") )
}
SELECT NON EMPTY {[Measures].[CS01.TenorTotal] } ON COLUMNS, NON EMPTY {[FilteredSet]} ON ROWS FROM [TraderCube] WHERE ([Date].[Date].[2022-04-01],[Epoch].[Epoch].[Branch].[master].[17])
But we will often filter on the 2nd (or 3rd, 4th etc) dimension, like so and the total (AllMember) is filtered out from Book as it does match the INSTR comparisons.
How do I get my Aggregate MEMBER to work properly here? I've placed [?] in position as I can get this sort of setup to work on one dimension (E.g. [CDR] as shown in first code block) but here the query returns [CDR] and then the [Book] members that match and I want to 'glue' my Agregate MEMBER [?] of the measure as a grand total.
WITH
SET [FilteredSet] AS
{
FILTER(CROSSJOIN(HIERARCHIZE(DRILLDOWNLEVEL([CDR].[CDR].[ALL].[AllMember]),POST),HIERARCHIZE(DRILLDOWNLEVEL([Book].[Book].[ALL].[AllMember]),POST)),(LEFT([CDR].[CDR].CURRENTMEMBER.MEMBERVALUE,2)="66")
AND (INSTR([Book].[Book].CURRENTMEMBER.MEMBERVALUE,"72")>0 OR INSTR([Book].[Book].CURRENTMEMBER.MEMBERVALUE,"73")>0))
}
MEMBER [?] AS Aggregate([FilteredSet])
SELECT NON EMPTY {[Measures].[CS01.TenorTotal] } ON COLUMNS, NON EMPTY {[FilteredSet], [?]} ON ROWS FROM [TraderCube] WHERE ([Date].[Date].[2022-04-01],[Epoch].[Epoch].[Branch].[master].[17])
Current MDX error back is:
'The 2 objects do not have the same dimensionality: [[CDR], [Book]] and [[Measures]]'
Thanks to more advanced MDX-ers out there! :)
Leigh Tilley
TilleyTech Ltd
P.S. Sorry no screenshots...client browser trackers would detect it and I'd get in trouble!

MDX Result Count

I am a beginner in MDX queries. Can any one tell me how to get the record count that is a result of a MDX query?
The query is following:
select {[Measures].[Employee Department History Count],[Measures].[Rate]} on columns, Non Empty{{Filter([Shift].[Shift ID].[Shift ID].Members, ([Shift].[Shift ID].CurrentMember.name <> "1"))}*{[Employee].[Business Entity ID].[Business Entity ID].Members}} on rows from [Adventure Works2012].
I have tried various methods and I haven't really got a solution for that.
I assume you mean row count when you talk of "record count", as MDX does not know a concept of records, but the result shown from an MDX query is the space built by the tuples on the axes.
I see two possibilities to get the row count:
Just count the rows returned from your above query in the tool from which you call the MDX query.
If you want to count in MDX, then let's state what you want to have:
You want to know the number of members of the set of non empty combinations of [Shift ID]s and [Business Entity ID]s where the Shift ID is not "1" and at least one of the measures [Employee Department History Count] and [Rate] is not null.
To state that different: Let's call the tuples like above for which the first measure is not null "SET1", and the tuples like above for which teh second measure is not null "SET2". Then you you want to know the count of the the tuples which are contained in one of these sets (or in both).
To achieve this, we define these two sets and then a calculated menber (a new measure in our case) containing this calculation in its definition, and then use this calculated member in the select clause to show it:
WITH
SET SET1 AS
NonEmpty({{Filter([Shift].[Shift ID].[Shift ID].Members,
([Shift].[Shift ID].CurrentMember.name <> "1"))}
* {[Employee].[Business Entity ID].[Business Entity ID].Members}},
{[Measures].[Employee Department History Count])
SET SET2 AS
NonEmpty({{Filter([Shift].[Shift ID].[Shift ID].Members,
([Shift].[Shift ID].CurrentMember.name <> "1"))}
* {[Employee].[Business Entity ID].[Business Entity ID].Members}},
{[Measures].[Rate])
MEMBER [Measures].[MyCalculation] AS
COUNT(SET1 + SET 2)
SELECT [Measures].[MyCalculation] ON COLUMNS
FROM [Adventure Works2012]
Please note:
The sets SET1 and SET2 are not absolutely necessary, you could also put the whole calculation in one long and complicated definition of the MyCalculation measure, but splitting it up makes is easier to read. However, the definition of a new member is necessary, as in MDX you can only put members on axes (rows, columns, ...). These members can either already been defined in the cube, or you have to define them in the WITH clause of your query. There is no such thing as putting expressions/calculations on axes in MDX, only members.
The + for sets is a union which removes duplicates, hence this operation gives us the tuples which have an non empty value for at least one of the measures. Alternatively, you could have used the Union function equivalently to the +.
The Nonempty() I used in the definitions of the sets is the NonEmpty function, which is slightly different from the NON EMPTY keyword that you can use on the axes. We use one of the measures as second argument to this function in both set definitions.
I have currently no working SSAS installation available to test my statement, hence there might be a minor error or typo in my above statement, but the idea should work.

Last Available value MDX

I have a requirement where in i am to extract data from a cube, within the SSRS dataset using the query builder ,with the time dimension in the result set, across a range of dates. The conditions are
The measures are to be displayed for each day of the date range.
The sub total row should have the last available measures value for that time range.
There is a time filter (currently a single date filter with a multi select option).
my MDX is as below.
The measure has a 'Sum' as the aggregation type.
I have a calculated measure with the scope defined as below.
SCOPE([MEASURES].[Measure1]);
SCOPE([Date].[Date].MEMBERS);
THIS = TAIL(EXISTING ([Date].[Date].MEMBERS),1).ITEM(0) ;
END SCOPE;
END SCOPE;
This above scope statement works perfectly. however, when i select in more that one date member this query slows WAYYYYYYY down. Performance numbers are
Across 1 date - 4 seconds
Across 2 dates - 22 minutes
Across 3 dates - unknown (in Hours)
This drastic degradation in performance goes away if i remove the scope statement, which makes me thing that there should be a better way to do the same. the final report query is as below.
SELECT
NON EMPTY
{[Measures].[Measure1]} ON COLUMNS
,NON EMPTY
{ [Dimension1].[Dimension1].[Dimension1].ALLMEMBERS*
[Dimension2].[Dimension2].[Dimension2].ALLMEMBERS*
[Dimension3].[Dimension3].[Dimension3].ALLMEMBERS*
[Date].[Date].[Date].ALLMEMBERS
} ON ROWS
FROM (
SELECT {[Date].[Date].&[2014-06-13T00:00:00]
,[Date].[Date].&[2014-06-16T00:00:00] } ON COLUMNS
FROM [Cube]
)
So the question again is, Is there a way to do the last available value part of the scope statement so as to have a better performance? Also, if there is another way to write the final mdx that would help the performance?.
Please let me know if there are anythings unclear regarding the question.
Thanks
Srikanth
The first optimization step would be to change your query to
SELECT
NON EMPTY
{[Measures].[Measure1]} ON COLUMNS
,NON EMPTY
{ [Dimension1].[Dimension1].[Dimension1].ALLMEMBERS*
[Dimension2].[Dimension2].[Dimension2].ALLMEMBERS*
[Dimension3].[Dimension3].[Dimension3].ALLMEMBERS*
{[Date].[Date].&[2014-06-13T00:00:00], [Date].[Date].&[2014-06-16T00:00:00] }
} ON ROWS
FROM [Cube]
Furthermore, I am not sure why you added the SCOPE([Date].[Date].MEMEBER); (probably Date].[Date].MEMBERS, actually). Maybe it helps to omit it and the corresponding END SCOPE.

MDX WHERE vs FILTER options

I have a query in which I need to do some filtering. I can do it in a subcube, but I am wondering if I could do this in a WHERE clause without subcube. I think this solution would be faster/cleaner. I need to filter out product models with IB>0 in last month, this is my solution so far (only part of a query):
SELECT {[Measures].[AFR],[Measures].[IB]} ON COLUMNS,
([dim_ProductModel].[ODM].children)*[Dim_Date].[Date Full].children ON ROWS
FROM
(
SELECT
FILTER([dim_ProductModel].[Product Model].children,
([Measures].[IB]*[Dim_Date].[Date Full].&[2014-04-01]>0)) ON COLUMNS FROM
[cub_dashboard_spares]
)
however, I would prefer to have it in one query without subquery something like this (its not working though):
SELECT {[Measures].[AFR],[Measures].[IB]} ON COLUMNS,
([dim_ProductModel].[ODM].children)*[Dim_Date].[Date Full].children ON ROWS
FROM
[cub_dashboard_spares]
WHERE FILTER([dim_ProductModel].[Product Model].children,
([Measures].[IB]*[Dim_Date].[Date Full].&[2014-04-01]>0))
I get some error message kind of:
he MDX function CURRENTMEMBER failed because the coordinate for the ... contains a set..
I basically understand why is he not accepting is as in an WHERE clause I should be more specific but I wonder if there is some possibility to rewrite it so that it works.
I don't want that ProductModel appears in the results set.
SELECT {[Measures].[AFR],[Measures].[IB]} ON COLUMNS,
([dim_ProductModel].[ODM].children)*[Dim_Date].[Date Full].children ON ROWS
FROM
[cub_dashboard_spares]
WHERE
({[dim_ProductModel].[Product Model].children},
[Measures].[IB],
PERIODSTODATE(
[Dim_Date].[Date Full], //<<needs to be a level from your Dim_date
[Dim_Date].[Date Full].&[2014-04-01]) //<<needs to be a member from the levelyou have used in above argument
)

"Totals" Query: show last non-blank string

I have a totals query (one where I clicked the totals button, and it has "group by" columns) in Access 2007. Most of the columns are fine... group by columns, max columns, min columns, etc. For some of them though, I want to extract only the last non-blank (not "" or null) value of a string column.
Here's a sample of what my SQL looks like:
SELECT Min(Duplicates.AttendedODBefore) AS AttendedODBefore,
Min(Duplicates.ContactByPost) As ContactByPost,
Last(Duplicates.PlannedStart) As PlannedStart,
Min(Duplicates.AccessibilityRequirements) AS AccessibilityRequirements,
Last(Duplicates.UcasNumber) As UcasNumber
-- ^^^^
FROM DuplicateStudents As Duplicates
GROUP BY
Duplicates.ID
The expression highlighted is the one I want changing to the last non-blank field. Is there an Access-specific or plain SQL expression which will do this?
Edit: Turns out that Min() and Max() work on string values and ignores null values, taking the first and last values alphabetically. It's not perfect, because it doesn't guarantee that the value selected is the last one, but it's better than just a load of nulls which is what using Last() might give.
Access seems resistant to the idea of returning the last non-null value in a query using GROUP BY. Even if your FROM clause were modified to be something like
FROM (
SELECT allfieldsyouneed
FROM DuplicateStudents
ORDER BY PlannedStart
) AS SortedDuplicates
and the rest of your query were modified to use SortedDuplicates instead, Access doesn't seem to return the last value based on the order you specify. I tested on a table with exactly one blank row and specific orders that I could verify (both an auto-increment field and a value that I checked by using Min and Max), and Access chose to return some other value as Last.
Based on your comment it seems like your definition of "Last" is: the most recently added record, based on an auto-incrementing ID. As such, some form of the following should work (it uses a subquery to return the most recent non-null UcasNumber):
SELECT Min(Duplicates.AttendedODBefore) AS AttendedODBefore,
Min(Duplicates.ContactByPost) As ContactByPost,
Last(Duplicates.PlannedStart) As PlannedStart,
Min(Duplicates.AccessibilityRequirements) AS AccessibilityRequirements,
(SELECT TOP 1 D.UcasNumber FROM Duplicates AS D
WHERE D.UcasNumber Is Not Null
ORDER BY D.ID DESC) As UcasNumber
FROM DuplicateStudents As Duplicates