After some unsuccessful Googling, I decided to ask & answer this question. For some SQL DBAs like me, dealing with MDX is not a daily routine, but more of a "once in a while" thing. So when a seemingly simple problem comes up, it's nice to have a simple answer.
"How do I reference a Member by its Name (instead of its ID or identifier), in an MDX query?" E.g. in a STRTOMEMBER or STRTOSET function, or in a WHERE clause line (slicer) ((not to be confused with a SQL WHERE clause!))
Example--
Dimension hierarchy: [Family Hierarchy]
Dimensions within said hierarchy, from top to bottom: Parent, Child
Members of each dimension include (ID, "Name"):
Parent members:
1, "Mom"
2, "Pop"
Child members:
3, "Bro"
4, "Sis"
To slice on Child member "Bro", I would use the following expression in my WHERE clause:
[Family Hierarchy].[Child].&[3]
But I don't want my reporting layer to know/care about what that ID is. I want to use the name "Bro". How would I do this?
Simply remove the ampersand from the last element of the expression and use the name:
[Family Hierarchy].[Child].[Bro]
Or in STRTOMEMBER usage, STRTOMEMBER('[Family Hierarchy].[Child].[Bro]')
Related
For the life of me I cannot figure out why MDX defines dimensionality per-attribute on a dimension rather than on the key. Perhaps I'm misunderstanding something here, but it seems like a very odd way to do this if I'm understanding things correctly. Let's say I have the following data:
Person
ID (Key)
Name
Age
And I have some data like this: [('Tom123', 'Tom', 15), ('Brad456', 'Brad', 16).
Now, why I could select the two users the following two ways:
{Person.Name.Tom, Person.Name.Brad}
Or:
{Person.ID.Tom123, Person.ID.Brad456}
But not the following way:
{Person.Name.Tom, Person.ID.Brad456}
Yet all three use the same 'dimension' and even 'dimensionality' since all three ways uniquely address the same two Person entities!
This seems so odd to me, in that they are both using the same 'dimension' and 'dimensionality' and should be able to use the Key for that dimension rather than thinking each attribute is unique. Why is this so? Or, am I misunderstanding something in this.
If we use this image:
Why would it matter if we address the individual cube (tuple) by doing: {Product.TV, Geography.Asia, Time.Q1} or doing {Product.TV, Geography.Asia, Time.Quarter One}. They're just two ways of doing exactly the same thing but yet MDX considers them different dimensionality (?).
That is an excellect question.
Yet all three use the same 'dimension' and even 'dimensionality' since
all three ways uniquely address the same two Person entities!
This seems so odd to me, in that they are both using the same
'dimension' and 'dimensionality' and should be able to use the Key for
that dimension rather than thinking each attribute is unique. Why is
this so? Or, am I misunderstanding something in this.
So for any language, the syntax is checked before anything else. The case you are refering is the only instance where Dimensionality can be ignored. But it will not be wise for a language to go evaluate the data first(in terms of SSAS and MDX its called AttributeID). Secondly this will be more confusing to a user. building on your example
{Person.Name.Tom, Person.ID.Brad456}
lets we have 100 people in Newyork, so as per your logic the following
the set will be {Person.City.Newyork}, then MDX will replace all 100 IDs. This would prevent MDX from using Aggregates, which is the biggest advantge of a CUBE. Mostly the queries sent to a Cube are targeted towards summarized data. Most queries in MDX would need data of entire NEWYORK rather then a single person in NEWYORK
So to summarize, the compile time will increase and the Aggregates will not be useable.
I'm doing some exploratory work with tiny test dimensions and cubes. I'm hoping to finally fully understand SSAS's concept of a "dimension attribute", which I find pretty incomprehensible, though I've used SSAS for years without really getting to the bottom of it.
But that's too broad a question. My problem now is this:
I've created an Employee dimension. Key column is EID (int), Name column is Name (varchar). OK, my naming conventions aren't brilliant (but good naming conventions seem to be very difficult in SSAS dimension design, given the fact that all object names are exposed to users).
There is also an attribute called Salary.
What I'm trying to do here is explore the possibility of a "dimension leaf-level property". Not really "dimension attributes" in the sense SSAS uses them, though the only way to implement them is as dimension attributes. What I mean by a "leaf-level property" is a non-aggregatable "attribute", with the same cardinality as the leaf level (or, in any case, to be treated as having the same cardinality). I don't want to aggregate by Salary, for the purposes of this exercise: I just want Salary to be available as a property of each leaf-level member of the dimension. A bit like an extension of the Key Column and Name Column properties.
I'm familiar with this common way of showing member properties:
WITH MEMBER Measures.ESal AS Employee.EID.CurrentMember.Properties("Salary")
SELECT
Measures.ESal ON 0,
Employee.EID.EID.Members
ON 1
FROM $Employee
This works. To prove that I'm only returning members from the leaf level, here's the result set:
(It makes sense that a member property for a non-leaf level might not have a value - and I've read that in this case, MDX simply throw an error rather than showing values for the leaf-level members and nothing for non-leaf).
But trying to use DIMENSION PROPERTIES causes an error:
SELECT
{} ON 0,
Employee.EID.EID.Members
DIMENSION PROPERTIES Employee.Salary
ON 1
FROM $Employee
Result: Query (4, 22) The [Employee].[Salary] dimension attribute was not found.
If I put the hierarchy and level in, though, it works (though SSMS doesn't actually show anything specified in DIMENSION PROPERTIES; as I'd heard already):
SELECT
{} ON 0,
Employee.EID.EID.Members
DIMENSION PROPERTIES Employee.EID.EID.Salary
ON 1
FROM $Employee
Result: just like before, but with no ESal column (obviously).
Two questions:
Why does the Salary attribute have to be addressed as an attribute
of dimension/hierarchy/level Employee.EID.EID? Surely it's an
attribute in the dimension? (I find the way SSAS/SSDT implements
multiple hierarchies deeply confused and confusing)
What is the point of DIMENSION PROPERTIES? Do other clients
actually show what it returns? If so, isn't it then a nightmare to
test this code?
I'm trying to run the following MDX query (I'm newbie in the matter):
WITH MEMBER [Measures].[Not Null SIGNEDDATA] AS IIF( IsEmpty( [Measures].[SIGNEDDATA] ), 0, [Measures].[SIGNEDDATA] )
SELECT
{[Measures].[Not Null SIGNEDDATA]} ON COLUMNS,
{[Cuenta].[818000_001],[Cuenta].[818000_G02]} ON ROWS
FROM [Notas_SIC]
WHERE ([Auditoria].[AUD_NA],[Concepto].[CONCEPTO_NA],[Entidad].[CCB],
[Indicador].[INDICADOR_NA],[Interco].[I_NONE],[Moneda].[COP],
[Tiempo].[2010.01],[Version].[VERSION_NA])
Where 818000_001 is a base member of my 'Cuenta' dimension, and 818000_G02 is a node or aggregation. I receive the following message:
"Two sets specified in the function have different dimensionality"
What am I doing wrong? If I put the query with only base members (many) or only differents aggregations, the result is ok as expected.
Thanks in advance!
By using commas in your WHERE clause, you are trying to create a set out of members from different dimensions. You should use asterisks to make a CROSSJOIN instead.
So replace this:
WHERE ([Auditoria].[AUD_NA],[Concepto].[CONCEPTO_NA],[Entidad].[CCB],
[Indicador].[INDICADOR_NA],[Interco].[I_NONE],[Moneda].[COP],
[Tiempo].[2010.01],[Version].[VERSION_NA])
With this:
WHERE ({[Auditoria].[AUD_NA],[Concepto].[CONCEPTO_NA],[Entidad].[CCB]} *
{[Indicador].[INDICADOR_NA],[Interco].[I_NONE],[Moneda].[COP]} *
{[Tiempo].[2010.01],[Version].[VERSION_NA]})
I added curly braces though I'm not sure if they're absolutely required.
This is maybe your problem:
{[Cuenta].[818000_001],[Cuenta].[818000_G02]} ON ROWS
You have put them as a set but you can only make a set out of members with the same dimensionality. From your description it sounds like [Cuenta].[818000_G02] is being classed as a different hierarchy which is unexpected.
Is [Cuenta].[818000_G02] created using the Aggregate function? Can you swap to using the Sum function? Does the script then work?
(not a great answer - just more questions?)
{[Cuenta].[818000_001],[Cuenta].[818000_G02]}
Your problem probably lies in the above statement. As is clear, they could be from different hierarchies(which is not clear from your example and I come to that below).
Try replacing the above with
{[Cuenta].[Hierarchy1].[818000_001]} * {[Cuenta].[Hierarchy2].[818000_G02]}
where Hierarchy1 and Hierarchy2 are the hierarchies to which these members belong. (If you are not sure, just try {[Cuenta].[818000_001]} * {[Cuenta].[818000_G02]}). I am assuming that the second aggregate members may be built on a different hierarchy and thus the engine is throwing an error.
It is generally a good habit to always use the fully qualified member name because then the SSAS engine has to spend less time in searching for the member.
We have a cube called Revenue.
SELECT
{[Measures].[Billable Hours]} on columns,
[Period].[Financial Year].**[Financial Year]** on rows
FROM [REVENUE];
SELECT
{[Measures].[Billable Hours]} on columns,
[Period].[Financial Year].**Children** on rows
FROM [REVENUE];
There is a dimension "Period", with I guess the attribute hierarchy is Financial Year? and if I want rows like 2012, 2013, 2014, I have to say either [Period].[Financial Year].Children or [Period].[Financial Year].[Financial Year]. To get [Period].[Financial Year].[Financial Year], in SSMS, I opened that attribute hierarchy, dragged the little dot under Members that says Financial Year, and that's what it gave me.
Both ways get me the same result. So just wondering,
is .Children more preferable, or is it just preference?
Also, if one were to "diagram" [Period].[Financial Year].[Financial Year], would it be "Dimension.Level.Member"? so is [Financial Year]/Children a member, or how does one diagram that "whole thing" and
what should I call that whole thing?
Unsure what you mean by "diagram".
Both of you column expressions are, I think, short-cuts.
This
[Period].[Financial Year].[Financial Year]
Is short for this
[Period].[Financial Year].[Financial Year].MEMBERS
And this:
[Period].[Financial Year].CHILDREN
Is short for this:
[Period].[Financial Year].[All].CHILDREN
(To rewind a bit)
All attribute hierarchies have two levels - an all level and a leaf level. The all level only has a single member [All] which in turn has members - the leaves. There is also the leaf level, made up of the leaves.
So it looks like you've tapped into these two different levels of the hierarchy [Financial Year].
Which should you use? Sometimes I don't believe it matters. In more complex scenarios maybe which you use will have an impact on performance.
Sometimes you might like to grab the All member and the leaves - then you can do something like the following:
SELECT
{[Measures].[Billable Hours]} on columns,
[Period].[Financial Year].MEMBERS on rows
FROM [REVENUE];
This should return a set that includes the All member?
Q2
This [Period].[Financial Year].[Financial Year] is Dimension.Hierarchy.Level. The hierarchy's All member is not accessible at this level.
To repeat Greg Galloway's comment:
[Period].[Financial Year].CHILDREN assumes the All member is the
default member and the current member in the context and won't work as
intended in all contexts.
Preferable to use [Period].[Financial Year].[All].CHILDREN or
[Period].[Financial Year].[Financial Year].MEMBERS
John, I have been in a similar situation before and had posted a question on SO on that.
The point is that it is not just a shortcut, but a risky practice to write [Dim].[Attribute].CHILDREN, since the [All] member, which the other answer talks about is a scoped member and it can't always be assumed that you are actually writing [Dim].[Attribute].[All].CHILDREN unless you write that additional [All]. Best to avoid it.
Also, the [Period].[Financial Year].[Financial Year] is not really a suggested way of invoking members.
The moral of the story is we definitely should try to not fall for any "shortcuts".
EDIT: Why it is imperative to be explicit!
The reason why .MEMBERS is the "suggested" way is because the SSAS engine does an additional check when you use the .MEMBERS function.
If a newbie MDX coder accidentally use a nonexistent hierarchy name(typo..uses Calendar1 instead of Calendar, the engine surprisingly doesn't throw any error. (Pardon me for the white gaps below the following images)
But if .MEMBERS is used, an extra layer of checking takes place, which gives a clear and definitive error message:
Obviously, here the engine is to blame, probably this is a bug. But, we must do our part at using the tools at hand.
In a similar way, 9 out of 10 times, Dim.Hier.CHILDREN syntax will work, but the fact is, it is misleading. A newbie might look at the code and assume that a hierarchy can have children. While the truth is only a member can have children.
Little off topic, but in SQL too, it is a standard ANSI syntax to use a semicolon at the end of a statement, but very few people actually use that syntax. The engine in turn punishes us by throwing error when semicolons are not used with some operators, like when we use CTE. SO should we make it a habit to use more semicolons? Hell yeah! Is it a really a shortcut? No!
I have a situation where I have a product and a time dimension, with a fact table of sales volume. Over time, various details about the product changes, with the except of the business key for the product. In my flat reporting from the cube, I want to include some aggregration at the 'business key' level, regardless of what other parts of the product dimension are shown.
In sql this would be trivial as something like:
select sum(volume) over (partition by productKey,year) as Total
Regardless of whatever else I had selected, the Total column would be aggregated only on those two fields.
In MDX I have managed to achieve the same result, but it seems like there must be a simpler way.
WITH MEMBER Measures.ProductKeyTotal AS
'SUM(([Product].[ProductKey],[Time].[Year]
,[Product].[Product Name].[Product Name].ALLMEMBERS
,[Volume Type].[Volume Type Id].[Volume Type Id].ALLMEMBERS)
,[Measures].[Volume])'
SELECT {[Measures].[Volume],[Measures].[ProductKeyTotal]} ON COLUMNS,
NONEMPTYCROSSJOIN ([Product].[ProductKey].[ProductKey].ALLMEMBERS
,[Time].[Time].[Year].ALLMEMBERS
,[Product].[Product Name].[Product Name].ALLMEMBERS
,[Volume Type].[Volume Type Id].[Volume Type Id].ALLMEMBERS) ON ROWS
FROM [My Cube]
WHERE ([Product].[Include In Report].&[True])
1) If I don't include the allmembers for the rows I don't want in the calculated member the total is not correct, is there a shortcut to force it to ignore all the dimensions other that what you specify?
Part of the reason I ask is that I need to add a bunch of other calculated members, some of which will be using parameters and if I use the method from the example above I am going to need to duplicate the same stuff in multiple places, and the code will get weighty.
Well, first of all, don't use NonEmptyCrossJoin--it's been deprecated. Use non empty and then the cross join operator (*).
It's important to understand how tuples and tuple sets work to answer your question. Essentially, any dimension not explicitly stated will always get the CurrentMember of a given dimension. Typically, this is DefaultMember, but if you have it set to something else in your query, that will change this up. The reason you have to specify ALLMEMBERS for those dimensions is because it will use CurrentMember, otherwise. You could just use the [All] member in lieu of trying to sum up ALLMEMBERS (especially if they're not flat!), which will give you a bit better performance.
The most performant way to do this is to add another Measure Group to your cube, and then remove the keys that don't apply to the measure from that Measure Group. This way, you get a native calculation for these rather than a run-time calculation (which tend to be slow, especially when you're adding up everything in your cube). Moreover, you can even set up some aggregation design on that Measure Group, and it will be very performant.