Rounding Datetime to nearest 15 minutes in SQL Server - sql-server

I have a problem in SQL Server with rounding datetime. I got datetime in column rec_datetime, but I want to round this datetime in a new column r_datetime, which has to be rounded to nearest 15 min, for the whole column rec_datetime.
Example:
[2015-11-24 19:06:00.000] - expected result -> [2015-11-24 19:00:00.000]
[2015-11-24 19:09:00.000] - expected result -> [2015-11-24 19:15:00.000]
Is it possible to round it via select for whole column? Something like :
select round(rec_datetime.......

Round-down, round-nearest & round-up to nearest 15mins
DATEADD( minute, ( DATEDIFF( minute, 0, dateTimeX ) / 15 ) * 15, 0 ) AS dateTimeRoundDown
DATEADD( minute, ( DATEDIFF( minute, 0, DATEADD( second, ( 15 * 60 ) / 2, dateTimeX ) ) / 15 ) * 15, 0 ) AS dateTimeRoundNearest
DATEADD( minute, ( DATEDIFF( minute, 0, DATEADD( minute, 15 , dateTimeX ) ) / 15 ) * 15, 0 ) AS dateTimeRoundUp
Round Down
DATEADD( minute, ( DATEDIFF( minute, 0, dateTimeX ) / 15 ) * 15, 0 ) AS dateTimeRoundDown
Get the offset in minutes (number of minutes since the base-date):
DATEDIFF( minute, 0, dateTimeX )
Round-down to 15 minute block by integer dividing:
DATEDIFF( minute, 0, dateTimeX ) / 15 ) * 15
Add the base-date back in minutes:
DATEADD( minute, ( DATEDIFF( minute, 0, dateTimeX ) / 15 ) * 15, 0 )
Round Nearest
DATEADD( minute, ( DATEDIFF( minute, 0, DATEADD( second, ( 15 * 60 ) / 2, dateTimeX ) ) / 15 ) * 15, 0 ) AS dateTimeRoundNear
15 / 2 minutes is added to the offset.
Needs to be in seconds due to the integer division.
Round Up
DATEADD( minute, ( DATEDIFF( minute, 0, DATEADD( minute, 15, dateTimeX ) ) / 15 ) * 15, 0 ) AS dateTimeRoundUp
15 minutes is added to the offset
Base Date
I generally use a base date of 0 which is the SQL Server 'epoch'
SELECT DATEADD( minute, 0, 0 ) -- '1900-01-01 00:00:00.000'
Because DATEADD() & DATEDIFF() use the SQL Server data type of INT (32 bits) for the parameters, for dates in the very far future, this may cause an overflow.
Using another fixed date, eg '2010-01-01', will avoid the overflow.
The chosen base-date must have a time part of 00:00:00
Using a base-date and integer division, no casting & no floating point operations are required.
Unit Testing
DECLARE #start DATETIME = '2017-04-20 21:00:00'
DECLARE #end DATETIME = '2017-04-20 22:00:00'
;WITH CTE_dateTimes AS
(
SELECT #start AS dateTimeX
UNION ALL
SELECT DATEADD( minute, 1, dateTimeX )
FROM CTE_dateTimes
WHERE DATEADD( minute, 1, dateTimeX ) <= #end
)
SELECT dateTimeX,
DATEADD( minute, ( DATEDIFF( minute, 0, dateTimeX ) / 15 ) * 15, 0 ) AS dateTimeRoundDown,
DATEADD( minute, ( DATEDIFF( minute, 0, DATEADD( second, ( 15 * 60 ) / 2, dateTimeX ) ) / 15 ) * 15, 0 ) AS dateTimeRoundNearest,
DATEADD( minute, ( DATEDIFF( minute, 0, DATEADD( minute, 15 , dateTimeX ) ) / 15 ) * 15, 0 ) AS dateTimeRoundUp
FROM CTE_dateTimes

Try something like this:
SELECT
dateadd(minute, datediff(minute, '1999-12-31 23:52:30', col) / 15*15, '2000-01-01')
FROM (values('2015-11-24 19:06:00.000'),('2015-11-24 19:09:00.000')) x(col)
Result:
2015-11-24 19:00:00.000
2015-11-24 19:15:00.000

