How to Get Set of Second Highest Values? - sql

Suppose I have the following table:
employee_id salary
34 100
22 49
19 49
29 30
17 22
And I want to return the set of employees with the second highest salaries (when there are ties), as follows:
employee_id salary
22 49
19 49
How would I do that?

Use DENSE_RANK:
SELECT employee_id, salary
FROM
(
SELECT employee_id, salary, DENSE_RANK() OVER (ORDER BY salary DESC) dr
FROM yourTable
) t
WHERE dr = 2;

You can use nested query.
Steps taken :
Get all salary values ( sort it and obtain 2nd highest value ) :
SELECT salary FROM employee GROUP BY 1 ORDER BY 1 DESC limit 1 OFFSET 1;
OR can be written as :
SELECT salary FROM employee GROUP BY employee_id ORDER BY employee_id DESC limit 1 OFFSET 1;
Now use the query within employee table
SELECT * FROM employee where salary=(SELECT salary FROM employee GROUP BY 1 ORDER BY 1 DESC limit 1 OFFSET 1);

this can be done also in using query below,
scenario 1: Output two records
WITH employee
AS (
SELECT 34 emp_id, 100 rate FROM DUAL
UNION
SELECT 22 emp_id, 49 rate FROM DUAL
UNION
SELECT 19 emp_id, 49 rate FROM DUAL
UNION
SELECT 29 emp_id, 30 rate FROM DUAL
UNION
SELECT 17 emp_id, 22 rate FROM DUAL),
emp_rate_cnt AS
(SELECT rownum rown, rate, same_rate_count
FROM (SELECT rate, count(1) same_rate_count
FROM employee
GROUP BY rate
ORDER BY rate DESC))
SELECT *
FROM employee a
WHERE exists (SELECT 1
FROM emp_rate_cnt b
WHERE b.rate = a.rate
AND b.rown = 2
AND b.same_rate_count > 1);
scenario 2: Output no records
WITH employee
AS (
SELECT 34 emp_id, 100 rate FROM DUAL
UNION
SELECT 22 emp_id, 49 rate FROM DUAL
UNION
SELECT 19 emp_id, 50 rate FROM DUAL
UNION
SELECT 29 emp_id, 30 rate FROM DUAL
UNION
SELECT 17 emp_id, 22 rate FROM DUAL),
emp_rate_cnt AS
(SELECT rownum rown, rate, same_rate_count
FROM (SELECT rate, count(1) same_rate_count
FROM employee
GROUP BY rate
ORDER BY rate DESC))
SELECT *
FROM employee a
WHERE exists (SELECT 1
FROM emp_rate_cnt b
WHERE b.rate = a.rate
AND b.rown = 2
AND b.same_rate_count > 1);

I hope this is the easiest of all. Use rownum as it is Oracle.
SELECT t.employee_id, t.salary
FROM
(
SELECT distinct employee_id, salary, rownum as row from
FROM yourTable order by salary desc
) t
WHERE t.row = 2;

Related

How to get max salary and count from employee table

