I want to query difference between first and second highest salary - sql

I already got the second-highest salary.
I would like to display: employee name, salary, department name, and the difference between the highest and second-highest salary.
Here is my current code.
select max(SAL) SecondHighestSalary
from EMP A, DEPT B
where SAL < (
select max(A.SAL)
from EMP A, DEPT B
where A.DEPTNO = B.DEPTNO and B.LOC = 'New York'
);
Thanks

Use the DENSE_RANK (if you want highest two unique salary values) or ROW_NUMBER (if you want the salary of the two highest earning people regardless of ties) analytic functions and then use conditional aggregation:
SELECT MAX( CASE WHEN sal_rank = 1 THEN sal END )
- MAX( CASE WHEN sal_rank = 2 THEN sal END ) AS sal_difference_ignore_ties,
MAX( CASE WHEN sal_rownum = 1 THEN sal END )
- MAX( CASE WHEN sal_rownum = 2 THEN sal END ) AS sal_difference_with_ties
FROM (
SELECT e.deptno,
e.sal,
DENSE_RANK() OVER ( ORDER BY e.sal DESC ) AS sal_rank,
ROW_NUMBER() OVER ( ORDER BY e.sal DESC ) AS sal_rownum
FROM emp e
INNER JOIN dept d
ON ( e.deptno = d.deptno )
WHERE d.loc = 'New York'
);
Which, for the sample data:
CREATE TABLE emp ( deptno, sal ) AS
SELECT 1, 2000 FROM DUAL UNION ALL
SELECT 1, 2000 FROM DUAL UNION ALL
SELECT 1, 1000 FROM DUAL UNION ALL
SELECT 1, 500 FROM DUAL UNION ALL
SELECT 1, 200 FROM DUAL UNION ALL
SELECT 2, 2000 FROM DUAL UNION ALL
SELECT 2, 1500 FROM DUAL UNION ALL
SELECT 2, 200 FROM DUAL UNION ALL
SELECT 2, 700 FROM DUAL;
CREATE TABLE dept ( deptno, loc ) AS
SELECT 1, 'New York' FROM DUAL UNION ALL
SELECT 2, 'Beijing' FROM DUAL;
Outputs:
SAL_DIFFERENCE_IGNORE_TIES | SAL_DIFFERENCE_WITH_TIES
-------------------------: | -----------------------:
1000 | 0
Or, if you want all the departments:
SELECT deptno,
MAX( CASE WHEN sal_rank = 1 THEN sal END )
- MAX( CASE WHEN sal_rank = 2 THEN sal END ) AS sal_difference_ignore_ties,
MAX( CASE WHEN sal_rownum = 1 THEN sal END )
- MAX( CASE WHEN sal_rownum = 2 THEN sal END ) AS sal_difference_with_ties
FROM (
SELECT e.deptno,
e.sal,
DENSE_RANK() OVER ( PARTITION BY e.deptno ORDER BY e.sal DESC ) AS sal_rank,
ROW_NUMBER() OVER ( PARTITION BY e.deptno ORDER BY e.sal DESC ) AS sal_rownum
FROM emp e
INNER JOIN dept d
ON ( e.deptno = d.deptno )
)
GROUP BY deptno;SELECT deptno,
MAX( CASE WHEN sal_rank = 1 THEN sal END )
- MAX( CASE WHEN sal_rank = 2 THEN sal END ) AS sal_difference_ignore_ties,
MAX( CASE WHEN sal_rownum = 1 THEN sal END )
- MAX( CASE WHEN sal_rownum = 2 THEN sal END ) AS sal_difference_with_ties
FROM (
SELECT e.deptno,
e.sal,
DENSE_RANK() OVER ( PARTITION BY e.deptno ORDER BY e.sal DESC ) AS sal_rank,
ROW_NUMBER() OVER ( PARTITION BY e.deptno ORDER BY e.sal DESC ) AS sal_rownum
FROM emp e
INNER JOIN dept d
ON ( e.deptno = d.deptno )
)
GROUP BY deptno;
Which outputs:
DEPTNO | SAL_DIFFERENCE_IGNORE_TIES | SAL_DIFFERENCE_WITH_TIES
-----: | -------------------------: | -----------------------:
1 | 1000 | 0
2 | 500 | 500
db<>fiddle here

