I am attempting to find all employees that worked during a 24 hour period. Their shift starts at 08:00 and ends the next day at 08:00. Most start at 08:00 and end the next day at 08:00. However, the start and end times may occur at any time during the 24 hour period. My goal would be to have all of those that worked during the 24 hour period to be marked as starting on the day the shift started.
For example, in the table below even though there are multiple start times over 2 days they all occurred within the 24 hours of the shift start time beginning on 7/26/22 at 08:00. I would like a new column showing a shift day of 7/26/22.
first
start_time
end_time
hours
Nicole
7/26/22 8:00
7/27/22 8:00
24
Callan
7/26/22 8:00
7/27/22 8:00
24
Bob
7/26/22 18:30
7/27/22 6:30
12
Kevin
7/27/22 7:00
7/27/22 8:00
1
Michael
7/27/22 7:00
7/27/22 8:00
1
If the start time is greater than or equal to 8 am you want the shift date to be the same date as the start time, otherwise it is the previous day:
create table #shifts
(
[first] varchar(32), -- this is a very weird column name. first_name, perhaps?
start_time datetime,
end_time datetime,
[hours] tinyint
);
set dateformat mdy; -- your sample data uses an ambiguous date format so I have to do this
insert #shifts values
('Nicole ', '7/26/22 08:00', '7/27/22 8:00', 24),
('Callan ', '7/26/22 08:00', '7/27/22 8:00', 24),
('Bob ', '7/26/22 18:30', '7/27/22 6:30', 12),
('Kevin ', '7/27/22 07:00', '7/27/22 8:00', 1),
('Michael', '7/27/22 07:00', '7/27/22 8:00', 1);
select [first],
start_time,
end_time,
[hours],
shift_date = iif
(
cast(start_time as time) >= '8:00',
cast(start_time as date),
cast(dateadd(day, -1, start_time) as date)
)
from #shifts;
Produces:
first
start_time
end_time
hours
shift_date
Nicole
2022-07-26 08:00:00.000
2022-07-27 08:00:00.000
24
2022-07-26
Callan
2022-07-26 08:00:00.000
2022-07-27 08:00:00.000
24
2022-07-26
Bob
2022-07-26 18:30:00.000
2022-07-27 06:30:00.000
12
2022-07-26
Kevin
2022-07-27 07:00:00.000
2022-07-27 08:00:00.000
1
2022-07-26
Michael
2022-07-27 07:00:00.000
2022-07-27 08:00:00.000
1
2022-07-26
Related
I need some help regarding sum of production count for overnight shifts.
The table just contains a timestamp (that is automaticaly generated by SQL server during INSERT), the number of OK produced pieces and the number of NOT OK produced pieces in that given timestamp.
CREATE TABLE [machine1](
[timestamp] [datetime] NOT NULL,
[OK] [int] NOT NULL,
[NOK] [int] NOT NULL
)
ALTER TABLE [machine1] ADD DEFAULT (getdate()) FOR [timestamp]
The table holds values like these (just an example, there are hundreds of lines each day and the time stamps are not fixed like each hour or each 30mins):
timestamp
OK
NOK
2022-08-01 05:30:00.000
15
1
2022-08-01 06:30:00.000
18
3
...
...
...
2022-08-01 21:30:00.000
10
12
2022-08-01 22:30:00.000
0
3
...
...
...
2022-08-01 23:59:00.000
1
2
2022-08-02 00:01:00.000
7
0
...
...
...
2022-08-02 05:30:00.000
12
4
2022-08-02 06:30:00.000
9
3
The production works in shifts like so:
morning shift: 6:00 -> 14:00
afternoon shift: 14:00 -> 22:00
night shift: 22:00 -> 6:00 the next day
I have managed to get sums for the morning and afternoon shifts without issues but I can't figure out how to do the sum for the night shift (I have these SELECTs for each shift stored as a VIEW for easy access).
For the morning shift:
SELECT CAST(timestamp AS date) AS Morning,
SUM(OK) AS SUM_OK,
SUM(NOK) AS SUM_NOK
FROM [machine1]
WHERE DATEPART(hh,timestamp) >= 6 AND DATEPART(hh,timestamp) < 14
GROUP BY CAST(timestamp AS date)
ORDER BY Morning ASC
For the afternoon shift:
SELECT CAST(timestamp AS date) AS Afternoon,
SUM(OK) AS SUM_OK,
SUM(NOK) AS SUM_NOK
FROM [machine1]
WHERE DATEPART(hh,timestamp) >= 14 AND DATEPART(hh,timestamp) < 22
GROUP BY CAST(timestamp AS date)
ORDER BY Afternoon ASC
Since we identify the date of each shift by its start, my idea would be that the result for such SUM of night shift would be
Night
SUM_OK
SUM_NOK
2022-08-01
xxx
xxx
for interval 2022-08-01 22:00:00.000 -> 2022-08-02 05:59:59.999
2022-08-02
xxx
xxx
for interval 2022-08-02 22:00:00.000 -> 2022-08-03 05:59:59.999
2022-08-03
xxx
xxx
for interval 2022-08-03 22:00:00.000 -> 2022-08-04 05:59:59.999
2022-08-04
xxx
xxx
for interval 2022-08-04 22:00:00.000 -> 2022-08-05 05:59:59.999
...
...
...
After few days of trial and error I have probably managed to find the needed solution. Using a subquery I shift all the times in range 00:00:00 -> 05:59:59 to the previous day and then I use that result in same approach as for morning and afternon shift (because now all the production data from night shift are in the same date between 22:00:00 and 23:59:59).
In case anyone needs it in future:
SELECT
CAST(nightShift.shiftedTime AS date) AS Night,
SUM(nightShift.OK) AS SUM_OK,
SUM(nightShift.NOK) AS SUM_NOK
FROM
(SELECT
CASE WHEN (DATEPART(hh, timestamp) < 6 AND DATEPART(hh, timestamp) >= 4) THEN DATEADD(HOUR, -6, timestamp)
WHEN (DATEPART(hh, timestamp) < 4 AND DATEPART(hh, timestamp) >= 2) THEN DATEADD(HOUR, -4, timestamp)
WHEN (DATEPART(hh, timestamp) < 2 AND DATEPART(hh, timestamp) >= 0) THEN DATEADD(HOUR, -2, timestamp)
END AS shiftedTime,
[OK],
[NOK]
FROM [machine1]
WHERE (DATEPART(hh, cas) >= 0 AND DATEPART(hh, cas) < 6)) nightShift
WHERE DATEPART(hh,nightShift.shiftedTime) >= 22
GROUP BY CAST(nightShift.shiftedTime AS date)
ORDER BY Night ASC
PS: If there is anything wrong with this approach, please feel free to correct me as I'm just newbie in SQL. So far this seems to do exactly what I needed.
I have a table that holds vehicle activity data. There is a start date and end date columns in the table. I wish to construct a query that will show the amount of time in minutes that each vehicle is active or working on a particular day. My problem is that the time between the start date and end date may span a number of days.
Example:
dwVehicleIDFK StartDate EndDate Minutes HrsMins
731 18/09/2019 08:00 18/09/2019 13:00 300 05:00
797 18/09/2019 08:00 18/09/2019 12:00 240 04:00
687 17/09/2019 16:00 17/09/2019 21:00 300 05:00
826 17/09/2019 16:00 17/09/2019 21:00 300 05:00
734 18/09/2019 10:00 18/09/2019 15:30 330 05:30
843 18/09/2019 14:00 18/09/2019 18:00 240 04:00
662 18/09/2019 09:00 18/09/2019 14:00 300 05:00
662 17/09/2019 09:00 17/09/2019 14:00 300 05:00
662 16/09/2019 09:00 16/09/2019 14:00 300 05:00
817 18/09/2019 14:00 19/09/2019 08:00 1080 18:00
In the data above the vehicle with the ID of 817 extends over two days. How would I get the query to return the time period for the 18/09 only or up until midnight on the 18/09?
The query to return the data above:
Select
dwVehicleIDFK,
StartDate,
EndDate,
DATEDIFF(MINUTE, Convert(DateTime, StartDate, 103),
Convert(DateTime, EndDate, 103)) as Minutes,
CONVERT(varchar(5), DATEADD(minute, DATEDIFF(minute,
Convert(DateTime, StartDate, 103),
Convert(DateTime, EndDate, 103)), 0), 114) as HrsMins
from
VehHistory
Assume you loaded the data for the specified date #Day and converted the start/end date to datetime in temp table #temp:
Select
dwVehicleIDFK,
StartDate,
EndDate,
DATEDIFF(MINUTE, CASE WHEN StartDate<#Day THEN #Day ELSE StartDate END,
CASE WHEN EndDate>DATEADD(DAY,1,#Day) THEN DATEADD(DAY,1,#Day) ELSE EndDate END) AS UsedMinutes
from #temp;
I have table in which Sunday to Saturdy "Doctor Start" and "End Time" is given.
I want to create time slots of 15 minutes.
On the basis of that, the patient clicks on calendar datetime interval which shows slots that have already been booked.
The following example shows how to split time into slices of 15 minutes. It uses hierarchical query. A little bit of explanation:
line 2: trunc function, applied to a date value, returns "beginning" of that day (at midnight). Adding 15 / (24*60) adds 15 minutes (as there are 24 hours in a day and 60 minutes in an hour). Multiplying 15 by level works as a "loop", i.e. adds 15-by-15-by-15 ... minutes to previous value.
line 4: similar to line 2, but it makes sure that a day (24 hours * 60 minutes) is divided to 15-minutes parts
line 6: start time is trivial
line 7: end time just adds 15 minutes to start_time
line 9: return only time between 10 and 16 hours (you don't have patients at 02:15 AM, right?)
SQL> with fifteen as
2 (select trunc(sysdate) + (level * 15)/(24*60) c_time
3 from dual
4 connect by level <= (24*60) / 15
5 )
6 select to_char(c_time, 'hh24:mi') start_time,
7 to_char(c_time + 15 / (24 * 60), 'hh24:mi') end_time
8 from fifteen
9 where extract(hour from cast (c_time as timestamp)) between 10 and 15;
START_TIME END_TIME
---------- ----------
10:00 10:15
10:15 10:30
10:30 10:45
10:45 11:00
11:00 11:15
11:15 11:30
11:30 11:45
11:45 12:00
12:00 12:15
12:15 12:30
12:30 12:45
12:45 13:00
13:00 13:15
13:15 13:30
13:30 13:45
13:45 14:00
14:00 14:15
14:15 14:30
14:30 14:45
14:45 15:00
15:00 15:15
15:15 15:30
15:30 15:45
15:45 16:00
24 rows selected.
SQL>
I have a question and hopefully someone can help, because i have been stuck on this for a long time.
I have a column with remaining minutes for a task to expire and i want to calculate when this task will expire within the business days timeframe starting from the current sysdate day lets say weekdays from 09:00 to 17:00.
| Task No | Minutes Remaining | Expiration date |
| Task1 | 1800 | 27-10-16 9:45 AM |
| Task2 | 3400 | 28-10-16 9:45 AM |
| Task3 | 400 | 29-10-16 9:45 AM |
| Task4 | 180 | 30-10-16 9:45 AM |
| Task5 | 8400 | 31-10-16 9:45 AM |
| Task6 | 5000 | 1-11-16 9:45 AM |
OK, this was a fun problem. To summarize: You are given a date (which in Oracle always includes the time-of-day) from which you start measurement, and an initial duration in minutes. You need to find the expiration date (meaning date and time-of-day as always), which is calculated by adding the duration in minutes to the "clock-starting" date, but the clock should only run during business hours - 9 to 17, Monday to Friday only (not on weekends).
I assume if the "minutes remaining" is 0, then the expiration should be the same as the "clock-starting" date if it falls within work hours, or 9 am on the next work day otherwise.
To understand the solution, let's break it down in two parts. First let's consider a very special case: the "clock starts" on a Monday at 9 am. Then break down minutes remaining into an integer multiple of 2400 (5*8*60 = 2400 minutes in a full work week), plus an integer multiple of 480 from what's left (480 minutes to a work day), plus whatever is left, if anything. Then: the expiration date is the "clock-starting" date, plus however many weeks, plus however many whole days (between 0 and 4), plus the remaining minutes. One exceptional case here: if the "minutes remaining" is an exact multiple of 480 minutes, then expiration is at 5 pm on a certain work day, and not 9 am on the next work day. This requires special handling in the formula. All this is done in the outer query (at the bottom of the solution below).
Then we need to reduce the general case to this special case. This is done in the subquery prep in the solution. I simply increase the "minutes remaining" by the work minutes elapsed from 9 am on Monday at the beginning of the week. This is a relatively simple computation. Note that if the "clock starting" date is after 5 pm on a Friday (or any time on Saturday or Sunday), I must add exactly 2400 minutes, a full work week.
In the solution, I show a variety of "clock starting" dates, dt, and minutes remaining, rm. I tested a variety of situations, and I think the solution is correct, but you may want to test on more data (other situations I didn't include in the tests).
with
inputs ( task, min_rem, dt ) as (
select 'Task1', 1800, to_date('27-10-16 9:45 AM', 'dd-mm-yy hh:mi AM') from dual union all
select 'Task2', 3400, to_date('28-10-16 9:45 AM', 'dd-mm-yy hh:mi AM') from dual union all
select 'Task3', 400, to_date('29-10-16 3:45 AM', 'dd-mm-yy hh:mi AM') from dual union all
select 'Task4', 180, to_date('30-10-16 9:45 AM', 'dd-mm-yy hh:mi AM') from dual union all
select 'Task5', 8400, to_date('31-10-16 9:45 PM', 'dd-mm-yy hh:mi AM') from dual union all
select 'Task6', 5000, to_date('01-11-16 5:00 PM', 'dd-mm-yy hh:mi AM') from dual union all
select 'Task7', 0, to_date('01-12-16 5:00 PM', 'dd-mm-yy hh:mi PM') from dual
),
prep ( task, min_rem, dt, adj_min, adj_dt ) as (
select task, min_rem, dt,
min_rem + case when dt > trunc(dt, 'iw') + 5 + 17/24 then 2400
else (trunc(dt) - trunc(dt, 'iw')) * 480 +
least(480, greatest(0, 1440 * (dt - trunc(dt) - 9/24)))
end,
trunc(dt, 'iw') + 9/24
from inputs
)
select task, min_rem, dt,
adj_dt + 7 * trunc(adj_min / 2400)
+ case when adj_min/480 = trunc(adj_min/480)
then mod(adj_min, 2400) / 480 - 1 + 8/24
else trunc(mod(adj_min, 2400) / 480) + mod(adj_min, 480) / 1440
end as expiration
from prep
order by task
;
Output:
TASK MIN_REM DT EXPIRATION
----- ---------- ----------------- -----------------
Task1 1800 27-10-16 09:45 AM 01-11-16 03:45 PM
Task2 3400 28-10-16 09:45 AM 08-11-16 10:25 AM
Task3 400 29-10-16 03:45 AM 31-10-16 03:40 PM
Task4 180 30-10-16 09:45 AM 31-10-16 12:00 PM
Task5 8400 31-10-16 09:45 PM 24-11-16 01:00 PM
Task6 5000 01-11-16 05:00 PM 16-11-16 12:20 PM
Task7 0 01-12-16 05:00 PM 01-12-16 05:00 PM
7 rows selected
My table has start_date and end_date from which I need to find the hour difference. The issue is that both of these date times are not on the same day.
user start_date end_date difference
Alex 7/25/2016 16:00 7/26/2016 0:30 8.5
Alex 7/24/2016 16:00 7/25/2016 0:30 8.5
Alex 7/21/2016 16:00 7/22/2016 0:30 8.5
Alex 7/20/2016 16:00 7/21/2016 0:30 8.5
Alex 7/19/2016 16:00 7/20/2016 0:30 8.5
Alex 7/18/2016 16:00 7/19/2016 0:30 8.5
Alex 7/17/2016 16:00 7/18/2016 0:30 8.5
Alex 7/14/2016 16:00 7/15/2016 0:30 8.5
Alex 7/13/2016 16:00 7/14/2016 0:30 8.5
Alex 7/12/2016 16:00 7/13/2016 0:30 8.5
Alex 7/11/2016 16:00 7/12/2016 0:30 8.5
Alex 7/10/2016 16:00 7/11/2016 0:30 8.5
Usually it is 5 working days and I get the answer if I group them by start_date. But I need an new date column where I need the output as below. Please note that 15/7/2016 and 22/7/2016 was not present in the above table. I need the additional 0.5 hour & date for the 6th day to be included to my derived table.
User Date difference
Alex 7/25/2016 8.5
Alex 7/24/2016 8.5
Alex 7/22/2016 0.5
Alex 7/21/2016 8.0
Alex 7/20/2016 8.5
Alex 7/19/2016 8.5
Alex 7/18/2016 8.5
Alex 7/17/2016 8.5
Alex 7/15/2016 0.5
Alex 7/14/2016 8.0
Alex 7/13/2016 8.5
Alex 7/12/2016 8.5
Alex 7/11/2016 8.5
Alex 7/10/2016 8.5
I calculate the difference as
round(cast(datediff(seconds, start_date, end_date) as decimal)/3600,2)
Whenever there is sophisticated logic, I'd suggest to use union queries and split the logic into a select query (or even table) each. Then you'd be able to calculate this in two steps. The main difference seems to be whether the 0.5 between 00:00:00 and 00:30:00 should be counted to the previous workday or whether it should stand alone. The latter seems to be determined based on whether the end_date is also a workday itself. I see three cases:
Next day is a workday:
Report all hours on start_date
Next day is not a workday:
Report hours from start_date to midnight on start_date
Report hours from midnight to end_date on end_date
I used the following example data based on your description:
create temporary table _test (user varchar(20), start_date timestamp, end_date timestamp);
insert into _test values ('Alex', '7/25/2016 16:00', '7/26/2016 0:30'), ('Alex', '7/24/2016 16:00', '7/25/2016 0:30'), ('Alex', '7/21/2016 16:00', '7/22/2016 0:30'), ('Alex', '7/20/2016 16:00', '7/21/2016 0:30'), ('Alex', '7/19/2016 16:00', '7/20/2016 0:30'), ('Alex', '7/18/2016 16:00', '7/19/2016 0:30'), ('Alex', '7/17/2016 16:00', '7/18/2016 0:30'), ('Alex', '7/14/2016 16:00', '7/15/2016 0:30'), ('Alex', '7/13/2016 16:00', '7/14/2016 0:30'), ('Alex', '7/12/2016 16:00', '7/13/2016 0:30'), ('Alex', '7/11/2016 16:00', '7/12/2016 0:30'), ('Alex', '7/10/2016 16:00', '7/11/2016 0:30');
We will need to know whether the next day is a workday, so I suggest using the lead() window function (see documentation) which will give you the start_date from the next row.
create temporary table _differences as (
select
user_name
, start_date::date as start_date
, end_date::date as end_date
/**
* Calculate difference in hours between start_date and end_date: */
, round(cast(datediff(seconds, start_date, end_date) as decimal)/3600,2) as hours_start_to_end
/**
* Calculate difference in hours between start_date and midnight: */
, round(cast(datediff(seconds, start_date, dateadd(day, 1, start_date::date)) as decimal)/3600,2) as hours_start_to_midnight
/**
* Calculate difference between midnight on end_date and end_date: */
, round(cast(datediff(seconds, end_date::date, end_date) as decimal)/3600,2) as hours_midnight_to_end
/**
* Calculate number of days from end_date until next start_date: */
, datediff(day, end_date::date, lead(start_date::date) over(partition by user_name order by start_date::date)) as days_until_next_workday
from
_test
);
Then the following query:
select
user_name as user_name
, start_date as ref_date
, hours_start_to_end as difference
from
_differences
where
days_until_next_workday = 0 -- report all work hours on start_date
union
select
user_name as user_name
, start_date as ref_date
, hours_start_to_midnight as difference
from
_differences
where
days_until_next_workday > 0 -- report partial work hours on start_date
union
select
user_name as user_name
, end_date as ref_date
, hours_midnight_to_end as difference
from
_differences
where
days_until_next_workday > 0 -- report partial work hours on end_date
order by
user_name
, ref_date desc
;
Would yield the following result:
user_name | ref_date | difference
-----------+------------+------------
Alex | 2016-07-24 | 8.50
Alex | 2016-07-22 | 0.50
Alex | 2016-07-21 | 8.00
Alex | 2016-07-20 | 8.50
Alex | 2016-07-19 | 8.50
Alex | 2016-07-18 | 8.50
Alex | 2016-07-17 | 8.50
Alex | 2016-07-15 | 0.50
Alex | 2016-07-14 | 8.00
Alex | 2016-07-13 | 8.50
Alex | 2016-07-12 | 8.50
Alex | 2016-07-11 | 8.50
Alex | 2016-07-10 | 8.50
(13 rows)
You can see that 7/25/2016 is missing because there is no start_date on or after 7/26/2016, so you'll need to figure out how to account for that special case.
here is how I have done the calc and it works perfectly
select user, trunc(start_time) as date1,
SUM(case when id = 1 then round(cast(datediff(seconds, start_time, st_t1) as decimal)/3600,2) end) as SCHEDULE
from
(
select user, start_time,
case when trunc(start_time) <> trunc(end_time) then cast(to_char(start_time,'yyyy-mm-dd 23:59:59') as timestamp) else cast(to_char(end_time,'yyyy-mm-dd hh24:mi:ss') as timestamp) end as st_t1
from table1 a
where id = 1
group by user_name, trunc(start_time)
union
select user_name, trunc(end_time) as date1,
SUM(case when id = 1 then round(cast(datediff(seconds, st_t2, end_time) as decimal)/3600,2) end) as SCHEDULE
from
(
select user_name, end_time,
case when trunc(start_time) <> trunc(end_time) then cast(to_char(end_time,'yyyy-mm-dd 00:00:00') as timestamp) else cast(to_char(end_time,'yyyy-mm-dd hh24:mi:ss') as timestamp) end as st_t2
from table1 a
where id = 1
)
group by user, trunc(end_time)