Oracle query to fetch top 3 salaries of each department - oracle

I want a SQL query to fetch top 3 salaries of each department
Table :- sample
Name Salary Dept
AA 1000 Hr
BB 7520 Store
CC 12500 Hr
DD 9850 Store
EE 10250 Finance
FF 12560 Hr
GG 13500 Store
HH 15680 Store
KK 12853 Hr
MM 17582 Finance
NN 16852 Finance
I used the below query but it is not fetching proper result
SELECT dept, fname,lname,sal from sample where rownum<4 group by(fname,lname,sal,desg) order by sal desc

What you need is the analytic function row_number
select *
from (select a.*, row_number() over (PARTITION by dept order by salary desc) as num
from sample a
)
where num < 4;

Try group by and then where rownum < 4
SELECT dept,fname,lname,sal
FROM sample
GROUP BY(fname,lname,sal,desg)
WHERE ROWNUM < 4
ORDER BY sal DESC
;
Also check SELECT and GROUP BY columns as the table only showing NAME and not fname and lname.

SELECT *
FROM(SELECT Name ,Dept ,Salary ,
DENSE_RANK() OVER (PARTITION BY Dept ORDER BY Salary DESC)AS D_RANK
FROM sample )
WHERE D_RANK <=3;

SELECT *
FROM sample a
WHERE
3 >= ( SELECT COUNT(DISTINCT salary)
FROM sample b
WHERE a.salary <= b.salary
AND a.dept = b.dept
)

Related

Oracle SQL: How to remove duplicate employee Ids from results?

I need to write query to remove duplicate employee ids among 10,000 results
EmpID name
1 x
1 x
2 y
2 y
3 z
4 A
The result should be only:
EmpID name
3 z
4 A
Select * from EMPLOYEE where ?
How can I do this?
You need aggregation :
select e.empid, e.name
from employee e
group by e.empid, e.name
having count(*) = 1;
You can try below -
Select empid,name from EMPLOYEE
group by empid,name
having count(*)=1
I would really ask you to use other than the GROUP BY method as you will be able to fetch other fields of the EMPLOYEE table along with EMPID and NAME.
Using analytical function
SELECT * FROM
(SELECT T.*, COUNT(1) OVER (PARTITION BY EMPID) AS CNT
FROM EMPLOYEE)
WHERE CNT = 1;
Using NOT EXISTS
SELECT * FROM EMPLOYEE T
WHERE NOT EXISTS
(SELECT 1 FROM EMPLOYEE TIN
WHERE TIN.ID = T.ID AND TIN.ROWID <> T.ROWID);
Cheers!!

Oracle double select issue

So I have these 2 tables on Oracle:
CLIENT
cl_id cl_name
1 John
2 Maria
PAYMENTS
pa_id pa_date pa_status cl_id
1 2017-01-01 1 1
2 2017-01-01 1 2
3 2017-02-01 1 1
4 2017-02-01 1 2
5 2017-03-01 0 1
6 2017-03-01 1 2
I need a select statemant that gives me the client ID, NAME and the STATUS of his last payment. So the end result of my select should be:
cl_id cl_name pa_status
1 John 0
2 Maria 1
This is the CLIENT select that works:
select cl_id, cl_name from CLIENT;
This is the last status of the PAYMENT select that works:
select * from (
select pa_status from PAYMENT ORDER BY PA_DATE DESC)
where rownum = 1;
So now, I need to make them work together. I tried 2 ways that didn't work:
select cl_id, cl_name, (select * from (
select pa_status from PAYMENT ORDER BY PA_DATE DESC)
where rownum = 1 and PAYMENT.cl_id = CLIENT.CL_ID) as last_status from CLIENT;
error: invalid identifier
AND this:
select cl_id, cl_name, (select * from (
select pa_status from PAYMENT ORDER BY PA_DATE DESC)
where rownum = 1 ) as last_status from CLIENT;
which don't give me any errors, but only shows the same last status of John that is the last record:
cl_id cl_name last_status
1 John 0
2 Maria 0
Can anyone give me a hint?
Thanks
you need to use analystic function.
This kind of functions let you split your data to some groups, and rank the data for each group as you wish.
In your case:
Select * from (
Select id, name, status, row_number () over (partition by p.cl_id order by p.pa_date desc) as rw
From client c join payments p on p.cl_id = c.cl_id)
Inn where inn.rw = 1;
First take the max of date from for each clientid.
Select cl_id, max(pa_date) as pa_date from PAYMENTS group by cl_id
Now you take ur client table and join with above subquery
select c.cl_id, c.cl_name,
(select pa_status from PAYMENT t where t.pa_date=p.pa_date and t.cl_id=p.cl_id)
from CLIENT c join (Select cl_id, max(pa_date) as pa_date from PAYMENTS group by cl_id) p on p.cl_id=c.cl_id
You can use Oracle's KEEP LAST here:
select cl_id, c.cl_name, last_payment.status
from client
join
(
select
cl_id,
max(pa_status) keep (dense_rank last order by pa_date) as status
from payments
group by cl_id
) last_payment using (cl_id);
(If you want to include clients without payments, change the join to LEFT OUTER JOIN.)
This gets the max date for the client
and then gets the highest payment id with that date.
with max_date as (
select max(date) as max_date, cl_id from payments group by cl_id
)
select c.cl_id, c.cl_name, p.pa_sttus from client c
join payments p
on c.cl_id = p.cl_id
where p.pa_id = (select max(p2.pa_id) from payments p2
join max_date md
on p2.cl_id = md.cl_id
where p.cl_id = p2.cl_id
and p2.pa_date = md.max_date
)

