Combine top and join in sql - sql

I have two tables. One for employees
LAST_NAME SALARY DEPARTMENT_ID
------------------------- ---------- -------------
Vargas 2500 50
Zlotkey 10500 50
Abel 11000 80
Taylor 8600 80
One for department name
DEPARTMENT_ID DEPARTMENT_NAME
------------- ------------------------------
50 Shipping
80 Sales
I want to select the top three employees who have the max salary in the employees table.After get, I want to get their department_name. Resulting like this.
LAST_NAME SALARY DEPARTMENT_NAME
------------------------- ---------- -------------
Abel 11000 Sales
Zlotkey 10500 Shipping
Taylor 8600 Sales
I had try this:
SELECT last_name, salary, department_id, ROWNUM as RANK
FROM (SELECT last_name, salary, department_id
FROM employees
ORDER BY salary DESC)
WHERE ROWNUM <= 3;
But i don't know how to use join on to get department_name.
Platform: windows10
SQLDeveloper version: 18.01

You can do this using window functions:
select e.last_name, e.salary, d.department_name
from (select e.*, max(sum_salary) over () as max_sum_salary
from (select e.*, sum(e.salary) over (partition by department_id) as sum_salary
from employees e
) e
) e join
department d
on e.department_id = d.department_id
where max_sum_salary = sum_salary

try this
select top(3)LAST_NAME, SALARY, DEPARTMENT_NAME
from employees e
inner join department d on e.DEPARTMENT_ID = d.DEPARTMENT_ID
where d.DEPARTMENT_NAME = 'sales'
order by SALARY desc

You can do JOIN with subquery :
SELECT e.LAST_NAME, e.SALARY, d.DEPARTMENT_NAME
FROM employees e INNER JOIN
department d
ON d.DEPARTMENT_ID = e.DEPARTMENT_ID
WHERE e.DEPARTMENT_ID = (SELECT e1.DEPARTMENT_ID
FROM employees e1
ORDER BY e1.SALARY DESC
FETCH FIRST 1 ROWS ONLY
);
EDIT : If you want only three employees then you can do :
SELECT e.LAST_NAME, e.SALARY, d.DEPARTMENT_NAME
FROM employees e INNER JOIN
department d
ON d.DEPARTMENT_ID = e.DEPARTMENT_ID
ORDER BY e.SALARY DESC
FETCH FIRST 3 ROWS ONLY

Related

Only show one row per title in SQL query

I have the following query:
select distinct p.title, e.first_name, e.last_name, max(e.salary)
from employees as e
inner join employees_projects as ep
on e.id = ep.employee_id
inner join projects as p
on p.id = ep.project_id
group by 1,2,3
order by p.title
Which returns multiple rows per title. I only want the max salary for each title.
title | first_name | last_name | max
--------------------------+------------+-----------+-------
Build a cool site | Cailin | Ninson | 30000
Build a cool site | Ian | Peterson | 80000
Build a cool site | Mike | Peterson | 20000
Design 3 New Silly Walks | Ava | Muffinson | 10000
Update TPS Reports | John | Smith | 20000
Tweaked #zealous code and this works:
select
title,
first_name,
last_name,
salary
from
(select
distinct p.title,
e.first_name,
e.last_name,
e.salary,
dense_rank() over (partition by p.title order by e.salary desc) as rnk
from employees as e
inner join employees_projects as ep
on e.id = ep.employee_id
inner join projects as p
on p.id = ep.project_id
group by 1,2,3, 4
) t
where rnk = 1
order by title
Try this window function dense_rank(). If there is tie in salary then it will return both records with max salary.
If you just want one record with max salary then use row_number().
select
title,
first_name,
last_name,
salary
from
(select
distinct p.title,
e.first_name,
e.last_name,
salary,
dense_rank() over (partition by title order by salary desc) as rnk
from employees as e
inner join employees_projects as ep
on e.id = ep.employee_id
inner join projects as p
on p.id = ep.project_id
group by 1,2,3
) t
where rnk = 1
order by title
Assuming you want one row per title, then use distinct on:
select distinct on (p.title) p.title, e.first_name, e.last_name, e.salary
from employees e join
employees_projects ep
on e.id = ep.employee_id join
projects p
on p.id = ep.project_id
order by title, salary desc;
If you can have multiple titles, then you can use rank() or dense_rank() in a subquery:
select title, first_name, last_name, salary
from (select p.title, e.first_name, e.last_name, e.salary,
rank() over (partition by p.title order by e.salary desc) as seqnum
from employees e join
employees_projects ep
on e.id = ep.employee_id join
projects p
on p.id = ep.project_id
) p
where seqnum = 1
order by title;
This is basically the case when group by cant be used meaning you wanna group with no loss of all columns too.
You could simply use below
Select * from (
select distinct p.title, e.first_name,
e.last_name, e.salary
, rank()
Over(partition by 1 order by
e.salary desc)
) rn
from employees as e
inner join employees_projects as ep
on e.id = ep.employee_id
inner join projects as p
on p.id = ep.project_id)
Where rn=1

