Oracle rewrite query using rank or dense_rank - oracle

I have the following query and sample data, which is working fine. It finds the max salary for employee(s) for each department.
Although it works, I like to change it to use rank or dense_rank (unsure how to do this) to achieve the output I GENERATED below but
Note department_id=1 has 2 different employees making the exact salary and I need to keep the result like that. In addition, is there a way I can only compare e.department_id = d.department_id once?
Any help would be greatly appreciated. Below is my test CASE and sample data.
CREATE table dept (department_id, department_name) AS
SELECT 1, 'IT' FROM DUAL UNION ALL
SELECT 2, 'SALES' FROM DUAL;
CREATE TABLE employees (employee_id, manager_id, first_name, last_name, department_id, sal,
serial_number) AS
SELECT 1, NULL, 'Alice', 'Abbot', 1, 100000, 'D123' FROM DUAL UNION ALL
SELECT 2, 1, 'Beryl', 'Baron',1, 50000,'D124' FROM DUAL UNION ALL
SELECT 3, 1, 'Carol', 'Chang',1, 100000, 'A1424' FROM DUAL UNION ALL
SELECT 4, 2, 'Debra', 'Dunbar',1, 75000, 'A1425' FROM DUAL UNION ALL
SELECT 5, NULL, 'Emily', 'Eden',2, 90000, 'C1725' FROM DUAL UNION ALL
SELECT 6, 3, 'Fiona', 'Finn',1, 88500,'C1726' FROM DUAL UNION ALL
SELECT 7,5, 'Grace', 'Gelfenbein',2, 55000, 'C1727' FROM DUAL;
select
e.employee_id,
e.first_name,
e.last_name,
e.department_id,
d.department_name,
e.sal
from employees e join dept d on e.department_id = d.department_id
where not exists
( select null
from employees
where department_id = e.department_id
and sal > e.sal );
EMPLOYEE_ID FIRST_NAME LAST_NAME DEPARTMENT_ID DEPARTMENT_NAME SAL
1 Alice Abbot 1 IT 100000
3 Carol Chang 1 IT 100000
5 Emily Eden 2 SALES 90000

You may use RANK as follows:
WITH cte AS (
SELECT e.employee_id, e.first_name, e.last_name, e.department_id,
d.department_name, e.sal,
RANK() OVER (PARTITION BY e.department_id ORDER BY e.sal DESC) rnk
FROM employees e
INNER JOIN dept d ON e.department_id = d.department_id
)
SELECT employee_id, first_name, last_name, department_id, department_name, sal
FROM cte
WHERE rnk = 1;

Related

Using JOIN with cte

I have the following setup, which seems to be working fine. I am having trouble modifying the query to include the department_name in the output.
I can't seem to get the JOIN working with the CTE. Its probably something trivial but after many attempts I can't get it to work.
Any help would be appreciated.
Below is my setup and test case.
CREATE TABLE departments( department_id, department_name) AS
SELECT 1, 'IT' FROM DUAL UNION ALL
SELECT 2, 'DBA' FROM DUAL;
CREATE TABLE employees (employee_id, first_name, last_name, hire_date, salary, department_id) AS
SELECT 1, 'Lisa', 'Saladino', DATE '2001-04-03', 100000, 1 FROM DUAL UNION ALL
SELECT 2, 'Abby', 'Abbott', DATE '2001-04-04', 50000, 1 FROM DUAL UNION ALL
SELECT 3, 'Beth', 'Cooper', DATE '2001-04-05', 60000, 1 FROM DUAL UNION ALL
SELECT 4, 'Carol', 'Orr', DATE '2001-04-06', 70000,1 FROM DUAL UNION ALL
SELECT 5, 'Vicky', 'Palazzo', DATE '2001-04-07', 88000,2 FROM DUAL UNION ALL
SELECT 6, 'Cheryl', 'Ford', DATE '2001-04-08', 110000,1 FROM DUAL UNION ALL
SELECT 7, 'Leslee', 'Altman', DATE '2001-04-10', 666666, 1 FROM DUAL UNION ALL
SELECT 8, 'Jill', 'Coralnick', DATE '2001-04-11', 190000, 2 FROM DUAL UNION ALL
SELECT 9, 'Faith', 'Aaron', DATE '2001-04-17', 122000,2 FROM DUAL;
WITH cte AS (
SELECT department_id,
first_name,
last_name,
salary,
DENSE_RANK() OVER(PARTITION BY department_id ORDER BY salary DESC) AS rnk
FROM employees
)
SELECT department_id,
/* department_name */
first_name,
last_name,
salary
FROM cte
WHERE rnk=1
You did not join the table.
WITH cte AS (
SELECT department_id,
first_name,
last_name,
salary,
DENSE_RANK() OVER(PARTITION BY department_id ORDER BY salary DESC) AS rnk
FROM employees
)
SELECT e.department_id,
d.department_name,
e.first_name,
e.last_name,
e.salary
FROM cte e
INNER JOIN departments d
ON (d.department_id = e.department_id)
WHERE rnk=1
or:
WITH cte AS (
SELECT e.department_id,
d.department_name,
e.first_name,
e.last_name,
e.salary,
DENSE_RANK() OVER(PARTITION BY e.department_id ORDER BY e.salary DESC) AS rnk
FROM employees e
INNER JOIN departments d
ON (d.department_id = e.department_id)
)
SELECT department_id,
department_name,
first_name,
last_name,
salary
FROM cte
WHERE rnk=1
or using a sub-query, instead of the sub-query factoring clause:
SELECT e.department_id,
d.department_name,
e.first_name,
e.last_name,
e.salary
FROM (
SELECT department_id,
first_name,
last_name,
salary,
DENSE_RANK() OVER(PARTITION BY department_id ORDER BY salary DESC) AS rnk
FROM employees
) e
INNER JOIN departments d
ON (d.department_id = e.department_id)
WHERE rnk=1
or:
SELECT department_id,
department_name,
first_name,
last_name,
salary
FROM (
SELECT e.department_id,
d.department_name,
e.first_name,
e.last_name,
e.salary,
DENSE_RANK() OVER(PARTITION BY e.department_id ORDER BY e.salary DESC) AS rnk
FROM employees e
INNER JOIN departments d
ON (d.department_id = e.department_id)
)
WHERE rnk=1
fiddle

