Numbering of groups in select (Oracle) - sql

I have a following example - table with name, department and country. I need to create a select statement that lists all records and assigns unique number to each group of department and country (column Group in the example):
Name Department Country Group
====== ============ ========= =====
James HR UK 1
John HR UK 1
Alice Finance UK 2
Bob Finance DE 3
Frank Finance DE 3
I thought of some select with analytic function but I found only row_number() over (partition by department, country) which numbers records inside the group and not groups themselves. Do you have any idea how to solve this problem? Thank you!

SELECT t.*, q.grp
FROM (
SELECT q.*, rownum AS grp
FROM (
SELECT DISTINCT department, country
FROM mytable
ORDER BY
department, country
) q
) q
JOIN mytable t
ON t.department = q.department
AND t.country = q.country
or
SELECT t.*, DENSE_RANK() OVER (ORDER BY department desc, country desc) AS grp
FROM mytable

Its a bit clunky, but you could do a a sub query ( or in this case using the with clause ) on the table to get the distinct department per country then get the rownum from that.
set echo on
DROP TABLE TESTXX
DROP TABLE TESTXX succeeded.
CREATE
TABLE TESTXX
(
NAME VARCHAR2 ( 10 )
, DEPARTMENT VARCHAR2 ( 15 )
, COUNTRY VARCHAR2 ( 2 )
)
CREATE succeeded.
INSERT INTO TESTXX VALUES
( 'James', 'HR', 'UK'
)
1 rows inserted
INSERT INTO TESTXX VALUES
( 'John', 'HR', 'UK'
)
1 rows inserted
INSERT INTO TESTXX VALUES
( 'Alice', 'FI', 'UK'
)
1 rows inserted
INSERT INTO TESTXX VALUES
( 'Bob', 'FI', 'DE'
)
1 rows inserted
INSERT INTO TESTXX VALUES
( 'Frank', 'FI', 'DE'
)
1 rows inserted
.
WITH
X AS
(SELECT
XX.*
, ROWNUM R
FROM
(SELECT
DEPARTMENT
, COUNTRY
FROM
TESTXX
GROUP BY
COUNTRY
, DEPARTMENT
ORDER BY
COUNTRY DESC
, DEPARTMENT DESC
) XX
)
SELECT
T.*
, X.R
FROM
TESTXX T
INNER JOIN X
ON
T.DEPARTMENT = X.DEPARTMENT
AND T.COUNTRY = X.COUNTRY
ORDER BY
T.COUNTRY DESC
, T.DEPARTMENT DESC
NAME DEPARTMENT COUNTRY R
---------- --------------- ------- ----------------------
James HR UK 1
John HR UK 1
Alice FI UK 2
Bob FI DE 3
Frank FI DE 3
5 rows selected

Related

excluding dups which are lower than max values in SQL

