T-SQL: Rounding Time in to 15 minutes but only after 5 minutes past the 15min - sql

I have a table with a datetime field. I'm trying to round up/down the time in 15 minute intervals. But with non standard mathematical rounding rules where rounding up happens if its greater than 5 minutes past the 15 minute interval.
For example
IF 06:05 round down to 06:00
IF 06:06 round up to 06:15
IF 06:20 round down to 06:15
IF 06:21 round up to 06:30
and so on..
I've managed to find here T-SQL: Round to nearest 15 minute interval to round the nearest 15 minutes but this uses mathematical rounding meaning 06:07 would still round down to 06:00 instead of rounding up to 06:15.
Below code where i've got to:
cast(datepart(hour, getdate()) + datepart(minute, getdate()) / 60.00 as decimal(5, 2))

Just use a couple of date tricks.
This code will give you the top of the hour for the time you're evaluating (minutes effectively removed by adding up the hours since the 0 date in SQL):
select dateadd(hour, datediff(hour, 0, getdate()), 0)
From there, you need a CASE expression to evaluate which quartile of the hour the time in question falls into (just a snippet here):
case
when datepart(minute, dtm) > 50 then 60
when datepart(minute, dtm) > 35 then 45
when datepart(minute, dtm) > 20 then 30
when datepart(minute, dtm) > 5 then 15
else 0
end
Put those two pieces together with a DATEADD to decide how many minutes we're adding to that even hour mark:
declare #dtms table (dtm datetime);
insert #dtms (dtm)
values ('2019-07-16T12:05:00'),
('2019-07-16T12:06:00'),
('2019-07-16T12:21:00'),
('2019-07-16T12:29:00'),
('2019-07-16T12:35:00'),
('2019-07-16T12:38:00'),
('2019-07-16T12:56:00')
select
dtm,
dateadd(minute,
case
when datepart(minute, dtm) > 50 then 60
when datepart(minute, dtm) > 35 then 45
when datepart(minute, dtm) > 20 then 30
when datepart(minute, dtm) > 5 then 15
else 0
end, dateadd(hour, datediff(hour, 0, dtm), 0)) as rounded
from #dtms;
Results:
+-------------------------+-------------------------+
| dtm | rounded |
+-------------------------+-------------------------+
| 2019-07-16 12:05:00.000 | 2019-07-16 12:00:00.000 |
| 2019-07-16 12:06:00.000 | 2019-07-16 12:15:00.000 |
| 2019-07-16 12:21:00.000 | 2019-07-16 12:30:00.000 |
| 2019-07-16 12:29:00.000 | 2019-07-16 12:30:00.000 |
| 2019-07-16 12:35:00.000 | 2019-07-16 12:30:00.000 |
| 2019-07-16 12:38:00.000 | 2019-07-16 12:45:00.000 |
| 2019-07-16 12:56:00.000 | 2019-07-16 13:00:00.000 |
+-------------------------+-------------------------+

Related

SUM of production counts for "overnight work shift" in MS SQL (2019)

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.

Take value of some date that fall in specific hours in weekday but take different date in specific hours in weekend