Oracle - Return a column per date in a range

Let's say I have Table A: assistance
PersonID Date CHECK
123456 2012-01-01 F
213415 2012-01-03 A
PersonID ArrivalDate Jan-01 Jan-02 Jan-03
123456 2012-01-01 F NULL NULL
213415 2012-01-03 NULL NULL A
The system is for checks, between 1 to 15 days but no more than that. Any ideas would be very much appreciated.
you can try this, but I'm not sure if this is what you need,
with inputs_
as (select 123456 person_id, to_date('2012-01-01', 'YYYY-MM-DD') date_, 'F' check_
from dual
union all
select 213415 person_id, to_date('2012-01-03', 'YYYY-MM-DD'), 'A' check_
from dual
UNION ALL
select 123456 person_id, to_date('2012-01-01', 'YYYY-MM-DD') date_, 'F' check_
from dual
union all
select 213415 person_id, to_date('2012-01-03', 'YYYY-MM-DD'), 'A' check_
from dual
union all
select 123456 person_id, to_date('2012-01-04', 'YYYY-MM-DD') date_, 'F' check_
from dual
union all
select 213415 person_id, to_date('2012-01-05', 'YYYY-MM-DD'), 'A' check_
from dual
union all
select 123456 person_id, to_date('2012-01-02', 'YYYY-MM-DD') date_, 'A' check_
from dual
union all
select 213415 person_id, to_date('2012-01-04', 'YYYY-MM-DD'), 'A' check_
from dual
UNION ALL
select 213415 person_id, to_date('2012-01-02', 'YYYY-MM-DD'), 'F' check_
from dual)
select *
from (select person_id, date_ arrival_date, check_, TO_CHAR(date_, 'DD-MON') date_
from inputs_)
pivot (min(check_) for date_ in ('01-JAN', '02-JAN', '03-JAN', '04-JAN', '05-JAN')
)
order by 2;
Output:
Also this is not dynamic, so if you want dynamic pivot, you can see this link, Dynamic pivot in oracle sql
You could construct dynamic date values for PIVOT's IN clause for the range min date to max date using a query. Then open a dynamic cursor with the required arguments in the IN clause of PIVOT.
DBMS_SQL.RETURN_RESULT ( 12c and later ) will display the desired result.
For older versions, you may refer my answer here to easily display the cursor's output: Display result
DECLARE
v_instring VARCHAR2 (1000);
v_cur SYS_REFCURSOR;
BEGIN
WITH dt ( min_t ,max_t ) AS
( SELECT MIN(Date_t) ,MAX(Date_t) FROM TableA
) ,
datevalues (date_ch) AS
(SELECT TO_CHAR(min_t + lvl - 1, 'DD-MON')
FROM dt CROSS APPLY
( SELECT LEVEL lvl FROM DUAL CONNECT BY LEVEL <= max_t - min_t + 1
)
)
SELECT LISTAGG(''''
|| date_ch
|| ''' AS "'
|| date_ch, '",') WITHIN GROUP (
ORDER BY date_ch )||'"'
INTO v_instring
FROM
( SELECT DISTINCT date_ch FROM datevalues
);
OPEN v_cur FOR 'select * from (select PersonID, date_t arrival_date, check_t,
TO_CHAR(date_t, ''DD-MON'') date_t from TableA) pivot ( min(check_t)
for date_t in ('||v_instring||')) ORDER BY arrival_date';
DBMS_SQL.RETURN_RESULT(v_cur);
END;
/
PERSONID ARRIVAL_DATE 01-JAN 02-JAN 03-JAN 04-JAN 05-JAN 06-JAN
------------- --------------- ------ ------ ------ ------ ------ ------
123456 01-01-12 F
213415 03-01-12 A
213416 04-01-12 F
345677 06-01-12 A