I have task where I need to filter in such a way that need to print the max(salary * months) and number of times the max(salary * months) occurred.
My data:
employee_id , name , months , salary
12 , Ami , 15 , 1230
14 , Bik , 1 , 678
15 , Tom , 10 , 500
16 , Bob , 12 , 3456
17 , Wil , 1 , 9000
18 , Tim , 14 , 1239
22 , Jil , 15 , 1230
Note: the table as only 4 columns -> employee_id, name, months, salary
I have written a query where I am able to get max(salary * months)
Query #1:
select
max(msal)
from
(select
employee_id,
name,
salary,
months,
(salary * months) as msal
from
employee);
Query #2:
select
e1.employee_id,
e1.months,
e1.salary,
e2.employee_id,
(e2.salary * e2.months) as earnings
from
employee e1
join
employee e2 on e1.employee_id = e2.employee_id;
Query #3:
select
emp,earnings
from
(
select
e1.employee_id as emp,
e1.months,
e1.salary,
e2.employee_id,
(e2.salary * e2.months) as earnings
from
employee e1
join employee e2 on e1.employee_id = e2.employee_id
)
order by
earnings desc;
I calculated on paper these details:
15 * 1230 -> 18450
1 * 678 -> 678
10 * 500 -> 5000
12 * 3456 -> 41472
1 * 9000 -> 9000
14 * 1239 -> 17346
15 * 1230 -> 18450
According my analysis my output should be: 41472 1
41472-> max(salary * months)
1 -> occurred twice
Here's one option:
Sample data:
SQL> with emp (employee_id, name, months, salary) as
2 (select 12, 'ami', 15, 1230 from dual union all
3 select 14, 'bik', 1 , 678 from dual union all
4 select 15, 'tom', 10, 500 from dual union all
5 select 16, 'bob', 12, 3456 from dual union all
6 select 17, 'wil', 1 , 9000 from dual union all
7 select 18, 'tim', 14, 1239 from dual union all
8 select 22, 'jil', 15, 1230 from dual
9 ),
Query begins here: rank values in descending order ...
10 temp as
11 (select salary * months max_value,
12 count(*) cnt,
13 rank() over (order by salary * months desc) rnk
14 from emp
15 group by salary * months
16 )
17 select max_value, cnt
18 from temp
19 where rnk = 1; --> ... and, finally, fetch row(s) that rank as the highest
MAX_VALUE CNT
---------- ----------
41472 1
SQL>
Although you didn't ask for it, but - such an approach is good because you can easily get - for example:
the second highest value simply by modifying the where clause to
where rnk = 2
top 3 values
where rnk <= 3
etc.
You want to look at salary * month results, count their occurence and only show the top figure. So, group by salary * month, count and take the top row:
select salary * month, count(*)
from employee
group by salary * month
order by salary * month desc
fetch first row only;
Finally I was able to write a Query
Incase any efficient way of writing Query. Please provide your solution
Query :
SELECT
msal,
COUNT(*)
FROM
(
SELECT
months * salary AS msal
FROM
employee_salary
WHERE
( months * salary ) IN (
SELECT
MAX(months * salary)
FROM
employee_salary
)
)
GROUP BY
msal;

Getting department names that have maximum staff count

I have 2 tables, one table storing details of staff (columns are staff_id, staff_name, department_id) and another table storing details of department (columns are department_id, department_name, department_block_num).
I need to write a query to display names of department that has maximum staff count order by department_name. It is also given that multiple department can also have staff count same as maximum staff count. Another condition is group function is not allowed.
Here is code
SELECT department_name
FROM department
WHERE department_id IN (SELECT department_id
FROM ( SELECT department_id, COUNT (*) AS cnt1
FROM staff
WHERE COUNT (*) =
(SELECT cnt
FROM ( SELECT department_id,
COUNT (*) AS cnt
FROM staff
GROUP BY department_id
ORDER BY cnt DESC)
WHERE ROWNUM = 1)
GROUP BY department_id));
You can use the RANK analytic function to find the rows with the maximum counts:
SELECT department_id,
COUNT(*) AS cnt,
RANK() OVER ( ORDER BY COUNT(*) DESC ) AS rnk
FROM staff
GROUP BY department_id
Will rank the rows in order of DESCending count of members of staff and then you can just filter on rows where the rank is 1 (and have the highest count).
Oracle Setup:
CREATE SEQUENCE staff__id__seq;
CREATE TABLE departments (
id INT PRIMARY KEY,
department_name VARCHAR2(20)
);
INSERT INTO departments ( id, department_name )
SELECT 1, 'Aaa' FROM DUAL UNION ALL
SELECT 2, 'Bbb' FROM DUAL UNION ALL
SELECT 3, 'Ccc' FROM DUAL UNION ALL
SELECT 4, 'Ddd' FROM DUAL;
CREATE TABLE staff (
id INT PRIMARY KEY,
department_id INT REFERENCES departments( id )
);
INSERT INTO staff ( id, department_id )
SELECT staff__id__seq.NEXTVAL, department_id
FROM (
SELECT 1 AS department_id FROM DUAL CONNECT BY LEVEL <= 3 UNION ALL
SELECT 2 FROM DUAL CONNECT BY LEVEL <= 5 UNION ALL
SELECT 3 FROM DUAL CONNECT BY LEVEL <= 2 UNION ALL
SELECT 4 FROM DUAL CONNECT BY LEVEL <= 5
);
Query to count staff:
SELECT department_id,
COUNT(*) AS cnt,
RANK() OVER ( ORDER BY COUNT(*) DESC ) AS rnk
FROM staff
GROUP BY department_id
DEPARTMENT_ID | CNT | RNK
------------: | --: | --:
2 | 5 | 1
4 | 5 | 1
1 | 3 | 3
3 | 2 | 4
Query 2 - Get the department name for the highest counts:
Just join the previous query to the departments table and filter to return the rows when the rank is 1 (the highest count).
SELECT d.department_name
FROM (
SELECT department_id,
RANK() OVER ( ORDER BY COUNT(*) DESC ) AS rnk
FROM staff
GROUP BY department_id
) s
INNER JOIN departments d
ON ( d.id = s.department_id )
WHERE s.rnk = 1
Output:
| DEPARTMENT_NAME |
| :-------------- |
| Bbb |
| Ddd |
db<>fiddle here
If you are trying to avoid group by, then use subqueries:
select d.*,
(select count(*)
from staff s
where s.department_id = d.department_id
) as staff_cnt
from department d;
If you then want the top departments, with ties, use subqueries and window functions:
select . . . -- whatever columns you want
from (select d.*,
rank() over (order by staff_cnt desc) as seqnum
from (select d.*,
(select count(*)
from staff s
where s.department_id = d.department_id
) as staff_cnt
from department d
) d
) d
where seqnum = 1;
Assuming this is homework or similar and that windows function are not allowed here is a solution using more basic sql
SELECT department_name
FROM department d
JOIN staff s ON d.department_id = s.department_id
GROUP BY department_name
HAVING COUNT(s.department_id) = (SELECT COUNT(*) as stat
FROM staff
GROUP BY department_id
ORDER BY stat DESC
FETCH FIRST ROW ONLY)
ORDER BY department_name

