MDX rather complicated sorting - sorting

I can't find out a way, how to sort my query, this is the simple query:
SELECT {[Measures].[IB]}
ON COLUMNS,
{[Dim_Product_Models_new].[PLA].members } *
{[Dim Dates_new].[Date Full].&[2013-02-01]:[Dim Dates_new].[Date Full].&[2014-01-01]}
ON ROWS
FROM [cub_dashboard_spares]
The think is, I would get a result for 6 PLAs combined across 12 months (72 rows in total), however it is sorted alphabetically upon PLA.
What i need, is to sort the PLAs based on a measure in last month (2014-01-01 in this case).
Is there any way to perform this task so that the groupping (PLAs, Dates from 2013-02 to 2013-12) is perserved, but only the order of my PLAs is different. (PLA with highest measure in last month would be first, and so on)
Thank you very much for any kind of help

Just put the sorted set on the rows, using the Order function. The third parameter of this function is DESC if you want to sort within each hierarchy level, but still want to get parents before children (like ALL before the single attribute members), or BDESC if you want to sort across all levels.
SELECT {[Measures].[IB]}
ON COLUMNS,
Order({[Dim_Product_Models_new].[PLA].members },
([Measures].[IB], [Dim Dates_new].[Date Full].&[2014-01-01]),
DESC)
*
{[Dim Dates_new].[Date Full].&[2013-02-01]:[Dim Dates_new].[Date Full].&[2014-01-01]}
ON ROWS
FROM [cub_dashboard_spares]

The order function over a crossjoin should preserve the initial order of the first set so reversing the order of the tuple will do the job:
SELECT
{
[Measures].[IB]
} ON COLUMNS,
order(
{[Dim Dates_new].[Date Full].&[2013-02-01]:[Dim Dates_new].[Date Full].&[2014-01-01]} *
{[Dim_Product_Models_new].[PLA].members } ,
[Measures].[IB],
desc
) ON ROWS
FROM [cub_dashboard_spares]
If you want to preserve the oder of appearance of the column labels, you can use the generate function like in the following example from the AW cube:
SELECT
{[Measures].[Internet Sales Amount]} ON 0
,Generate
(
{[Customer].[Country].&[Australia]:[Customer].[Country].&[United Kingdom]}
,(
Order
(
[Date].[Calendar Year].[Calendar Year].MEMBERS
,(
[Customer].[Country].CurrentMember
,[Measures].[Internet Sales Amount]
)
,DESC
)
,[Customer].[Country].CurrentMember
)
) ON 1
FROM [Adventure Works];
Philip,

Related

Add indicator to top and bottom 10%

I'm trying to capture the average of FIRST_CONTACT_CAL_DAYS but what I would like to do is create an indicator for the top and bottom 10% of values so I can exclude those (outliers) from my average calculation.
Not sure how to go about do this, any thoughts?
SELECT DISTINCT
TO_CHAR(A.FIRST_ASSGN_DT,'DAY') AS DAY_NUMBER,
A.FIRST_ASSGN_DT,
A.FIRST_CONTACT_DT,
TO_CHAR(A.FIRST_CONTACT_DT,'DAY') AS DAY_NUMBER2,
A.FIRST_CONTACT_DT AS FIRST_PHONE_CONTACT,
A.ID,
ABS(TO_DATE(A.FIRST_CONTACT_DT, 'DD/MM/YYYY') - TO_DATE(A.FIRST_ASSGN_DT, 'DD/MM/YYYY')) AS FIRST_CONTACT_CAL_DAYS,
FROM HIST A
LEFT JOIN CONTACTS D ON A.ID = D.ID
WHERE 1=1
You may be looking for something like this. Please adapt to your situation.
I assume you may have more than one "group" or "partition" and you need to compute the average for each group separately, after throwing out the outliers in each partition. (An alternative, which can be easily accommodated by adapting the query below, is to throw out the outliers at the global level, and only then to group and take the average for each group.)
If you don't have any groups, and everything is one big pile of data, it's even easier - you don't need GROUP BY and PARTITION BY.
Then: the function NTILE assigns a bucket number, in this example between 1 and 10, to each row, based on where they fall (first decile, i.e. first 10%, next decile, ... all the way to the last decile). I do this in a subquery. Then in the outer query just filter out the first and last bucket before you group by and you compute the average.
For testing purposes I create three groups with 10,000 random numbers each in a WITH clause - no need to spend any time on that portion of the code, since it is not part of the solution (the SQL code to solve your problem) - it's just a dirty trick to create test data on the fly.
with
inputs ( grp, val ) as (
select ceil(level/10000), dbms_random.value(0, 150)
from dual
connect by level <= 30000
)
select grp, avg(val) as avg_val
from (
select grp, val, ntile(10) over (partition by grp order by val) as bkt
from inputs
)
where bkt between 2 and 9
group by grp
;
GRP AVG_VAL
--- -----------------------
1 75.021614866547043734458
2 74.286117923344418598032
3 75.437412573353736953791