I want to take the value that falls between the current day (today) and yesterday but only when after 9 am yesterday and before 9 am today. The current day must be only on Tuesday - Friday. But, if the current day is Monday, it will take value from Friday after 9 am to Monday before 9 am.
Samples
+---------+------------------------------+
| ID | registration_started_at |
+---------+------------------------------+
| 1 | 2021-05-13 07:00:00.000 |
| 2 | 2021-05-13 11:00:00.000 |
| 3 | 2021-05-14 08:00:00.000 |
| 4 | 2021-05-14 10:00:00.000 |
| 5 | 2021-05-15 12:00:00.000 |
| 6 | 2021-05-16 13:00:00.000 |
| 7 | 2021-05-17 08:00:00.000 |
| 8 | 2021-05-17 10:00:00.000 |
+---------+------------------------------+
So let say when the current_day (today) is Friday (14 May 2021),
When I run the query it must return
Desired Result 1
+---------+------------------------------+
| ID | registration_started_at |
+---------+------------------------------+
| 2 | 2021-05-13 11:00:00.000 |
| 3 | 2021-05-14 08:00:00.000 |
+---------+------------------------------+
But when current_day (today) is Monday (17 May 2021) it should return
Desired Result 2
+---------+------------------------------+
| ID | registration_started_at |
+---------+------------------------------+
| 4 | 2021-05-14 10:00:00.000 |
| 5 | 2021-05-15 12:00:00.000 |
| 6 | 2021-05-16 13:00:00.000 |
| 7 | 2021-05-17 08:00:00.000 |
+---------+------------------------------+
I only manage to get the desired result 1 with this query and I think this still not correct tho for desired result 2
SELECT ID,
DATETIME(registration_started_at, 'Asia/Jakarta') as registration_started_at
FROM `table`
WHERE
DATETIME_DIFF(CURRENT_DATETIME('Asia/Jakarta'), DATETIME(registration_started_at, 'Asia/Jakarta'), week) = 0
AND DATE(DATETIME_ADD(DATETIME(registration_started_at, 'Asia/Jakarta'), INTERVAL -9 HOUR)) = CURRENT_DATE('Asia/Jakarta') - 1
---------edit 1
Using the Mr. Caius Jard answer
WHERE
(DATETIME(registration_started_at, 'Asia/Jakarta') BETWEEN (
(CASE
WHEN EXTRACT(
DAYOFWEEK
FROM CURRENT_DATE('Asia/Jakarta')
) = 2 -- if Monday
THEN DATETIME_ADD(
CURRENT_DATETIME('Asia/Jakarta'),
INTERVAL -63 HOUR
) -- then 63 hours back from midnight today
ELSE DATETIME_ADD(
CURRENT_DATETIME('Asia/Jakarta'),
INTERVAL -15 HOUR
)
END)
) -- else 15 hours back from midnight today
AND DATETIME_ADD(
CURRENT_DATETIME('Asia/Jakarta'),
INTERVAL 9 HOUR
)) -- 9am today
It returns the 63 hours before today's time for Monday or 15 hours before today's time if not Monday, which is incorrect because if I run the query on 15.00 it only returns value from 00.00 today
I think this captures the logic you want:
WHERE DATETIME(registration_started_at, 'Asia/Jakarta') < DATETIME_ADD(DATETIME(DATE(CURRENT_DATETIME('Asia/Jakarta'))), INTERVAL 9 HOUR) AND
(EXTRACT(DAYOFWEEK, DATE(CURRENT_DATETIME('Asia/Jakarta'))) = 2 AND
DATETIME(registration_started_at, 'Asia/Jakarta') > DATETIME_ADD(DATETIME_ADD(DATETIME(DATE(CURRENT_DATETIME('Asia/Jakarta'))), INTERVAL 9 HOUR), INTERVAL -3 DAY) OR
DATETIME(registration_started_at, 'Asia/Jakarta') > DATETIME_ADD(DATETIME_ADD(DATETIME(DATE(CURRENT_DATETIME('Asia/Jakarta'))), INTERVAL 9 HOUR), INTERVAL -1 DAY)
)
What are the important components of this?
This expressoin:
DATETIME_ADD(DATETIME(DATE(CURRENT_DATETIME('Asia/Jakarta'))), INTERVAL 9 HOUR)
Returns 9:00 on the current date in Jakarata. No matter what, you want registration_started_at before that date/time.
This expression
EXTRACT(DAYOFWEEK, DATE(CURRENT_DATETIME('Asia/Jakarta')))
Returns the current day of the week, with 2 for Monday.
These expressions:
DATETIME_ADD(DATETIME_ADD(DATETIME(DATE(CURRENT_DATETIME('Asia/Jakarta'))), INTERVAL 9 HOUR), INTERVAL -3 DAY)
DATETIME_ADD(DATETIME_ADD(DATETIME(DATE(CURRENT_DATETIME('Asia/Jakarta'))), INTERVAL 9 HOUR), INTERVAL -1 DAY)
Just subtract 1 or 3 days from the current 9:00 datetime.
Perhaps something like:
WHERE
registration_started_at
BETWEEN
CASE WHEN EXTRACT(DAYOFWEEK FROM CURRENT_DATE()) = 2 -- if Monday
THEN DATETIME_ADD(CURRENT_DATE(), INTERVAL -63 HOUR)) -- then 63 hours back from midnight today
ELSE DATETIME_ADD(CURRENT_DATE(), INTERVAL -15 HOUR)) END -- else 15 hours back from midnight today
AND
DATETIME_ADD(CURRENT_DATE(), INTERVAL 9 HOUR)) -- 9am today
Never used bigquery, so it might need some fiddling, but the basic idea is that we ask via case when what the current day is and use it to change how much we go back in time
Using Mr. Caius Jard idea, I am able to make it works. I just need to cast CURRENT_DATE to DATETIME() to take the today's 00:00
WHERE
(DATETIME(registration_started_at, 'Asia/Jakarta') BETWEEN (
(CASE
WHEN EXTRACT(
DAYOFWEEK
FROM CURRENT_DATE('Asia/Jakarta')
) = 2 -- if Monday
THEN DATETIME_ADD(
DATETIME(CURRENT_DATE('Asia/Jakarta')),
INTERVAL -63 HOUR
) -- then 63 hours back from midnight today
ELSE DATETIME_ADD(
DATETIME(CURRENT_DATE('Asia/Jakarta')),
INTERVAL -15 HOUR
)
END)
) -- else 15 hours back from midnight today
AND DATETIME_ADD(
DATETIME(CURRENT_DATE('Asia/Jakarta')),
INTERVAL 9 HOUR
)) -- 9am today

