MDX ORDER by multiply measures - mdx

Suppose that i have next MDX query
select
{
[Measures].[Measure1],
[Measures].[Measure2],
} on columns,
{
ORDER([Dim].Children, [Measures].[Measure1], desc)
} on rows
from [Cube]
which selects two measures and sorts rows by first one.
How can sort by two measures, frist sort by Measure1 and then sort by Measure2?

Try this:
select
{
[Measures].[Measure1],
[Measures].[Measure2]
} on columns,
{
ORDER(ORDER([Dim].Children, [Measures].[Measure1], desc), [Measures].[Measure2], DESC)
} on rows
from [Cube]
EDIT: Reason why it will not work is because the order of measures needed to be changed. Below is what I think happens:
Control first goes to inner set and it is ordered by the inner measure. This set returned is then sorted by the outer measure. The overall effect is that the outer measure supersedes the sorting by inner measure. So if we wanted to sort by m1->m2 then m1 needs to be the outer measure and m2, the inner measure.

This works on AdvWrks for me:
SELECT
{
[Measures].[Reseller Order Quantity]
,[Measures].[Reseller Sales Amount]
} ON 0
,Order
(
Order
(
[Product].[Product].[Product].MEMBERS
,[Measures].[Reseller Sales Amount]
,BDESC
)
,[Measures].[Reseller Order Quantity]
,BDESC
) ON 1
FROM [Adventure Works];
Maybe you need to swap the order around and use BDESC:
SELECT
{
[Measures].[Measure1]
,[Measures].[Measure2]
} ON COLUMNS
,{
Order
(
Order
(
[Dim].Children
,[Measures].[Measure2]
,BDESC
)
,[Measures].[Measure1]
,BDESC
)
} ON ROWS
FROM [Cube];

Related

How do i select only a column from my sub-query with aggregate function?

I have data in below format.
Q. Find the average and total revenue by each subcategory for the categories which are among top 5 categories in terms of quantity sold.
I wrote code mentioned below.
Now, I know that my subquery has an aggregate function and the column i want. I want the output to be only column "PROD_CAT_CODE" but also want the sum of quantities in sub query as it helps me in finding out the prod cat code with most quantities sold.
SELECT PROD_SUBCAT_CODE, SUM(TOTAL_AMT)[SUM], AVG(TOTAL_AMT)[AVG]
FROM TRANSACTIONS
WHERE PROD_CAT_CODE = (
SELECT
TOP 5 PROD_CAT_CODE, SUM(T1.QTY) [Quantity Sold]
FROM TRANSACTIONS
GROUP BY PROD_CAT_CODE
ORDER BY SUM(T1.QTY)
DESC
)
GROUP BY PROD_SUBCAT_CODE
ORDER BY PROD_SUBCAT_CODE
Thanks in advance.
You could use an inner join on subquery
SELECT PROD_SUBCAT_CODE, SUM(TOTAL_AMT)[SUM], AVG(TOTAL_AMT)[AVG]
FROM TRANSACTIONS
INNER JOIN (
SELECT
TOP 5 PROD_CAT_CODE, SUM(T1.QTY) [Quantity Sold]
FROM TRANSACTIONS
GROUP BY PROD_CAT_CODE
ORDER BY SUM(T1.QTY)
DESC
) T ON T.PROD_CAT_CODE = TRANSACTIONS.PROD_CAT_CODE
GROUP BY PROD_SUBCAT_CODE
ORDER BY PROD_SUBCAT_CODE
I would use window functions. Here is one method:
select top (1) with ties prod_cat_code, prod_subcat_code,
total_amt, avg_total_amt
from (select prod_cat_code, prod_subcat_code, sum(total_amt) as total_amt,
avg(total_amt) as avg_total_amt,
sum(sum(qty)) over (partition by prod_cat_code) as category_sum
from transactions t
group by prod_cat_code, prod_subcat_code
) cs
order by dense_rank() over (order by category_sum desc, prod_cat_code);
EDIT:
You can also take your approach. Your query just needs a few fixer-uppers:
SELECT PROD_CAT_CODE, PROD_SUBCAT_CODE,
SUM(TOTAL_AMT) as [SUM], AVG(TOTAL_AMT) as [AVG]
FROM TRANSACTIONS t
WHERE t.PROD_CAT_CODE IN (SELECT TOP 5 t2.PROD_CAT_CODE
FROM TRANSACTIONS t2
GROUP BY t2.PROD_CAT_CODE
ORDER BY SUM(T2.QTY) DESC
)
GROUP BY PROD_CAT_CODE, PROD_SUBCAT_CODE
ORDER BY PROD_CAT_CODE, PROD_SUBCAT_CODE;
The major issues with your query:
Use IN instead of = with the subquery.
Return only one column with the subquery.
Include PROD_CAT_CODE in the outer query, for both the SELECT and GROUP BY.
Use table aliases to distinguish the table references in the subquery and the outer query.

Finding cumulative totals with condition and group by

Here is my case:
I would like to calculate quantity and price for a given item on any given date.
Prices are calculated using total item quantity and unit price so price changes with respect to item's quantity.
warehouse_1 states that item was shipped from that warehouse, warehouse_2 states that item was sent to that warehouse.
Here is my logic:
Fetch deliveries for each item and sum their quantities. (1st CTE)
Find the sum of quantities in both warehouses separately. (2nd CTE)
Calculate final quantity and multiply it by unit price.
Show result which consists of item id, quantity and price.
I wrote a query which does the calculations correctly BUT it gets exponentially slower when data count gets bigger. (Takes 5 seconds on my DB with 6k rows, almost locks DB on my coworker's DB with 21k rows)
How can I optimize this query? I am doing cumulative calculations on 2nd CTE for each row coming from 1st CTE and that needs a rework I believe.
Can I use
LAG()
function for this use case? I tried that with something like
LAG(a.deliveryTotal) over(order by a.updated desc rows between unbounded preceding and current row)
instead of the CASE block in 2nd CTE but I can't seem to figure out how to use filter() or put a condition inside LAG() statement.
Here is my query:
`
with deliveriesCTE as (
select
row_number() over(partition by it.id
order by
dd.updated asc) as rn,
sum(dd.quantity) as deliveryTotal,
dd.updated as updated,
it.id as item_id,
d.warehouse_1 as outWH,
d.warehouse_2 as inWH,
d.company_code as company
from
deliveries d
join deliveries_detail dd on
dd.deliveries_id = d.id
join items it on
it.id = dd.item_id
where
...
group by
dd.updated,
it.id,
d.warehouse_1,
d.warehouse_2,
d.company_code
order by
dd.updated asc),
cumulativeTotalsByUnit as (
select
distinct on
(a.item_id) a.rn,
a.deliveryTotal,
a.updated,
a.item_id,
a.outWH,
a.inWH,
a.company,
case
when a.rn = 1
and a.outWH is not null then coalesce(a.deliveryTotal,
0)
else (
select
coalesce(sum(b.deliveryTotal) filter(
where b.outWH is not null),
0)
from
deliveriesCTE b
where
a.item_id = b.item_id
and b.rn <= a.rn)
end as outWHTotal,
case
when a.rn = 1
and a.inWH is not null then coalesce(a.deliveryTotal,
0)
else (
select
coalesce(sum(b.deliveryTotal) filter(
where b.inWH is not null),
0)
from
deliveriesCTE b
where
a.item_id = b.item_id
and b.rn <= a.rn)
end as inWHTotal
from
deliveriesCTE a
order by
a.item_id,
a.updated desc)
select
resultView.item_id,
resultView.quantity,
resultView.price
from
(
select
cumTotals.item_id,
cumTotals.inWHTotal - cumTotals.outWHTotal as quantity,
p.price * (cumTotals.inWHTotal - cumTotals.outWHTotal) as price
from
prices p
join cumulativeTotalsByUnit cumTotals on
cumTotals.item_id = p.item_id ) resultView
where
resultView.rn = 1;
`
It's hard to say for use without a MCV, but my guess on what you are trying to do is do a Windowed SUM() calculation as opposed to LAG(). There is documentation Here.
The query cumulativeTotalsByUnit shouldn't be necessary and is likely quadratic to do the complex self-referential join.
Your delivery CTE should look like:
select
sum(dd.quantity) over (partition by it.id ORDER BY dd.updated asc) as deliveryTotal,
dd.updated as updated,
it.id as item_id,
d.warehouse_1 as outWH,
d.warehouse_2 as inWH,
d.company_code as company
from
deliveries d
join deliveries_detail dd on
dd.deliveries_id = d.id
join items it on
it.id = dd.item_id
where
...
group by
dd.updated,
it.id,
d.warehouse_1,
d.warehouse_2,
d.company_code
order by
dd.updated asc

PostgreSQL: Select rows until sum-reduction of a single column reaches over a threshold

I would like to write a query that takes rows from an ordered table, simultaneously aggregating one column's value until said aggregated value meets a desired threshold.
An additional criteria is that the violating row which passes the threshold should be included in the query results.
I have looked for other solutions done in PostgreSQL, leading me to creating the following query:
SELECT * FROM (
SELECT *, SUM(amount) OVER (ORDER BY amount DESC) AS running_amount
FROM public.orders WHERE price = 0.09) AS t
WHERE t.running_amount <= 15;
The issue with this query however is that it represents a PostgreSQL window query, which skips the aggregation of a columns value over all rows if the columns value at a given row is not unique.
Window queries unfortunately do not support taking into account the consideration of distinct-valued columns.
Some alternatives I heard for still making this possible would be through creating a PostgreSQL function, though I have no idea where to start for this sort of aggregation query.
If anyone has any ideas or know-how, I would greatly appreciate it.
Add a unique column (primary key) to the ORDER BY clause of the window function, e.g.:
SELECT * FROM (
SELECT *, SUM(amount) OVER (ORDER BY amount DESC, id) AS running_amount
FROM public.orders WHERE price = 0.09
) AS t
WHERE t.running_amount <= 15;
In the lack of a unique column you can use the system column ctid.
You can use UNION ALL to get the violating row which passes the threshold, e.g.:
WITH cte AS (
SELECT *, SUM(amount) OVER (ORDER BY amount DESC, id) AS running_amount
FROM public.orders
WHERE price = 0.09
)
SELECT *
FROM cte
WHERE running_amount <= 15
UNION ALL (
SELECT *
FROM cte
WHERE running_amount > 15
LIMIT 1
);
If you want the final row (the one that crosses the threshold), you have two relatively simple choices. My preference is:
SELECT o.*
FROM (SELECT o.*,
SUM(amount) OVER (ORDER BY amount DESC) AS running_amount
FROM public.orders
WHERE price = 0.09
) o
WHERE o.running_amount - o.amount <= 15;
The alternative is a windowing clause:
SELECT o.*
FROM (SELECT o.*,
SUM(amount) OVER (ORDER BY amount DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
) AS running_amount
FROM public.orders
WHERE price = 0.09
) o
WHERE o.running_amount <= 15;

reset a running total in same partition based on a condition

To make running total over groups is very easy today with sum over partition.
I have a need to reset the total in the same partition based on a condition, if some field in a row is false, the sum should be reset and begin from this row.
In code this is very easy, just loop over the rows and check for the condition. but how can we achieve this in SQL?
Here is a sample, it contains a table with four fields, and a query to sum the running amounts. the sum should be reset if the ResetSum field is true.
CREATE TABLE dbo.Table_1
(
PersonID int NOT NULL,
Amount money NOT NULL,
PayDate date NOT NULL,
ResetSum bit NOT NULL
)
INSERT INTO Table_1 (PersonID, Amount, PayDate, ResetSum)
VALUES (1, 100, '2015-1-1', 0)
,(1,200,'2015-1-2',0)
,(1,180,'2015-1-3',0)
,(1,200,'2015-1-4',1)
,(1,200,'2015-1-5',0)
,(1,360,'2015-1-6',0)
SELECT *,SUM(Amount) over(PARTITION BY PersonID ORDER BY PayDate) as SumAmount
FROM Table_1
Desired result should be 760, not 1140.
The records cannot be grouped by the ResetSum field, because if it is true, all the fields below this should be reset though the ResetField in this row is false.
here is a sample of my .net code, it is very simple:
Public Function SumTest() As Decimal
Dim lst As New List(Of TestRecords)
Dim sum As Decimal = 0
For Each tst As TestRecords In lst
If tst.ResetSum = true Then
sum = fcf.Amount
Else
sum += fcf.Amount
End If
Next
Return sum
End Function
Do a running total on ResetSum in a derived table and use that as a partition column in the running total on Amount.
select T.PersonID,
T.Amount,
T.PayDate,
sum(T.Amount) over(partition by T.PersonID, T.ResetSum
order by T.PayDate rows unbounded preceding) as SumAmount
from (
select T1.PersonID,
T1.Amount,
T1.PayDate,
sum(case T1.ResetSum
when 1 then 1
else 0
end) over(partition by T1.PersonID
order by T1.PayDate rows unbounded preceding) as ResetSum
from dbo.Table_1 as T1
) as T;
SQL Fiddle

How to return most recent record

I have two tables, Sales and SalesNotes, as below
Sales
SO No......Cust.........Date
1..........Me..........22-04-13
2..........You.........23-04-13
SalesNotes
SO No.......Note.......Notedate
1...........Blah.......24-04-13
2...........Bleh.......23-04-13
2...........Bluh.......27-04-13
How can I return a result set showing Cust, date and the most recent dated note for the corresponding SO no?
I have tried using MAX() but cannot use an aggregate in the where clause and do not understand how I could implement HAVING to do what I need.
What I am trying to achieve is:
SO No.......Cust........Note
1...........Me..........Blah
2...........You.........Bluh
One way of doing this is with the row_number window function:
SELECT s.[So no], [cust], [Note]
FROM [Sales] s
LEFT JOIN (SELECT [So no], [Note],
ROW_NUMBER() OVER (PARTITION BY [So no]
ORDER BY [Notedate] DESC) rn
FROM [SalesNotes]) n ON s.[So no] = n.[So no] AND rn = 1
You can use outer apply for this:
select s.*, sn.note
from sales s outer apply
(select top 1 sn.*
from salesnotes sn
where sn.so_no = s.so_no
order by sn.notedate desc
) sn;
You can use window function FIRST_VALUE to get the most recent Note value per [SO No.]:
SELECT s.[SO No.], Cust, Note
FROM Sales AS s
INNER JOIN (SELECT DISTINCT [SO No.],
FIRST_VALUE(Note) OVER (PARTITION BY [SO No.]
ORDER BY NoteDate DESC) AS Note
FROM SalesNotes) AS sn
ON s.[SO No.] = sn.[SO No.]
This way you avoid using a correlated subquery, which, I think, is less performant.
FIRST_VALUE is available from SQL Server 2012+.
Something like this:
select s.so_no, s.cust,
(select top (1) n.note from salesnotes n where s.so_no = n.so_no order by notedate desc) as note
from sales s
I understand your output as last sale and last note for that sale. If you want to do this for all sales, just remove first Top 1.You can do it with Apply:
Select Top 1 s.SoNo, s.Cust, oa.Note
From Sales s
Outer Apply (Select Top 1 Note From Notes n Where n.SoNo = s.SoNo Order By Date Desc) oa
Order By s.Date desc
Since you were talking about 'max' in where clause , this is just for you to understand 'max' . And just so you know this is not a good solution because there are number of problems like what if notes are same for two different customer, what if customers have the same name and so on and so forth. You should follow the outer apply concept as others have suggested for the right solution .
Select s.SoNo, s.cust, sn.notes, max(sn.noteDate) from sales s inner join salesnotes sn on s.sono=sn.sono group by s.sono,s.cust,sn.notes