How about this?
Based on Scott's EMP table:
SQL> select ename, sal
2 from emp
3 order by sal desc;
ENAME SAL
---------- ----------
KING 5001 --> highest salary
FORD 3000 --> Ford and Scott share the
SCOTT 3000 --> 2nd highest salary
JONES 2975
BLAKE 2850
CLARK 2450
ALLEN 1600
TURNER 1500
MILLER 1300
WARD 1250
MARTIN 1250
ADAMS 1100
SMITH 1000
JAMES 950
14 rows selected.
Then you'd rank them and - using LISTAGG (to display all employees that share the same highest or second highest salary) - get the final result:
SQL> with temp as
2 (select e.ename,
3 e.sal,
4 dense_rank() over (order by e.sal desc) rnk,
5 d.dname
6 from emp e join dept d on d.deptno = e.deptno
7 )
8 select listagg(case when rnk = 1 then ename ||' - '|| dname || ' ($' || sal ||')' end, ', ')
9 within group (order by ename) highest,
10 --
11 listagg(case when rnk = 2 then ename ||' - '|| dname || ' ($' || sal ||')' end, ', ')
12 within group (order by ename) second_highest,
13 --
14 max(case when rnk = 1 then sal end) -
15 max(case when rnk = 2 then sal end) diff
16 from temp t
17 where t.rnk in (1, 2);
HIGHEST SECOND_HIGHEST DIFF
------------------------------ -------------------------------------------------- ----------
KING - ACCOUNTING ($5001) FORD - RESEARCH ($3000), SCOTT - RESEARCH ($3000) 2001
SQL>

Related

Sum of multiple union select showing every result and total

My SQL shows the count of records in multiple tables
SELECT 'S_MH_DSC_Abandoned' Table_N ,COUNT(*) Count FROM S_MH_DSC_Abandoned
UNION SELECT 'S_MH_DSC_ExclAbandoned' Table_N, COUNT(*) Count FROM S_MH_DSC_ExclAbandoned
UNION SELECT 'S_MH_Private_All' Table_N, COUNT(*) Count FROM S_MH_Private_All
I want to show this plus the total of the values in the last column as above.
I have tried
Select 'Sum Total' Table_N, sum(a) as Total from
(
SELECT 'S_MH_DSC_Abandoned',COUNT(*) a FROM S_MH_DSC_Abandoned
UNION SELECT 'S_MH_DSC_ExclAbandoned', COUNT(*) a FROM S_MH_DSC_ExclAbandoned
UNION SELECT 'S_MH_Private_All', COUNT(*) a FROM S_MH_Private_All
)
But only get the total without the other values.
How can I get both the working counts and the total?
This is using QGIS flavour of SQL - https://sqlite.org/lang.html
I don't use SQLite.
However, reviewing its documentation, it looks as if it doesn't know ROLLUP (which comes handy in such a case):
SQL> with temp (job, sumsal) as
2 (select 'Clerk', sum(sal) from emp where job = 'CLERK' union all
3 select 'Mgr' , sum(sal) from emp where job = 'MANAGER' union all
4 select 'Sale' , sum(sal) from emp where job = 'SALESMAN'
5 )
6 select job, sum(sumsal) sumsal
7 from temp
8 group by rollup (job);
JOB SUMSAL
----- ----------
Clerk 4150
Mgr 8275
Sale 5600
18025
SQL>
Therefore, simulate it with a union:
SQL> with temp (job, sumsal) as
2 (select 'Clerk', sum(sal) from emp where job = 'CLERK' union all
3 select 'Mgr' , sum(sal) from emp where job = 'MANAGER' union all
4 select 'Sale' , sum(sal) from emp where job = 'SALESMAN'
5 )
6 select job, sumsal
7 from temp
8 union
9 select 'Total', sum(sumsal)
10 from temp;
JOB SUMSAL
----- ----------
Clerk 4150
Mgr 8275
Sale 5600
Total 18025
SQL>
Using your query:
with temp (tname, cnt) as
(SELECT 'S_MH_DSC_Abandoned' , COUNT(*) FROM S_MH_DSC_Abandoned union all
SELECT 'S_MH_DSC_ExclAbandoned', COUNT(*) FROM S_MH_DSC_ExclAbandoned union all
SELECT 'S_MH_Private_All' , COUNT(*) FROM S_MH_Private_All
)
select tname, cnt
from temp
union
select 'Total', sum(cnt)
from temp;

