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

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

Related

How to use GROUP BY when fetching values from More than one Table [duplicate]

This question already has answers here:
Get top 1 row of each group
(19 answers)
Closed 4 months ago.
We have 2 Tables Employees and Department.
We want to show the maximum salary from each department and their corresponding employee name from the employee table and the department name from the department table.
Employee Table
EmpId | EmpName |salary |DeptId
101 shubh1 1000 1
101 shubh2 4000 1
102 shubh3 3000 2
102 shubh4 5000 2
103 shubh5 12000 3
103 shubh6 1000 3
104 shubh7 1400 4
104 shubh8 1000 4
Department Table
DeptId | DeptName
1 ComputerScience
2 Mechanical
3 Aeronautics
4 Civil
I tried doing it but was getting error
SELECT DeptName FROM Department where deptid IN(select MAX(salary),empname,deptid
FROM Employee
GROUP By Employee.deptid)
Error
Token error: 'Column 'Employee.EmpName' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.' on server 4e0652f832fd executing on line 1 (code: 8120, state: 1, class: 16)
Can someone please help me.
select salary
,EmpName
,DeptName
from (
select e.salary
,e.EmpName
,d.DeptName
,rank() over(partition by e.DeptId order by e.salary desc) as rnk
from Employee e join Department d on d.DeptId = e.DeptId
) t
where rnk = 1
salary
EmpName
DeptName
4000
shubh2
ComputerScience
5000
shubh4
Mechanical
12000
shubh5
Aeronautics
1400
shubh7
Civil
Fiddle
Now that I know it's MS SQL Server, technically; we could use cross or outer Apply; it's a table value function not a join per say... but this will depend on the version of SQL Server; and if you want data if it doesn't exist in another
I find this the "Best" Design pattern to use for this type of query.
What the engine does is for each record in department, it runs a query for the employees Finding those in that department returning the 1 record having the max salary. With top we could specify with ties to return more than one. but we would need to know how to handle Ties of salary. Use top 1 with ties or order the results so you get the "Top" result you want.
Demo: dbfddle.uk
SELECT Sub.empName, Sub.Salary, D.DeptName
FROM Department D
CROSS Apply (SELECT Top 1 *
--(SELECT TOP 1 with Ties * -- could use this if we ties
FROM Employee E
WHERE E.DeptID = D.DeptID
ORDER BY Salary Desc) Sub --add additional order by if we don't want ties.
The cross apply gives us:
+---------+--------+-----------------+
| empName | Salary | DeptName |
+---------+--------+-----------------+
| shubh2 | 4000 | ComputerScience |
| shubh4 | 5000 | Mechanical |
| shubh5 | 12000 | Aeronautics |
| shubh7 | 1400 | Civil |
+---------+--------+-----------------+
Before window functions, before cross Apply or lateral... We'd write an inline view
It would get us the max salary for each dept, we then join that back to our base tables to find the employee within each dept with max salary...
Demo: DbFiddle.uk
SELECT E.*, D.*
FROM Employee E
INNER JOIN Department D
on E.DeptID = D.DeptID
INNER JOIN (SELECT MAX(SALARY) maxSal , DeptID
FROM Employee
GROUP BY DeptID) Sub
on Sub.DeptID = E.DeptID
and Sub.MaxSal = E.Salary
One has to do a join to get the department info an the employee info. However, we can eliminate the join for salarymax by using exists and correlation instead.
Demo DbFiddle.uk
SELECT E.*, D.*
FROM Employee E
INNER JOIN Department D
on E.DeptID = D.DeptID
WHERE EXISTS (SELECT MAX(Sub.SALARY) maxSal , Sub.DeptID
FROM Employee Sub
WHERE sub.DeptID=E.DeptID --correlation 1
GROUP BY Sub.DeptID
HAVING E.Salary = max(Sub.Salary)) --correlation 2
We could eliminate the last join too I suppose:
Demo: Dbfiddle.uk
SELECT E.*, (SELECT DeptName from Department where E.DeptID = DeptID)
FROM Employee E
WHERE EXISTS (SELECT MAX(Sub.SALARY) maxSal , Sub.DeptID
FROM Employee Sub
WHERE sub.DeptID=E.DeptID --correlation 1
GROUP BY Sub.DeptID
HAVING E.Salary = max(Sub.Salary)) --correlation 2
The top 3 give us this result:
+-----+---------+--------+--------+--------+-----------------+
| id | empName | salary | deptID | DeptID | DeptName |
+-----+---------+--------+--------+--------+-----------------+
| 101 | shubh2 | 4000 | 1 | 1 | ComputerScience |
| 102 | shubh4 | 5000 | 2 | 2 | Mechanical |
| 103 | shubh5 | 12000 | 3 | 3 | Aeronautics |
| 104 | shubh7 | 1400 | 4 | 4 | Civil |
+-----+---------+--------+--------+--------+-----------------+

How do I find the highest salary from each department using SUBQUERIES

I'm really new to this and this particular question has been bugging me for days. I do know there are similar questions to this but I kept wondering how it would be done in subqueries.
SALARY TABLE
[Emp_ID] [SalaryPM]
001 | 10,500
002 | 50,000
003 | 8,000
004 | 10,000
DEPT TABLE
[Emp_ID] [Dept_ID]
001 | A
002 | B
003 | C
004 | C
I want it to look like this
[Emp_ID] [Dept_ID] [SalaryPM]
001 | A | 10,000
002 | B | 50,000
004 | C | 10,000
What I have tried so far, but it only gives the highest salary of the employee##
SELECT * FROM DEPT
WHERE EMP_ID IN
(SELECT Emp_ID
FROM SALARY
WHERE SalaryPM = (SELECT MAX(SalaryPM)
FROM SALARY));
Would this qualify as a subquery solution?
select *
from (
select s.*, e.deptid,
rank() over(partition by e.dept order by s.salaries desc) rn
from employees e
inner join salaries s on s.id = e.id
) rn
where rn = 1
Note: your does not look good. The data you are showing suggests a 1-1 relationship between the two tables (wich I called employees and salaries): if so, both tables should be combined in a single table.
I would be good to know what do you mean by "using subqueries" ;)
here's another solution using subquery in the SELECT clause
SELECT
d.id,
d.deptid,
(
SELECT
MAX(s.salary)
FROM
my_salaries s
WHERE
s.id = d.id
) max_salary
FROM
my_departments d;
here's solution without joining tables (IMHO not joining tables is just overcomplicating things). Why 'not using join' is so important for you?
SELECT
sub.id,
MAX(sub.deptid),
MAX(sub.salary)
FROM
(
SELECT
d.id,
d.deptid,
NULL salary
FROM
my_departments d
UNION ALL
SELECT
s.id,
NULL deptid,
s.salary
FROM
my_salaries s
) sub
GROUP BY
sub.id
ORDER BY
sub.id;

