Query simplification by using oracle sample database - sql

Task :
List Name and salary of highest and lowest paid employee from each department.
You can perform the queries for highest and lowest separately also.
Tried Query :
SELECT dept.deptno,
dname,
minsal,
maxsal
FROM dept,
(SELECT deptno,
Max (sal) MAXSAL
FROM emp
GROUP BY deptno) MAXSALARY,
(SELECT deptno,
Min (sal)MINSAL
FROM emp
GROUP BY deptno) MINSALARY
WHERE MAXSALARY.deptno = dept.deptno
AND MINSALARY.deptno = dept.deptno;
Result
The Result is correct.
Question)
Is there any other way to simplify the query?

You can use ORACLE's window functions like row_number combined with conditional aggregation using CASE EXPRESSION :
SELECT t.ename,t.dname,
MAX(CASE WHEN t.low_ind = 1 then t.salary END),
MAX(CASE WHEN t.high_ind = 1 then t.salary END)
FROM (SELECT e.name as ename,d.name as dname,e.salary,
ROW_NUMBER() OVER(PARTITION BY d.name ORDER BY e.salary ASC) as low_ind,
ROW_NUMBER() OVER(PARTITION BY d.name ORDER BY e.salary DESC) as high_ind
FROM emp e
INNER JOIN dept d
ON(d.deptno = e.deptno)) t
GROUP BY t.ename,t.dname
EDIT: if all you need is the min and max salary on each department then thats a simple group by clause :
SELECT d.deptno,d.dname,MIN(e.salary) as min_sal,MAX(e.salary) as max_sal
FROM dept d
INNER JOIN emp e
ON(d.deptno = e.deptno)
GROUP BY d.deptno,d.name

SELECT d.dname, Max(e.sal) MAXSAL, Min(e.sal) MINSAL
FROM dept d, emp e
WHERE d.deptno = e.deptno
group by d.dname

Related

getting multiple records in second query

select max(salary)
from employees
group by department_id
select * from employees
where salary in (select max(salary)
from employees
group by department_id )
I think that you are looking for a correlated subquery:
select e.*
from employees e
where e.salary = (
select max(e1.salary)
from employees e1
where e1.department_id = e.department_id
)
This query gives you employees that have the greatest salary in their department (ties included).
For performance, consider an index on employees(department_id, salary).
You can use the analytical function as follows:
Ties included:
select * from
(select e.*, dense_rank() over (partition by e.department_id order by e.salary) as rn
from employees e)
where rn = 1
Ties excluded (one random record when ties)
select * from
(select e.*, row_number() over (partition by e.department_id order by e.salary) as rn
from employees e)
where rn = 1

Displaying the statistics from emp and dept table in a single query

I have two table emp and dept
Emp table has following fields:
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
Dept table has following fields:
DEPTNO DNAME LOC
Rules are:-
a. total employees in each department
b. highest salary of each department
c. least salary of each department.
d. No. of emps with highest salary in each department.
e. No. of emps with least salary in each department.
f. employee name/id with highest salary in each department.
g. employee name/id with least salary in each department.
h. Names of all employees belonging to eachdepartment.
o/p is as follows:
for data of emp and dept table refer this link
http://sqlfiddle.com/#!4/1bc2b8
Thanks in advance..
You can use analytical function and GROUP BY as follows:
SELECT D.deptno,
COUNT(1) TOTAL_EMPS,
MAX(sal) AS MAX_SAL,
MIN(sal) AS MIN_SAL,
SUM(CASE WHEN SAL = MINSAL THEN 1 END) EMP_MIN_SAL,
SUM(CASE WHEN SAL = MAXSAL THEN 1 END) EMP_MAX_SAL,
MAX(CASE WHEN RNMIN = 1 THEN E.empno END) EMP_WITH_MIN_sAL,
MAX(CASE WHEN RNMAX = 1 THEN E.empno END) EMP_WITH_MAX_sAL,
LISTAGG(E.ename, ',') WITHIN GROUP (ORDER BY E.EMPNO) ALL_EMPS
FROM dept D JOIN (SELECT T.*,
MIN(SAL) OVER (PARTITION BY deptno) MINSAL,
MAX(SAL) OVER (PARTITION BY deptno) MAXSAL,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY SAL) RNMIN,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY SAL DESC) RNMAX
FROM emp T) E
ON E.deptno = D.deptno
GROUP BY D.deptno;
SQLFiddle demo
Using aggregate functions and group by....without using any analytical functions
Select deptno,
COUNT(*) tot_emps,
MAX(sal) max_sal,
MIN(sal) min_sal,
COUNT(sal) KEEP (DENSE_RANK FIRST ORDER BY sal desc) emp_max_salary,
COUNT(sal) KEEP (DENSE_RANK FIRST ORDER BY sal asc) emp_min_salary,
MAX(empno || '-' || ename) KEEP (DENSE_RANK FIRST ORDER BY sal DESC) emp_with_max_salary,
MIN(empno || '-' || ename) KEEP (DENSE_RANK FIRST ORDER BY sal ASC)emp_with_min_salary,
LISTAGG(ename,',') WITHIN GROUP (ORDER BY ename) emp_list
FROM emp
GROUP BY deptno
ORDER BY deptno;
SQLFiddle_Demo