Same record returns multiple times based on deptno

I have a EMP table like below
Eid Ename Deptno
1 xyz 3
2 abc 5
If I select eid=1 it should display deptno =3 times. I f i select eid=2 it should display deptno=5.like below.
1 xyz 3
1 xyz 3
1 xyz 3
So please do help me. Thanks in advance.
You can try using a recursive cte
DEMO
;with cte
as
(select eid, ename, deptno, deptno AS counter from emp
union all
select cte.eid, cte.ename, deptno, cte.counter - 1
from cte
where cte.counter - 1 > 0
)
select eid, ename, deptno from cte
ORDER BY cte.eid

Getting values as rows with its corresponding values using oracle

I have data in table as follows
DEPTNO ENAME SALARY
------ ---------- ------
Developer SENIOR 100000
Developer JUNIOR 100000
Tester SENIOR 200000
Tester JUNIOR 100000
Architect SENIOR 300000
Architect JUNIOR 300000
I need to show them like
Occupation Senior Sal Junior Sal
------ ---------- ------
Developer SENIOR JUNIOR
Developer 100000 100000
Tester SENIOR JUNIOR
Tester 200000 100000
Architect SENIOR JUNIOR
Architect 300000 300000
Am stuck with this how to achieve this
Use
DECODE
ROW_NUMBER
UNION ALL
SELECT deptno,
senior_sal,
junior_sal,
row_number() over(partition BY deptno order by senior_sal DESC) rn
FROM
(SELECT A.deptno, A.salary senior_sal, b.salary junior_sal
FROM
(SELECT deptno, TO_CHAR(salary) salary FROM your_table WHERE ename = 'SENIOR'
) A,
(SELECT deptno, TO_CHAR(salary) salary FROM your_table WHERE ename = 'JUNIOR'
) b
WHERE A.deptno = b.deptno
UNION ALL
SELECT A.deptno, A.salary senior_sal, b.salary junior_sal
FROM
(SELECT deptno, DECODE(ename, 'SENIOR', 'SENIOR') salary FROM your_table
WHERE ename = 'SENIOR'
) A,
(SELECT deptno, DECODE(ename, 'JUNIOR', 'JUNIOR') salary FROM your_table
WHERE ename = 'JUNIOR'
) b
WHERE A.deptno = b.deptno
);
Working demo
In SQL*Plus:
SQL> column rn noprint
SQL> WITH sample_data AS(
2 SELECT 'Developer' DEPTNO, 'SENIOR' ENAME, 100000 SALARY FROM dual UNION ALL
3 SELECT 'Developer' DEPTNO, 'JUNIOR' ENAME, 100000 SALARY FROM dual UNION ALL
4 SELECT 'Tester' DEPTNO, 'SENIOR' ENAME, 200000 SALARY FROM dual UNION ALL
5 SELECT 'Tester' DEPTNO, 'JUNIOR' ENAME, 100000 SALARY FROM dual UNION ALL
6 SELECT 'Architect' DEPTNO, 'SENIOR' ENAME, 300000 SALARY FROM dual UNION ALL
7 SELECT 'Architect' DEPTNO, 'JUNIOR' ENAME, 300000 SALARY FROM dual
8 )
9 -- end of sample_data mimicking real table
10 SELECT deptno,
11 senior_sal,
12 junior_sal,
13 row_number() over(partition BY deptno order by senior_sal DESC) rn
14 FROM
15 (SELECT A.deptno, A.salary senior_sal, b.salary junior_sal
16 FROM
17 (SELECT deptno, TO_CHAR(salary) salary FROM sample_data WHERE ename = 'SENIOR'
18 ) A,
19 (SELECT deptno, TO_CHAR(salary) salary FROM sample_data WHERE ename = 'JUNIOR'
20 ) b
21 WHERE A.deptno = b.deptno
22 UNION ALL
23 SELECT A.deptno, A.salary senior_sal, b.salary junior_sal
24 FROM
25 (SELECT deptno, DECODE(ename, 'SENIOR', 'SENIOR') salary FROM sample_data
26 WHERE ename = 'SENIOR'
27 ) A,
28 (SELECT deptno, DECODE(ename, 'JUNIOR', 'JUNIOR') salary FROM sample_data
29 WHERE ename = 'JUNIOR'
30 ) b
31 WHERE A.deptno = b.deptno
32 );
Output:
DEPTNO SENIOR_SAL JUNIOR_SAL
--------------- --------------- ---------------
Architect SENIOR JUNIOR
Architect 300000 300000
Developer SENIOR JUNIOR
Developer 100000 100000
Tester SENIOR JUNIOR
Tester 200000 100000
6 rows selected.
SQL>
You can use LISTAGG.
WITH sample_data AS(
SELECT 'Developer' DEPTNO, 'SENIOR' ENAME, 100000 SALARY FROM dual UNION ALL
SELECT 'Developer' DEPTNO, 'JUNIOR' ENAME, 100000 SALARY FROM dual UNION ALL
SELECT 'Tester' DEPTNO, 'SENIOR' ENAME, 200000 SALARY FROM dual UNION ALL
SELECT 'Tester' DEPTNO, 'JUNIOR' ENAME, 100000 SALARY FROM dual UNION ALL
SELECT 'Architect' DEPTNO, 'SENIOR' ENAME, 300000 SALARY FROM dual UNION ALL
SELECT 'Architect' DEPTNO, 'JUNIOR' ENAME, 300000 SALARY FROM dual
)
SELECT occupation, SUBSTR(name_sal,1,INSTR(name_sal,',')-1) "Senior Sal", SUBSTR(name_sal,INSTR(name_sal,',')+1) "Junior Sal"
FROM
(
SELECT DEPTNO as occupation,LISTAGG (ename, ',') WITHIN GROUP (ORDER BY ename DESC) name_sal
FROM sample_data
GROUP BY DEPTNO
UNION
SELECT deptno as occupation,LISTAGG (salary, ',') WITHIN GROUP (ORDER BY ename DESC)
FROM sample_data
GROUP BY deptno
)
ORDER BY 1, 2 DESC