3rd highest salary in oracle

I had been looking for the query to find the 3rd highest salary from the database (using Oracle database). I found the below query -
SELECT *
FROM
( SELECT e.*, row_number() over (order by sal DESC) rn FROM emp e
)
WHERE rn = 3;
I do not have oracle installed in my system, so I'm not try it out. But I want to know if the below query will work or not. If not, then why ?
WITH Sal_sort AS
(SELECT DISTINCT sal FROM salary ORDER BY sal DESC
)
SELECT * FROM Salary S, Sal_sort SS WHERE S.Sal = SS.Sal AND SS.rownum = 3;
Input Data
emp_no emp_fname emp_lname salary
1 aa bb 30
2 ee yy 31
3 rr uu 32
4 tt ii 33
5 tt ii 33
6 tt ii 33
7 tt ii 33
8 tt ii 30
9 tt ii 31
Example:
select * from ee;
select emp_no,salary ,dense_rank() over (order by salary ) dr
from ee
Output
emp_no salary dr
1 30 1
8 30 1
9 31 2
2 31 2
3 32 3
4 33 4
5 33 4
6 33 4
7 33 4
So much easier in version 12 of the database and higher now.
SELECT *
FROM employees
ORDER BY salary DESC OFFSET 2 ROWS FETCH NEXT 1 ROWS ONLY
Tim talks about this feature here.
And if you take a look at the plan, you can see it's not magic, the optimizer is using analytic functions to derive the results.
JUST ONE LINE
select * from (
select salary,dense_rank() over (order by salary desc) rank from employees) where rank=3;
or
select * from (
select a.*,dense_rank() over (order by a.salary desc) rank from employees a) where rank=3;
Without Dense_rank()
SELECT salary FROM employees
ORDER BY salary DESC
OFFSET 2
FETCH 1 NEXT ONE ROWS ONLY;
With Dense_rank()
SELECT salary
FROM
(
SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) as rank from employees
)
WHERE rank = 3;

Oracle sql retrive records based on maximum time

i have below data.
table A
id
1
2
3
table B
id name data1 data2 datetime
1 cash 12345.00 12/12/2012 11:10:12
1 quantity 222.12 14/12/2012 11:10:12
1 date 20/12/2012 12/12/2012 11:10:12
1 date 19/12/2012 13/12/2012 11:10:12
1 date 13/12/2012 14/12/2012 11:10:12
1 quantity 330.10 17/12/2012 11:10:12
I want to retrieve data in one row like below:
tableA.id tableB.cash tableB.date tableB.quantity
1 12345.00 13/12/2012 330.10
I want to retrieve based on max(datetime).
The data model appears to be insane-- it makes no sense to join an ORDER_ID to a CUSTOMER_ID. It makes no sense to store dates in a VARCHAR2 column. It makes no sense to have no relationship between a CUSTOMER and an ORDER. It makes no sense to have two rows in the ORDER table with the same ORDER_ID. ORDER is also a reserved word so you cannot use that as a table name. My best guess is that you want something like
select *
from customer c
join (select order_id,
rank() over (partition by order_id
order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
from order) o on (c.customer_id=o.order_id)
where o.rnk = 1
If that is not what you want, please (as I asked a few times in the comments) post the expected output.
These are the results I get with my query and your sample data (fixing the name of the ORDER table so that it is actually valid)
SQL> ed
Wrote file afiedt.buf
1 with orders as (
2 select 1 order_id, 'iphone' order_name, '20121201 12:20:23' order_time from dual union all
3 select 1, 'iphone', '20121201 12:22:23' from dual union all
4 select 2, 'nokia', '20110101 13:20:20' from dual ),
5 customer as (
6 select 1 customer_id, 'paul' customer_name from dual union all
7 select 2, 'stuart' from dual union all
8 select 3, 'mike' from dual
9 )
10 select *
11 from customer c
12 join (select order_id,
13 rank() over (partition by order_id
14 order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
15 from orders) o on (c.customer_id=o.order_id)
16* where o.rnk = 1
SQL> /
CUSTOMER_ID CUSTOM ORDER_ID RNK
----------- ------ ---------- ----------
1 paul 1 1
2 stuart 2 1
Try something like
SELECT *
FROM CUSTOMER c
INNER JOIN ORDER o
ON (o.CUSTOMER_ID = c.CUSTOMER_ID)
WHERE TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS') =
(SELECT MAX(TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS')) FROM ORDER)
Share and enjoy.

Issue in SQL query full scanning twice?

Table A
ID EmpNo Grade
--------------------
1 100 HIGH
2 105 LOW
3 100 MEDIUM
4 100 LOW
5 105 LOW
Query:
select *
from A
where EMPNO = 100
and rownum <= 2
order by ID desc
I tried this query to retrieve max and max-1 value; I need to compare the grade from max and max-1, if equals I need to set the flag as 'Y' or 'N' without using a cursor. Also I don't want to scan the entire record twice.
Please help me.
ROWNUM is applied before ORDER BY, so you need to nest the query like this:
select * from
(select * from A where EMPNO =100 order by ID desc)
where rownum<=2
That only performs one table scan (or it may use an index on EMPNO).
select *
from (
select id, emp_no, grade
, case
when lag(grade) over (order by emp_no desc) = grade
then 'Y'
else 'N'
end
as flag
, dense_rank() over( order by emp_no desc) as rank
from t
)
where rank <=2
;

Resources