Are the two statements equivalent?
Tuple:
SELECT {[Measures].[Volume]} ON COLUMNS,
([Product].[Product Id].[Product Id].AllMembers
,[Time].[Time].[Year].AllMembers) ON ROWS
FROM [My Cube]
Versus explicit crossjoin:
SELECT {[Measures].[Volume]} ON COLUMNS,
[Product].[Product Id].[Product Id].AllMembers
* [Time].[Time].[Year].AllMembers ON ROWS
FROM [My Cube]
They seem to return the same result, but it seemed from the reading I have done that they shouldn't (at least not always).
What you have in your first query is not a tuple. Tuples are made up of a collection of one or more members
eg. (member1, member2, ...)
Where as what you have is (set1, set2 ...). which I think of as a subcube as this is what is use when defining subcubes for scope statements. And a subcube is essentially an implied crossjoin, so your two queries should return the same result.
Related
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.
I recently posted a question on how to get the ALL label to appear in a SSRS report. "whytheq" was kind enough to post an example that works. Below is the MDX with an added dimension that if I uncomment it will give me an error:
Query (8, 5) Two sets specified in the function have different dimensionality.
WITH
MEMBER [Due Date].[Calendar Month].[All].[YTD] AS
[Due Date].[Calendar Month].[All]
SELECT
NON EMPTY
{[Measures].[Freight]} ON COLUMNS
,NON EMPTY
{
//[Product].[Color].[Color].MEMBERS*
[Due Date].[Calendar Month].[All].[YTD],
[Due Date].[Calendar Month].[Calendar Month].MEMBERS
} ON ROWS
FROM [Adventure Works Cube];
I get that I have a Dimension at the .MEMBERS level and I've also added the .[YTD] dimension.
I did noted that if I comment out the .[YTD] and uncomment the [Product] dimension this works but I have to use an * instead of a comma like so.
[Product].[Color].[Color].MEMBERS*
//[Due Date].[Calendar Month].[All].[YTD],
[Due Date].[Calendar Month].[Calendar Month].MEMBERS
Is it possible to have the Product dimension include in the rows along with the YTD and Calendar Month.MEMEBERS?
Greg's answer to your question is fine, I just wanted to show you the nuts and bolts of your query so maybe you get a better understanding of how you use an MDX query and a cube's structure to produce the result you want.
First, here's the basic structure of most MDX queries:
SELECT
Set_Expression ON COLUMNS
,
Set_Expression ON ROWS
FROM CUBE;
(There's a bit more to MDX, but this is probably 80-90%.)
A set expression is an expression that returns a set - so as you can see it's pretty important that understand what a set is and how to build a valid expression that returns the right set.
So first, what is a set, again? A set is -drum roll- .... a set of tuples1. But not just any tuples - only tuples which have the same dimensional hierarchical structure (also called dimensionality). Let's look at some examples of potential set expressions, and I'll tell you if they're valid or not.
A set with one tuple? VALID.
{ Product.Product.Laptop }
A set with two tuples from the same dimension hierarchy? VALID.
{ Product.Product.Laptop , Product.Product.Desktop }
A set with two tuples from the same dimension hierarchy but different levels? VALID.
{ Date.DateHierarchy.Year.2008 , Date.DateHierarchy.Quarter.2009Q1 }
A set with two tuples from the same dimension, but a different hierarchy? INVALID.
{ Product.Product.[Laptop] , Product.Category.[Hardware] }
Why is this invalid? Because the two tuples have different dimensionalities. If you consider back to the literal metaphor of a "cube", each dimension hierarchy is a "face" and each tuple is a slice of the cube along the faces that make up its dimensionality (and any dimension hierarchy that isn't part of the tuple's explicit dimensionality is treated as an implicit "All members" at execution time) So in order to combine two or more tuples into a set - so you can extract them from the cube in a single slice - they must all come from the same set of faces - the same dimensionality.
A set with a tuple and a set function (an MDX function which returns a set of tuples) from the same dimensional hierarchy? VALID.
{
Tail ( Order ( Product.Product.Members, Measures.Profit, BASC ), 5),
Product.Product.All
}
A set with two set functions from the same dimensional hierarchy? VALID
{ Subset(Product.Product.Members,0,5), Subset(Product.Product,Members,6,5) }
A set with a tuple with two dimensions? VALID but! you have to put parentheses around the tuple. (You can put parentheses around tuples with just one dimension, but it's not necessary.)
{ ( Product.Product.Laptop, Date.Year.2015 ) }
A set with two tuples each with two dimensions at the same respective hiearchical levels? VALID
{
( Product.Product.Laptop, Date.Year.2015 ),
( Product.Product.Tablet, Date.Year.2013 )
}
(Note the members don't have to be the same at all, just the hierarchies. Hopefully this is the point where you recognize what "dimensionality" means.)
A set with two tuples each with two dimensions at the same respective hiearchical levels but not in the same order within the tuples? INVALID
{
( Product.Product.Laptop, Date.Year.2015 ),
( Date.Year.2013, Product.Product.Tablet )
}
(Compare to UNION in SQL - you have to have consistent ordering so MDX can build the set up to perform other tasks, such as nesting each member of a tuple correctly on a query axis.)
And finally, if we want to combine two sets from different dimensional hierarchies into a single set, we use the handy dandy CrossJoin function:
CrossJoin ( { Product.Product.Laptop } , Date.Year.Members )
As you've discovered, you can also use an asterisk (*) to perform a cross join of two sets
Product.Product.Laptop * Date.Year.Members
Now it's important to understand what's going on here. I said "combine sets" earlier, but as you've seen with all the examples, you can't just throw various sets of tuples with different dimensionality together into a single set.
So what's a CrossJoin doing?
Well, given a CrossJoin of
{Set With Tuple Dimensionality (A)} * {Set with Tuple Dimensionality (B)}
The resulting set has a tuple dimensionality of (A, B).
So looking back at our CrossJoin example, our left-hand side has just one tuple, and its dimensionality is (Product.Product). And our right-hand side has many tuples (every member of the Date.Year hierarchy) with the dimensionality of (Date.Year). So our final set of tuples will have the dimensionality of (Product.Product, Date.Year).
So say we had every year from 2010-2015 in our cube, our final set would be 6 tuples of
{
( Product.Product.Laptop, Date.Year.2010),
( Product.Product.Laptop, Date.Year.2011),
( Product.Product.Laptop, Date.Year.2012),
( Product.Product.Laptop, Date.Year.2013),
( Product.Product.Laptop, Date.Year.2014),
( Product.Product.Laptop, Date.Year.2015)
}
So you could in theory tack on more tuples to this set as long as they're simpatico with the same dimensionality - say, (Product.Product.Tablet, Date.Year.2013)
So this would also be a VALID set expression:
{
Product.Product.Laptop * Date.Year.Members,
(Product.Product.Tablet, Date.Year.2013)`
}
So now going back to your problem, if we uncomment your broken line of code we see you've got
{
[Product].[Color].[Color].MEMBERS *
[Due Date].[Calendar Month].[All].[YTD],
[Due Date].[Calendar Month].[Calendar Month].MEMBERS
}
So, here you've got a set of tuples
[Product].[Color].[Color].MEMBERS *
[Due Date].[Calendar Month].[All].[YTD]
whose tuples have the dimensionality of
(Product.Color, Due Date.Calendar Month)
And then you're trying to tack on another set of tuples to this set
[Due Date].[Calendar Month].[Calendar Month].MEMBERS
whose tuples have a dimensionality of
(Due Date.Calendar Month)
Now do you understand the error message you got?
And this leads me to why I wrote this very long post at 1 in the morning: your error is an error of fundamentally misunderstanding the concept of a set in MDX, not simply an error of syntax.
Obviously you want your final set's tuple's dimensional hierarchy structure to be
(Product.Color, Due Date.Calendar Month)
And again, Greg's answer achieves this. So really I hope my post has explained how Greg's answer achieves this!
Also, here's two alternate syntactical ways to achieve the same set result you want on the COLUMNS axis:
NON EMPTY { ([Product].[Color].[Color].Members, [Due Date].[Calendar Month].Members ) }
and
NONEMPTY ( [Product].[Color].[Color].Members, [Due Date].[Calendar Month].Members )
Basically, the (set, set) syntax also performs a crossjoin, as does the NONEMPTY(set1, set2) function. Just so you can see that there are multiple syntactical ways to achieve the same concept.
Quick refresher on tuples: Each cell in your cube represents a unique set of dimension hierarchy members. A tuple is a set of dimension hierarchy members which constitute a set of cells (or coordinate space) in your cube. (N.B.: Any dimension hierarchy not explicitly named in a tuple expression uses its default member, which if not explicitly set in an SSAS cube model is the All member.)
So the tuple expression (Product.Product.Laptop) points to the cellset in the cube where the member of the Product.Product dimension hierarchy is Product.Product.Laptop, and for all the other dimension hierarchies the member is the default member (if it's the All member, the cube basically doesn't do any work, since no slicing is required on that dimension hierarchy.)
In the image below, you can see how the tuple expression (Time.[2nd half], Source.nonground.air) is applied to the cube to produce its coordinate space.
Okay, back to the top.
Try this:
NON EMPTY
{
[Product].[Color].[Color].MEMBERS*
{
[Due Date].[Calendar Month].[All].[YTD],
[Due Date].[Calendar Month].[Calendar Month].MEMBERS
}
} ON ROWS
When I run this mdx query, works fine (get the children members from a hierarchy level):
select {} on columns,
[Dimension].[hierarchy].[level].children on rows
from [Cube]
But, when I add some tuple on rows, doesn't filter filter the children members (shows all the members) :S
select {} on columns,
[Dimension].[hierarchy].[level].children
* [Dimension2].[hierarchy2].[level2].allmembers on rows
from [Cube]
* is a cross join - you will get the Cartesian product of [Dimension].[hierarchy].[level].children and [Dimension2].[hierarchy2].[level2].allmembers because they are different dimensions.
If they were two hierarchies from the same dimension then auto exist behaviour would limit the results e.g. Year2014 crossed with month should just show the months in 2014.
Try using DESCENDANTS function + you might not require NULLs so try the NON EMPTY
SELECT
{} ON COLUMNS,
NON EMPTY
DESCENDANTS(
[Dimension].[hierarchy].[level].[PickAHigherUpMember],
[Dimension].[hierarchy].[PickTheLevelYouWantToDrillTo]
)
*
[Dimension2].[hierarchy2].[level2].allmembers ON ROWS
FROM [Cube]
if you look at the mdx language reference for children, you will also find another example of how to use the function with a hierarchy in stead of a member_expression.
http://msdn.microsoft.com/en-us/library/ms146018.aspx
but it won't work with a hierarchy level.
Maybe the row expression was initialy a hierarchy that you've have changed into a level expression.
in the following a similar working mdx with a hierarchy on rows:
select {} on 0,
[Product].[Model Name].children
*
[Geography].[Country].[All Geographies]
on 1
FROM [Adventure Works
Philip,
I guess you want only those rows where the children have a value on the default measure. In that case you could try the following:
select {} on columns,
Nonempty([Dimension].[hierarchy].[level].children
* [Dimension2].[hierarchy2].[level2].allmembers) on rows
from [Cube]
Now if, for the children, you'd need all the members from Dimension2 then you could try:
select {} on columns,
Nonempty([Dimension].[hierarchy].[level].children, [Dimension2].[hierarchy2].[level2].allmembers)
* [Dimension2].[hierarchy2].[level2].allmembers) on rows
from [Cube]
In the second case the Nonempty function takes a second parameter and the cross join is done with the result of the Nonempty function. For the documentation on Nonempty including the usage of the second parameter see https://learn.microsoft.com/en-us/sql/mdx/nonempty-mdx
In a previous post I had this script:
WITH
SET [orderedSet] AS
ORDER(
[Operator].members,
[Operator].currentmember.name,
BASC
)
MEMBER [Measures].[newMeasure] AS
RANK(
[Operator].currentmember,
[orderedSet]
)
SELECT
[Measures].[newMeasure] ON COLUMNS,
[orderedSet] ON ROWS
FROM [ourCube]
Plus further reference to the MSDN page
Can the RANK function be used in any clauses other than WITH?
It's first argument is a tuple so I'm not sure how to use it in other clauses such as the SELECT.
It can be used anywhere where a numeric expression can be used.
Please note that in MDX, the axes in the select clause are sets, hence you cannot use Rank or any function that returns a numeric expression in an axis clause, but only functions returning sets (or some data types like tuples which are implicitly converted to sets).
And all members that appear in an expression returning a set have to be defined before you start with this expression. Hence, you cannot define them in the axis clause like you can use expressions to define result columns in SQL selects.
However, to literally answer your question, you can use Rank indirectly in the select clause in MDX e. g. if the outer function is Filter, which returns a set. The following is a slightly inefficient way to show the first three countries according to attribute order:
SELECT {[Measures].[Internet Sales Amount]}
ON COLUMNS,
Filter(
[Customer].[Customer Geography].[Country].Members as C,
Rank(C.Current, [Customer].[Customer Geography].[Country].Members) <= 3
)
ON ROWS
FROM [Adventure Works]
Some people use Rank within Generate to reverse sets, which would be another use of Rank that would be legal in the select clause.
I have three queries to filter by a member using the currentmember function. When the filter is applied to the hierarchy that has the member I want to filter by, I can match the members using the IS operator and get the correct result. It does not work though when the filtered set and the member are in different hierarchies. Yet, I can get the filtered results correctly for the second case if instead of objects comparison I just do a caption comparison. The examples use the AdventureWorks database.
This query is working as expected with the IS operator:
select non empty [Measures].[Reseller Sales Amount] on 0,
Filter (NonEmpty({[Geography].[Country].[Country].ALLMEMBERS * [Geography].[City].[City].ALLMEMBERS}), [Geography].[City].Currentmember IS [Geography].[City].&[Seattle]&[WA]) on 1
from [Adventure Works]
This one uses a caption comparison (different result, as expected)
select non empty [Measures].[Reseller Sales Amount] on 0,
Filter (NonEmpty({[Geography].[Country].[Country].ALLMEMBERS}), [Geography].[City].Currentmember.MEMBER_CAPTION = 'Seattle') on 1
from [Adventure Works]
This one though, which should produce the same result as the previous query, does not return anything:
select non empty [Measures].[Reseller Sales Amount] on 0,
Filter (NonEmpty({[Geography].[Country].[Country].ALLMEMBERS }), [Geography].[City].Currentmember IS [Geography].[City].&[Seattle]&[WA]) on 1
from [Adventure Works]
Thanks.
In fact, this is a bit strange. For me, the most surprising result is the second one. No reference in the set to be filtered to the city, and nevertheless, a filter is applied. I would think the reason for the second result is that somehow "implicit overwrite" kicks in.
And probably, the second and third case are treated differently as the optimizer somehow choses different ways to interpret the statement. Normally, string operations like the reference to caption are less efficient than the IS operator which works on object identity.
It looks like most comments confirm that the result of the second query is a bug. Some more comments here in this other thread