GROUP BY - inline view and unjoined tables

I have the following ORACLE query where I attempt to find the department with the highest average salary. I would like to use in-line view (i.e. retain the b dataset) for this implementation, but struggle to get the right part at the WHERE and GROUP BY components. I know the below GROUP BY and WHERE (which is non-existant) is wrong. But how do i correct them?
select a.deptno from emp a,
(select max(avg_sal) max_avg_sal from (select
avg(sal) avg_sal from emp group by deptno) ) b
group by a.deptno, b.max_avg_sal
having avg(a.sal) = b.max_avg_sal
Expected Result
deptno
10
Emp Structure
deptno staff sal
10 A 1000
10 B 1500
11 C 1100
12 D 1000
12 E 900
12 F 1000
Is this what you want?
select e.*
from (select e.*, avg(e.salary) over (partition by e.deptno) as avg_salary
from emp e
) e
order by avg_salary desc
fetch first 1 row only;
fetch first is available in Oracle 12c+. You can do similar things with an additional subquery in earlier versions.
You can use subquery
select deptno from tablename
group by deptno
having avg(sal)= (select max(asal) from (select avg(sal) as asal from tablename group by deptdno)A)
The straight-forward way is:
select deptno
from emp
group by deptno
order by avg(salary) desc
fetch first row with ties;
FETCH FIRST is available as of Oracle 12c.
In Oracle 11g we could use this instead:
select deptno
from
(
select deptno, avg(salary) as avg_salary, max(avg(salary)) over () as max_avg_salary
from emp
group by deptno
)
where avg_salary = max_avg_salary;
But you want an inline view, another word for a derived table (a subquery in the from clause). That looks way more clumsy. One example without FETCH FIRST and without window functions:
with d as
(
select deptno, avg(salary) as avg_salary
from emp
group by deptno
)
, dmax as
(
select max(avg_salary) as max_avg_salary
from d
)
select d.*
from d
join dmax on dmax.max_avg_salary = d.avg_salary;
I find this very obfuscated and don't recommend it at all. You can do the same without WITH clauses of course. Then it is even less readable.
I don't know why you'd want to write it this way, but if you really want only inline views and no windowing clauses, you can write it this way:
select b.deptno
from (SELECT deptno, avg(sal) avgsal from emp group by deptno ) b
cross join (SELECT max(avgsal) maxavgsal FROM (SELECT avg(sal) avgsal FROM emp group by deptno )) c
where b.avgsal = c.maxavgsal;
This the same thing, if you don't like CROSS JOIN for some reason:
select b.deptno
from (SELECT deptno, avg(sal) avgsal from emp group by deptno ) b
inner join ( SELECT max(avgsal) maxavgsal FROM
( SELECT avg(sal) avgsal FROM emp group by deptno ) ) c
on b.avgsal = c.maxavgsal;

oracle SQL : getting DEPT by min and max sums