Recursive hierarchical Oracle SQL query

I have a source table like below:
Emp_ID| Name| Manager_ID
001|abc|005
005|cde|010
010|xyz|050
050 | bcg| 100
100|sta|NULL
My requirement is to populate the target table like below:
Emp_ID| Name| Manager_1| Manager_2| Manager_3| Manager_4
005|cde|xyz|bcg|sta|NULL
050|bcg|sta| NULL|NULL|NULL
100|sta|NULL|NULL|NULL
001|abc|cde|xyz|bcg|sta
I am able to use recursive select through Connect by clause and populate the value for Manager_1 but not able to get through the logic to populate Manager_2, Manager_3 , Manager_4 values as different column values in a single row depending on how many level of hierarchy is present for a certain employee.
Please help.
I think the following query will help you. But to split the string to individual manager id, you need to know the max no of level of managers.
WITH data_set AS
(SELECT '001' emp_id, 'aaa' emp_name, '005' mgr_id
FROM DUAL
UNION
SELECT '005' emp_id, 'bbb' emp_name, '010' mgr_id
FROM DUAL
UNION
SELECT '010' emp_id, 'ccc' emp_name, '050' mgr_id
FROM DUAL
UNION
SELECT '020' emp_id, 'ddd' emp_name, '050' mgr_id
FROM DUAL
UNION
SELECT '050' emp_id, 'eee' emp_name, '100' mgr_id
FROM DUAL
UNION
SELECT '100' emp_id, 'fff' emp_name, '200' mgr_id
FROM DUAL
UNION
SELECT '200' emp_id, 'ggg' emp_name, NULL mgr_id
FROM DUAL)
SELECT emp_id, emp_name, mgr_id,
LTRIM (SYS_CONNECT_BY_PATH (emp_id, '-'), '-') chain
FROM data_set
START WITH mgr_id IS NULL
CONNECT BY mgr_id = PRIOR emp_id
ORDER SIBLINGS BY emp_id;
If your hierarchy only extends to 4 levels deep, the following query may be used:
select t1.Emp_ID,
t1.Name,
t2.Name as Manager_1,
t3.Name as Manager_2,
t4.Name as Manager_3,
t5.Name as Manager_4
from tmp t1
left join tmp t2 on t2.Emp_ID = t1.Manager_ID
left join tmp t3 on t3.Emp_ID = t2.Manager_ID
left join tmp t4 on t4.Emp_ID = t3.Manager_ID
left join tmp t5 on t5.Emp_ID = t4.Manager_ID;
Pivot option:
SELECT * FROM
(
SELECT emp_id, name, manager_id
FROM employees
)
PIVOT
(
COUNT(manager_id)
FOR manager_id IN ('005', '100', '050')
)
ORDER BY emp_id;

Select Maximum record

