How to sort records in MDX query when numeric expression is used with TopCount function - ssas

I am trying to sort the Top 201 records in my SSAS cube either ascending or descending based on requirements in my C# code.
Here I a using Adventure Works Database, where [Reseller Order Count] is the measure and [Reseller].[Business Type] is the dimension.
Please find the details below here.
MDX Query For Ascending Sorting
SELECT{
[Measures].[Reseller Order Count]} ON COLUMNS ,NONEMPTY((TOPCOUNT(
(Order((([Reseller].[Business Type].[Business Type].ALLMEMBERS)), [Measures].[Reseller Order Count] ,ASC)),201,[Measures].[Reseller Order Count])),{
[Measures].[Reseller Order Count]}) ON ROWS FROM [Adventure Works] CELL PROPERTIES VALUE, FORMATTED_VALUE, FORMAT_STRING
Sorting Result:
In the following result, descending sorting is applied instead of ascending sorting.
While checking the documentation, I have found that by default the Descending sorting will be applied.
Link: https://learn.microsoft.com/en-us/sql/mdx/topcount-mdx?view=sql-server-ver15#remarks
Question:
My question is how to achieve ascending sorting as well as descending when the numeric expression is added in Top Count in MDX query in a generic manner?

Try changing the ASC keyword (to signify ascending order) to BASC instead. The B indicates that this will break the hierarchy of the [Reseller] dimension whose members are being listed.
The phrase "break the hierarchy" means that the ordering of values takes precedence over the ordering of the dimension's hierarchy.
(With ASC the measures are only ordered within items that are grouped together at the same level.)

Related

Show items with no data on rows | SSAS , MDX

Working on a task that required to bring all a dimension’s data to the Pivot Table(Excel Sheet) even if they are not related to the fact.
first I was able to do it by using the option “PivotTable Options” -> “Display” -> “Show items with no data on rows” from Excel. The problem here is that using this option is going to affect the other dimensions and the requirement is to only this work for the Student dimension only and the user doesn’t like to keep changing the option back every time. then I found this solution using SCOPE, below, but just like above I could not find an away to just have the scope ignore the logic if any other dimension added so the data does not get duplicated.
SCOPE ([Program].[Program Hierarchy].MEMBERS, [Measures].[Number of Students]);
THIS = IIF(ISEMPTY([Measures].[Number of Students]), 0, ([Measures].[Number of Students]));
END SCOPE;
So is there something that I can add the SCOPE above to just work in the program dimension and get ignore/ skip and work as normal if any other dimension added to the pivot table?
Any suggestion will be appreciated.
Lets suppose that for Dimension1 ,attribute1, and the attribute "Value 1", you had nothing in Fact, so this is removed from the result, now you forced Excel to display it by selecting the option. When you add another dimension's attribute lets say Dimension2.Attribute1, since "Value 1" had nothing in Fact, therefore Cube will not understand which value of of Dimension2.Attribute1 is to be displayed in front of Dimension1.Attribute1, therefore it will display all its values. So if we have 3 values in Dimension2, attribute 1 then "Value 1" will be repeated three times. Now with Excel you cannot solve the issue, however it might just be possible to write an MDX query that works.
Edit: Query Added.
The below sample query is based on AdventureWorks, the first sample shows that the result has some nulls, if i un-comment the "non-empty" all null values will vanish, go ahead try it.
select [Measures].[Internet Sales Amount] on columns,
--non empty
[Product].[Subcategory].[Subcategory]
on rows
from
[Adventure Works]
Result without non empty
Now lets add another dimension to the query. Notice that the null value for the first row(Bib-shorts) is now repeated for all values of the second dimension, Since cube has no way to determine which value to display.
select [Measures].[Internet Sales Amount] on columns,
--non empty
([Product].[Subcategory].[Subcategory],[Date].[Calendar Quarter of Year].[Calendar Quarter of Year])
on rows
from
[Adventure Works]
Result
Now the above result shows the issue you are facing. What we now need to do is whenever there is a null value we dont need the individual members of the second dimension, rather a place holder to satisfy the tuple, will work.
In the query below I have two tuples
1) for the not null data-points. Here we display the actual member of the second dimension.
2) for the null data-points, here we use ".defaultmember" which basically means that the second dimension will behave as it was never selected. Have a close look at the second dimension it says "All Period"
select [Measures].[Internet Sales Amount] on columns,
--non empty
{filter(([Product].[Subcategory].[Subcategory],[Date].[Calendar Quarter of Year].[Calendar Quarter of Year]),[Measures].[Internet Sales Amount]>0),
filter(([Product].[Subcategory].[Subcategory],[Date].[Calendar Quarter of Year].defaultmember),[Measures].[Internet Sales Amount]=null)
}
on rows
from
[Adventure Works]
Result:

