How would one find the total cases by manager - sql

I have two tables: one that has cases alongside the employees who resolve them and one that has information on the employees including their managers. I want to make a query that finds the amount of cases under each manager. Currently my query looks like this
select m.id, COUNT(distinct case when e.manager_id = m.id and a.resolver_id = e.id then a.ticket_id ELSE NULL END)
from tickets a
left join employee_info e on a.resolver_id= e.id
join employee_info m on e.manager_id = m.id
group by m.id
This query only gave me the count of the direct employees under a manager, not the count of all employees (including the ones under the people reporting directly to the manager). How would I tweak my query to include all employees under a person?
EDIT
So this is an obfuscated example of how my tables look
Ticket_id resolver_id
0001 11
0002 11
0003 13
0004 13
0005 12
0006 19
Id manager_id
11 01
12 01
13 11
19 12
and this is how I want my result to look
Id count
01 6
11 4
12 2
13 2
19 1

You essentially have a "tree" of employees, and want to recursively traverse it. This is what recursive CTEs are for.
We'll define a "subordinate" cte, that will tell us all the "subordinate relationships" between all employees, including "multi-level" subordination. If A is manager of B, and B is manager of C, then A has as subordinates B and C.
First, we start with all employees being their own subordinates. This is the non-recursive part.
SELECT id AS id1, id AS id2
FROM employee
Then, we "expand one level down" the subordinate relationships. if B is subordinate of A, all employees with B as manager are also subordinates of A. id1 stys as-is, id2 becomes the id of the "lower" employee. This is the recursive part.
SELECT s.id1, e.id
FROM subordinate s
JOIN employee e ON s.id2 = e.manager_id
Then we stick both in a recursive CTE. Postgres will iterate the 2nd part as many times as needed, until no new rows are added. This way we recusrively traverse the entire employee tree.
WITH RECURSIVE subordinate AS (
SELECT id AS id1, id AS id2
FROM employee
UNION
SELECT s.id1, e.id
FROM subordinate s
JOIN employee e ON s.id2 = e.manager_id
)
select * from subordinate order by id1, id2;
Let's check the result:
id1 | id2
-----+-----
1 | 1
1 | 11
1 | 12
1 | 13
1 | 19
11 | 11
11 | 13
12 | 12
12 | 19
13 | 13
19 | 19
Looking great! 1 has everyone as subordinates. 11 has 11 and 13, and lowst employees such as 13 and 19 only have themselves.
Once we have done this, the rest is easy.
We can do another CTE counting the resolved tickets per employee:
SELECT resolver_id as id, COUNT(*) as count
FROM tickets
GROUP BY resolver_id
and then we stick everything into the final query. For every employee, sum the resolved count of all its subordinates.
WITH RECURSIVE subordinate AS (
SELECT id AS id1, id AS id2
FROM employee
UNION
SELECT s.id1, e.id
FROM subordinate s
JOIN employee e ON s.id2 = e.manager_id
),
resolved as (
SELECT resolver_id as id, COUNT(*) as count
FROM tickets
GROUP BY resolver_id
)
SELECT s.id1, SUM(r.count)
FROM subordinate s
JOIN resolved r ON r.id = s.id2
GROUP BY s.id1;

Related

SQL query to find the faculty which have taught every subject