Here is my Table EMP_EARN_DETAILS.
Emp_Ern_No is the primary key.
I need to get the amount for each emp_no for each earn_no where the emp_earn_no is the maximum.
The output should be as follows.
0004321 ERN001 2345 11
0004321 ERN002 345 10
0004321 ERN003 345 9
000507 ER-01 563 4
000732 ERN001 2345 12
000732 ERN002 9 13
000732 ERN003 678 8
Please help me with the query
You can aggregate by the fields you need and, at the same time, order by the EMP_EARN_NO value; this can be a solution, by analytic functions:
WITH TEST(emp_no, earn_no, amount, emp_earn_no) AS
(
SELECT '0004321' , 'ERN001' ,2345 ,11 FROM DUAL UNION ALL
SELECT '0004321' , 'ERN002' ,345 , 10 FROM DUAL UNION ALL
SELECT '0004321' , 'ERN003' ,345 ,9 FROM DUAL UNION ALL
SELECT '000507' , 'ER-01' ,56 ,1 FROM DUAL UNION ALL
SELECT '000507' , 'ER-01' ,563 , 2 FROM DUAL UNION ALL
SELECT '000507' , 'ER-01' ,563 ,3 FROM DUAL UNION ALL
SELECT '000507' , 'ER-01' ,563 ,4 FROM DUAL UNION ALL
SELECT '00732' , 'ERN001' ,123 ,7 FROM DUAL UNION ALL
SELECT '00732' , 'ERN001' ,2345 ,12 FROM DUAL UNION ALL
SELECT '00732' , 'ERN002' ,9 ,13 FROM DUAL UNION ALL
SELECT '00732' , 'ERN003' ,67 ,5 FROM DUAL UNION ALL
SELECT '00732' , 'ERN003' ,456 ,6 FROM DUAL UNION ALL
SELECT '00732' , 'ERN003' ,678 ,8 FROM DUAL
)
SELECT emp_no, earn_no, amount, emp_earn_no
FROM (
SELECT emp_no,
earn_no,
amount,
emp_earn_no, ROW_NUMBER() OVER ( PARTITION BY EMP_NO, EARN_NO ORDER BY emp_earn_no DESC) AS ROW_NUM
FROM TEST
)
WHERE ROW_NUM = 1
Give this a shot,
SELECT EMP_NO, SUM(AMOUNT)
FROM EMP_EARN_DETAILS
GROUP BY EMP_NO
HAVING EMP_EARN_NO = MAX(EMP_EARN_NO)
Try this query:
select emp_no, earn_no,
sum(amount) keep (dense_rank last order by emp_earn_no) as sum_amount
from emp_earn_details
group by emp_no, earn_no
First by following query , your conditions achieved :
select t.emp_no a ,t.earn_no b ,max(t.amount) c
from EMP_EARN_DETAILS t
group by t.emp_no,t.earn_no
order by t.emp_no
Only things that you must specify , in a same record with different EMP_EARN_NO. You have to specify in same record which must be in result.
So if you want maximum EMP_EARN_NO be in result you can use following query as final query (exactly your target in question):
select t.emp_no a ,t.earn_no b ,max(t.amount) c, max(t.emp_earn_no) emp_earn_no
from EMP_EARN_DETAILS t
group by t.emp_no,t.earn_no
order by t.emp_no
If you want minimum or others EMP_EARN_NO be in result you can above query replace max function by your conditions.

Create timeline from overlapping dates

Given this table:
CREATE TABLE positions
( "EMP_ID" CHAR(10 BYTE),
"GTYPE" NUMBER,
"AMT" NUMBER,
"START_DATE" DATE,
"STOP_DATE" DATE
)
and this data:
Insert Into positions (Emp_Id,Gtype,Amt,Start_Date,Stop_Date)
select 'XA0022',1,1000,'01-MAY-2010','08-MAY-2012' from dual union
Select 'XA0022',1,1000,'01-MAY-2010','31-DEC-2012' From Dual Union
Select 'XA0022',2,500,'03-APR-2012','15-JUL-2012' From Dual Union
Select 'XA0022',1,421,'01-MAY-2012','23-MAY-2012' From Dual Union
Select 'XA0022',1,1514,'09-MAY-2012','31-DEC-2012' From Dual union
select 'XA0022',1,600,'24-MAY-2012','24-MAY-2012' from dual;
How do I get to this:
from to type1 type2
01-May-2010 02-Apr-2012 2000 0
03-Apr-2012 30-Apr-2012 2000 500
01-May-2012 07-May-2012 2421 500
08-May-2012 08-May-2012 2421 500
09-May-2012 22-May-2012 2935 500
23-May-2012 23-May-2012 2935 500
24-May-2012 15-Jul-2012 3114 500
16-Jul-2012 31-Dec-2012 3014 0
Note: The amount is in effect on the start_date and is not in effect the day after the stop_date.
Any pointers gratefully received!
Use Oracle's pivot.
Oracle Pivot
select * from
(select emp_id, gtype, amt, start_date, stop_date
from positions)
pivot (sum(amt) as amt for (gtype) in (1 as "TYPE1", 2 as "TYPE2"))
order by emp_id, start_date;
With some more information (thanks), something like this?
select emp_id, gtype, start_date,
case when next_amt <> amt then next_start_date -1 end as to_date
from
(select emp_id, gtype, start_date, amt,
lead(start_date,1) over (order by emp_id, start_date) next_start_date,
lead(amt,1) over (order by emp_id, start_date) next_amt
from
positions)

Resources