Oracle SQL - How to get distinct rows using RANK() or DENSE_RANK() or ROW_NUMBER() analytic function?

I am looking to get the top 3 distinct salaries of each department. I was able to do it either using RANK() or DENSE_RANK() or ROW_NUMBER() but my table is having some records with same salaries.
Mentioned below is my query and its result.
The top 3 salaries of Dept 20 should be 6000, 3000, 2975.
But there are 2 employees with salary 3000 and both of them have rank 2. So it is giving me 4 records for this department (1 for rank 1, 2 records for rank2 and 1 record for rank3).
Please suggest/advise about how can get the distinct top 3 salaries for each department.
Query:
SELECT * FROM (
SELECT EMPNO, DEPTNO, SAL,
DENSE_RANK() over (partition by deptno order by sal DESC) as RANK,
row_number() over (partition by deptno order by sal DESC) as ROWNO
from EMP)
WHERE RANK <= 3;
RESULT:
Empno Deptno Salary Rank Rowno
----------------------------------------
7839 10 5000 1 1
7782 10 2450 2 2
7934 10 1300 3 3
7935 20 6000 1 1
7788 20 3000 2 2
7902 20 3000 2 3
7566 20 2975 3 4
7698 30 2850 1 1
7499 30 1600 2 2
7844 30 1500 3 3
If you get more specific in row_number, with partitioning by dept,salary then you can combine row_number and dense_rank as in this query:
with data_row as
(
select 7839 as empno, 10 as deptno, 5000 as salary from dual union all
select 7782 as empno, 10 as deptno, 2450 as salary from dual union all
select 7934 as empno, 10 as deptno, 1300 as salary from dual union all
select 1111 as empno, 10 as deptno, 1111 as salary from dual union all
select 7935 as empno, 20 as deptno, 6000 as salary from dual union all
select 7788 as empno, 20 as deptno, 3000 as salary from dual union all
select 7902 as empno, 20 as deptno, 3000 as salary from dual union all
select 7566 as empno, 20 as deptno, 2975 as salary from dual union all
select 2222 as empno, 20 as deptno, 2222 as salary from dual union all
select 7698 as empno, 30 as deptno, 2850 as salary from dual union all
select 7499 as empno, 30 as deptno, 1600 as salary from dual union all
select 7844 as empno, 30 as deptno, 1500 as salary from dual union all
select 3333 as empno, 30 as deptno, 1333 as salary from dual
)
select *
from
(
select
deptno,
salary,
dense_rank() over (partition by deptno order by salary desc) as drank,
row_number() over (partition by deptno, salary order by salary desc) as rowno
from data_row
)
where drank <=3 and
rowno =1
The row_number function you used should do the trick:
SELECT *
FROM (SELECT empno, deptno, sal
DENSE_RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) as rk,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY sal DESC) as rowno
FROM emp)
WHERE rowno <= 3;
Please try:
SELECT empno, deptno, DISTINCT(sal)
FROM (SELECT empno, deptno, sal
DENSE_RANK() OVER (PARTITION BY deptno ORDER BY sal DESC) as rk,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY sal DESC) as rowno
FROM emp)
WHERE rowno <= 3;