I have 2 working subqueries that checks what are the min and max SUMS in all departments (DEPTNO). Table EMP and DEPT has DEPTNO cells
(SELECT min(sum(e.SAL)) FROM EMP e GROUP by e.DEPTNO);
(SELECT max(sum(e.SAL)) FROM EMP e GROUP by e.DEPTNO);
How to check what is the DEPTNO for min- and max- subquery?
My code is with ERROR:
SELECT d.DEPTNO
FROM DEPT d
WHERE sum(e.SAL) = (SELECT max(sum(e.SAL)) FROM EMP e GROUP by d.DEPTNO);
If you want the department withe the max sum, you can use rownum or row_number():
select ed.*
from (select e.deptno, sum(e.sal) as sums,
row_number() over (order by sum(e.sal) desc) as seqnum
from emp e
group by e.deptno
) ed
where seqnum = 1;
In Oracle 12g+, this can also be written as:
select e.deptno, sum(e.sal) as sums,
row_number() over (order by sum(e.sal) desc) as seqnum
from emp e
group by e.deptno
order by sum(e.sal) desc
fetch first 1 row only;
This is one way to do it using cte
with salsums as (select deptno, sum(sal) salsum from emp group by deptno)
, maxandmin as (select max(salsum) maxsal, min(salsum) minsal from salsums)
select deptno
from salsums cross join maxandmin
where salsum = maxsal or salsum = minsal
Ok, this is harder than it looks at first. This is how I ended up getting it to work but I like #vkp's answer more.
with sums as
(
SELECT DEPTNO, SUM(SAL) AS SSAL
FROM EMP
GROUP BY DEPTNO
), mm as
(
SELECT DEPTNO, SSAL, MIN(SSAL) OVER () as MIN_SSAL, MAX(SSAL) OVER () as MAX_SSAL
FROM SUMS
)
SELECT 'MIN', DEPTNO, SSAL FROM mm WHERE SSAL=MIN_SSAL
UNION ALL
SELECT 'MAX', DEPTNO, SSAL FROM mm WHERE SSAL=MAX_SSAL
http://sqlfiddle.com/#!6/410c8/8

Group by clause to get name of highest paid employee

I have a table Employee with fields dept, employee ans salary. I want a query to list Department wise highest salaries and name of the employee with that salary.
I know it is simple. I googled but found answers like this, which lists only the department and salary
SELECT dept, SUM (salary)
FROM employee
GROUP BY dept;
SELECT e1.*
FROM employee e1
JOIN (SELECT dept, MAX(salary) FROM employee GROUP BY dept) e2 ON
e1.dept = e2.dept AND e1.salary = e2.salary
SQL Server 2008 supports Window Functions which help you get what you want.
WITH recordList
AS
(
SELECT dept, employeeName, salary,
DENSE_RANK() OVER (PARTITION BY dept ORDER BY salary DESC) rn
FROM employee
)
SELECT dept, employeeName, salary
FROM recordList
WHERE rn = 1
SQLFiddle Demo
TSQL Ranking Function
SELECT e.*, d.deptname
FROM employee e
JOIN department d ON e.deptid = d.deptid
WHERE EXISTS (SELECT 1
FROM employee e_in
JOIN department d_in ON e_in.deptid = d_in.deptid
WHERE d_in.deptid = d.deptid
GROUP BY d_in.deptid
HAVING MAX(e_in.salary) = e.salary)
This will do it.
SELECT E1.DEPT, E2.ENAME, E1.HIGHEST_SALARY
FROM
(SELECT DEPT, MAX(SALARY) HIGHEST_SALARY
FROM EMPLOYEE
GROUP BY DEPT) E1
INNER JOIN EMPLOYEE E2 ON E1.HIGHEST_SALARY = E2.SALARY
AND E1.DEPT = E2.DEPT
select * from (select salary,last_name,dense_rank()
over (order by salary desc)
sal_rank from employees) where sal_rank <=3;
That's all... this is the output below:
SALARY LAST_NAME SAL_RANK
---------- ------------------------- ----------
24000 King 1
17000 Kochhar 2
17000 De Haan 2
14000 Russell 3