How to query multiple Child data against one Master data?

I want to SELECT one parent and all the child under it. What Oracle does is it keeps showing the parents against its child.
Please tell me how to show the following query in ORACLE from this:
select d.department_name, e.last_name from employees e join departments d
on (d.department_id=e.department_id)
group by d.department_name, e.last_name
order by 1;
DEPARTMENT_NAME LAST_NAME
------------------------------ ------------
Accounting Gietz
Accounting Higgins
Executive De Haan
Executive King
Executive Kochhar
-------------------------------------------
Like this:
DEPARTMENT_NAME LAST_NAME
------------------------------ ------------
Accounting Gietz
Higgins
Executive De Haan
King
Kochhar
-------------------------------------------
You can use analytical function as following:
Select case when rn = 1 then department_name end as dept
,last_name from
(select d.department_name, e.last_name,
Row_number() over (partition by d.department_name order by e.last_name) as rn
from employees e join departments d
on (d.department_id=e.department_id)
group by d.department_name, e.last_name)
order by department_name, rn;
Cheers!!
This type of data transformation is best done at the application layer. But if you want to do it in SQL, you can use window functions -- but with the caveat that the ordering of the rows is very important. I would suggest:
select (case when row_number() over (partition by d.department_id order by e.last_name) = 1
then d.department_name
end) as dept,
last_name
from employees e join
departments d
on d.department_id = e.department_id
group by d.department_name, e.last_name
order by d.department_name, dept nulls last;
I also sincerely doubt that you need the group by, unless the tables have duplicates. So:
select (case when row_number() over (partition by d.department_id order by e.last_name) = 1
then d.department_name
end) as dept,
last_name
from employees e join
departments d
on d.department_id = e.department_id
order by d.department_name, dept nulls last;

how select max(salary) of employee each department with employee_id and emp_name

I want to select emp_id,department_id,max(salary) each department but I use group by department_id and it has error ora-00979
3 column is in the same table(employees)
How can I fix it
select department_id, employee_id as "ID",first_name || ' ' || last_name as "Name",max(salary)as "SALARY"
from EMPLOYEES
group by department_id
order by department_id;
Use a subquery with department_id and the max salary and then join to the main table:
select
e.department_id,
t.employee_id as id,
t.first_name || ' ' || t.last_name as name,
e.maxsalary
from (
select
department_id,
max(salary) as maxsalary
from
EMPLOYEES
group by
department_id
) e
inner join
EMPLOYEES t
on
t.department_id = e.department_id and t.salary = e.maxsalary
order by e.department_id;
See the demo
EMPLOYEES
EMPLOYEE_ID DEPARTMENT_ID SALARY FIRST_NAME LAST_NAME
1 1 10000 A B
2 1 20000 C D
3 1 150000 E F
4 2 12000 G H
5 2 10000 I J
6 3 20000 K L
7 4 11000 M N
8 4 11000 O P
9 4 11000 Q R
10 4 10000 S T
Result
DEPARTMENT_ID ID NAME MAXSALARY
1 3 E F 150000
2 4 G H 12000
3 6 K L 20000
4 7 M N 11000
4 8 O P 11000
4 9 Q R 11000
You can use keep:
select department_id,
max(employee_id) keep (dense_rank first order by salary desc) as "ID",
max(first_name || ' ' || last_name) keep (dense_rank first order by salary desc, employee_id desc) as "Name",
max(salary) as "SALARY"
from employees e
group by department_id
order by department_id;
Try this one:
SELECT department_id, salary AS "Salary", employee_id AS "ID", first_name || ' ' || last_name AS "Name" FROM employees
WHERE salary = (SELECT MAX(salary) FROM employees) GROUP BY department_id;
I hope it works. :)
The error is because employee_id is not in the group.
A possible solution is:
select department_id, ID, Name, SALARY
from (
select distinct department_id,
first_value(employee_id) over (partition by department_id order by salary desc) as ID,
first_value(first_name || ' ' || last_name) over (partition by department_id order by salary desc) as Name,
first_value(salary) over (partition by department_id order by salary desc)as SALARY
from EMPLOYEES
)
order by department_id;