how to delete 5th max salary from emp table and he should also be from dallas in dept table

I am not getting the result l thought.
I have tried using:
DELETE FROM emp c
WHERE SAL IN (
SELECT SAL
FROM EMP A
WHERE DEPTNO IN (
SELECT DEPTNO
FROM DEPT
WHERE LOC IN ('DALLAS')
)
AND 4 = (
SELECT COUNT(*)
FROM EMP B
WHERE A.SAL< B.SAL
)
);
DELETE FROM emp c
WHERE SAL IN (
SELECT TOP 1
sub1.SAL
FROM (
SELECT TOP 5
SAL
FROM EMP A
inner join DEPT ON a.DEPTNO=DEPT.DEPTNO and DEPT.LOC='DALLAS'
ORDER BY SAL DESC
) sub1
ORDER BY sub1.SAL ASC
);
Rank over Salary must help in detecting the 5th max salary:
DELETE FROM emp where sal in(
select sal from(
SELECT sal, RANK() OVER (order by sal DESC) myrank
FROM emp)
where myrank=5
)
and DEPTNO IN (
SELECT DEPTNO
FROM DEPT
WHERE LOC IN ('DALLAS')
If you want the 5th salary to be within a department in Dallas, i.e. not across all the salaries, try something like this:
DELETE FROM emp where sal in(
select sal from(
SELECT sal, RANK() OVER (partition by depno order by sal DESC) myrank
FROM emp)
where myrank=5
)
and DEPTNO IN (
SELECT DEPTNO
FROM DEPT
WHERE LOC IN ('DALLAS')