How can I display highest, and second highest salary using a single query

How can I display the highest, and second highest salary for an employee table in sql by using a single query?
use rownum pseudocolumn https://docs.oracle.com/cd/B19306_01/server.102/b14200/pseudocolumns009.htm#i1006297
with emp(emp_id, salary)
as (select 1, 1000 from dual union
select 2, 3000 from dual union
select 3, 5000 from dual)
select *
from (select *
from emp
order by salary desc)
where rownum <=2
output
EMP_ID SALARY
---------- ----------
3 5000
2 3000
A short answer to your short question is:
SELECT salary
FROM employees
ORDER BY salary DESC
FETCH FIRST 2 ROWS ONLY
select empid, salary, rn
from (
select empid, salary , rank() over(order by salary desc) as rn
from emp) t
where rn<=2;

Find the employee with max salary in his department

Table structure is as below
id dept salary
1 30 2000
2 20 5500
3 30 6700
4 30 8900
5 30 9900
6 10 1120
7 20 8900
8 10 2400
9 30 2600
10 10 2999
I need the output to have two columns:
Id and Salary
Id should be unique and salary should have the max salary
Although data structure is a bit strange, the query below will give the desired result:
create table #tmp(
id numeric(10,0),
dept int,
salary numeric(10,0))
insert #tmp select 1, 30, 2000
insert #tmp select 2, 20, 5500
insert #tmp select 3, 30, 6700
insert #tmp select 4, 30, 8900
insert #tmp select 5, 30, 9900
insert #tmp select 6, 10, 1120
insert #tmp select 7, 20, 8900
insert #tmp select 8, 10, 2400
insert #tmp select 9, 30, 2600
insert #tmp select 10, 10, 2999
select * from #tmp order by dept
id dept salary
------------ ----------- ------------
6 10 1120
8 10 2400
10 10 2999
2 20 5500
7 20 8900
1 30 2000
9 30 2600
3 30 6700
4 30 8900
5 30 9900
select id, salary
from #tmp t1, (select dept=dept, salaryMax=max(salary) from #tmp group by dept) t2
where t1.dept = t2.dept
and t1.salary = t2.salaryMax
id salary
------------ ------------
7 8900
10 2999
5 9900
SELECT ID,Salary FROM TABLE_NAME WHERE SALARY =(SELECT MAX(SALARY) FROM TABLE_NAME)
SELECT a.Id, a.Salary
FROM table a
WHERE a.Salary = (SELECT MAX(Salary) FROM table
WHERE Id = a.Id)
select * from (select * from dep order by salary desc, dep, id) t
group by t.dep
Use NOT EXISTS to return an epmloyee if no other within same department has a higher salary.
select * from table t1
where not exists (select 1 from table t2
where t2.dept = t1.dept
and t2.salary > t1.salary)
Core SQL-99, will work with both Oracle and Sybase!
Alternative solution:
select * from table
where (dept, salary) = (select dept, max(salary)
from table
group by dept)
Using the following feature outside Core SQL-2003: F641, "Row and table constructors". (Don't know what Sybase and Oracle supports here...)
Using single scan:
WITH SALARIES AS (
SELECT 1 ID, 30 DEPT, 2000 SALARY FROM DUAL UNION ALL
SELECT 2 ID, 20 DEPT, 5500 SALARY FROM DUAL UNION ALL
SELECT 3 ID, 30 DEPT, 6700 SALARY FROM DUAL UNION ALL
SELECT 4 ID, 30 DEPT, 8900 SALARY FROM DUAL UNION ALL
SELECT 5 ID, 30 DEPT, 9900 SALARY FROM DUAL UNION ALL
SELECT 6 ID, 10 DEPT, 1120 SALARY FROM DUAL UNION ALL
SELECT 7 ID, 20 DEPT, 8900 SALARY FROM DUAL UNION ALL
SELECT 8 ID, 10 DEPT, 2400 SALARY FROM DUAL UNION ALL
SELECT 9 ID, 30 DEPT, 2600 SALARY FROM DUAL UNION ALL
SELECT 10 ID, 10 DEPT, 2999 SALARY FROM DUAL
)
SELECT
MAX(ID) KEEP (DENSE_RANK LAST ORDER BY SALARY) ID,
MAX(SALARY) SALARY
FROM
SALARIES
GROUP BY
DEPT;
All the below three queries worked :
select t.dept, t.salary
from
(select dept,max(salary) as m_sal from tempdb..test
group by dept)x, tempdb..test t
where x.dept = t.dept
and x.m_sal = t.salary
select dept, id, salary as m_sal
from tempdb..test t1
where salary = (select max(salary) from tempdb..test t2 where t2.dept = t1.dept)
select t1.dept,t1.salary from tempdb..test t1
inner join
(select dept,max(salary) as m_sal from tempdb..test
group by dept)x on
t1.salary=x.m_sal and
t1.dept=x.dept

Oracle SQL to generate interleaved SQL results

Hi i am looking for a way to write a SQL statement which will come out with the following results :-
Lets say we have Dept & Emp ID i would like to generate like records from Dept 3 for the first two rows then followed by Dept 2 with one row then continue Dept 3 and so on :
DEPT EMPID
----- ------
3 1
3 2
2 3
3 7
3 8
2 9
Thank You.
You could use something like this
SELECT
DEPT,
EMPID
FROM (
SELECT
*,
ceil((row_number() OVER (PARTITION BY dept ORDER BY EMPID ))/ 2::numeric(5,2)) AS multiple_row_dept,
row_number() OVER (PARTITION BY dept ORDER BY EMPID ) AS single_row_dept
FROM
test_data2
) sub_query
ORDER BY
CASE
WHEN DEPT = 2 THEN single_row_dept
ELSE multiple_row_dept
END,
DEPT DESC,
EMPID
single_row_dept specifics which dept should appear only once, in this case its DEPT 2 followed by multiple other departments
First order a table by empid in a subquery,
then calculate a remainder of rowids divided by 3,
then depending on a result of calculation, return 2 or 3, using case expression,
like this
SELECT
CASE REMAINDER( rownum, 3 )
WHEN 0 THEN 2
ELSE 3
END As DeptId,
empid
FROM (
SELECT empid
FROM table1
ORDER BY empid
)
demo: http://sqlfiddle.com/#!4/bd1bb/3
The following code has some limitations:
1) There are only 2 DepId's in the source table
2) Ratio between records having different DepId is exactly 2:1
3) Ordering in one place of the query should be changed depending whether DeptId having more records is naturally greater then the other or not
with t as (
select 3 dept_id, 1 emp_id from dual
union all
select 3, 2 from dual
union all
select 3, 7 from dual
union all
select 3, 8 from dual
union all
select 2, 3 from dual
union all
select 2, 9 from dual)
select dept_id, emp_id
from (select dept_id,
emp_id,
dense_rank() over(partition by dept_id order by in_num + mod(in_num, out_num)) ord
from (select dept_id,
emp_id,
row_number() over(partition by dept_id order by emp_id) in_num,
/*in the following ORDER BY change to DESC or ASC depending on which dept_id has more records*/
dense_rank() over(order by dept_id) out_num
from t))
order by ord, dept_id desc;
DEPT_ID EMP_ID
---------- ----------
3 1
3 2
2 3
3 8
3 7
2 9