In this case you need to round the minute to nearest 15
try query like this:
SELECT
original_datetime
--: this will give you the minute part
, datepart(minute, original_datetime) AS minuteFromDate
--: now get the nearest minute part to 15min, round it nearest to 15
, round(datepart(minute, original_datetime) * 1.0 / 15 , 0) * 15.0 AS roundedToNearest15
-- now remove the minute from original datetime, & add the rounded minute to the resultant datetime value
-- this will give you expected result
, dateadd(minute
, (round(datepart(minute, original_datetime) * 1.0 / 15 , 0) * 15.0)
, dateadd(minute, -datepart(minute, original_datetime), original_datetime)
) as rec_datetime
FROM (values('2015-11-24 19:06:00.000')
,('2015-11-24 19:09:00.000')
,('2015-11-24 19:56:00.000')
,('2015-11-24 19:48:00.000')
) x(original_datetime)

Nearest 15 minutes:
SELECT DateTimeX = GETDATE(), RoundedDateTimeX = CAST(CAST(CAST(GETDATE() as float) * (24 * 60/15) + 0.5 as bigint) / (24.0 * 60/15) as datetime)
Nearest 15 minutes rounded up:
SELECT DateTimeX = GETDATE(), RoundedDateTimeX = CAST(CAST(CAST(GETDATE() as float) * (24 * 60/15) + 1 as bigint) / (24.0 * 60/15) as datetime)
Note: The decimal 24.0 in devisor changes type back to float.

Related

Interval aggregation of 5 minutes for last 24 hours in database