SQL Database Table Joining to create an IdealTable

I need some helps to join the tables I have currently.
Leave, Overtime And Roster's Date, EmployeeID need to match
Note: ShiftDuration is set to default value = 8.25
Note: Leave and Overtime table will only have entries when an employee applies for leave and overtime.
Employee
EmplyeeeID | Username | Password | GivenName | FamilyName | TeamID | ContactNo | StaffType
------------------------------------------------------------------------------------------
123 123 abc John Snow 1 999 1
1234 1234 abcd Jack Waller 2 223 1
12345 12345 abcde Ali Saw 1 123 1
123456 123456 abcdef Peter Peter 2 223 1
1234567 1234567 abcdeg Bryan Peter 1 333 1
Roster
Duty_ID | EmployeeID | Date | ShiftType | ShiftDuration
--------------------------------------------------------------------
2 123 2018-05-05 1 8.25
4 1234 2018-05-04 1 8.25
5 12345 2018-05-05 1 8.25
7 123456 2018-05-04 1 8.25
8 1234567 2018-05-05 1 8.25
Overtime
OTID | EmployeeID | Date | OT_Duration | OT_Reason
------------------------------------------------------------
2 1234 2018-05-04 2 Cover Duty
Leave
LeaveID | EmployeeID | Date | Duration_Off | Reason
----------------------------------------------------------
3 123 2018-05-05 2 NIL
IdealTable (Via Query)
Date | EmployeeID | GivenName | FamilyName | TeamID | ShiftType | ShiftDuration | Duration_Off | OT_Duration | Total_Hours
---------------------------------------------------------------------------------------------------------------------------------
2018-05-05 123 John Snow 1 1 8.25 2 0 6.25
2018-05-04 1234 Jack Waller 1 1 8.25 0 2 10.25
2018-05-05 12345 Ali Saw 1 1 8.25 0 0 8.25
2018-05-04 123456 Peter Peter 1 1 8.25 0 0 8.25
2018-05-05 1234567 Bryan Peter 1 1 8.25 2 0 8.25
I have 4 tables, they are Employee, Leave, Overtime, Roster
Employee
-EmployeeID (PK)
-Username
-Password
-GivenName
-FamilyName
-TeamID
-ContactNo
-StaffType
Leave
-LeaveID (PK)
-EmployeeID (FK)
-Date
-Duration_Off
-Reason
Overtime
-OTID (PK)
-EmployeeID (FK)
-Date
-OT_Duation
-OT_Reason
Roster
-DutyID (PK)
-EmployeeID (FK)
-Date
-ShiftType
-Shift Duration (Default Value = 8.25)
What I am trying to do is join the data from this 4 tables using Query
Ideal Table
-Date (From Leave, Overtime and Roster Table)
-EmployeeID (Employee Table)
-GivenName (Employee Table)
-FamilyName (Employee Table)
-TeamID (Employee Table)
-ShiftType (Roster Table)
-ShiftDuration (Roster Table)
-Duration_Off (Leave Table)
-OT_Duration (Overtime Table)
-Total_Hours (Calculation from joint table [(ShiftDuration + OT_Duration) - Duration_Off]
My database diagram design Do ignore the TimeData table as I initially wanted to use the TimeData table to achieve the IdealTable
My current query
USE [SMRT Dashboard]
GO
;With Dates
AS
(
SELECT [Date] FROM dbo.Roster
UNION
SELECT [Date] FROM dbo.Leave
UNION
SELECT [Date] FROM dbo.Overtime
),
Work_Matrix
AS
(
SELECT EmployeeID,[Date],ShiftType,ShiftDuration,CAST(NULL AS Decimal(30,2)) AS Duration_Off,CAST(NULL AS Decimal(30,2)) AS OT_Duration
FROM dbo.Roster
UNION ALL
SELECT EmployeeID,[Date], NULL, NULL,Duration_Off
FROM dbo.Leave
UNION ALL
SELECT EmployeeID,[Date],NULL,NULL,NULL,OT_Duration
FROM dbo.Overtime
)
SELECT d.[Date],
e.EmployeeID,
e.GivenName,
e.FamilyName,
e.TeamID,
w.ShiftType,
w.ShiftDuration,
w.Duration_Off,
w.OT_Duration,
w.Total_Hours
FROM Dates d
INNER JOIN
(
SELECT EmployeeID,
[Date],
MAX(ShiftType) AS ShiftType,
SUM(ShiftDuration) AS ShiftDuration,
SUM(Duration_Off) AS Duration_Off,
SUM(OT_Duration) AS OT_Duration,
SUM(ShiftDuration) + SUM(OT_Duration) - SUM(Duration_Off) AS Total_Hours
FROM Work_Matrix
GROUP BY EmployeeID,
[Date]
)w
ON d.[Date] = w.[Date]
JOIN dbo.Employee e
ON e.EmployeeID = w.EmployeeID
Current Errors:
Msg 205, Level 16, State 1, Line 4
All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.
I have not tested this but you may now check this. Hope your problem will be solved.
SELECT
a.EmployeeID,
a.GivenName,
a.FamilyName,
a.TeamID,
d.ShiftType,
d.ShiftDuration,
b.Duration_Off,
c.OT_Duration,
b.Date,
(d.ShiftDuration + c.OT_Duration) - b.Duration_Off as Total_Hours
FROM Employee a
INNER JOIN Roster d ON a.EmployeeID = d.EmployeeID
LEFT JOIN Leave b ON a.EmployeeID = b.EmployeeID
LEFT JOIN Overtime c ON a.EmployeeID = c.EmployeeID
I assume Date is same from all three tables (Leave, Overtime and Roster Table)
Try this query :
select
l.Date,
o.Date,
r.Date,
e.EmployeeID,
e.GivenName,
e.FamilyName,
e.TeamID,
r.ShiftType,
r.ShiftDuration,
l.Duration_Off,
o.OT_Duration,
((r.ShiftDuration+o.OT_Duration)-l.Duration_Off) as Total_Hours
FROM Employee e
INNER JOIN Leave l ON e.EmployeeID = l.EmployeeID
INNER JOIN Overtime o ON e.EmployeeID = o.EmployeeID
INNER JOIN Roster r ON r.EmployeeID = r.EmployeeID;
Try this Query ! You will get the correct output .
SELECT
e.EmployeeID AS 'Emp ID',
e.GivenName AS 'Emp Name',
l.Date AS Date,
e.FamilyName AS 'Family Name',
e.TeamID AS 'Team ID',
r.ShiftType AS 'Shift Type',
r.ShiftDuration AS 'Shift Duration',
l.Duration_Off AS 'Duration Off',
o.OT_Duration AS 'OT Duration',
(r.ShiftDuration + o.OT_Duration) - l.Duration_Off as 'Total Hours'
FROM
Employee e,
Leave l,
Overtime o,
Roster r
WHERE
e.EmployeeID = l.EmployeeID
AND
e.EmployeeID = o.EmployeeID
AND
e.EmployeeID = r.EmployeeID
I think the issue here is that an employee may have leave, may have overtime, or neither. By using INNER JOIN, you're filtering down the set of employees to ones that have Leave, and on a Roster, and have Overtime.
I created a SqlFiddle with the data you present (see here) that should get you closer.
SELECT
COALESCE(r.Date, o.Date, l.Date) as Date,
e.EmployeeID,
e.GivenName,
e.FamilyName,
e.TeamID,
r.ShiftType,
r.ShiftDuration,
IFNULL(l.Duration_Off, 0) as Duration_Off,
IFNULL(o.OT_Duration, 0) as OT_Duration,
r.ShiftDuration + IFNULL(o.OT_Duration, 0) - IFNULL(l.Duration_Off, 0) as Total_Hours
FROM Employee e
INNER JOIN Roster r on
e.EmployeeID = r.EmployeeID
LEFT JOIN Overtime o on
e.EmployeeID = o.EmployeeID
LEFT JOIN `Leave` l on
e.EmployeeID = l.EmployeeID
There's a few cavaets that this query won't solve:
If an employee has multiple leave entries or multiple overtime entries, the person will be listed N times. You can potentially use a GROUP BY statement to deal with this
The sample data in your question doesn't reconcile correctly. For example, Employe 1234 has a shift on 5/4/2018 and OT on 5/5/2018 but gets 10.25 on 5/4/2018.
If you use TimeData, this becomes a lot easier (assuming one record per date per employee):
SELECT
COALESCE(r.Date, o.Date, l.Date) as Date,
e.EmployeeID,
e.GivenName,
e.FamilyName,
e.TeamID,
r.ShiftType,
IFNULL(r.ShiftDuration, 0) as ShiftDuration,
IFNULL(l.Duration_Off, 0) as Duration_Off,
IFNULL(o.OT_Duration, 0) as OT_Duration,
IFNULL(r.ShiftDuration, 0) + IFNULL(o.OT_Duration, 0) - IFNULL(l.Duration_Off, 0) as Total_Hours
from TimeData t
INNER JOIN Employee e on
t.EmployeeID = e.EmployeeID
LEFT JOIN Roster r on
t.Duty_ID = r.Duty_ID
LEFT JOIN Overtime o on
t.OTID = o.OTID
LEFT JOIN `Leave` l on
t.LeaveID = l.LeaveID
Example SqlFiddle can be found here

How to get two different total in two different tables in firebird? (Joining 3 Tables using left outer join)

I have 3 tables and the sample data is listed below
Table #1 EMPLOYEE
EMPLOYEE ID EMPLOYEE_NAME
1 Juan Dela Cruz
2 Jobert Saver
Table #2 ADD_TABLE
ADD_TABLE_PK EMPLOYEE_ID ADD_AMOUNT
1 1 10.00
2 1 13.00
Table #3 SUBTRACT_TABLE
SUBTRACT_PK EMPLOYEE_ID SUBTRACT_AMOUNT
1 1 2.00
2 1 3.00
3 1 4.00
My desired output is just like this:
SELECT PROCEDURE
EMPLOYEE_ID EMPLOYEE_NAME TOTAL(Total = sum of ADD_TABLE.ADD_AMOUNT - sum of SUBTRACT_TABLE.SUBTACT_AMOUNT)
1 Juan Dela Cruz 14.00 ( = 23.00 - 9.00)
but the result of my select procedure is:
EMPLOYEE_ID EMPLOYEE_NAME TOTAL
1 Juan Dela Cruz 51.00
here is my select procuder using left outer join
CREATE PROCEDURE SAMPLE_SELECT
RETURNS(
EMPLOYEE_ID INTEGER,
EMPLOYEE_NAME VARCHAR(50) CHARACTER SET ISO8859_1 COLLATE ISO8859_1,
TOTAL DECIMAL(12, 2))
AS
BEGIN
FOR
SELECT
A.EMPLOYEE_ID,
A.EMPLOYEE_NAME,
SUM(B.ADD_AMOUNT) - SUM(C.SUBTRACT_AMOUNT)
FROM EMPLOYEE A
LEFT OUTER JOIN ADD_TABLE B ON A.EMPLOYEE_ID = B.EMPLOYEE_ID
LEFT OUTER JOIN SUBTRACT_TABLE C ON A.EMPLOYEE_ID = C.EMPLOYEE_ID
GROUP BY
A.EMPLOYEE_ID,
A.EMPLOYEE_NAME
INTO
:EMPLOYEE_ID,
:EMPLOYEE_NAME,
:TOTAL
DO
BEGIN
SUSPEND;
END
END;
You need to do the aggregation before joining the tables:
select e.*, coalesce(add_amount, 0) - coalesce(subtract_amount, 0)
from employee e left join
(select employee_id, sum(add_amount) as add_amount
from add_table a
group by employee_id
) a
on a.employee_id = e.employee_id left join
(select employee_id, sum(subtract_amount) as subtract_amount
from subtract_table s
group by employee_id
) s
on s.employee_id = e.employee_id;
Note: This will keep all employees. Your query attempt uses LEFT JOINs, so this does as well.
Tested with Firebird 2.5, more direct:
Select e1.employee_id, e1.employee_name,
(Select Coalesce(Sum(a1.add_amount), 0)
From add_table a1
Where a1.employee_id = e1.employee_id) -
(Select Coalesce(Sum(s1.subtract_amount), 0)
From subtract_table s1
Where s1.employee_id = e1.employee_id) As total
From employee e1

Select records that appear more than once

I am trying to select records that appear more than once and are part of a specific department plus other departments.
So far the query that I have is this:
SELECT employeeCode, employeeName
FROM
Employees
WHERE
Department <> 'Technology'
AND employeeCode IN (SELECT employeeCode
FROM Employees
GROUP BY employeeCode HAVING COUNT(*) > 1)
The problem is that I want to select employees which are part of the Technology department, but they also participate in other departments.
So, they must be from the Technology department, but they could also be from the Household department. In the database it could look like:
1 | A1 | Alex | Technology
2 | A2 | Thor | Household
3 | A3 | John | Cars
4 | A3 | John | Technology
5 | A4 | Kim | Technology
6 | A4 | Kim | Video Games
So basically the query should return:
A3 | John |
A4 | Kim |
I think it's a small part that I am missing but..
Any ideas on how to filter/sort it so that it always uses the technology and the other departments?
Btw, I tried searching but I couldn't find a problem like mine..
If you want employees that could be in the technology department and another department:
select e.employeeCode, e.employeeName
from employees e
group by e.employeeCode, e.employeeName
having sum(case when e.department = 'Technology' then 1 else 0 end) > 0 and
count(*) > 1;
This assumes no duplicates in the table. If it can have duplicates, then use count(distinct department) > 1 rather than count(*) > 1.
Try this:
SELECT E.employeeCode, E.employeeName
FROM Employees E
INNER JOIN (SELECT DISTINCT E1.employeeCode, E1.employeeName
FROM Employees E
WHERE E.Department = 'Technology'
) AS A ON E.employeeCode = A.employeeCode AND E.employeeName = A.employeeName
GROUP BY E.employeeCode, E.employeeName
HAVING COUNT(*) > 1;
You can use EXISTS with correlated sub-query joining on the same table with different condition.
SELECT e1.employeeCode, e1.employeeName
FROM Employees e1
WHERE e1.Department = 'Technology'
AND EXISTS (SELECT * FROM Employees e2
WHERE e1.employeeCode = e2.employeeCode
AND e2.Department <> 'Technology')
This will work for your case:
SELECT a.employeeCode, a.employeeName
FROM Employees a, Employees b
WHERE
a.Department = 'Technology'
AND
b.Department <> 'Technology'
AND
a.employeeCode = b.employeeCode
AND
a.employeeID <> b.employeeID