How to list amount of emp in a dept for depts that have more than 5 emp in the dept

I need to know how can I Write a query to display deptno and count of emp in that dept, show only the depts that have more than 5 employees. I tried this:
SELECT D.DNAME FROM
dept D WHERE (SELECT COUNT(*)
FROM emp E
WHERE E.DEPTNO = D.DEPTNO) > 3
Though I only got the dept no
You could use HAVING clause to filter rows after aggregation:
SELECT d.dname, COUNT(*) AS num_of_emp
FROM dept d
JOIN emp e
ON d.deptno = e.deptno
GROUP BY d.dname
HAVING COUNT(*) > 5

Oracle: replace "rollup" in query with something else

I need to rewrite a simple query without rollup function. Could you help me?
This is an original query:
SELECT e.department_id,
e.job_id,
SUM(e.salary)
FROM EMPLOYEES e
GROUP BY ROLLUP(e.department_id, e.job_id);
I guess it is possible to rewrite using UNION statement, yea?
No need for a UNION, you can use GROUPING SETS. This will produce the same results and even the same explain plan:
SELECT e.department_id,
e.job_id,
SUM(e.salary)
FROM EMPLOYEES e
GROUP BY GROUPING SETS( (e.department_id, e.job_id), (e.department_id), () )
The following should return the same result as a rollup, but with worse performance and less controll over the "levels".
select e.department_id
,e.job_id
,SUM(e.salary)
from EMPLOYEES e
group
by e.department_id
,e.job_id
union all
select e.department_id
,null
,SUM(e.salary)
from EMPLOYEES e
group
by e.department_id
union all
select null
,null
,SUM(e.salary)
from EMPLOYEES e;
You can use a CTE to handle it (note I created the EMPLOYEE table via the connect by just to have sample data). There are probably better ways to do this, but this is a way!
WITH EMPLOYEES AS(
SELECT MOD(LEVEL,5) DEPARTMENT_ID
, LEVEL JOB_ID
, 1000*LEVEL SALARY
FROM DUAL
CONNECT BY LEVEL < 10
)
, SUMMEDDATA AS(
SELECT e.department_id,
e.job_id,
SUM(e.salary) SUMMED_SALARY
FROM EMPLOYEES e
GROUP BY e.department_id, e.job_id
)
, SUMMEDJOB_ID AS(
SELECT e.department_id,
SUM(e.salary) SUMMED_SALARY
FROM EMPLOYEES e
GROUP BY e.department_id
)
, SUMMEDTOTAL AS(
SELECT
SUM(e.salary) SUMMED_SALARY
FROM EMPLOYEES e
)
SELECT DEPARTMENT_ID ,
JOB_ID ,
SUMMED_SALARY
FROM SUMMEDDATA
UNION ALL
SELECT DEPARTMENT_ID ,
NULL ,
SUMMED_SALARY
FROM SUMMEDJOB_ID
UNION ALL
SELECT NULL ,
NULL ,
SUMMED_SALARY
FROM SUMMEDTOTAL
ORDER BY 1 NULLS LAST, 2 NULLS LAST ;
DEPARTMENT_ID JOB_ID SUMMED_SALARY
---------------------- ---------------------- ----------------------
0 5 5000
0 5000
1 1 1000
1 6 6000
1 7000
2 2 2000
2 7 7000
2 9000
3 3 3000
3 8 8000
3 11000
4 4 4000
4 9 9000
4 13000
45000