Getting department names that have maximum staff count - sql

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

Related

Oracle SQL Query to get Latest Data from multiple columns in one row

I have table MY_TABLE like this, which stores changed data(update tracking) from many tables. So whenever other table has some update, I am storing new data in MY_TABLE using AFTER UPDATE TRIGGER on base tables.
ID RECORD_DESC EMP_ID FIRST_NAME LAST_NAME GENDER SALARY
1 EMP 5 ABC XYZ
2 EMP 5 M
3 EMP 5 XYZ-NEW F
4 SAL 5 1000
5 EMP 5 M
6 SAL 5 ABC-NEW 750
Now I want to query MY_TABLE to get employee data with latest changes from all columns and result should be like this row:
EMP_ID FIRST_NAME LAST_NAME GENDER SALARY
5 ABC-NEW XYZ-NEW M 750
What I did up to now is, getting MAX(ID) for each column and from that ID I am querying table again to get the column value for that ID.
But problem is this query will experience quite a load on db because I have 25 columns like this and table will get larger with time.
So, can some one suggest me better way to write the query below:
SELECT (SELECT FIRST_NAME FROM MY_TABLE WHERE ID = T2.FIRST_NAME_PK) AS FIRST_NAME
, (SELECT LAST_NAME FROM MY_TABLE WHERE ID = T2.LAST_NAME_PK ) AS LAST_NAME
, (SELECT GENDER FROM MY_TABLE WHERE ID = T2.GENDER_PK ) AS GENDER
, (SELECT SALARY FROM MY_TABLE WHERE ID = T2.SALARY_PK ) AS SALARY
FROM (SELECT (SELECT MAX(ID) FROM MY_TABLE WHERE EMP_ID = T1.EMP_ID AND FIRST_NAME IS NOT NULL) FIRST_NAME_PK -- ID = 6
, (SELECT MAX(ID) FROM MY_TABLE WHERE EMP_ID = T1.EMP_ID AND LAST_NAME IS NOT NULL) LAST_NAME_PK -- ID = 3
, (SELECT MAX(ID) FROM MY_TABLE WHERE EMP_ID = T1.EMP_ID AND GENDER IS NOT NULL) GENDER_PK -- ID = 5
, (SELECT MAX(ID) FROM MY_TABLE WHERE EMP_ID = T1.EMP_ID AND SALARY IS NOT NULL) SALARY_PK -- ID = 6
FROM (SELECT DISTINCT EMP_ID
FROM MY_TABLE
) T1
) T2;
Try this
SELECT DISTINCT EMP_ID,
, LAST_VALUE(FIRST_NAME) IGNORE NULLS OVER (PARTITION BY EMP_ID ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
, LAST_VALUE(LAST_NAME) IGNORE NULLS OVER (PARTITION BY EMP_ID ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
, LAST_VALUE(GENDER) IGNORE NULLS OVER (PARTITION BY EMP_ID ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
, LAST_VALUE(SALARY) IGNORE NULLS OVER (PARTITION BY EMP_ID ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
FROM MY_TABLE
You can use the KEEP clause as follows:
SELECT ID,
MAX(FIRST_NAME) KEEP (DENSE_RANK FIRST ORDER BY CASE WHEN FIRST_NAME IS NULL THEN NULL ELSE ID END DESC NULLS LAST) AS FIRST_NAME,
MAX(LAST_NAME) KEEP (DENSE_RANK FIRST ORDER BY CASE WHEN LAST_NAME IS NULL THEN NULL ELSE ID END DESC NULLS LAST) AS LAST_NAME,
MAX(GENDER) KEEP (DENSE_RANK FIRST ORDER BY CASE WHEN GENDER IS NULL THEN NULL ELSE ID END DESC NULLS LAST) AS GENDER,
MAX(SALARY) KEEP (DENSE_RANK FIRST ORDER BY CASE WHEN SALARY IS NULL THEN NULL ELSE ID END DESC NULLS LAST) AS SALARY
FROM MY_TABLE
GROUP BY ID;
How about this? See comments within code.
SQL> with my_table (id, record_Desc, emp_id, first_name, last_name, gender, salary) as
2 -- sample data
3 (select 1, 'emp', 5, 'abc', 'xyz', null , null from dual union all
4 select 2, 'emp', 5, null, null, 'm', null from dual union all
5 select 3, 'emp', 5, null, 'xyz-new', 'f', null from dual union all
6 select 4, 'emp', 5, null, null, null, 1000 from dual union all
7 select 5, 'emp', 5, null, null, 'm', null from dual union all
8 select 6, 'emp', 5, 'abc-new', null, null, 750 from dual
9 ),
10 temp as
11 -- find last values
12 (select a.id,
13 a.record_desc,
14 a.emp_id,
15 last_value(a.first_name ignore nulls) over (partition by a.record_desc, a.emp_id order by a.id) first_name,
16 last_value(a.last_name ignore nulls) over (partition by a.record_desc, a.emp_id order by a.id) last_name,
17 last_value(a.gender ignore nulls) over (partition by a.record_desc, a.emp_id order by a.id) gender,
18 last_value(a.salary ignore nulls) over (partition by a.record_desc, a.emp_id order by a.id) salary
19 from my_table a
20 )
21 -- extract only the last row per RECORD_DESC and EMP_ID
22 select *
23 from temp c
24 where c.id = (select max(b.id) From my_table b
25 where b.record_desc = c.record_Desc
26 and b.emp_id = c.emp_id
27 );
ID REC EMP_ID FIRST_N LAST_NA G SALARY
---------- --- ---------- ------- ------- - ----------
6 emp 5 abc-new xyz-new m 750
SQL>
Your employee table has the current data. You only want to show the data that has been altered, though.
What I'd do is show the employees data in case the column has an update in the log table. We don't have to find the latest update, because no matter how often the column was updated, the employee table contains the last value. This is a very simple operation in spite of having to read the whole log table.
select
e.emp_id,
case when log.some_first_name is not null then e.first_name end as first_name,
case when log.some_last_name is not null then e.last_name end as last_name,
case when log.some_gender is not null then e.gender end as gender,
case when log.some_salary is not null then e.salary end as salary
from employees e
join
(
select
emp_id,
min(first_name) as some_first_name,
min(last_name) as some_last_name,
min(gender) as some_gender,
min(salary) as some_salary
from my_table
group by emp_id
) log on log.emp_id = e.emp_id
order by e.emp_id;
An alternative to running this query again and again would be a last_updates table with one row per employee and a trigger that fills it on every insert into the existing log table. If you need this often, that's the route I'd choose.

Write a Query to show Id, Name and No. of department?

I am trying to write a Query to show Id, Name and No. of department in given Table which are referring more than one department.
ID Name Department
-- ---- ----------
1 Sam HR
1 Sam FINANCE
2 Ron PAYROLL
3 Kia HR
3 Kia IT
Result :
ID Name Department
-- ---- ----------
1 Sam 2
3 Kia 2
I tried using group by id and using count(*), but query is giving error.
How can I do this?
Without seeing your query, a blind guess is that you wrongly wrote the GROUP BY clause (if you used it) and forgot to include the HAVING clause.
Anyway, something like this might be what you're looking for:
SQL> with test (id, name, department) as
2 (select 1, 'sam', 'hr' from dual union
3 select 1, 'sam', 'finance' from dual union
4 select 2, 'ron', 'payroll' from dual union
5 select 3, 'kia', 'hr' from dual union
6 select 3, 'kia', 'it' from dual
7 )
8 select id, name, count(*)
9 from test
10 group by id, name
11 having count(*) > 1
12 order by id;
ID NAM COUNT(*)
---------- --- ----------
1 sam 2
3 kia 2
SQL>
You were right about using count(). You need to group by other columns though and only count unique departments then filter on the number in having clause.
select id, name, count(distinct department) as no_of_department
from table
group by id, name
having count(distinct department) > 1
This can also be done using analytic functions like below:
select *
from (
select id, name, count(distinct department) over (partition by id, name) as no_of_department
from table
) t
where no_of_department > 1
You can use window function with subquery :
select distinct id, name, Noofdepartment
from (select t.*, count(*) over (partition by id,name) Noofdepartment
from table t
) t
where Noofdepartment > 1;
However, you can also use group by clause:
select id, name, count(*) as Noofdepartment
from table t
group by id, name
having count(*) > 1;

How to Get Set of Second Highest Values?

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;

Insert column with Max in sql

I did not know how to insert column with max.
Select id,MAX(salary),Min(Salary)
from C
GROUP BY id;
it is give me the all id with it is maximum
and I want just the id with maximum and minimum of salary!!
Several options for you that only require a single scan of the table:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE C ( ID, SALARY ) AS
SELECT 1, 100 FROM DUAL
UNION ALL SELECT 2, 110 FROM DUAL
UNION ALL SELECT 3, 100 FROM DUAL
UNION ALL SELECT 4, 110 FROM DUAL
UNION ALL SELECT 5, 90 FROM DUAL
Query 1 - Get a single ID:
SELECT *
FROM (
SELECT ID, SALARY
FROM c
ORDER BY SALARY DESC
)
WHERE ROWNUM = 1
Results:
| ID | SALARY |
|----|--------|
| 2 | 110 |
Query 2 - Get a single ID (alternate method that will get min and max IDs):
SELECT MAX( ID ) KEEP ( DENSE_RANK LAST ORDER BY SALARY ) AS MAX_SALARY_ID,
MAX( SALARY ) AS MAX_SALARY,
MIN( ID ) KEEP ( DENSE_RANK FIRST ORDER BY SALARY ) AS MIN_SALARY_ID,
MIN( SALARY ) AS MIN_SALARY
FROM C
Results:
| MAX_SALARY_ID | MAX_SALARY | MIN_SALARY_ID | MIN_SALARY |
|---------------|------------|---------------|------------|
| 4 | 110 | 5 | 90 |
Query 3 - Get all the IDs with the maximum salary:
SELECT ID, SALARY
FROM (
SELECT ID,
SALARY,
RANK() OVER ( ORDER BY SALARY DESC ) AS RNK
FROM C
)
WHERE RNK = 1
Results:
| ID | SALARY |
|----|--------|
| 2 | 110 |
| 4 | 110 |
Query 4 - Get all IDs for min and max salary:
SELECT LISTAGG( CASE MIN_RANK WHEN 1 THEN ID END, ',' ) WITHIN GROUP ( ORDER BY ID ) AS MIN_SALARY_IDS,
MAX( CASE MIN_RANK WHEN 1 THEN SALARY END ) AS MIN_SALARY,
LISTAGG( CASE MAX_RANK WHEN 1 THEN ID END, ',' ) WITHIN GROUP ( ORDER BY ID ) AS MAX_SALARY_IDS,
MAX( CASE MAX_RANK WHEN 1 THEN SALARY END ) AS MAX_SALARY
FROM (
SELECT ID,
SALARY,
RANK() OVER ( ORDER BY SALARY ASC ) AS MIN_RANK,
RANK() OVER ( ORDER BY SALARY DESC ) AS MAX_RANK
FROM C
)
Results:
| MIN_SALARY_IDS | MIN_SALARY | MAX_SALARY_IDS | MAX_SALARY |
|----------------|------------|----------------|------------|
| 5 | 90 | 2,4 | 110 |
Query 5:
SELECT ID,
SALARY,
CASE WHEN MIN_RANK = 1 THEN 'MIN'
WHEN MAX_RANK = 1 THEN 'MAX' END AS MIN_MAX
FROM (
SELECT ID,
SALARY,
RANK() OVER ( ORDER BY SALARY ASC ) AS MIN_RANK,
RANK() OVER ( ORDER BY SALARY DESC ) AS MAX_RANK
FROM C
)
WHERE MIN_RANK = 1 OR MAX_RANK = 1
Results:
| ID | SALARY | MIN_MAX |
|----|--------|---------|
| 2 | 110 | MAX |
| 4 | 110 | MAX |
| 5 | 90 | MIN |
Select id,
salary
from C
where salary = (select MAX(salary)
from C)
you can use first_value or last_value
The FIRST_VALUE analytic function is similar to the FIRST analytic
function, allowing you to return the first result from an ordered set.
https://oracle-base.com/articles/misc/first-value-and-last-value-analytic-functions
create table C (id int, salary int);
insert into c values(1, 1);
insert into c values(2, 2);
insert into c values(3, 3);
insert into c values(4, 4);
insert into c values(5, 5);
Select distinct first_value(id) over ( order by salary desc)
from C
FIRST_VALUE(ID)OVER(ORDERBYSAL
1 5

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