I need to write a SQL query to find the faculty which has taught every subject (ie Sam)
With nested queries
Without using aggregate functions (no count, avg, min etc).
I can't seem to figure this out, would really appreciate some help =)
Faculty
fid
fname
fqualifications
fexperience
salary
deptname
100
Sam
ME CS
10
100000
IT
101
John
ME IT
8
80000
IT
102
Max
ME CS
9
90000
CS
103
Jenny
ME CS
5
50000
CS
Course
cid
cname
semester
1
SE
4
2
WT
4
3
CG
5
4
DBMS
5
Teaches
fid
cid
year
100
1
2019
100
2
2018
100
3
2020
100
4
2021
101
1
2017
101
2
2018
102
2
2018
102
3
2019
103
3
2020
103
4
2021
I used this query to find the output but according to the question I can't.
select * from faculty f
-> inner join teaches t
-> on f.fid=t.fid
-> inner join course c
-> on t.cid=c.cid
-> group by f.fid,f.fname
-> having count(*)=4;
OUTPUT:
fid
fname
fqualifications
fexperience
salary
deptname
fid
cid
year
cid
cname
semester
100
Sam
ME CS
10
100000
IT
100
1
2019
1
SE
4
Not the most efficient way to proceed, but with the requirements given to you, I would try and rephrase the query like this:
"A faculty that has taught every subject is a faculty that has not skipped even one subject".
Now, faculties that have skipped a subject will have a NULL when LEFT JOINed with the syllabus and all the subjects. Pseudo-SQL:
SELECT DISTINCT faculty.id FROM faculties
LEFT JOIN has_taught ON (has_taught.faculty_id = faculty.id)
LEFT JOIN subjects ON (has_taught.subject_id = subjects.id)
WHERE has_taught.faculty_id IS NULL;
or in some databases you maybe need
SELECT DISTINCT faculty.id FROM faculties
CROSS JOIN subjects
LEFT JOIN has_taught ON
(has_taught.faculty_id = faculty.id AND has_taught.subject_id = subjects.id)
WHERE has_taught.faculty_id IS NULL;
So, faculties that are NOT IN this list would naturally be
SELECT * FROM faculties
WHERE faculty.id NOT IN (
SELECT DISTINCT faculty.id ...
);
and this should only use nested queries, as requested.
Or with a further join
SELECT faculties.* FROM faculties
LEFT JOIN (
SELECT DISTINCT faculty.id ...
) AS they_skipped_some
ON (they_skipped_some.id = faculties.id)
WHERE they_skipped_some.id IS NULL

joining 2 tables in sql which has no dependency on each other

I have 2 tables in the following way
Table 1:
e_id e_name e_salary e_age e_gender e_dept
---------------------------------------------------
1 sam 95000 45 male operations
2 bob 80000 21 male support
3 ann 125000 25 female analyst
Table 2:
d_salary d_age d_gender e_dept
----------------------------------
34000 25 male Admin
56000 41 female Tech
77000 35 female HR
I want the output something like this:
e_id e_name e_salary e_age e_gender e_dept d_salary d_age d_gender e_dept
1 sam 95000 45 male operations 34000 25 male Admin
2 bob 80000 21 male support 56000 41 female Tech
3 ann 125000 25 female analysts 77000 35 female HR
There is no dependency between the tables. No common columns. No primary or foreign key.
I tried using cross join that results in duplicate rows because it works on M X N
I am new to this SQL thing. Can someone help me, please? Thanks in advance
Though I didn't get the reason behind your desired output but you can get that with below query:
select a.e_id ,a.e_name ,a.e_salary ,a.e_age ,a.e_gender ,a.e_dept,b.d_salary ,b.d_age ,b.d_gender ,b.e_dept
from
(select e_id ,e_name ,e_salary ,e_age ,e_gender ,e_dept, row_number()over(order by e_id)rn
from table1)a
inner join
(select d_salary d_age d_gender e_dept,row_number()over(order by d_salary) rn
from table 2) b
on a.rn=b.rn
Generally you can create a row count using the row_number() window function on both tables and use this as join criterion. But this requires a certain order for both tables, which means that you have explicitly tell the query why is the Admin record ordered first and must be joined on the first record of table 1:
SELECT
*
FROM (
SELECT
*,
row_number() OVER (ORDER BY e_id) as row_count -- assuming e_id is your order criterion
FROM table1
) t1
JOIN (
SELECT
*,
row_number() OVER (ORDER BY /*whatever you expect to be ordered*/) as row_count
FROM table2
) t2
ON t1.row_count = t2.row_count

An SQL query to pull count of employees absent under each manager on all dates