Get hourly data based on StartDate

For Example,
I had data something like this :-
batch MIN MAX TIME
X 10 20 2018-07-12 10:29:00.000
X 30 50 2018-07-12 10:30:00.000
X 50 30 2018-07-12 10:31:00.000
| | | |
X 40 20 2018-07-12 11:45:00.000
Now I want hourly data based on start time, For example :-
DURATION MIN
2018-07-12 10:29:00.000-2018-07-12 11:29:00.000 10
2018-07-12 11:30:00.000-2018-07-12 12:30:00.000 10
How can I get this?(Get Min Value For every hour based on Start Time)
dateadd function allows you to add or subtract days,hours, minutes to a date.
Consider The below query
select dateadd(HOUR, -1, getdate()) as time_added,
getdate() as curr_date
The -1 is for subtracting one hour (adding negative one hour)
The result of above query is :
timeadded curr_date
2018-07-12 13:25:31.603 2018-07-12 14:25:31.603
Instead of getdate() use your startdate
In your case it would be
select min from table where time<#starttime and time> dateadd(HOUR, -1, #starttime)

difference in time is giving incorrect results

I have a table as test
shiftend | out |
---------------------------------------------
15:00:00.0000000 | 2016-07-22 14:42:00 |
16:00:00.0000000 | 2016-07-22 16:06:00 |
Shiftend is having a datatype as time
out is having a datatype as smalldatetime
I am expecting the output as
shiftend | out | Output
-----------------------------------------------------------------------------
15:00:00.0000000 | 2016-07-22 14:42:00 | -00:18:00
16:00:00.0000000 | 2016-07-22 16:06:00 | 00:06:00
I am trying this query:
select shiftend,out,CAST((out-Shiftend) as time(0)) as Output from test
where
CAST(CONVERT(NVARCHAR(10), out, 101) AS SMALLDATETIME) = CAST(CONVERT(NVARCHAR(10),'2016-07-22', 101) AS SMALLDATETIME)
But i am getting the output as
shiftend | out | Output
-----------------------------------------------------------------------------
15:00:00.0000000 | 2016-07-22 14:42:00 | 23:42:00
16:00:00.0000000 | 2016-07-22 16:06:00 | 00:06:00
23:42:00 is incorrect. How to calcualte the time.
Try the following query:
select
case when (cast(out as time) < shiftend) then '-' else '' end +
convert(varchar(8),
dateadd(minute,
abs(
DATEDIFF(minute,
cast(out as time)
, shiftend)
)
,0)
,108) as Output
Explanation:
You're getting the difference between the two dates with DATEDIFF(minute, cast(out as time), shiftend).
You need just the time component to avoid going to the previous day, so you use cast(out as time). shiftend as you mentioned is already of datatype time
abs returns the absolute value, so -18 becomes 18.
Then generate a date by adding the above value as minutes to 00:00:00 using dateadd(minute, [above value], 0)
The final convert(varchar(8),____,108) is since you required the output as a time.
iif(cast(out as time) < shiftend,'-','') adds negative sign or not to the beginning of the word.
Unfortunately, you can't have negative values in the time datatype. It's turning -18 into 00:00 - 18 which is 23:42. You could:
Use datediff and save the difference as the number of seconds for example.
Save only the modular difference and have a separate column for deciding whether it's a positive or negative difference.
Write your own SQL function for computing this as a varchar as above

SQL calculate Datediff for half of the 8 working day