I have the following simple table (Table1), where each row is a student_ID and their name, and each student has one or multiple wins (Wins). I would like to output: Student_ID, Student_name, count of Wins, sorted by count of Wins (descending) and then Student_ID (ascending), excluding those students who have the same count of Wins which is less than the max of the Wins (i.e.5). In other words, Lizzy and Mark have the same count of wins, and 3 is lower than 5, so the output will exclude the two students, Lizzy and Mark.
From comments: "Betty, David and Cathy should be excluded", also.
Table1:
student_id
student_name
wins
1
John
YES
1
John
YES
1
John
YES
1
John
YES
1
John
YES
2
Brandon
YES
2
Brandon
YES
2
Brandon
YES
2
Brandon
YES
2
Brandon
YES
3
Lizzy
YES
3
Lizzy
YES
3
Lizzy
YES
4
Mark
YES
4
Mark
YES
4
Mark
YES
5
Betty
YES
6
David
YES
7
Cathy
YES
8
Joe
YES
8
Joe
YES
Desired output:
student_id
student_name
cnt_wins
1
John
5
2
Brandon
5
8
Joe
2
Here is my SQL in Oracle. I can't figure out what went wrong. The log says "(SELECT b.cnt_wins, count(b.student_id) has too many values".
WITH st_cte AS
(SELECT student_id, student_name, count(wins) cnt_wins
FROM Table1
GROUP BY student_id, student_name
ORDER BY count(wins) DESC, student_id)
SELECT *
FROM st_cte a
WHERE a.cnt_wins not in
(SELECT b.cnt_wins, count(b.student_id)
FROM st_cte b
WHERE b.cnt_wins <
(SELECT max(c.cnt_wins) FROM st_cte c)
GROUP BY b.cnt_wins
HAVING count(b.student_id) > 1);
There are too many values selected inside the 'in' select:
WHERE a.cnt_wins -- 1 value
not in
(SELECT b.cnt_wins, count(b.student_id) -- 2 values
FROM st_cte b
you shoud either do :
WHERE a.cnt_wins not in
(SELECT b.cnt_wins
FROM st_cte ...
or
WHERE (a.cnt_wins, count(something)) not in
(SELECT b.cnt_wins, count(b.student_id)
FROM st_cte ...
Updated based on updated requirements...
The requirement was ambiguous in that Betty, David, and Cathy seem to also meet the criteria to be removed from the result. This requirement was clarified and those rows should have been removed.
Logic has been added to allow only all max_cnt rows, plus any students with a unique count.
Also note that if wins can be any other non-null value, COUNT(wins) is not correct.
Given all that, maybe something like this is a starting point:
Fiddle
WITH cte AS (
SELECT student_id, student_name
, COUNT(wins) cnt_wins
, MAX(COUNT(wins)) OVER () AS max_cnt
FROM Table1
GROUP BY student_id, student_name
)
, cte2 AS (
SELECT cte.*
, COUNT(*) OVER (PARTITION BY cnt_wins) AS cnt_students
FROM cte
)
SELECT student_id, student_name, cnt_wins
FROM cte2
WHERE max_cnt = cnt_wins
OR cnt_students = 1
ORDER BY cnt_wins DESC, student_id
;
and to handle wins that can be other non-null values:
WITH cte AS (
SELECT student_id, student_name
, COUNT(CASE WHEN wins = 'YES' THEN 1 END) cnt_wins
, MAX(COUNT(CASE WHEN wins = 'YES' THEN 1 END)) OVER () AS max_cnt
FROM Table1
GROUP BY student_id, student_name
)
, cte2 AS (
SELECT cte.*
, COUNT(*) OVER (PARTITION BY cnt_wins) AS cnt_students
FROM cte
)
SELECT student_id, student_name, cnt_wins
FROM cte2
WHERE max_cnt = cnt_wins
OR cnt_students = 1
ORDER BY cnt_wins DESC, student_id
;
Result (with data to test the new requirement, one student (Joe) with unique counts (2)):
STUDENT_ID
STUDENT_NAME
CNT_WINS
1
John
5
2
Brandon
5
8
Joe
2
Setup:
CREATE TABLE table1 (
Student_ID int
, Student_Name VARCHAR2(20)
, Wins VARCHAR2(10)
);
BEGIN
-- Assume only wins are stored.
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 3, 'Lizzy', 'YES');
INSERT INTO table1 VALUES ( 3, 'Lizzy', 'YES');
INSERT INTO table1 VALUES ( 3, 'Lizzy', 'YES');
INSERT INTO table1 VALUES ( 4, 'Mark', 'YES');
INSERT INTO table1 VALUES ( 4, 'Mark', 'YES');
INSERT INTO table1 VALUES ( 4, 'Mark', 'YES');
INSERT INTO table1 VALUES ( 5, 'Betty', 'YES');
INSERT INTO table1 VALUES ( 6, 'David', 'YES');
INSERT INTO table1 VALUES ( 7, 'Cathy', 'YES');
INSERT INTO table1 VALUES ( 8, 'Joe', 'YES');
INSERT INTO table1 VALUES ( 8, 'Joe', 'YES');
END;
/
Correction to the original query in the question:
WITH st_cte AS
(SELECT student_id, student_name, count(wins) cnt_wins
FROM Table1
GROUP BY student_id, student_name
ORDER BY count(wins) DESC, student_id
)
SELECT *
FROM st_cte a
WHERE a.cnt_wins not in
(SELECT b.cnt_wins
FROM st_cte b
WHERE b.cnt_wins < (SELECT max(c.cnt_wins) FROM st_cte c)
GROUP BY b.cnt_wins
HAVING count(b.student_id) > 1
)
;

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.

Is it possible to update rows randomly with a group of set values?

I have a work assignment table that I would like help with. What I would like to do is randomly assign peoples names to the rows in the table. For example, the table currently looks like:
TASK |NAME
1 Get Chicken |
2 Clean Chicken|
3 Cook Chicken |
4 Eat Chicken |
5 Eat Corn |
6 Takeout Trash|
I have 4 employees that I want to assign these tasks to, but do not want to show any favoritism. Here is what that table looks like:
NAME
John
Lucy
Fred
Jasmine
How can I randomly update the NAME field based on the above names?
edit based on comments. I changed the number of tasks to something not divisible by 4. In this case the number of tasks is now 6. I want to make it to where no one can get 2 or more tasks more then the rest of their colleagues. But in this case, it's ok for someone to have 1 more task then their colleagues. he result should be something like (but random):
TASK |NAME
1 Get Chicken |John
2 Clean Chicken|Jasmine
3 Cook Chicken |Lucy
4 Eat Chicken |Fred
5 Eat Corn |Fred
6 Takeout Trash|Jasmine
Here is a pure SQL way to do it.
MERGE INTO so_tasks t USING (
WITH numbered_tasks AS ( SELECT t.*,
row_number() OVER (ORDER BY dbms_random.value) task_number,
count(*) OVER () total_tasks FROM so_tasks t ),
numbered_employees AS ( SELECT e.*,
row_number() OVER (ORDER BY dbms_random.value) employee_number,
count(*) OVER () total_employees FROM so_employees e)
SELECT nt.task,
ne.name
FROM numbered_tasks nt
INNER JOIN numbered_employees ne
ON ne.employee_number-1 = mod(nt.task_number-1, ne.total_employees) ) u
ON ( t.task = u.task )
WHEN MATCHED THEN UPDATE SET t.name = u.name;
It sorts each list randomly and assigned a number to each row in each list. It then gets the row from the employee list whose number matched the task number MOD the total number of employees.
Here is a fully example:
Create tables
CREATE TABLE so_tasks
( task VARCHAR2(30) NOT NULL PRIMARY KEY,
name VARCHAR2(30) );
INSERT INTO so_tasks ( task ) VALUES ('Get Chicken');
INSERT INTO so_tasks ( task ) VALUES ('Clean Chicken');
INSERT INTO so_tasks ( task ) VALUES ('Cook Chicken');
INSERT INTO so_tasks ( task ) VALUES ('Eat Chicken');
INSERT INTO so_tasks ( task ) VALUES ('Eat Corn');
INSERT INTO so_tasks ( task ) VALUES ('Takeout Trash');
CREATE TABLE so_employees
( name VARCHAR2(30) NOT NULL PRIMARY KEY );
INSERT INTO so_employees ( name ) VALUES ('John');
INSERT INTO so_employees ( name ) VALUES ('Lucy');
INSERT INTO so_employees ( name ) VALUES ('Fred');
INSERT INTO so_employees ( name ) VALUES ('Jasmine');
COMMIT;
Merge
MERGE INTO so_tasks t USING (
WITH numbered_tasks AS ( SELECT t.*,
row_number() OVER (ORDER BY dbms_random.value) task_number,
count(*) OVER () total_tasks FROM so_tasks t ),
numbered_employees AS ( SELECT e.*,
row_number() OVER (ORDER BY dbms_random.value) employee_number,
count(*) OVER () total_employees FROM so_employees e)
SELECT nt.task,
ne.name
FROM numbered_tasks nt
INNER JOIN numbered_employees ne
ON ne.employee_number-1 = mod(nt.task_number-1, ne.total_employees) ) u
ON ( t.task = u.task )
WHEN MATCHED THEN UPDATE SET t.name = u.name;
Results
SELECT * FROM so_tasks;
+---------------+---------+
| TASK | NAME |
+---------------+---------+
| Get Chicken | John |
| Clean Chicken | Jasmine |
| Cook Chicken | Lucy |
| Eat Chicken | Fred |
| Eat Corn | Jasmine |
| Takeout Trash | Fred |
+---------------+---------+
Your exact assignments for each task will be different, but there will never be more than a one task difference between any two employees.
You can give the tasks random sequential numbers and the employees another random sequential number and then join the two tables using those numbers and then use a MERGE statement to update the table correlating on the ROWID pseudo-column to uniquely identify each task.
Oracle Setup:
CREATE TABLE table_name ( task VARCHAR2(20), name VARCHAR2(20) );
INSERT INTO table_name ( TASK )
SELECT 'Get Chicken' FROM DUAL UNION ALL
SELECT 'Clean Chicken' FROM DUAL UNION ALL
SELECT 'Cook Chicken' FROM DUAL UNION ALL
SELECT 'Eat Chicken' FROM DUAL UNION ALL
SELECT 'Eat Corn' FROM DUAL UNION ALL
SELECT 'Takeout Trash' FROM DUAL;
CREATE TABLE employees ( NAME ) AS
SELECT 'John' FROM DUAL UNION ALL
SELECT 'Lucy' FROM DUAL UNION ALL
SELECT 'Fred' FROM DUAL UNION ALL
SELECT 'Jasmine' FROM DUAL;
Merge:
MERGE INTO table_name dst
USING (
WITH random_tasks ( rid, rn ) AS (
SELECT ROWID,
ROW_NUMBER() OVER ( ORDER BY DBMS_RANDOM.VALUE )
FROM table_name
),
random_names ( name, rn, num_employees ) AS (
SELECT name,
ROW_NUMBER() OVER ( ORDER BY DBMS_RANDOM.VALUE ),
COUNT(*) OVER ()
FROM employees
)
SELECT rid,
name
FROM random_tasks t
INNER JOIN
random_names n
ON ( MOD( t.rn, n.num_employees ) + 1 = n.rn )
) src
ON ( src.rid = dst.ROWID )
WHEN MATCHED THEN
UPDATE SET name = src.name;
Result:
SELECT * FROM table_name;
TASK | NAME
:------------ | :------
Get Chicken | John
Clean Chicken | Jasmine
Cook Chicken | Fred
Eat Chicken | Lucy
Eat Corn | Fred
Takeout Trash | Lucy
db<>fiddle here
Assuming you're fine with a PL/SQL solution (you could do it in a single update statement but unless it's performance critical, I'd find the loop easier to follow)
begin
for src in (select t.task_id, e.name
from (select t.*,
row_number() over (order by dbms_random.value) rnk
from task t) t
join
(select e.*,
row_number() over (order by dbms_random.value) rnk,
count(*) over () num_emps
from employee e) e
on( mod( t.rnk, e.num_emps ) = e.rnk - 1 ) )
loop
update task
set name = src.name
where task_id = src.task_id;
end loop;
end;
/
Basically, this is randomly sorting both lists and then going down the list of tasks and assigning the next employee to that task. If the number of tasks isn't a multiple of the number of employees, some employees will get an extra task but no employee will have more than 1 more task than another.

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

Query to get only the duplicate data

I have a table with data
ID Name
1 John
2 Robert
3 John
4 Sam
5 Jack
6 Sam
Now i want ony the the duplicate names ony through query
ie..,
Name
John
Sam
SELECT Name
FROM YourTable
GROUP BY Name
HAVING COUNT(*) > 1
CREATE TABLE MyTable (
ID int
, Name nvarchar(50)
)
INSERT MyTable VALUES ( 1, 'John' )
INSERT MyTable VALUES ( 2, 'Robert' )
INSERT MyTable VALUES ( 3, 'John' )
INSERT MyTable VALUES ( 4, 'Sam' )
INSERT MyTable VALUES ( 5, 'Jack' )
INSERT MyTable VALUES ( 6, 'Sam' )
SELECT
Name
FROM
MyTable
GROUP BY
Name
HAVING
COUNT(*) > 1
DROP TABLE MyTable
Results:
Name
--------------------------------------------------
John
Sam
with temp as (
select Name, count(Name) as countOfNames
from myTable
group by Name
)
select Name from temp
where countOfNames > 1
select columnname,count(column name) from tablename group by column name having count(*)>1