The objective of the query is get a count of employees absent under each manager.
Attendance (Dates when employees are present)
id date
1 16/05/2020
2 16/05/2020
1 17/05/2020
2 18/05/2020
3 18/05/2020
Employee
id manager_id
1 2
2 3
3 NA
The desired output should be in this format:
Date manager_id Number_of_absent_employees
16/05/2020 NA 1
17/05/2020 3 1
17/05/2020 NA 1
18/05/2020 2 1
I have tried writing code but partially understood it, intuition being calculating total number of actual employees under each manager and subtracting it from number of employees present on given day. Please help me in completing this query, many thanks!
with t1 as /* for counting total employees under each manager */
(
select employee.manager_id,count(*) as totalc
from employee as e
inner join employee on e.employee_id=employee.employee_id
group by employee.manager_id
)
,t2 as /* for counting total employees present each day */
(
select Attendence.date, employee.manager_id,count(*) as present
from employee
Left join Attendence on employee.employee_id=Attendence.employee_id
group by Attendence.date, employee.manager_id
)
select * from t2
Left join t1 on t2.manager_id=t1.manager_id
order by date
Cross join the distinct dates from Attendance to Employee and left join Attendance to filter out the matching rows.
The remaining rows are the absences so then you need to aggregate:
select d.date, e.manager_id,
count(*) Number_of_absent_employees
from (select distinct date from Attendance) d
cross join Employee e
left join Attendance a on a.date = d.date and a.id = e.id
where a.id is null
group by d.date, e.manager_id
See the demo.
Results:
| date | manager_id | Number_of_absent_employees |
| ---------- | ---------- | -------------------------- |
| 16/05/2020 | NA | 1 |
| 17/05/2020 | 3 | 1 |
| 17/05/2020 | NA | 1 |
| 18/05/2020 | 2 | 1 |
Try this query. In first cte just simplify your code. And in the last query calculate absent employees.
--in this CTE just simplify counting
with t1 as /* for counting total employees under each manager */
(
select employee.manager_id,count(*) as totalc
from employee
group by manager_id
)
,t2 as
(
select Attendence.date, employee.manager_id,count(*) as present
from employee
Left join Attendence on employee.employee_id=Attendence.employee_id
group by Attendence.date, employee.manager_id
)
select t2.date,t2.manager_id, (t1.totalc-t2.present) as employees_absent from t2
Left join t1 on t2.manager_id=t1.manager_id
order by date
Select ec.manager_id, date, (total_employees - employee_attended) as employees_absent from
(Select manager_id, count(id) as total_employees
from employee
group by manager_id) ec,
(Select distinct e.manager_id, a.date, count(a.id) over (partition by e.manager_id, a.date) as employee_attended
from Employee e, attendence, a
where e.id = a.id(+)) ea
where ec.manager_id = ea.manager_id (+)
I guess this should work

Logical Processing of Recursive CTE

I don't understand why the anchor is not called when the recursive member calls the cte.
Why does it go to the latest record (?) in the recursive part instead?
WITH Managers AS
(
--initialization
SELECT EmployeeID, LastName, ReportsTo
FROM Employees
WHERE ReportsTo IS NULL
UNION ALL
--recursive execution
SELECT e.employeeID,e.LastName, e.ReportsTo
FROM Employees e INNER JOIN Managers m
ON e.ReportsTo = m.employeeID
)
SELECT * FROM Managers
This is famous Microsoft sample database Northwind (immutable since 1997). The Emploeees table demonstrates hierarchical data and usage of recursive CTE (not available in 1997) to access it.
For better understanding / testing / training purpose add an extra column
WITH Managers AS
(
--initialization
SELECT EmployeeID, LastName, ReportsTo, /*extra column*/ 0 [level]
FROM Employees
WHERE ReportsTo IS NULL
UNION ALL
--recursive execution
SELECT e.employeeID,e.LastName, e.ReportsTo, [level]+1
FROM Employees e
INNER JOIN Managers m ON e.ReportsTo = m.employeeID
)
SELECT * FROM Managers
This is result set.
EmployeeID LastName ReportsTo level
----------- -------------------- ----------- -----------
2 Fuller NULL 0 --anchor call
--now 'Managers' CTE is the anchor (level 0)
1 Davolio 2 1
3 Leverling 2 1
4 Peacock 2 1
5 Buchanan 2 1
8 Callahan 2 1
--now 'Managers' CTE is resultset level 1
6 Suyama 5 2
7 King 5 2
9 Dodsworth 5 2
So the anchor executes always, recursive part executes iff the anchor returns row(s).