I am trying to aggregate the data for last 24 hours on 5 min interval basis. I had written following query:
select
DATEADD(MINUTE, DATEDIFF(MINUTE, '2000', t.datetime) / 5 * 5, '2000')
as datetimevalue,
cast(sum(t.value) as decimal(10,2)) as value
from
dbo.data t
where
t.datetime <= '2020-09-12 19:23:00.000'
and t.datetime > DATEADD(day,-1,'2020-09-12 19:23:00.000')
group by
DATEADD(MINUTE, DATEDIFF(MINUTE, '2000', t.datetime) / 5 * 5, '2000')
order by
datetimevalue asc
The result is returned like this:
datetimevalue value
------------------------------------
2020-09-12 18:45:00.000, 54227.16
2020-09-12 18:50:00.000, 53681.54
2020-09-12 18:55:00.000, 49379.01
2020-09-12 19:00:00.000, 50751.53
2020-09-12 19:05:00.000, 55033.14
2020-09-12 19:10:00.000, 55858.37
2020-09-12 19:15:00.000, 54236.57
2020-09-12 19:20:00.000, 26731.36
I need aggregation of last 5 minute starting from current timestamp insteead of above which is aggregating last 3 minutes and then 5 minutes after that.
For example
datetimevalue value
--------------------------------------
2020-09-12 19:03:00.000, 22444.47
2020-09-12 19:08:00.000, 45674.46
2020-09-12 19:13:00.000, 35737.23
2020-09-12 19:18:00.000, 34675.34
Any assistance appreciated.
You need to calculate the offset to the aggregation time window (5 minutes), substract it while calculating values and the add it again to the final times:
DECLARE #dt DATETIME = GETDATE()
DECLARE #offset INT = (DATEDIFF(SECOND, CAST(CAST(#dt AS DATE) AS DATETIME), #dt) % (60 * 5)) / 60 * 60
-- In case you want an offset in seconds
--DECLARE #diff INT = DATEDIFF(SECOND, CAST(CAST(#dt AS DATE) AS DATETIME), #dt) % (60 * 5)
SELECT DATEADD(SECOND, #offset, datetimevalue) AS datetimevalue, value
FROM (
SELECT
DATEADD(MINUTE, DATEDIFF(MINUTE, '2000', DATEADD(SECOND, -#offset, t.DATETIME)) / 5 * 5, '2000') AS datetimevalue
,cast(sum(t.value) AS DECIMAL(10, 2)) AS value
FROM dbo.data t
WHERE t.DATETIME <= #dt
-- in case you want only for last 24 hours
-- AND t.DATETIME > DATEADD(DAY, - 1, #dt)
-- in case if you want to include offset in last timestamp
AND t.DATETIME > DATEADD(SECOND, -#offset, DATEADD(DAY, - 1, #dt))
GROUP BY DATEADD(MINUTE, DATEDIFF(MINUTE, '2000', DATEADD(SECOND, -#offset, t.DATETIME)) / 5 * 5, '2000')
) T
ORDER BY datetimevalue DESC

fiscal year sql server

I want to have a function that calculates the fiscal year. The fiscal year must begin on the first Monday in March. Thank you!
example:
CREATE FUNCTION dbo.fnc_FiscalYear( #AsOf DATETIME )
RETURNS INT
AS BEGIN
DECLARE #Answer INT
SET DATEFIRST 1
IF ( MONTH(#AsOf) < 3 )
or MONTH(#AsOf=3) and datename(weekday, #AsOf) = 'Monday' and datepart(day, #AsOf)>=1 and datepart(day, #AsOf)<=7;
SET #Answer = YEAR(#AsOf) - 1
ELSE SET #Answer = YEAR(#AsOf)
RETURN #Answer
END
GO
but it's not working
It looks as though there are a number of syntax errors with your script. Try this instead. I've removed the SETs and returned at the point of the if statements. Also, note the grouping of the if statements.
CREATE FUNCTION dbo.fnc_FiscalYear( #AsOf DATETIME )
RETURNS INT
AS BEGIN
DECLARE #Answer INT
IF (( MONTH(#AsOf) < 3 )
OR (MONTH(#AsOf) = 3
AND DATENAME(weekday, #AsOf) = 'Monday'
AND datepart(day, #AsOf) >= 1
AND datepart(day, #AsOf)<=7))
RETURN (YEAR(#AsOf) - 1)
RETURN YEAR(#AsOf)
END
GO
The logic for this is tricky -- especially for the first week of March:
CREATE FUNCTION dbo.fnc_FiscalYear (
#AsOf DATETIME
)
RETURNS INT AS
BEGIN
RETURN( CASE WHEN MONTH(#AsOf) < 3 THEN YEAR(#AsOf) - 1
WHEN MONTH(#AsOf) > 3 THEN YEAR(#AsOf)
WHEN DAY(#AsOf) >= 7 THEN YEAR(#AsOf)
WHEN DATENAME(#AsOf) = 'Monday' OR
DATENAME(#AsOf) = 'Tuesday' AND DAY(#AsOf) >= 2 OR
DATENAME(#AsOf) = 'Wednesday' AND DAY(#AsOf) >= 3 OR
DATENAME(#AsOf) = 'Thursday' AND DAY(#AsOf) >= 4 OR
DATENAME(#AsOf) = 'Friday' AND DAY(#AsOf) >= 5 OR
DATENAME(#AsOf) = 'Saturday' AND DAY(#AsOf) >= 26
THEN YEAR(#AsOf)
ELSE YEAR(#AsOf) - 1
END);
END ;
GO
( ##DateFirst + DatePart( weekday, SampleDate ) - 1 ) % 7 + 1 will always return an integer from 0 to 6 with 0 corresponding to Sunday regardless of the setting of DateFirst or Language and without string manipulation.
Tweaking the expression to ( ##DateFirst + DatePart( weekday, SampleDate ) - 2 ) % 7 shifts the start of the cycle from Sunday to Monday and the range to 0 to 6 which, in turn, simplifies the following code:
create function dbo.FiscalYear( #Date as Date )
returns Int
begin
return Year( #Date ) - case
when Month( #Date ) <= 2 then 1 -- January and February are always part of the prior year.
-- In March it is the prior year only in the first week and if the calculated
-- DoW is less than the day of the month.
when Month( #Date ) = 3 and Day( #Date ) < 7 and
( ##DateFirst + DatePart( weekday, #Date ) - 2 ) % 7 >= Day( #Date ) then 1
else 0 end;
end;
Test the function with sample data:
with
-- Generate sample dates for 20 years.
YearOffsets as (
select 0 as YearOffset
union all
select YearOffset + 1
from YearOffsets
where YearOffset < 20 ),
SampleDates as (
select Cast( DateAdd( year, YearOffset, '2000-02-28' ) as Date ) as SampleDate, 1 as Counter
from YearOffsets
union all
select DateAdd( day, 1, SampleDate ), Counter + 1
from SampleDates
where Counter < 10 )
-- Calculate the Fiscal Year for each sample date.
select SampleDate, DateName( weekday, SampleDate ) as WeekDay,
( ##DateFirst + DatePart( weekday, SampleDate ) - 2 ) % 7 as DayOfWeek, -- 0 = Monday.
dbo.FiscalYear( SampleDate ) as FiscalYear
from SampleDates
order by SampleDate;

Can't convert time to mm:ss

I have a table with 7 columns:
Start_hour | Start_minute | Start_second | End_hour | End_minute | end_second | date
My task is to sum the differences between start and end times on current day. And my query already does that. Sadly I need the output to be in minutes:seconds only. For Example the total sum of time differences is 2 hour 26 minutes and 52 seconds.
I need my output to like this:
126:52
My query right now looks like this:
SELECT
RIGHT(CONVERT(CHAR(8),
DATEADD(SECOND,
SUM(DATEDIFF(SECOND,
Timefromparts(start_hour, start_minute, start_second, 0, 0),
Timefromparts(end_hour, end_minute, end_second, 0, 0))
), 0), 108), 5)
FROM
opoznienia
WHERE
YEAR(data) = YEAR(GETDATE())
AND MONTH(data) = MONTH(GETDATE())
AND DAY(data) = DAY(GETDATE())
Sample data:
Start_hour | Start_minute | Start_second | End_hour | End_minute | end_second | date
10 15 0 10 30 30 2018-11-27 14:40:53.680
10 15 0 10 30 30 2018-11-30 10:16:20.610
6 10 30 6 23 45 2018-12-02 01:00:27.243
8 10 0 8 53 45 2018-12-02 14:42:48.663
10 5 13 10 55 23 2018-12-02 14:53:03.560
Output of the query above:
47:13 (without RIGHT command it would be 01:47:13)
The wanted output:
107:13
You need to calculate the number of seconds and then format it as you want. From number of seconds you can calculate the minutes as division by 60, where the remainder are the seconds:
declare #NumberOfSeconds int = 7612
-- Returns 126:52
select concat(#NumberOfSeconds / 60, ':', FORMAT(#NumberOfSeconds % 60, 'D2'))
Here is one option:
SELECT
numSeconds,
CASE WHEN numSeconds / 60 <= 100
THEN RIGHT('00' + CONVERT(VARCHAR(20), numSeconds / 60), 2)
ELSE CONVERT(VARCHAR(20), numSeconds / 60) END
+ ':' +
CASE WHEN numSeconds / 60 <= 100
THEN RIGHT('00' + CONVERT(VARCHAR(20), numSeconds % 60), 2)
ELSE CONVERT(VARCHAR(20), numSeconds % 60) END AS output
FROM yourTable;
Demo
The ugliness in the code has to do with that you expect a minimum of two digits for the minute and second components. So, we have to pad each component with zeroes in the case where minutes or seconds happens to be just a single digit.
The other answers are better, but to understand what may be missing in what you're trying to do - you can look at following where DatePart is used.
-->"
..(without RIGHT command it would be 01:47:13)
" This is your clue that you that you need to work with the parts and not the whole...
SELECT Cast
(
Datepart
(
hour,
DATEADD(second,sum(datediff(second,
TIMEFROMPARTS ( Start_hour, Start_minute, Start_second, 0, 0),
TIMEFROMPARTS ( End_hour, End_minute, End_second, 0, 0))),0)
) * 60
+
Datepart
(
minute,
DATEADD(second,sum(datediff(second,
TIMEFROMPARTS ( Start_hour, Start_minute, Start_second, 0, 0),
TIMEFROMPARTS ( End_hour, End_minute, End_second, 0, 0))),0)
) As varchar)
+
':'
+
Cast
(
Datepart
(
second,
DATEADD(second,sum(datediff(second,
TIMEFROMPARTS ( Start_hour, Start_minute, Start_second, 0, 0),
TIMEFROMPARTS ( End_hour, End_minute, End_second, 0, 0))),0)
) As varchar) as result
from minsec
Try the following
CREATE TABLE T(
StartHour INT,
StartMinute INT,
StartSecond INT,
EndHour INT,
EndMinute INT,
EndSecond INT,
[Date] DATE
);
INSERT INTO T VALUES
(10, 15, 0 , 10, 30, 30, '2018-11-27'),
(10, 15, 0 , 10, 30, 30, '2018-11-30'),
(6 , 10, 30, 6 , 23, 45, '2018-12-02'),
(8 , 10, 0 , 8 , 53, 45, '2018-12-02'),
(10, 5 , 13, 10, 55, 23, '2018-12-02');
SELECT *,
CAST( (DATEDIFF(Hour, StartTime, EndTime) * 60) +
(DATEDIFF(Minute, StartTime, EndTime) % 60) AS VARCHAR
) + ':' +
CAST(DATEDIFF(Second, StartTime, EndTime) % 60 AS VARCHAR)
FROM
(
SELECT [Date],
TIMEFROMPARTS(StartHour, StartMinute, StartSecond, 0, 0) StartTime,
TIMEFROMPARTS(EndHour, EndMinute, EndSecond, 0, 0) EndTime
FROM T
) TT
You can also SUM() and GROUP BY [Date] if you want to.
SELECT [Date],
CAST( SUM( (DATEDIFF(Hour, StartTime, EndTime) * 60) +
(DATEDIFF(Minute, StartTime, EndTime) % 60)
) AS VARCHAR
) + ':' +
CAST(SUM(DATEDIFF(Second, StartTime, EndTime) % 60) AS VARCHAR)
FROM
(
SELECT [Date],
TIMEFROMPARTS(StartHour, StartMinute, StartSecond, 0, 0) StartTime,
TIMEFROMPARTS(EndHour, EndMinute, EndSecond, 0, 0) EndTime
FROM T
) TT
GROUP BY [Date]
Demo
UPDATE
It seems like you are looking for
SELECT [Date],
CAST(SUM(DATEDIFF(Second, StartTime, EndTime)) / 60 AS VARCHAR) + ':' +
CAST(SUM(DATEDIFF(Second, StartTime, EndTime)) % 60 AS VARCHAR) [MM:SS]
FROM
(
SELECT [Date],
TIMEFROMPARTS(StartHour, StartMinute, StartSecond, 0, 0) StartTime,
TIMEFROMPARTS(EndHour, EndMinute, EndSecond, 0, 0) EndTime
FROM T
) TT
GROUP BY [Date];
Returns:
+---------------------+--------+
| Date | MM:SS |
+---------------------+--------+
| 27/11/2018 00:00:00 | 15:30 |
| 30/11/2018 00:00:00 | 15:30 |
| 02/12/2018 00:00:00 | 107:10 |
+---------------------+--------+
Demo

DATEADD MINUTES Between Range MSSQL

I am having some difficulty in trying to figure out something, lets say I have a date and time;
And I want to add 180 minutes to it so;
SELECT DATEADD(MINUTE,180,'2018-05-24 15:00')
This would give me answer of "2018-05-24 18:00" but I want to do it in a range so ADD the minutes if you are between 09:00 - 17:00 so something like this;
SELECT DATEADD(MINUTES,180,'2018-05-24 15:00') WHERE '2018-05-24 15:00' BETWEEN '2018-05-24 09:00' AND '2018-05-24 17:00'
So the answer to this would be "2018-05-25 10:00"
Was hard, but this should work for all your cases. This solution works for any amount of (positive) minutes and result will always be inside the parametrized hours, adding the corresponding amount of days.
DECLARE #RangeHourStart INT = 9
DECLARE #RangeHourEnd INT = 17
DECLARE #MinutesToAdd INT = 120
DECLARE #Date DATETIME = '2018-05-24 15:00'
SELECT
FinalDate = CASE
WHEN -- When final hour exceeds the range hour
DATEPART(HOUR, #Date) * 60 +
DATEPART(MINUTE, #Date) +
#MinutesToAdd % ((#RangeHourEnd - #RangeHourStart) * 60) > #RangeHourEnd * 60
THEN
DATEADD(HOUR, -1 * (#RangeHourStart - 1),
DATEADD(DAY, 1,
DATEADD(MINUTE, #MinutesToAdd % ((#RangeHourEnd - #RangeHourStart) * 60),
DATEADD(
DAY,
#MinutesToAdd / ((#RangeHourEnd - #RangeHourStart) * 60),
#Date))))
ELSE
DATEADD(MINUTE, #MinutesToAdd % ((#RangeHourEnd - #RangeHourStart) * 60),
DATEADD(
DAY,
#MinutesToAdd / ((#RangeHourEnd - #RangeHourStart) * 60),
#Date))
END
I made it so you don't need to hard-code any value.
This doesn't look particularly pretty, however...
USE Sandbox;
GO
CREATE TABLE Times (DateNTime datetime2(0));
INSERT INTO Times
VALUES ('20180520 10:00:00'),
('20180520 15:20:00'),
('20180521 09:32:00'),
('20180521 14:17:00'),
('20180522 16:54:00'),
('20180523 12:46:00'),
('20180524 15:32:00');
GO
SELECT *
FROM Times;
GO
SELECT T.DateNTime,
CASE WHEN CONVERT(time,T.DateNTime) <= '14:00' THEN DATEADD(MINUTE, 180,T.DateNTime)
ELSE DATEADD(MINUTE, 180 - DATEDIFF(MINUTE,T.DateNTime,DATEADD(HOUR,17,DATEADD(DAY, DATEDIFF(DAY, 0, T.DateNTime),0))), DATEADD(HOUR,9,DATEADD(DAY, DATEDIFF(DAY, 0, T.DateNTime) + 1,0))) END
FROM Times T;
GO
DROP TABLE Times;
you can try this:
DECLARE #input DATETIME='2018-05-24 15:00'
DECLARE #min INT=180
SELECT CASE WHEN DATEADD(MINUTE,#min,#input)>DATEADD(HOUR, 17,DateAdd(Day, Datediff(Day,0, #input), 0))
THEN DATEADD(MINUTE,
DATEDIFF(MINUTE,
DATEADD(HOUR, 17,
DATEADD(Day,
DATEDIFF(Day,0, #input),
0)
),
DATEADD(MINUTE,#min,#input)),
DATEADD(Hour,9,
DATEADD(Day,1,
DateAdd(Day,
Datediff(Day,0, #input),
0)
)
)
)
ELSE DATEADD(MINUTE,#min,#input)
END

sql not in between two weekdays and times

I have a query that monitors connection process. Now I'm stuck and need to set a proper monitoring for weekday and time range.
The process starts on Sunday 22:00, and goes down for 5 min. at 21:55 - every day to Friday. (not goes up from Friday 21:55 till 22:00 on Sunday)
Below is the SQL Query I tried:
IF CASE
WHEN (100 * DATEPART(hh, GETDATE()))
+ DATEPART(MINUTE, GETDATE())
BETWEEN 2155 AND 2200 -- Monitoring for whole day, wen connection is up
AND DATEPART(dw,GETDATE()), (100 * DATEPART(hh, GETDATE()))
+ DATEPART(MINUTE, GETDATE())
NOT BETWEEN (5, 2155) AND (0, 2200) --except trough Friday night to Sunday (weekdays and time).
THEN 1 ELSE 0 END = 0
You needed to add some SELECT statements into the parts of the case where you were getting the values to range between. Try this:
SELECT
CASE
WHEN(100 * DATEPART(hh, GETDATE())) + DATEPART(MINUTE, GETDATE()) -- = 731
BETWEEN 2155 AND 2200 -- Monitoring for whole day, wen connection is up
AND (
(
SELECT
DATEPART(dw, GETDATE())
) NOT BETWEEN(5) AND(0)
AND
(
SELECT
(100 * DATEPART(hh, GETDATE())) + DATEPART(MINUTE, GETDATE())
) NOT BETWEEN(2155) AND(2200)
)
THEN 1
ELSE 0
END;
The first calculation equals 731 so looking at the query I would except it to return '0' which it does

Resources