Calculate total hours for a day - sql-server

I'm working on a report and I want to display the total time an employee have spent in office for each day. An employee makes in and out of office multiple times so each time the data gets saved.
I have table and records like this.
CREATE TABLE Attendance
(
ID Int,
TimeIn datetime,
TimeOut datetime
);
INSERT INTO Attendance VALUES (1, '2018-01-18 09:37:25.000', '2018-01-18 11:12:25.000');
INSERT INTO Attendance VALUES (1, '2018-01-18 11:21:25.000', '2018-01-18 16:32:25.000');
INSERT INTO Attendance VALUES (1, '2018-01-18 16:37:25.000', '2018-01-18 17:55:25.000');
INSERT INTO Attendance VALUES (2, '2018-01-18 09:56:25.000', '2018-01-18 14:37:25.000');
INSERT INTO Attendance VALUES (2, '2018-01-18 15:00:25.000', '2018-01-18 18:27:25.000');
INSERT INTO Attendance VALUES (1, '2018-01-19 09:12:25.000', '2018-01-19 11:41:25.000');
INSERT INTO Attendance VALUES (1, '2018-01-19 13:23:25.000', '2018-01-19 13:31:25.000');
INSERT INTO Attendance VALUES (2, '2018-01-19 09:12:25.000', '2018-01-19 09:59:25.000');
INSERT INTO Attendance VALUES (2, '2018-01-19 12:55:25.000', '2018-01-19 13:12:25.000');
INSERT INTO Attendance VALUES (2, '2018-01-19 14:01:25.000', '2018-01-19 18:10:25.000');
INSERT INTO Attendance VALUES (1, '2018-01-19 17:31:25.000', '2018-01-19 18:51:25.000');
I want the result to be displayed like this.
ID TimeIn TimeOut TimeIn TimeOut TimeIn TimeOut TimeSpent Day
1 Calculated Hours 2018-01-18
2 Calculated Hours 2018-01-18
1 Calculated Hours 2018-01-19
2 Calculated Hours 2018-01-19
And if possible can we make the TimeIn and TimeOut be displayed as well dynamically ?

Find an employee with maximum number of in/outs in a single day. And change number of columns in the query according to that
select
id, day, TimeIn = max(iif(rn = 1, TimeIn, '')), TimeOut = max(iif(rn = 1, TimeOut, ''))
, TimeIn = max(iif(rn = 2, TimeIn, '')), TimeOut = max(iif(rn = 2, TimeOut, ''))
, TimeIn = max(iif(rn = 3, TimeIn, '')), TimeOut = max(iif(rn = 3, TimeOut, ''))
, TimeIn = max(iif(rn = 4, TimeIn, '')), TimeOut = max(iif(rn = 4, TimeOut, ''))
, TimeIn = max(iif(rn = 5, TimeIn, '')), TimeOut = max(iif(rn = 5, TimeOut, ''))
, TimeSpent = concat(right(concat('0',sum(TimeSpent) / 60),2), ':', right(concat('0',sum(TimeSpent)%60),2))
, Day
from (
select
id, cast(TimeIn as date) day, TimeIn = convert(char(8), TimeIn, 108)
, TimeOut = convert(char(8), TimeOut, 108)
, datediff(mi, TimeIn, TimeOut) TimeSpent
, row_number() over (partition by id, cast(TimeIn as date) order by TimeIn) rn
from
#Attendance
) t
group by id, day

The grouping itself can be done with a regular group by, but perhaps are stuck by grouping on the date? If so, the group by clause can have the same cast/conversions used in the display:
SELECT ID, sum(DATEDIFF(MINUTE, TimeIn,TimeOut)) TotalMinutes,CAST(TimeIn as date) Day
FROM Attendance
group by ID, CAST(TimeIn as date)
Subsequently, formatting of the time can be done with
select ID, format(TotalMinutes / 60, '0') + ':' + format(TotalMinutes % 60, '00'), Day
from(
SELECT ID, sum(DATEDIFF(MINUTE, TimeIn,TimeOut)) TotalMinutes,CAST(TimeIn as date) Day
FROM #Attendance
group by ID, CAST(TimeIn as date)
) a
Hadn't seen the added info on the dynamic columns yet. Will try to dive into that later
edit A possibility for the In/Out columns:
;with a as
(
select *, cast(TimeIn as date) Day ,
format(dateadd(MINUTE, sum( DATEDIFF( MINUTE , TimeIn,TimeOut)) over (partition by ID,cast(TimeIn as date)),0),'H:mm') TimeSpent,
cast(row_number() over (partition by ID, cast(TimeIn as date) order by TimeIn) as varchar) rnr
from Attendance
)
select * from
(select ID, Day, TimeSpent, 'In' + rnr Name, FORMAT(TimeIn, 'H:mm') T from a
union all
select ID, Day, TimeSpent,'Out' +rnr, FORMAT(TimeOut, 'H:mm') from a) u
pivot(min(T) for Name in ([In1], [Out1], [In2], [Out2],[In3],[Out3], [In4], [Out4])) p --as much as desired
Sql Fiddle

Related

How to count number of report runs last 7 days? Year to date? All time?

I'm trying to create an SSRS report that looks similar to the table below:
Report
Earliest Run
Recent Run
Runs Last 7 days
Runs YTD
Runs All Time
Report 1
3/3/19 1:30
7/8/22 2:45
8
86
233
I know how to query the last 3 columns individually, but is it possible to get all 3 columns using 1 query? I have tried the query below to show my line of thinking but its not working as desired.
SELECT Report
,Min(TimeStart) AS EarliestRun
,Max(TimeStart) AS RecentRun
,CASE WHEN TimeStart BETWEEN GETDATE()-7 AND GETDATE() THEN COUNT(Report) END AS RunsLast7Days
FROM ReportHistory
WHERE TimeStart BETWEEN '1/1/2019 00:00' AND GETDATE()
GROUP BY Report
Yes - use conditional aggregation. Don't filter the query at all since you need an "all time" value. Instead, use sum with a conditional expression for the periods of interest.
select ...
sum(case when TimeStart >= dateadd(day, -7, getdate()) then 1 else 0 end) as [Runs Last 7 days],
sum(case when TimeStart >= datefromparts(year(getdate()), 1, 1) then 1 else 0 end) as [Runs YTD],
...
from dbo.ReportHistory
order by ...;
I was going to propose using CROSS APPLY but SMor has done it with less code
CREATE TABLE #Reports (
ReportId INT NOT NULL,
ReportName VARCHAR(20) NOT NULL
);
INSERT INTO #Reports(ReportId, ReportName)
VALUES(1, 'Report 1');
CREATE TABLE #ReportRun (
ReportId INT,
RunDateTime DATETIME2(2)
);
INSERT INTO #ReportRun(ReportId, RunDateTime)
VALUES
(1, '20220508 10:00:00'),
(1, '20220502 10:00:00'),
(1, '20220101 10:00:00'),
(1, '20210501 10:00:00'),
(1, '20210209 10:00:00'),
(1, '20200509 10:00:00'),
(1, '20190509 10:00:00');
GO
-- SELECT * FROM #Reports
-- SELECT * FROM #ReportRun
SELECT R.ReportName, B.RunLast7Days, C.RunYearToDate, D.RunAllTime
FROM #Reports AS R
CROSS APPLY (
SELECT TOP 1 RunDateTime
FROM #ReportRun
WHERE ReportId = R.ReportId
ORDER BY RunDateTime DESC
) AS ER
CROSS APPLY (
SELECT COUNT(*) AS RunLast7Days
FROM #ReportRun
WHERE ReportId = R.ReportId
AND RunDateTime >= DATEADD(day, -7, CONVERT(date, GETDATE())) -- best to set it to the start of the day
GROUP BY ReportId
) AS B
CROSS APPLY (
SELECT COUNT(*) AS RunYearToDate
FROM #ReportRun
WHERE ReportId = R.ReportId
AND RunDateTime >= DATEADD(yy, DATEDIFF(yy, 0, GETDATE()), 0)
GROUP BY ReportId
) AS C
CROSS APPLY (
SELECT COUNT(*) AS RunAllTime
FROM #ReportRun
WHERE ReportId = R.ReportId
GROUP BY ReportId
) AS D

How to get next row value based on previous row excluding week ends in SQL Server?

I have a table as follows: (Expected result without weekend exclude logic)
Start Date
End Date(Expected Date)
No of Days(input)
01-01-2021
02-01-2021
2
03-01-2021
08-01-2021
5
09-01-2021
10-01-2021
2
11-01-2021
20-01-2021
10
21-01-2021
09-02-2021
20
10-02-2021
10-02-2021
1
I want to re-generate the StartDate and EndDate data based on the NumberOfDays values, and the StartDate for subsequent rows based on previous row's EndDate + 1 day and in this sequence, I need to exclude the weekend dates as well, and I have another scenario to include weekend dates based on condition.
I want to apply this logic and select the data in same select query using SQL Server.
This is what I have tried
declare #t table ( StartDate date, EndDate date, DaysToAdd int );
insert into #t(StartDate, EndDate, DaysToAdd)
values('20210217', '20210227', 10), ('20210312', '20210310', 10), ('20210326', '20210401', 10), ('20210409', '20210401', 10), ('20210507', '20210401', 10), ('20210606', '20210529', 10), ('20210618', '20210417', 3), ('20210620', '20210309', 2), ('20300913', '20210227', 2), (null, '20300914', 4);
select * from #t
select dateadd(day, -DaysToAdd-1+count(*) over(order by isnull(StartDate, EndDate), EndDate) + sum(DaysToAdd) over(order by isnull(StartDate, EndDate), EndDate), min(StartDate) over()) as NewStartDate, dateadd(day, -1+count(*) over(order by isnull(StartDate, EndDate), EndDate) + sum(DaysToAdd) over(order by isnull(StartDate, EndDate), EndDate), min(StartDate) over()) as NewEndDate, * from #t;
My Expected result:
Start Date
End Date(Expected Date)
No of Days(input)
01-01-2021
04-01-2021
2
05-01-2021
11-01-2021
5
12-01-2021
13-01-2021
2
14-01-2021
27-01-2021
10
28-01-2021
24-02-2021
20
25-02-2021
25-02-2021
1
it is best if you have a calendar table
for the solution, i create a simple calendar table
create table calendar
(
CalDate date,
isWeekEnd bit
);
then populate it with dates
with rcte as
(
select CalDate = convert(date, '2021-01-01')
union all
select CalDate = dateadd(day, 1, CalDate)
from rcte
where CalDate <= '2021-12-30'
)
insert into calendar (CalDate, isWeekEnd)
select CalDate,
case when left(datename(weekday, CalDate), 3) in ('Sat', 'Sun') then 1 else 0 end
from rcte
option (maxrecursion 0)
your sample table & data
declare #t table (id int identity, StartDate date, EndDate date, DaysToAdd int );
insert into #t(StartDate, EndDate, DaysToAdd)
values('2021-01-01', '2021-01-02', 2),
('2021-01-03', '2021-01-08', 5),
('2021-01-09', '2021-01-10', 2),
('2021-01-11', '2021-01-20', 10),
('2021-01-21', '2021-02-09', 20),
('2021-02-10', '2021-02-10', 1);
Since you only interested in the StartDate of first row, I select it into a variable
The actual query
declare #StartDate date;
select #StartDate = StartDate
from #t
where id = 1;
with
cal as
(
select CalDate, rn = row_number() over (order by CalDate)
from Calendar
where CalDate >= #StartDate
and isWeekEnd = 0
),
t as
(
select t.id, t.DaysToAdd,
s = sum(t.DaysToAdd) over (order by t.id) - t.DaysToAdd + 1,
e = sum(t.DaysToAdd) over (order by t.id)
from #t t
)
select t.id,
t.DaysToAdd,
StartDate = s.CalDate,
EndDate = e.CalDate
from t
inner join cal s on t.s = s.rn
inner join cal e on t.e = e.rn
order by t.id
db<>fiddle demo

TSQL: Continuous period for whole year / each month

I try to find all Cust who have membership for at least for one day in each month during 2018.
I came up with solution checking their membership at the beginning / middle / end end of each month like in snippet below, but trying to find more intelligent solution.
I know that I can use tally table for each of 365 days to check this but probably there is more elegant solution ? I'm bit new to SQL, I think I'm missing something in GROUPing area.
In the code snippet shown below, both Cust have at least one day membership.
Desired output:
CustID
------
1
22
Code:
with data as
(
select *
from (values (1, 1, '2017-12-11', '2018-01-16'), (1, 22, '2018-01-28', '2018-03-9' ), (1, 333, '2018-03-1', '2018-12-31') , -- island
(22, 1, '2017-12-31', '2018-01-11'), (22, 2, '2017-2-11', '2019-12-31')) as t (CustID, ContractID, StartDD, EndDD) ---
)
select
isdate(startDD), isdate(EndDD)
from
data
), gaps as
(
select
*,
datediff(day, lag(EndDD, 1, StartDD) over (partition by CustID order by StartDD), StartDD) as BreakDD -- negative is island
from
data
)
select
*,
datepart(month,StartDD) mmS , datepart(month,EndDD) mmE
from
gaps
-- and was active any 1+ day during each of the 12 months in 2018 ????
where
1 = 1
/* and (cast('1/1/2018' as date) between StartDD and EndDD
or cast('1/15/2018' as date) between StartDD and EndDD
or cast('1/31/2018' as date) between StartDD and EndDD)
---- etc.. for each month
and ( cast('12/1/2018' as date) between StartDD and EndDD
or cast('12/15/2018' as date) between StartDD and EndDD
or cast('12/31/2018' as date) between StartDD and EndDD
)
*/
--select CustID, max(BreakDD) Max_Days
--from gaps
--group by CustID
Try this answer.
First create a function to return all the month and year between the given dates.
Function:
--SELECT * FROM dbo.Fn_GetMonthYear('2017-12-11','2018-01-16')
ALTER FUNCTION dbo.Fn_GetMonthYear(#StartDate DATETIME,#EndDate DATETIME)
RETURNS TABLE
AS
RETURN(
SELECT DATEPART(MONTH, DATEADD(MONTH, x.number, #StartDate)) AS [Month]
,DATEPART(YEAR, DATEADD(MONTH, x.number, #StartDate)) AS [Year]
FROM master.dbo.spt_values x
WHERE x.type = 'P'
AND x.number <= DATEDIFF(MONTH, #StartDate, #EndDate)
)
Table Schema:
CREATE TABLE #t(CustID INT, ContractID INT, StartDD date, EndDD date)
INSERT INTO #t values (1, 1, '2017-12-11', '2018-01-16'), (1, 22, '2018-01-28', '2018-03-9' ), (1, 333, '2018-03-1', '2018-12-31') , -- island
(22, 1, '2017-12-31', '2018-01-11'), (22, 2, '2017-2-11', '2019-12-31')
Here is the T-SQL Query for your requirement.
SELECT CustID
,COUNT(DISTINCT [Month]) NoOfMonths
FROM(
SELECT *
FROM #t t
CROSS APPLY dbo.Fn_GetMonthYear(StartDD,EndDD)
)D
WHERE [Year] = 2018
GROUP BY CustID
HAVING COUNT(DISTINCT [Month])=12
Result:
CustID NoOfMonths
1 12
22 12
find all Cust who have membership for at least for one day in each
month during 2018
I think this mean that data must be present between '2018-01-01' and '2018-12-31' for each custid.
CREATE TABLE #t(CustID INT, ContractID INT, StartDD date, EndDD date)
INSERT INTO #t values (1, 1, '2017-12-11', '2018-01-16'), (1, 22, '2018-01-28', '2018-03-9' ), (1, 333, '2018-03-1', '2018-12-31') , -- island
(22, 1, '2017-12-31', '2018-01-11'), (22, 2, '2017-2-11', '2019-12-31')
declare #From Datetime='2018-01-01'
declare #To datetime='2018-12-31'
;with CTE as
(
select CustID,min(StartDD)StartDD
,max(EndDD)EndDD
from #t
group by CustID
)
select CustID,StartDD
,EndDD
from CTE
where StartDD<=#From and EndDD>=#To
This script is not tested across all sample data.
But logic is clear.So it can be corrected accordingly.
So tell for what sample data it is not working.

FirstIn and LastOut in sqlserver

I have a table name [EmployeeAttendance] and I have records in it in respective order.
SELECT EI.[FirstName]+' '+EI.[LastName] [EmployeeName], [Dpt].[FullName] [Department], [Desig].[FullName] [Designation], FirstIN = CAST(MIN([AttendanceTimeIn]) AS TIME), LastOUT = CAST(MAX([AttendanceTimeOut]) AS TIME), HoursSpent = DATEDIFF(HOUR, CAST(MIN(AttendanceTimeIn) AS TIME), CAST(MAX(AttendanceTimeOut) AS TIME))
FROM [HRM].[tbl_EmployeeInfo] [EI], [HRM].[tbl_Designation] [Desig], [HRM].[tbl_Department] [Dpt], [HRM].[tbl_EmployeeAttendance] [Attendance]
WHERE [Dpt].[ID] = [EI].[DeptCode] AND [Desig].[ID] = [EI].[DesignationCode] AND [Attendance].[EmpCode] = [EI].[ID] AND [EI].[RecordStatusCode] != '13'
AND CAST([AttendanceTimeIn] as date) = CAST(GetDate()-1 as Date)
GROUP BY
EI.[FirstName]+' '+EI.[LastName], [Dpt].[FullName], [Desig].[FullName], CAST([Attendance].[AttendanceTimeIn] AS DATE)
This is what I am getting as output.
Ajmal John Projects Project Associate 10:16:38.0000000 NULL NULL
Asif Asif Office Staff Office Boy 09:28:36.0000000 NULL NULL
Muhammad Asim Support Database Support Engineer 10:47:28.0000000 19:16:17.0000000 9
Sajjad Ahmed Projects Project Manager 09:41:34.0000000 NULL NULL
Sidra Khizar Quality Assurance SQA Engineer 10:18:48.0000000 NULL NULL
Because I have placed TimeIn and TimeOut in same row it is giving me TimeOut but as for other fields I have Time Out in 2nd row so it is giving Null. Not sure why
If Your Data Type Is TIME, then this works fine
DECLARE #T TABLE
([EmpID] int, [TimeIn] TIME, [TimeOut] TIME)
;
INSERT INTO #T
([EmpID], [TimeIn], [TimeOut])
VALUES
(1, '9:00', NULL),
(1, NULL, '11:00'),
(1, '11:30', NULL),
(1, NULL, '13:00'),
(1, '13:30', NULL),
(1, NULL, '18:00')
;
;WITH TM
AS
(
SELECT
EmpID,
MIN([TimeIn]) as StartTime,
MAX([TimeOut]) as EndTime
FROM #T
GROUP BY EmpId
)
SELECT
*,
HoursSpent = DATEDIFF(HOUR, StartTime, EndTime)
FROM TM
Result
If The DataType is different Try Casting it as Time inside the MAX and MIN Functions
If TimeIn and TimeOut are of TIME datatype then this works
DECLARE #EmployeeAttendance TABLE (EmpID INT, TimeIn TIME, [TimeOut] TIME);
INSERT INTO #EmployeeAttendance
(EmpID, TimeIn, TimeOut)
SELECT 1, '9:00', NULL UNION ALL
SELECT 1, NULL, '11:00' UNION ALL
SELECT 1, '11:30', NULL UNION ALL
SELECT 1, NULL,'13:00' UNION ALL
SELECT 1,'13:30', NULL UNION ALL
SELECT 1,NULL,'18:00';
SELECT
EmpID
, FirstIN = MIN([TimeIn])
, LastOUT = MAX([TimeOut])
, HoursSpent = DATEDIFF(HOUR, MIN(TimeIn), MAX(TimeOut))
--, MAX([TimeOut])-MIN([TimeIn]) AS HoursSpent
FROM
#EmployeeAttendance
GROUP BY
EmpID;
Output
If the columns are DATETIME then try this
DECLARE #EmployeeAttendance TABLE (EmpID INT, TimeIn DATETIME, [TimeOut] DATETIME);
INSERT INTO #EmployeeAttendance
(EmpID, TimeIn, TimeOut)
SELECT 1, '9:00', NULL UNION ALL
SELECT 1, NULL, '11:00' UNION ALL
SELECT 1, '11:30', NULL UNION ALL
SELECT 1, NULL,'13:00' UNION ALL
SELECT 1,'13:30', NULL UNION ALL
SELECT 1,NULL,'18:00';
SELECT
EmpID
, FirstIN = CAST(MIN([TimeIn]) AS TIME)
, LastOUT = CAST(MAX([TimeOut]) AS TIME)
, HoursSpent = DATEDIFF(HOUR, CAST(MIN(TimeIn) AS TIME), CAST(MAX(TimeOut) AS TIME))
--, MAX([TimeOut])-MIN([TimeIn]) AS HoursSpent
FROM
#EmployeeAttendance
GROUP BY
EmpID;
If you have the TimeIn & TimeOut as DateTime field in SQL server then it can be achieved as below:
SELECT
EmpID,
MIN(cast(TimeIN as time)) as FirstIN,
MAX(cast(TimeOut as time)) as LastOUT,
DATEDIFF(hour,MIN([TimeIn]),MAX([TimeOut])) AS HoursSpent
FROM [EmployeeAttendance]
GROUP BY EmpId
Query gives output for above sample data is as follows:
EmpID FirstIN LastOUT HoursSpent
1 09:00:00.0000000 18:00:00.0000000 9

How to split date column & sum it up

I have a query where i have a date column (time) which tells about "IN" & "OUT" timing of the people attendance by this single column
My queries are :-
1) How to get the daily attendance of each employee
2) How to come to know if the employee is present less than 5 hours
Please let me know the queries in SQL server.
You'll need to group the query by the user and the items for a particular day then compare the maximum and minimum values, e.g.
declare #users table (
UserId int,
DateColumn datetime
)
insert into #users values (1, '2008-10-31 15:15')
insert into #users values (1, '2008-10-31 10:30')
insert into #users values (1, '2008-10-30 16:15')
insert into #users values (1, '2008-10-30 10:30')
select
UserID
, cast(dt as datetime) dt
, [in]
, [out]
, case when datepart(hour, [out]-[in]) >= 5 then 'yes' else 'no' end [5Hours?],
, cast(datediff(minute, [in], [out]) as float)/60 [hours]
from (
select
UserID
, convert(varchar, DateColumn, 112) dt
, min(DateColumn) [in]
, max(DateColumn) [out]
from #users
group by
UserID, convert(varchar, DateColumn, 112)
) a
To find the difference between two datetimes you can use the following:
SELECT Datepart(hour, dateTimeEnd - dateTimeStart)
The DatePart function returns part of a date time variable, and the dateTimeEnd - dateTimeStart returns the difference between two dates as a new DateTime
select
datediff(minute, TimeFrom, TimeTo) as AttendedTimeInMinutes,
case when datediff(minute, sTimeFrom, sTimeTo) < 5 * 60
then
'less than 5 hours'
else '5 hours or more'
end
from YourTable

Resources