Select rows where every child row meets a condition

In my Oracle DB, I have two tables in a one-to-many relationship: Managers and Employees.
+------------+-------+------------+
| Manager_ID | Name | Department |
+------------+-------+------------+
| 1 | Steve | Sales |
| 2 | Ben | Sales |
| 3 | Molly | Accounts |
+------------+-------+------------+
+-------------+------------+--------+-----+
| Employee_ID | Manager_ID | Name | Age |
+-------------+------------+--------+-----+
| 1 | 1 | Kyle | 25 |
| 2 | 1 | Gary | 31 |
| 3 | 2 | Renee | 31 |
| 4 | 2 | Oliver | 32 |
+-------------+------------+--------+-----+
How do I select only those Managers where every one of his Employees is over the age of 30?
In my example data, the only Manager who meets this condition is Ben, because both of his employees are over 30.
I thought something like this would do it, but it's wrong:
SELECT m.manager_id
FROM managers m
WHERE m.manager_id IN (SELECT e.manager_id
FROM employees e
GROUP BY e.manager_id
HAVING e.age > 30)
Use not exists :
select m.*
from manager m
where not exists (select 1
from Employees e
where e.Manager_ID = m.Manager_ID and e.Age < 30
) and
exists (select 1 from Employees e where e.Manager_ID = m.Manager_ID)
The only thing I don't like about Yogesh's answer (which I upvoted, since it's probably the way I'd write it) is that you have to go to the employees table a second time, to make sure the manager actually has at least one employee.
On the plus side, the NOT EXISTS that Yogesh used will allow Oracle to stop looking at a manager's employees once it finds one that is too young. So, maybe it's a toss-up.
I'll offer this alternative. It is shorter than the NOT EXISTS and does not have to go to the employees table a second time.
SELECT m.*
FROM manager m
CROSS APPLY (
SELECT min(age) min_age
FROM employee e
WHERE e.manager_id = m.manager_id ) ma
where ma.min_age >= 30;
Using sub-query for counts
SQL> WITH manager(Manager_ID, Name, Department) AS (
2 SELECT 1, 'Steve', 'Sales' FROM dual UNION ALL
3 SELECT 2, 'Ben', 'Sales' FROM dual UNION ALL
4 SELECT 3, 'Molly', 'Accounts' FROM dual),
5 employee(Employee_ID, Manager_ID, Name, Age) AS (
6 SELECT 1 , 1, 'Kyle', 25 FROM dual UNION ALL
7 SELECT 2 ,1, 'Gary', 31 FROM dual UNION ALL
8 SELECT 3, 2, 'Renee', 31 FROM dual UNION ALL
9 SELECT 4, 2 , 'Oliver', 32 FROM dual)
10 ---------------------------
11 --- End of data preparation
12 ---------------------------
13 SELECT m.name
14 FROM manager m
15 JOIN (SELECT manager_id,
16 COUNT(1) total,
17 COUNT(CASE WHEN age > 30 THEN 1 ELSE NULL END) age_30_above
18 FROM employee
19 GROUP BY manager_id) ee
20 ON m.manager_id = ee.manager_id
21 WHERE total = age_30_above;
Output
NAME
-----
Ben
Your query will be:
SELECT m.name
FROM manager m
JOIN (SELECT manager_id,
COUNT(1) total,
COUNT(CASE WHEN age > 30 THEN 1 ELSE NULL END) age_30_above
FROM employee
GROUP BY manager_id) ee
ON m.manager_id = ee.manager_id
WHERE total = age_30_above;
SELECT manager_id
FROM employees -- managers
minus
select manager_id
from employees
where age <= 30
You can use ALL function like this:
SELECT m.manager_id
FROM managers m
WHERE (30 <= ALL (SELECT e.age FROM employees e WHERE e.manager_id = m.manager_id));
You might want to reverse the conditions, select all managers, who dont have any employee below 30
select * from managers
where manager_id not in (select manager_id
from employees
where age < 30)