Can RANK function be used anywhere other than the WITH clause

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.

Extracting second value from ordered list

The following gives me a list of the countries in alphabetical order.
How do I change it so that instead of returning the whole list it just returns the second country in this list? In terms of our cube this would just be the result for the Aland Islands.
SELECT
ORDER([Geolocation].[Geography].[Geography Country].MEMBERS,
[Geolocation].[Geography].CurrentMember.name, BASC ) ON ROWS,
[Measures].[DefaultMemberName] ON COLUMNS
FROM [MyCube]
You can use the item function; if I remember well it is 0-based :
ORDER( [Geolocation].[Geography] ... )(1)

Getting a count of users each day in Mondrian MDX

I'm trying to write a query to give me the total number of users for each customer per day.
Here is what I have so far, which for each customer/day combination is giving the total number of user dimension entries without splitting them up by customer/day.
WITH MEMBER [Measures].[MyUserCount]
AS COUNT(Descendants([User].CurrentMember, [User].[User Name]), INCLUDEEMPTY)
SELECT
NON EMPTY CrossJoin([Date].[Date].Members, [Customer].[Customer Name].Members) ON ROWS,
{[Measures].[MyUserCount]} on COLUMNS
FROM
[Users]
The problem with your calculated member is that [User].CurrentMember is set to the All member for every row tuple, and thus the count is the total. What you need is a way for the [Customer].CurrentMember and [Date].CurrentMember to effectively filter the [User] dimension.
You need to use a measure that makes sense, i.e. that will have a non-empty value for meaningful joins of the dimension members that you're interested in.
To find this out, you could start by running a query like this:
SELECT
NON EMPTY CrossJoin(
[User].[User Name].Members,
[Measures].[Some measuse]
) ON COLUMNS,
NON EMPTY CrossJoin(
[Date].[Date].Members,
[Customer].[Customer Name].Members
) ON ROWS
FROM [Project]
You would have selected Some measure adequately. The results of that query will be a lot of empty cells, but in a given row, the columns that do have a value correspond to the Users that are related to a given Customer x Date tuple (on the row). You want to count those columns for every row. COUNT and FILTER are what you need, then the query with the calculated member will be
WITH MEMBER [Measures].[User count] AS
COUNT(
FILTER(
[User].[User Name].Members,
NOT ISEMPTY([Measures].[Some measure])
)
)
SELECT
NON EMPTY {[Measures].[User count]} ON COLUMNS,
NON EMPTY CrossJoin(
[Date].[Date].Members,
[Customer].[Customer Name].Members
) ON ROWS
FROM [Users]
I am assuming a fair bit here, but with some experimentation you should be able to work it out.

Why does Order ignore subselect?

I've run into some confusing behavior in Analysis Services 2005 (and 2008 R2) and would appreciate it if someone could explain why this is happening. For the sake of this question, I've reproduced the behavior against the Adventure Works cube.
Given this MDX:
SELECT [Customer].[Education].[(All)].ALLMEMBERS ON COLUMNS,
Order(DrillDownLevel({[Customer].[Customer Geography].[All Customers]}),
([Measures].[Internet Order Count]),
ASC) ON ROWS
FROM (SELECT {[Customer].[Education].&[Partial High School]} ON COLUMNS FROM [Adventure Works])
WHERE [Measures].[Internet Order Count];
The query evaluates with the ordered set on rows:
All Customers: 2, 136
Germany: 269
France: 298
Canada: 304
United Kingdom: 311
United States: 457
Australia: 497
However, if I include the All Member (or defaultmember) for Education in the tuple used in the order statement:
SELECT [Customer].[Education].[(All)].ALLMEMBERS ON COLUMNS,
Order(DrillDownLevel({[Customer].[Customer Geography].[All Customers]}),
([Measures].[Internet Order Count], [Customer].[Education].[All Customers]),
ASC) ON ROWS
FROM (SELECT {[Customer].[Education].&[Partial High School]} ON COLUMNS FROM [Adventure Works])
WHERE [Measures].[Internet Order Count];
Then the the set comes back in a significantly different order:
All Customers: 2, 136
France: 298
Germany: 269
United Kingdom: 311
Canada: 304
Australia: 497
United States: 459
Note that France and Germany are out of order relative to each other. Same with Canada / UK and with USA / Australia. From what I can tell, it's ordering based on the aggregation before the sub-cube is evaluated.
Why does including this member (which should implicitly be in the tuple in the first example?) cause the evaluation of the order statement to look outside of the subcube's visual totals? Filter and TopCount etc functions seem to have the same behavior.
Cheers.
My guess is that since the 2 attributes are related only by key (no direct relationship exists) the resulting crossjoin of the attributes results on the key members being aggregated and this is where the visualtotals is not applied (it's one of the ways how to calculate tho nonvisualtotal with subselects).
I've constructed a query to demonstrate what is happening as a result of that crossjoin:
WITH
MEMBER sortExpr as
AGGREGATE(Descendants([Customer].[Customer Geography].CurrentMember), [Measures].[Internet Order Count])
SELECT
[Customer].[Education].[(All)].ALLMEMBERS
*
{[Measures].[Internet Order Count], sortExpr} ON COLUMNS,
Order
(
DrillDownLevel({[Customer].[Customer Geography].[All Customers]})
,sortExpr
,ASC
) ON ROWS
FROM
(
SELECT
{[Customer].[Education].&[Partial High School]} ON COLUMNS
FROM [Adventure Works]
)
;
EDIT:
Here is another query that shows that the expression works correctly with attribute overwrites once you solve the subselect problem using a query scoped set (well-known solution):
WITH
set sub as
[Customer].[Customer Geography].[Customer]
MEMBER sortExpr2 as
Aggregate(existing sub, [Measures].[Internet Order Count])
SELECT
[Customer].[Education].[(All)].ALLMEMBERS
*
{[Measures].[Internet Order Count], sortExpr2} ON COLUMNS,
Order
(
DrillDownLevel({[Customer].[Customer Geography].[All Customers]})
,
(
-- [Customer].[Customer Geography].CurrentMember,
[Customer].[Education].[All Customers],
sortExpr2
)
,ASC
) ON ROWS
FROM
(
SELECT
{[Customer].[Education].&[Partial High School]} ON COLUMNS
FROM [Adventure Works]
)
;
Thanks to Boyan for tweeting this question.
Regards, Hrvoje
Note: Have a look at http://www.bp-msbi.com/2011/07/mdx-subselects-some-insight/ where I explained the behaviour in more detail.
A great article about subselects is Mosha's 2008 MDX: subselects and CREATE SUBCUBE in non-visual mode
Note what happens when you use a subselect. Implicit Exists and visual totals are applied. The key bit of information here in regards to what you are experiencing is:
2. Applies visual totals to the values of physical measures even within expressions if there are no coordinate overwrites.
In your first query SSAS applies visual totals by default. You can change your query to not do this like this:
SELECT [Customer].[Education].[(All)].ALLMEMBERS ON COLUMNS,
Order(DrillDownLevel({[Customer].[Customer Geography].[All Customers]}),
([Measures].[Internet Order Count]),
ASC) ON ROWS
FROM NON VISUAL (SELECT {[Customer].[Education].&[Partial High School]} ON COLUMNS FROM [Adventure Works])
WHERE [Measures].[Internet Order Count];
The NON VISUAL keyword in a SELECT statement tells SSAS to only apply the implicit Exists, but not the visual totals part. The results of the query will be the same as in the second case, but you will also see the real numbers it is ordering by.
Because you are explicitly overwriting the All member in the second query, SSAS does not apply the visual totals to this expression and orders by the total amounts for all years. However, it still displays the visual totals for the selected on ROWS measure after it evaluates the order on non-visual totals.
I'm not a SSAS specialist but this is related to attribute overwrite.
You're overwriting your sub-select in the Order function evaluation. Note, this behavior is context dependent (axes eval, calculated members and pivot). In you example the context is a function during the evaluation of the axis. The behavior is different from the evaluation of the pivot (once you're axes are known). It's complicated but the way it is.
Note that in icCube and after discussing with some MDX specialist we decided to simplify and not follow this behavior: subselect filter is always applied.