I need to compare 2 dates and return the number of days in between. Here is a table as example:
+----+--------+-------------------------+-------------------------+
| id | userid | datestarted | datefinished |
+----+--------+-------------------------+-------------------------+
| | | | |
| 1 | 23 | 2014-03-25 09:05:00.000 | 2014-03-25 12:15:00.000 |
| 2 | 43 | 2014-03-25 09:05:00.000 | 2014-03-25 12:15:00.000 |
| 3 | 23 | 2014-03-31 09:05:00.000 | 2014-03-31 12:15:00.000 |
| 4 | 12 | 2014-03-25 09:05:00.000 | 2014-03-26 12:15:00.000 |
+----+--------+-------------------------+-------------------------+
In the first 3 cases, we have the same day, only the hours don't match.
Datestarted = 2014-03-25 09:05:00.000
Datefinished = 2014-03-25 12:15:00.000
We only input hours and minutes.
Until now, wee needed only to show the difference as whole number, without decimal points, and did it like this:
DATEDIFF(carsharing.datestarted, carsharing.datefinished)
But now, we have to show the difference between the dates as 0,5 day, if it is less than 4,5 hours. If the difference is greater it should stay as 1 day.
In the more complecated last case from the table, we should also compare and show difference between two different days
Datestarted = 2014-03-25 09:05:00.000
Datefinished = 2014-03-26 12:15:00.000
Here the result should be 1,5 days
I believe this is what you're looking for - this will round the difference to 0.5 for anything under 4.5 hours in the day, and everything else over that will go to a full day:
Declare #StartDate DateTime = '2014-03-25 09:05:00.000',
#EndDate DateTime = '2014-03-26 12:15:00.000'
;With TotalHours As
(
Select DateDiff(Minute, #StartDate, #EndDate) / 60.0 As TotalHours
)
Select Case
When TotalHours % 24 = 0
Then Floor(TotalHours / 24)
When TotalHours % 24 < 4.5
Then Floor(TotalHours / 24) + 0.5
Else Floor(TotalHours / 24) + 1.0
End As Days
From TotalHours
You can try this query. It gets the difference in minutes and multiply it by 2 in order to get a 0.5 day range. It then devide it by 24 hours and by 60 minutes before calculating the Ceiling value. Once you have it, it can be devide by 2 again.
When the value is over 4.5*24*60 (4.5 days in minutes), it only has to be devided by 24 and 60.
Query:
Select id, userid, datestarted, datefinished
, Days = Case When DATEDIFF(minute, datestarted, datefinished) > 4.5*60*24
then DATEDIFF(minute, datestarted, datefinished) / 24 / 60
else CEILING(((2.0*DATEDIFF(minute, datestarted, datefinished)) / 24 / 60)) / 2
end
From #dates
Output:
id userid datestarted datefinished Days
1 23 2014-03-25 09:05:00.000 2014-03-25 12:15:00.000 0.500000
2 43 2014-03-25 09:05:00.000 2014-03-25 12:15:00.000 0.500000
3 23 2014-03-31 09:05:00.000 2014-03-31 12:15:00.000 0.500000
4 12 2014-03-25 09:05:00.000 2014-03-26 12:15:00.000 1.500000
5 12 2014-03-25 09:05:00.000 2014-03-29 12:15:00.000 4.500000
6 12 2014-03-25 09:05:00.000 2014-03-29 22:15:00.000 4.000000
Sample Data
declare #dates table(id int, userid int, datestarted datetime, datefinished datetime);
insert into #dates(id, userid, datestarted, datefinished) values
(1, 23, '2014-03-25 09:05:00.000', '2014-03-25 12:15:00.000')
, (2, 43, '2014-03-25 09:05:00.000', '2014-03-25 12:15:00.000')
, (3, 23, '2014-03-31 09:05:00.000', '2014-03-31 12:15:00.000')
, (4, 12, '2014-03-25 09:05:00.000', '2014-03-26 12:15:00.000')
, (5, 12, '2014-03-25 09:05:00.000', '2014-03-29 12:15:00.000')
, (6, 12, '2014-03-25 09:05:00.000', '2014-03-29 22:15:00.000')
DECLARE
#StartDate datetime = '2014-03-25 09:05:00.000'
,#EndDate datetime = '2014-03-26 09:05:00.000'
;WITH d AS (SELECT DATEDIFF(d,#StartDate,#EndDate) Dys)
,h AS (SELECT DATEDIFF(hh,#StartDate,#EndDate) Hrs)
SELECT d.Dys + CASE WHEN (h.Hrs - d.Dys*24) = 0 THEN 0 ELSE CASE WHEN (h.Hrs - d.Dys*24) < 4.5 THEN 0.5 ELSE 1 END END
FROM d,h