MDX - How to select one column and sort the returned data

For a SSRS report, I'm trying to return a list of sorted data from a dimension to use with a parameter.
My dimension is [Radio].[Radio NO].[Radio NO] where the last Radio NO is a string.
I can find examples of returning one column while sorting on another but I can't figure out how to sort and return just one column.
Thanks whytheq! Based on your answer, here's what I came up with that works:
SELECT {} ON COLUMNS,
ORDER(
[Radio].[Radio NO].[Radio NO].MEMBERS
,[Radio].[Radio NO].CURRENTMEMBER.MEMBER_CAPTION
,BASC
) On ROWS
FROM [OurCube]
Without seeing the exact structure of your cube / query an avenue you could explore, if you'd like to order alphabetical, is the following
ORDER(
[Radio].[Radio NO].[Radio NO].MEMBERS
,[Radio].[Radio NO].CURRENTMEMBER.MEMBER_CAPTION
,BDESC
)
If you want to order by a measure in your cube, then something like the following:
ORDER(
[Radio].[Radio NO].[Radio NO].MEMBERS
,[Measures].[Profit]
,BDESC
)
This is a possible if you really need to change the column name before hitting SSRS but it has the disadvantage of changing it to a measure:
WITH
MEMBER [Measures].[thisIsTheNewName] AS
[Radio].[Radio NO].CURRENTMEMBER.MEMBER_CAPTION
SELECT
{[Measures].[thisIsTheNewName]} ON COLUMNS,
ORDER(
[Radio].[Radio NO].[Radio NO].MEMBERS
,[Radio].[Radio NO].CURRENTMEMBER.MEMBER_CAPTION
,BASC
) On ROWS
FROM [OurCube];

MDX Ordering By Dimension

Over in the SQL side, my data is looking like this:
Select f.id, f.TimeKey,t.CalendarYearMonth
from FactSubmission f
inner join DimTime t on t.TimeKey = f.TimeKey
order by f.Id asc
Sorting from MDX we have descending
SELECT
NON EMPTY ORDER(
[DimTime.CalendarYearMonth].[CalendarYearMonth].Members,
[DimTime.CalendarYearMonth].CurrentMember.Properties("MEMBER_KEY"),
DESC
) ON COLUMNS
FROM [PSE_FactSubmission]
And Ascending
The January dates aren't at the top of either sort, which suggests I'm sorting by the FactSubmission.ID key instead of DimTime.CalendarYearMonth
Is this how things are supposed to work? I'd like to pull back Jan,Feb,March.
DimTime.CalendarYearMonthNum is a column with data in the form 201501,201502,201503 etc. Here's an attempt at using this column to to sort the CalendarYearMonth data.
Debugging Query to Select Keys
NonEmpty Query
Try ordering using a different property:
SELECT
NON EMPTY ORDER(
[DimTime.CalendarYearMonth].[CalendarYearMonth].Members,
[DimTime.CalendarYearMonth].CurrentMember.Properties("MEMBER_Value"),
DESC
) ON COLUMNS
FROM [PSE_FactSubmission];
or maybe this:
SELECT
NON EMPTY ORDER(
[DimTime.CalendarYearMonth].[CalendarYearMonth].Members,
[DimTime.CalendarYearMonth].CurrentMember.MEMBERValue,
DESC
) ON COLUMNS
FROM [PSE_FactSubmission];
In the above you should be good using DESC - sometimes you need to break the underlying hierarchical ordering by adding a B i.e. BDESC
From here I cannot see MEMBER_VALUE: http://mondrian.pentaho.com/documentation/mdx.php
...but there is a function .VALUE so maybe try the following:
SELECT
NON EMPTY ORDER(
[DimTime.CalendarYearMonth].[CalendarYearMonth].Members,
[DimTime.CalendarYearMonth].CurrentMember.Value,
DESC
) ON COLUMNS
FROM [PSE_FactSubmission];
Strange that the key doesn't work. What values do you get if you run something like this?
WITH MEMBER [KEYcheck] AS
[DimTime.CalendarYearMonth].CurrentMember.Properties("MEMBER_KEY")
//[DimTime.CalendarYearMonth].CurrentMember.MEMBER_KEY
//[DimTime.CalendarYearMonth].[CalendarYearMonth].CurrentMember.MEMBER_KEY
//[DimTime].CurrentMember.MEMBER_KEY
SELECT
[KEYcheck] ON 0,
[DimTime.CalendarYearMonth].[CalendarYearMonth].Members ON 1
FROM [PSE_FactSubmission];
You are doing an alphabetical sort. February->January->March.
For doing a sort based on the month number, there needs to be a field which maps January-1, February-2, March-3.
If you have such a column in cube, use that to to sort. If not create a calculated member like below -
WITH MEMBER Measures.CalendarMonth AS
CASE [DimTime.CalendarYearMonth].CurrentMember
WHEN [DimTime.CalendarYearMonth].&[January] THEN 1
WHEN [DimTime.CalendarYearMonth].&[February] THEN 2
WHEN [DimTime.CalendarYearMonth].&[March] THEN 3
END
SELECT
NON EMPTY ORDER(
[DimTime.CalendarYearMonth].[CalendarYearMonth].Members,
Measures.CalendarMonth,
DESC
) ON COLUMNS
FROM [PSE_FactSubmission]
EDIT for Andrew
with member Measures.[MonthNum] as
NonEmpty
(
[DimTime.CalendarYearMonthNum].[CalendarMonthNum].members,
([DimTime.CalendarYearMonth].[CalendarYearMonth].currentmember, Measures.foo)
).item(0).membervalue
select
non empty
order
(
[DimTime.CalendarYearMonth].[CalendarYearMonth].members,
Measures.[MonthNum],
desc
) on rows
from [PSE_FactSubmission]
EDIT - with EXISTS
with member Measures.[MonthNum] as
EXISTS
(
[DimTime.CalendarYearMonthNum].[CalendarMonthNum].members,
[DimTime.CalendarYearMonth].[CalendarYearMonth].currentmember,
"SomeMeasureGroup"
).item(0).membervalue

How to sort specific Hierarchy Level in OLAP (SSAS) via MDX

I'm using SSAS OLAP and I want to apply sorting of the levels of a hierarchy.
I know that I can sort the whole hierarchy via ORDER function (I have some issues when I'm trying to apply DESC sorting on the whole hierarchy), but what I really want to achieve is sorting of a specific level. For example in the [Date].[Calendar] hierarchy (Adventure Works Cube), I want to have ASC sorting of years, DESC sorting of Quarter, ASC sorting of Months, etc.
I do not want to break the hierarchy (using BASC or BDESC), I just need them sorted on the same level. Do you have an idea if this is possible at all, as I was unable to find anything simillar?
What you can do is crossjoining the ordered correspoding attribute hierarchies. Could you verify the results of the following:
select
[Measures].[Internet Sales Amount] on 0,
Order(
[Date].[Calendar Year].[Calendar Year].MEMBERS,
[Measures].[Internet Sales Amount]
,ASC)
*
Order(
[Date].[Calendar Quarter of Year].[Calendar Quarter of Year].MEMBERS,
[Measures].[Internet Sales Amount]
,DESC)
*
Order(
[Date].[Calendar].[Month].MEMBERS,
[Measures].[Internet Sales Amount]
,ASC)
ON 1
from [Adventure Works]
Including the all members could help ([Date].[Calendar Year] instead of [Date].[Calendar Year] in case of the first crossjoin set)
Philip,
The following is a working solution, if not a really nice one:
WITH MEMBER Measures.[year] as
[Date].[Calendar].CurrentMember.Properties('Key0', typed)
MEMBER Measures.[qtr or mth] as
[Date].[Calendar].CurrentMember.Properties('Key1', typed)
MEMBER Measures.[sortKey] as
CASE
WHEN [Date].[Calendar].CurrentMember.Level IS [Date].[Calendar].[Calendar Year] THEN
Measures.[year] * 1000
WHEN [Date].[Calendar].CurrentMember.Level IS [Date].[Calendar].[Calendar Quarter] THEN
Measures.[year] * 1000 + (5 - Measures.[qtr or mth]) * 100
ELSE // month
Measures.[year] * 1000 + (4 - Int((Measures.[qtr or mth] - 1) / 3)) * 100 + Measures.[qtr or mth]
END
SELECT {Measures.[year], Measures.[qtr or mth], Measures.[sortKey]}
ON COLUMNS,
Order(
[Date].[Calendar].[Calendar Year]
+
[Date].[Calendar].[Calendar Quarter]
+
[Date].[Calendar].[Month]
,
Measures.[sortKey]
,BASC
)
ON ROWS
FROM [Adventure Works]
The measures on the columns are just for clarification how it works. And the main logic is in the generation of the sortKey measure which generates an integer key as follows: four digits for the year, one digit for the backwards quarter (Q4 -> 1, ... Q1 -> 4), two digits for the month. Then, using that, it is straightforward to call Order to return the sorted set.
It turned out that it is impossible to achieve the desired effect - there's no such MDX query that can sort different levels with different sorting type. More information is available in this thread.

How to get records randomly from the oracle database?

I need to select rows randomly from an Oracle DB.
Ex: Assume a table with 100 rows, how I can randomly return 20 of those records from the entire 100 rows.
SELECT *
FROM (
SELECT *
FROM table
ORDER BY DBMS_RANDOM.RANDOM)
WHERE rownum < 21;
SAMPLE() is not guaranteed to give you exactly 20 rows, but might be suitable (and may perform significantly better than a full query + sort-by-random for large tables):
SELECT *
FROM table SAMPLE(20);
Note: the 20 here is an approximate percentage, not the number of rows desired. In this case, since you have 100 rows, to get approximately 20 rows you ask for a 20% sample.
SELECT * FROM table SAMPLE(10) WHERE ROWNUM <= 20;
This is more efficient as it doesn't need to sort the Table.
SELECT column FROM
( SELECT column, dbms_random.value FROM table ORDER BY 2 )
where rownum <= 20;
In summary, two ways were introduced
1) using order by DBMS_RANDOM.VALUE clause
2) using sample([%]) function
The first way has advantage in 'CORRECTNESS' which means you will never fail get result if it actually exists, while in the second way you may get no result even though it has cases satisfying the query condition since information is reduced during sampling.
The second way has advantage in 'EFFICIENT' which mean you will get result faster and give light load to your database.
I was given an warning from DBA that my query using the first way gives loads to the database
You can choose one of two ways according to your interest!
In case of huge tables standard way with sorting by dbms_random.value is not effective because you need to scan whole table and dbms_random.value is pretty slow function and requires context switches. For such cases, there are 3 additional methods:
1: Use sample clause:
https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6
https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6
for example:
select *
from s1 sample block(1)
order by dbms_random.value
fetch first 1 rows only
ie get 1% of all blocks, then sort them randomly and return just 1 row.
2: if you have an index/primary key on the column with normal distribution, you can get min and max values, get random value in this range and get first row with a value greater or equal than that randomly generated value.
Example:
--big table with 1 mln rows with primary key on ID with normal distribution:
Create table s1(id primary key,padding) as
select level, rpad('x',100,'x')
from dual
connect by level<=1e6;
select *
from s1
where id>=(select
dbms_random.value(
(select min(id) from s1),
(select max(id) from s1)
)
from dual)
order by id
fetch first 1 rows only;
3: get random table block, generate rowid and get row from the table by this rowid:
select *
from s1
where rowid = (
select
DBMS_ROWID.ROWID_CREATE (
1,
objd,
file#,
block#,
1)
from
(
select/*+ rule */ file#,block#,objd
from v$bh b
where b.objd in (select o.data_object_id from user_objects o where object_name='S1' /* table_name */)
order by dbms_random.value
fetch first 1 rows only
)
);
To randomly select 20 rows I think you'd be better off selecting the lot of them randomly ordered and selecting the first 20 of that set.
Something like:
Select *
from (select *
from table
order by dbms_random.value) -- you can also use DBMS_RANDOM.RANDOM
where rownum < 21;
Best used for small tables to avoid selecting large chunks of data only to discard most of it.
Here's how to pick a random sample out of each group:
SELECT GROUPING_COLUMN,
MIN (COLUMN_NAME) KEEP (DENSE_RANK FIRST ORDER BY DBMS_RANDOM.VALUE)
AS RANDOM_SAMPLE
FROM TABLE_NAME
GROUP BY GROUPING_COLUMN
ORDER BY GROUPING_COLUMN;
I'm not sure how efficient it is, but if you have a lot of categories and sub-categories, this seems to do the job nicely.
-- Q. How to find Random 50% records from table ?
when we want percent wise randomly data
SELECT *
FROM (
SELECT *
FROM table_name
ORDER BY DBMS_RANDOM.RANDOM)
WHERE rownum <= (select count(*) from table_name) * 50/100;

Resources