How to get top n rows from a dense_rank() - sql

I'm trying to get collect the top customers from each region however I seem to run into the issue of not being able to specify that I want only the top customers.
I keep getting this error: ORA-00904: "RANK_UNITPRICE": invalid identifier and I know its coming from the where statement but I don't know how else to filter to get my expected output.
Here's what my code looks like:
select reg_name, cus_lname, sum(sale_units * sale_price) as total_sold,
rank() over (partition by reg_name order by sum(sale_units * sale_price) desc) as rank_unitprice
from dwregion
inner join dwcustomer on dwregion.reg_id = dwcustomer.reg_id
inner join dwsalesfact on dwcustomer.cus_code = dwsalesfact.cus_code
where rank_unitprice = 1
group by reg_name, cus_lname
It works if I take the where statement out the code works fine as expected (correct ranks and correct values) but its unfiltered (obviously).
How would I go about fixing this issue?
I'm using Oracle live and you can find the script here if it helps.

At first I didn't understand what something that you should do ("take the where clause out of code") doesn't work. Then I realized that you probably did it wrong - "out" here means that current query should be a subquery (or a CTE). Something like this:
WITH
temp
AS
( SELECT reg_name,
cus_lname,
SUM (sale_units * sale_price) AS total_sold,
RANK ()
OVER (PARTITION BY reg_name
ORDER BY SUM (sale_units * sale_price) DESC) AS rank_unitprice
FROM dwregion
INNER JOIN dwcustomer ON dwregion.reg_id = dwcustomer.reg_id
INNER JOIN dwsalesfact
ON dwcustomer.cus_code = dwsalesfact.cus_code
GROUP BY reg_name, cus_lname)
SELECT t.*
FROM temp
WHERE t.rank_unitprice = 1

Related

Conditionally use CASE...WHEN - Oracle SQL

I have two tables like so:
tblOrders: OrderNo (pk), CurrentStepNo (fk)
tblSteps: StepNo (pk), OrderNo (fk), StepName, StepType, StepStart, StepStop
tblOrders contains tons of information about our sales orders, while tblSteps contains tons of information regarding the proper sequential steps it takes to build the material we are selling.
I am trying to construct a query that follows this logic:
"For all orders, select the current step name from the step table. If
the Step Type is equal to 'XO', then select the most recently
completed (where StepStop is not null) regular step (where StepStop is
equal to 'YY')"
I have the following query:
SELECT
tblOrders.*,
tblSteps.StepName
FROM
tblOrders
INNER JOIN tblSteps
ON tblOrders.OrderNo = tblSteps.OrderNo
AND tblOrders.CurrentStepNo = tblSteps.StepNo
Which successfully returns to me the current step name for an in-process order. What I need to achieve is, when the tblOrders.CurrentStepNo is of type 'XO', to find the MAX(tblSteps.StepStop) WHERE tblSteps.StepType = 'YY'. However, I am having trouble putting that logic into my already working query.
Note: I am sorry for the lack of sample data in this example. I would normally post but cannot in this instance. This is also not a homework question.
I have reviewed these references:
Case in Select Statement
https://blogs.msdn.microsoft.com/craigfr/2006/08/23/subqueries-in-case-expressions/
But no luck so far.
I have tried this:
SELECT
tblOrders.*,
CASE
WHEN tblSteps.StepType = 'XO' THEN (-- Some logic here)
ELSE tblSteps.StepName
END AS StepName
FROM
tblOrders
INNER JOIN tblSteps
ON tblOrders.OrderNo = tblSteps.OrderNo
AND tblOrders.CurrentStepNo = tblSteps.StepNo
But am struggling to properly formulate the logic
Join all steps, rank them with ROW_NUMBER, and stay with the best ranked:
select *
from
(
select
o.*,
s.*,
row_number() over
(partition by o.orderno
order by case when s.steptype <> 'XO' and s.stepno = o.currentstepno then 1
when s.steptype <> 'YY' then 2
else 3 end, s.stepstop desc nulls last) as rn
from tblorders o
join tblsteps s on s.orderno = o.orderno
) ranked
where rn = 1
order by orderno;

ORA-00937: not a single-group group function for sum function

I am trying to sum up the COUNT(IHID.RSID_PROD_N) by IHID.CS_ID but facing a problem. How to solve it?
SELECT
IHID.CS_ID ,IHID.RSID_PROD_N,COUNT(IHID.RSID_PROD_N),
RSPF.RSPF_PROD_N,COUNT(RSPF.RSPF_PROD_N),sum(COUNT(IHID.RSID_PROD_N))
from IHIH
JOIN IHID
ON ihih.rsih_invoice_n = ihid.rsih_invoice_n AND ihih.cs_id = ihid.cs_id
JOIN RSPF
ON ihih.cs_id = rspf.cs_id AND ihid.rsid_prod_n=rspf.rspf_prod_n
WHERE rspf_desc LIKE '%SCISSOR LIFT'
GROUP BY IHID.CS_ID, IHID.RSID_PROD_N,RSPF.RSPF_PROD_N,IHID.CS_ID;
The table is something like this
16 SJIII4626 1 SJIII4626 1
16 SJIII4632 1 SJIII4632 1
I want 1+1=2 for 16
I think you need analytic functions rather than aggregates here. Something like:
SELECT
IHID.CS_ID
,IHID.RSID_PROD_N
,row_number() over (partition by IHID.CS_ID order by IHID.RSID_PROD_N) as IHID_RSID_PROD_N
,RSPF.RSPF_PROD_N
,row_number() over (partition by IHID.CS_ID order by RSPF.RSPF_PROD_N) as RSPF_RSPF_PROD_N
,COUNT(IHID.RSID_PROD_N) over (partition by IHID.CS_ID) as sum_count
from IHIH
JOIN IHID
ON ihih.rsih_invoice_n = ihid.rsih_invoice_n AND ihih.cs_id = ihid.cs_id
JOIN RSPF
ON ihih.cs_id = rspf.cs_id AND ihid.rsid_prod_n=rspf.rspf_prod_n
WHERE rspf_desc LIKE '%SCISSOR LIFT'
;
Not entirely sure because your question lacks a complete test case.
If this answer isn't quite what you want please edit your question to provide table structures and sample input data together with required output derived from that data.
Its not grouping as you would like because of the unique values in IHID.RSID_PROD_N and RSPF.RSPF_PROD_N. Remove those columns and it will group as expected.
One option is to use your current query (almost unchanged) as a CTE, and then apply SUM to a COUNT which you couldn't have done in a nested manner. Something like this:
with your_current_query as
-- removed nested SUM(COUNT)
(select
ihid.cs_id,
ihid.rsid_prod_n,
rspf.rspf_prod_n,
count(ihid.rsid_prod_n) cnt_rsid
count(rspf.rspf_prod_n) cnt_rspf
from ihih join ihid on ihih.rsih_invoice_n = ihid.rsih_invoice_n
and ihih.cs_id = ihid.cs_id
join rspf on ihih.cs_id = rspf.cs_id
and ihid.rsid_prod_n=rspf.rspf_prod_n
where rspf_desc like '%SCISSOR LIFT'
group by ihid.cs_id,
ihid.rsid_prod_n,
rspf.rspf_prod_n
)
select cs_id,
rsid_prod_n,
rspf_prod_n,
cnt_rsid,
cnt_rspf,
sum(cnt_rsid) sum_cnt_rsid --> this represents nested SUM(COUNT)
from your_current_query
group by cs_id,
rsid_prod_n,
rspf_prod_n,
cnt_rsid,
cnt_rspf;

SQL Grouping even and odd

I have to Group Certain data as so that it comes in 2 sets.
Attached image has details of actal data, expected result and data from query I used.
I am sure i am missing something in group by of max option .Please help
select agrmnt_id ,location_name, slab_no,target_start,target_end, tier_perc ,mod(RANK, 2) col from
(select agrmnt_id ,location_name, slab_no, target as target_start ,LAG(target) OVER (PARTITION BY location_name ORDER BY slab_no DESC)-1 as target_end ,PAY_PREC|| '%' as tier_perc,
DENSE_RANK() over(partition by agrmnt_id order by location_name) RANK
from plb_addnl_slab_details
where agrmnt_id='PLBCAI140262' order by location_name,slab_no
)) group by agrmnt_id,location_name ,slab_no
order by location_name1 ,slab_no1, location_name2 ,slab_no2
If I understand what you want, which is more than a little doubtful, it seems like you are able to generate a list of all the values you want, but you can't get them aligned in two sets? If so I think you need to treat your initial list as a base view and left outer join it to itself, using your col value to decide which is in first set and which in the second.
The criteria for joining seem a bit vague. If I add another ranking to stop the same values appearing twice in the second columns, I can get your expected result with this:
with t as (
select agrmnt_id, location_name, slab_no, target_start, target_end,
tier_perc , mod(col_rnk, 2) col, rnk
from (
select agrmnt_id, location_name, slab_no, target as target_start,
LAG(target) OVER (PARTITION BY location_name
ORDER BY slab_no DESC)-1 as target_end,
SLAB_PERC|| '%' as tier_perc,
DENSE_RANK() over(partition by agrmnt_id order by location_name) col_rnk,
RANK() over(partition by agrmnt_id, slab_no order by location_name) rnk
from plb_addnl_slab_details
where agrmnt_id='PLBCAI140262'
)
)
select t1.agrmnt_id as agrmnt_id_1, t1.location_name as location_name_1,
t1.slab_no as slab_no_1, t1.target_start as target_start_1,
t1.target_end as target_end_1,
t2.agrmnt_id as agrmnt_id_2, t2.location_name as location_name_2,
t2.slab_no as slab_no_2, t2.target_start as target_start_2,
t2.target_end as target_end_2
from t t1
left join t t2 on t2.agrmnt_id = t1.agrmnt_id
and t2.slab_no = t1.slab_no
and t2.rnk = t1.rnk + 1
and t2.col = 0
where t1.col = 1
order by t1.agrmnt_id, t1.location_name, t1.slab_no;
SQL Fiddle. I'm not convinced those join conditions (or the new rank) are quite right but can't really tell without more data, or more information about the logic you want to use. Hopefully this gives you something you can adapt though.

How do I get the top 10 results of a query?

I have a postgresql query like this:
with r as (
select
1 as reason_type_id,
rarreason as reason_id,
count(*) over() count_all
from
workorderlines
where
rarreason != 0
and finalinsdate >= '2012-12-01'
)
select
r.reason_id,
rt.desc,
count(r.reason_id) as num,
round((count(r.reason_id)::float / (select count(*) as total from r) * 100.0)::numeric, 2) as pct
from r
left outer join
rtreasons as rt
on
r.reason_id = rt.rtreason
and r.reason_type_id = rt.rtreasontype
group by
r.reason_id,
rt.desc
order by r.reason_id asc
This returns a table of results with 4 columns: the reason id, the description associated with that reason id, the number of entries having that reason id, and the percent of the total that number represents.
This table looks like this:
What I would like to do is only display the top 10 results based off the total number of entries having a reason id. However, whatever is leftover, I would like to compile into another row with a description called "Other". How would I do this?
with r2 as (
...everything before the select list...
dense_rank() over(order by pct) cause_rank
...the rest of your query...
)
select * from r2 where cause_rank < 11
union
select
NULL as reason_id,
'Other' as desc,
sum(r2.num) over() as num,
sum(r2.pct) over() as pct,
11 as cause_rank
from r2
where cause_rank >= 11
As said above Limit and for the skipping and getting the rest use offset... Try This Site
Not sure about Postgre but SELECT TOP 10... should do the trick if you sort correctly
However about the second part: You might use a Right Join for this. Join the TOP 10 Result with the whole table data and use only the records not appearing on the left side. If you calculate the sum of those you should get your "Sum of the rest" result.
I assume that vw_my_top_10 is the view showing you the top 10 records. vw_all_records shows all records (including the top 10).
Like this:
SELECT SUM(a_field)
FROM vw_my_top_10
RIGHT JOIN vw_all_records
ON (vw_my_top_10.Key = vw_all_records.Key)
WHERE vw_my_top_10.Key IS NULL

Why is this error coming Postgres

I have a following query
select wbod.subject, wbi.object,
age(dod.object,wbod.object) as ageOfPerson
from wasbornin as wbi,
wasbornondate as wbod,
diedondate as dod
where wbi.subject=wbod.subject
and wbod.subject=dod.subject
and age(dod.object,wbod.object) = (select max(age(dod1.object,wbod1.object))
from wasbornin as wbi1,
wasbornondate as wbod1,
diedondate as dod1
where wbi1.subject = wbod1.subject
and wbod1.subject=dod1.subject
group by wbi1.object)
group by wbi.object
ORDER BY wbi.subject;
But it is giving following error
column "wbod.subject" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: select wbod.subject, wbi.object
Why is this error coming
That's because you are selecting this column. And if given group (GROUP BY wbi.object) there are 150 different subjects, which one of them should be returned?
I initially misread the query - order is using wbi.subject, but error is about wbod.subject.
If I understand your query correctly, you actually don't need the sub-query or the group by:
select subject,
object,
ageOfPerson
from (
select wbod.subject,
wbi.object,
age(dod.object, wbod.object) as ageOfPerson,
dense_rank() over (partition by dod.subject order by age(dod.object, wbod.object) desc) as rnk
from wasbornin as wbi
join wasbornondate as wbod on wbi.subject=wbod.subject
join diedondate as dod on wbod.subject=dod.subject
) t
where rnk = 1;