简体   繁体   中英

Count number of days worked

You guys are so good about slicking up code. I can use a better SQL that will tell me the number of days in a month each employee worked. Each employee can punch in and out several times a day plus they can work over midnight. If they work over midnight, it counts as working 2 days. If they worked past midnight and came in the later on the same day and left before the next midnight, that time would have already been counted since it was on the same day.

This works but is there an easier way?

IF OBJECT_ID ('dbo.ZTable1', 'U') IS NOT NULL
    DROP TABLE dbo.ZTable1;
GO
CREATE TABLE dbo.ZTable1 ( [EmployeeId]  Numeric (5,0), [TimeIn] datetime, 
[TimeOut] datetime )

INSERT INTO dbo.ZTable1 ([EmployeeId],[TimeIn],[TimeOut]) SELECT 1,'2017-09-
13 12:19','2017-09-14 00:01'
INSERT INTO dbo.ZTable1 ([EmployeeId],[TimeIn],[TimeOut]) SELECT 1,'2017-09-
14 12:15','2017-09-15 00:01'
INSERT INTO dbo.ZTable1 ([EmployeeId],[TimeIn],[TimeOut]) SELECT 1,'2017-09-
15 12:35','2017-09-16 00:01'
INSERT INTO dbo.ZTable1 ([EmployeeId],[TimeIn],[TimeOut]) SELECT 1,'2017-09-
16 07:56','2017-09-16 10:31'
INSERT INTO dbo.ZTable1 ([EmployeeId],[TimeIn],[TimeOut]) SELECT 1,'2017-09-
16 11:56','2017-09-16 16:31'
INSERT INTO dbo.ZTable1 ([EmployeeId],[TimeIn],[TimeOut]) SELECT 2,'2017-09-
13 15:26','2017-09-14 00:00'
INSERT INTO dbo.ZTable1 ([EmployeeId],[TimeIn],[TimeOut]) SELECT 2,'2017-09-
14 15:29','2017-09-15 00:00'
INSERT INTO dbo.ZTable1 ([EmployeeId],[TimeIn],[TimeOut]) SELECT 2,'2017-09-
15 15:27','2017-09-16 00:01'
INSERT INTO dbo.ZTable1 ([EmployeeId],[TimeIn],[TimeOut]) SELECT 3,'2017-09-
13 15:25','2017-09-14 00:01'
INSERT INTO dbo.ZTable1 ([EmployeeId],[TimeIn],[TimeOut]) SELECT 3,'2017-09-
14 15:25','2017-09-15 00:00'
INSERT INTO dbo.ZTable1 ([EmployeeId],[TimeIn],[TimeOut]) SELECT 3,'2017-09-
15 15:26','2017-09-16 00:00'
INSERT INTO dbo.ZTable1 ([EmployeeId],[TimeIn],[TimeOut]) SELECT 3,'2017-09-
16 06:55','2017-09-16 15:27'

GO;

With Step1 as (  --<  Build temp table of in punch days
Select [EmployeeId], DATEPART ( DAY ,[TimeIn] )  as WorkDay
from dbo.ZTable1
),
Step2 as (  --<  Build temp table of out punch days
Select [EmployeeId], DATEPART ( DAY ,[TimeOut] )  as WorkDay
from dbo.ZTable1
),
Step3 as (  --<  merges both in and put punch tables
Select 
Case when s1.[EmployeeId] is NULL then s2.[EmployeeId] else s1.[EmployeeId] end as Employee,
case when s1.WorkDay is NULL then s2.WorkDay else s1.WorkDay end as WorkDate
from Step1 s1
full outer join Step2 s2 on s1.[EmployeeId] = s2.[EmployeeId] and s1.WorkDay = s2.WorkDay
),
Step4 as (  --<  Organizes temp table  
Select Distinct  Employee, WorkDate 
from Step3
group by Employee, WorkDate
)
Select Employee, Count (Employee) as NumDays
from Step4
Where Employee > 0
Group by Employee
Order by Employee


DROP TABLE dbo.ZTable1

Output (Result)
Employee    NumDays
1           4
2           4
3           4

Try this, no CTEs needed:

SELECT EmployeeID, COUNT(1) as NumDays
FROM (
    SELECT EmployeeID, CONVERT(DATE,TimeIn) DistinctDays
    FROM ZTable1

    UNION

    SELECT EmployeeID, CONVERT(DATE,TimeOut)
    FROM ZTable1
     ) A
GROUP BY EmployeeID

I changed DATEPART(Day, TimeIn/Out) to CONVERT(DATE, TimeIn/Out) to handle different months/years. It may not be necessary based on what you said, but the performance will be roughly the same as DATEPART() anyway.

Basically the approach here is to get a list of distinct days per employeeID, regardless of whether the day comes from the TimeIn or TimeOut column. UNION works perfectly for that, because it can combine the two columns, and it removes duplicate results from the result set , which leaves us with a distinct list of days/employeeID. Then it's just a matter of counting the rows per employeeID.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM