Check Time Range Lapse in Other Time Range - sql

I have a Time In and Time Out and there is a time range defined for Lunch Breakfast and Dinner. What i want is to Subtract these times from the attendance time (Time In And Time Out).
The sample data is
Attendance Table Data
EMPID 1095
TimeIN 2017-03-01 08:52:45.000
TimeOut 2017-03-01 19:59:18.000
The Mess Timings are
type StartTime EndTime
BreakFast 06:30:39 10:00:39
Dinner 19:00:39 21:00:39
Lunch 12:00:23 15:00:23
What i need is to subtract these mess timings from the actual attendance time to get actual employee duty time.
Thanks.

This approach utilises a numbers table to create a lookup table of all the seconds between your #TimeIn and #TimeOut values. This will work for periods covering multiple days, albeit with some severe caveats:
Breakfast, Lunch and Dinner are at the same time every day.
Your #TimeIn and #TimeOut period doesn't get so big it overflows the int value that contains the number of seconds.
In that case you will need to either just use minutes or find a different method
Your return value is less than 24 hours.
In that case, just don't return the difference as a time data type and handle it accordingly.
declare #TimeIn datetime = '2017-03-01 08:52:45.000'
,#TimeOut datetime = '2017-03-01 19:59:18.000'
,#BStart time = '06:30:39'
,#BEnd time = '10:00:39'
,#LStart time = '12:00:23'
,#LEnd time = '15:00:23'
,#DStart time = '19:00:39'
,#DEnd time = '21:00:39';
-- Create numbers table then use it to build a table os seconds between TimeIn and TimeOut
with n(n) as (select n from (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) as n(n))
,s(s) as (select top (select datediff(s,#TimeIn,#TimeOut)+1) dateadd(s,row_number() over (order by (select 1))-1,#TimeIn) from n n1,n n2,n n3,n n4,n n5,n n6)
select cast(dateadd(s,count(1),0) as time) as s
from s
where s between #TimeIn and #TimeOut -- Return all seconds that aren't within Breakfast, Lunch or Dinner
and cast(s as time) not between #BStart and #BEnd
and cast(s as time) not between #LStart and #LEnd
and cast(s as time) not between #DStart and #DEnd
Which returns: 05:59:58.0000000

I have daily timings of mess in other table so i created a view and took all fields in front of daily attendance then using case statement to match the timings with Daily Attendance Time.
EmployeeID AttendanceDate ShiftID TimeIn TimeOut BreakOut BreakIn LeaveType TotalHours LeaveHours ATOThours DeductedHrs OTHours UserID AudtDate Reason SM SY OTDed DutyDed Mark Expr1 MARKL BreakFastStart BreakFastEnd LunchStart LunchEnd DinnerStart DinnerEnd
1095 2017-03-01 00:00:00.000 1 2017-03-01 08:52:45.000 2017-03-01 19:59:18.000 NULL NULL NULL 0 NULL 0 0 0 NULL NULL NULL 3 2017 NULL NULL NULL NULL NULL 2017-02-20 06:30:34.000 2017-02-20 09:30:34.000 2017-02-20 12:00:26.000 2017-02-20 15:00:26.000 2017-02-20 19:00:59.000 2017-02-20 21:00:59.000
For now it's good will check it's credibility with the passage of time.
Thanks For the support

You can also use the following script in the View OR in JOIN query of the tables. Note I got a different answer which I think is correct.
SELECT CONVERT(varchar, DATEADD(ss,
(DATEDIFF(ss,TimeIn, [TimeOut]) -
(
DATEDIFF(ss,[BreakFastStartTime], [BreakFastEndTime]) +
DATEDIFF(ss,[LunchStartTime], [LunchEndTime]) +
DATEDIFF(ss,[DinnerStartTime], [DinnerEndTime])
)
), 0), 108)
FROM [Attendance Data]
For your example, answer is 02:36:33

Related

Adding minutes of runtime from on/off records during a time period

I have a SQL database that collects temperature and sensor data from the barn.
The table definition is:
CREATE TABLE [dbo].[DataPoints]
(
[timestamp] [datetime] NOT NULL,
[pointname] [nvarchar](50) NOT NULL,
[pointvalue] [float] NOT NULL
)
The sensors report outside temperature (degrees), inside temperature (degrees), and heating (as on/off).
Sensors create a record when the previous reading has changed, so temperatures are generated every few minutes, one record for heat coming ON, one for heat going OFF, and so on.
I'm interested in how many minutes of heat has been used overnight, so a 24-hour period from 6 AM yesterday to 6 AM today would work fine.
This query:
SELECT *
FROM [home_network].[dbo].[DataPoints]
WHERE (pointname = 'Heaters')
AND (timestamp BETWEEN '2022-12-18 06:00:00' AND '2022-12-19 06:00:00')
ORDER BY timestamp
returns this data:
2022-12-19 02:00:20 | Heaters | 1
2022-12-19 02:22:22 | Heaters | 0
2022-12-19 03:43:28 | Heaters | 1
2022-12-19 04:25:31 | Heaters | 0
The end result should be 22 minutes + 42 minutes = 64 minutes of heat, but I can't see how to get this result from a single query. It also just happens that this result set has two complete heat on/off cycles, but that will not always be the case. So, if the first heat record was = 0, that means that at 6 AM, the heat was already on, but the start time won't show in the query. The same idea applies if the last heat record is =1 at, say 05:15, which means 45 minutes have to be added to the total.
Is it possible to get this minutes-of-heat-time result with a single query? Actually, I don't know the right approach, and it doesn't matter if I have to run several queries. If needed, I could use a small app that reads the raw data, and applies logic outside of SQL to arrive at the total. But I'd prefer to be able to do this within SQL.
This isn't a complete answer, but it should help you get started. From the SQL in the post, I'm assuming you're using SQL Server. I've formatted the code to match. Replace #input with your query above if you want to test on your own data. (SELECT * FROM [home_network].[dbo]...)
--generate dummy table with sample output from question
declare #input as table(
[timestamp] [datetime] NOT NULL,
[pointname] [nvarchar](50) NOT NULL,
[pointvalue] [float] NOT NULL
)
insert into #input values
('2022-12-19 02:00:20','Heaters',1),
('2022-12-19 02:22:22','Heaters',0),
('2022-12-19 03:43:28','Heaters',1),
('2022-12-19 04:25:31','Heaters',0);
--Append a row number to the result
WITH A as (
SELECT *,
ROW_NUMBER() OVER(ORDER BY(SELECT 1)) as row_count
from #input)
--Self join the table using the row number as a guide
SELECT sum(datediff(MINUTE,startTimes.timestamp,endTimes.timestamp))
from A as startTimes
LEFT JOIN A as endTimes on startTimes.row_count=endTimes.row_count-1
--Only show periods of time where the heater is turned on at the start
WHERE startTimes.row_count%2=1
Your problem can be divided into 2 steps:
Filter sensor type and date range, while also getting time span of each record by calculating date difference between timestamp of current record and the next one in chronological order.
Filter records with ON status and summarize the duration
(Optional) convert to HH:MM:SS format to display
Here's the my take on the problem with comments of what I do in each step, all combined into 1 single query.
-- Step 3: Convert output to HH:MM:SS, this is just for show and can be reduced
SELECT STUFF(CONVERT(VARCHAR(8), DATEADD(SECOND, total_duration, 0), 108),
1, 2, CAST(FLOOR(total_duration / 3600) AS VARCHAR(5)))
FROM (
-- Step 2: select records with status ON (1) and aggregate total duration in seconds
SELECT sum(duration) as total_duration
FROM (
-- Step 1: Use LEAD to get next adjacent timestamp and calculate date difference (time span) between the current record and the next one in time order
SELECT TOP 100 PERCENT
DATEDIFF(SECOND, timestamp, LEAD(timestamp, 1, '2022-12-19 06:00:00') OVER (ORDER BY timestamp)) as duration,
pointvalue
FROM [dbo].[DataPoints]
-- filtered by sensor name and time range
WHERE pointname = 'Heaters'
AND (timestamp BETWEEN '2022-12-18 06:00:00' AND '2022-12-19 06:00:00')
ORDER BY timestamp ASC
) AS tmp
WHERE tmp.pointvalue = 1
) as tmp2
Note: As the last record does not have next adjacent timestamp, it will be filled with the end time of inspection (In this case it's 6AM of the next day).
I do not really think it would be possible to achieve within single query.
Option 1:
implement stored procedure where you can implement some logic how to calculate these periods.
Option 2:
add new column (duration) and on insert new record calculate difference between NOW and previous timestamp and update duration for previous record

How to get current date minimum datetime values in a table one column

I want get staff attendance records. office employees work started time and end time wise gathering record. Using SQL server DB, in table one field, here not using strat_time and end_time two fields. Easily get maximum date values, but minimum value get old date rather then today dates, as per SQL server query below:
SELECT Sy.SystemUserName,
MIN(Sc.CreatedOn) as StartedTime,
MAX(Sc.CreatedOn) as ExitTime,
datediff(MINUTE, MIN(Sc.CreatedOn) , MAX(Sc.CreatedOn)) as WorkingHours
from gunageorge.SystemDetails Sy
LEFT JOIN gunageorge.Screenshots Sc on Sy.id = Sc.SystemId
where Sy.CompanyGUID = '25' AND Sy.IsDeleted = 0
GROUP By Sy.SystemUserName
Where condition only that date value only returned, null value not returning.
Returned values like that
Name
Starttime
EndTime
Duration
Ray
2021-11-23 06:01:42.2300045
2022-03-14 09:19:44.9513129
160038
vijay
2022-03-14 04:09:49.4479046
2022-03-14 07:34:47.8999912
205
Parthi
2022-02-02 08:26:11.2394531
2022-03-14 09:19:47.1416970
57653
Sugu
2022-02-01 09:17:22.3333451
2022-03-14 09:19:07.0365219
59042
Uday
NULL
NULL
NULL
Here I want started date is today's started time only. Don't old records. Also want NULL records
You need to get to check that there are 0 days between today, using getdate(), and CreatedOn
WHERE datediff(dd,Sc.CreatedOn,getdate()) = 0
so the query becomes
SELECT Sy.SystemUserName,
MIN(Sc.CreatedOn) as StartedTime,
MAX(Sc.CreatedOn) as ExitTime,
datediff(MINUTE,
MIN(Sc.CreatedOn) ,
MAX(Sc.CreatedOn)) as WorkingHours
from gunageorge.SystemDetails Sy
LEFT JOIN gunageorge.Screenshots Sc on Sy.id = Sc.SystemId
where Sy.CompanyGUID = '25'
AND Sy.IsDeleted = 0
and (datediff(dd,Sc.CreatedOn,getdate()) = 0
Or SC.CreatedOn IS NULL)
GROUP By Sy.SystemUserName

sql query for finding ID numbers on date range

I want to get the ID numbers for the last 24 hour range. Say I run a task at 4:00AM each morning and want to get the previous 24 hours of data going back to 4:00AM the previous day. I need to get the id codes to search the correct tables. If the data is like this what would be the best way to query the ID numbers?
ID
Start Time
EndTime
2112
2021-08-10 23:25:28.750
NULL
2111
2021-08-06 17:42:27.400
2021-08-10 23:25:28.750
2110
2021-08-03 20:21:14.093
2021-08-06 17:42:27.400
So if I had the date range of 8/10 - 8/11 I would need to get two codes. 2111 and 2112. If I need to get 8/11 - 8/12 I would only get 2112 as the endtime is null.
Any thoughts on the best way to query this out?
You need to do something like that :
DECLARE #employee TABLE(
ID int,
StartTime datetime,
EndTime datetime
)
INSERT INTO #employee SELECT '2112','2021-08-10 23:25:28.750',NULL
INSERT INTO #employee SELECT '2111','2021-08-06 17:42:27.400','2021-08-10 23:25:28.750'
INSERT INTO #employee SELECT '2110','2021-08-03 20:21:14.093','2021-08-06 17:42:27.400'
SELECT ID,* from #employee where
EndTime >= GETDATE()-1 OR EndTime is null
It will takes -1 day from execution time . So if you execute it right now you will heave only null value in output - because now it's 14.08 and this Edtime is null ( still running i think ).
DBFiddleDemo

Finding original start date to absence period

Could you please take a look at the following task?
I have DATA table (it contains data for previous week):
CREATE TABLE DATA
(
EMPLOYEE nvarchar(50),
ABSENCE_START_DATE datetime,
ABSENCE_END_DATE datetime,
ABSENCE_TYPE nvarchar(50)
)
ABSENCE_START_DATE - date when absence starts
ABSENCE_END_DATE - date when absence ends
ABSENCE_TYPE - type of absence
Current table contains the following data:
INSERT INTO DATA(EMPLOYEE,ABSENCE_START_DATE,ABSENCE_END_DATE,ABSENCE_TYPE) VALUES
('EMP01','2017-09-04 00:00:00.000','2017-09-06 00:00:00.000','Sickness'),--Monday - Wednesday
('EMP01','2017-09-08 00:00:00.000','2017-09-08 00:00:00.000','Vacation'),--Friday - Friday
('EMP02','2017-09-04 00:00:00.000','2017-09-09 00:00:00.000','Sickness'),--Monday - Friday
('EMP03','2017-09-05 00:00:00.000','2017-09-09 00:00:00.000','Sickness')--Tuesday - Friday
Also, I have another table - STORAGE (it contains data for dates which are earlier than start of previous week).
CREATE TABLE STORAGE
(
EMPLOYEE nvarchar(50),
APPLY_DATE datetime,
ABSENCE_TYPE nvarchar(50)
)
There are daily records (excluding Saturdays and Sundays - they will never exist in this table)
INSERT INTO STORAGE(EMPLOYEE,APPLY_DATE,ABSENCE_TYPE) VALUES
('EMP01','2017-08-27 00:00:00.000','Sickness'),
('EMP01','2017-08-28 00:00:00.000','Worked'),
('EMP01','2017-08-29 00:00:00.000','Worked'),
('EMP01','2017-08-30 00:00:00.000','Sickness'),
('EMP01','2017-08-31 00:00:00.000','Sickness'),
('EMP01','2017-09-01 00:00:00.000','Sickness'),
('EMP02','2017-08-31 00:00:00.000','Worked'),
('EMP02','2017-09-01 00:00:00.000','Sickness')
So, the task is:
sql -script should find original start date to absence periods (from DATA table) which absence start date is Monday.
In other words, script should go day after day "in the past" and find date when appropriate absence period starts.
Not necessary that absence on Monday is 'Sickness'. It could be also 'Travel','Maternity'...
Expected result for examples below is (pay attention to first and third rows - absence start dates are different from appropriate rows in DATA table):
Thank you in advance.
From the sample data shared, it seems that you are looking to retrieve min date from storage to absence_start_date column of data table, below query can be an option.
SELECT d.Employee,
coalesce(CASE
WHEN d.ABSENCE_TYPE = 'Sickness' THEN
(SELECT min(apply_date)
FROM
STORAGE s
WHERE s.employee = d.employee
AND s.ABSENCE_TYPE = 'Sickness')
ELSE d.ABSENCE_START_DATE
END,d.ABSENCE_START_DATE) AS ABSENCE_START_DATE,
d.ABSENCE_END_DATE,
ABSENCE_TYPE
FROM DATA d;
Update 1:
A more generic query is below.
SELECT d.Employee,
coalesce(
(SELECT min(apply_date)
FROM
STORAGE s
WHERE s.employee = d.employee
AND s.ABSENCE_TYPE = d.ABSENCE_TYPE),d.ABSENCE_START_DATE) AS ABSENCE_START_DATE,
d.ABSENCE_END_DATE,
d.ABSENCE_TYPE
FROM DATA d
Update 2:
If you want to exclude weekends from the data, below is the query.
SELECT d.Employee,
coalesce(
(SELECT min(apply_date)
FROM
STORAGE s
WHERE s.employee = d.employee
AND s.ABSENCE_TYPE = d.ABSENCE_TYPE
AND DATENAME(dw,apply_date) NOT IN('Sunday','Saturday')),d.ABSENCE_START_DATE) AS ABSENCE_START_DATE,
d.ABSENCE_END_DATE,
d.ABSENCE_TYPE
FROM DATA d
Result:
Employee ABSENCE_START_DATE ABSENCE_END_DATE ABSENCE_TYPE
--------------------------------------------------------------------
EMP01 30.08.2017 00:00:00 06.09.2017 00:00:00 Sickness
EMP01 08.09.2017 00:00:00 08.09.2017 00:00:00 Vacation
EMP02 01.09.2017 00:00:00 09.09.2017 00:00:00 Sickness
EMP03 05.09.2017 00:00:00 09.09.2017 00:00:00 Sickness
you can check the demo here
Hope this will help.

How to get timebased data from MSSQL 2008 aggregated at time interval

I have an equipment that reports its number of produced pieces at random time intervals. At each record, the internal counter is reset, so if I want to get the total pieces, I would net to sum over an interval.
ts pieces
--------------------------------
2013-01-23 11:58 2013
2013-01-23 12:12 3025
2013-01-23 12:12 3025
2013-01-23 12:13 112
2013-01-23 12:17 1122
2013-01-23 12:34 3112
2013-01-23 12:36 3025
What if I want to query this data and I want the produced pieces from 12:00 to 12:30. I cannot simply do the following:
SELECT SUM(pieces)
FROM table
WHERE ts BETWEEN '2013-01-23 12:00' and '2013-01-23 12:30'
With this query, I would have too many pieces in the beginning (as of the 3025 pieces reported at 12:12 some were produced in the 2 minutes before 12:00) and I would have too little pieces at the end (pieces produced between 12:17 and 12:30 were only reported at 12:34).
Is there a built in feature in SQL server to do such calculations on timebased series, or would it require me to manually interpolate based on dateDiff between first/last values in the interval and last/first outside?
You have to do the interpolation manually. I've built some code that does it, based on various other assumptions (i.e. whether ts represents "all items produced up until and including this minute" or "all items produced before this minute"):
declare #t table (ts datetime2 not null,pieces int not null)
insert into #t(ts,pieces) values
('2013-01-23T11:58:00',2013),
('2013-01-23T12:12:00',3025),
--('2013-01-23T12:12:00',3025), --Error, two identical counts at same time?
('2013-01-23T12:13:00',112 ),
('2013-01-23T12:17:00',1122),
('2013-01-23T12:34:00',3112),
('2013-01-23T12:36:00',3025)
declare #Start datetime2
declare #End datetime2
select #Start = '2013-01-23T12:00:00', #End = '2013-01-23T12:30:00'
;With Numbers as (
select distinct number
from master..spt_values
), RowsNumbered as (
select
ROW_NUMBER() OVER (ORDER BY ts) as rn,
*
from #t
), Paired as (
select
rn2.ts as prevTime,
rn1.ts as thisTime,
rn1.pieces as pieces
from
RowsNumbered rn1
inner join
RowsNumbered rn2
on
rn1.rn = rn2.rn + 1
), Minutes as (
select
DATEADD(minute,-number,thisTime) as Time,
(pieces * 1.0) / DATEDIFF(minute,prevTime,thisTime) as Pieces
from
Paired p
inner join
Numbers n
on
number < 60 and --Reasonable?
DATEADD(minute,-number,thisTime) > prevTime and
number >= 0
)
select SUM(pieces)
from Minutes
where Time >= #Start and Time < #End
I'm also treating the start and end times as a semi-open interval with an inclusive start and exclusive end. This is normally the more sensible way to work with continuous data such as datetime.
Hopefully, you can see how I'm building up each CTE to get from the individual timestamps and piece counts to, for each minute, having a concept of how many pieces were produced in that minute. Once we've got to that point, we can just SUM over the minutes we want to include.