Filter time values that are a set period either side of a specified time - sql

Given a specified time value and an interval value:
Specified Time: 13:25:00
Interval Value: 00:20:00
How can I filter the following table of values to return times that are the specified Interval either side of the Specified Time.
12:45:24
13:05:00
13:50:30
14:50:32
15:15:10
I want a function or query to check if '13:25:00' has '00:20:00' difference with any of the times in table.
The output should return:
13:05:00

Based on the information you have provided, I assume you want to get values from the list that are the specified period either side of your "special time".
Here's one way to do it using DATEADD:
-- temp table for your sample data
CREATE TABLE #times ( val TIME )
INSERT INTO #times
( val )
VALUES ( '12:45:24' ),
( '13:05:00' ),
( '13:50:30' ),
( '14:50:32' ),
( '15:15:10' )
DECLARE #special_time TIME = '13:25:00'
DECLARE #diff_value TIME = '00:20:00'
-- variable will hold the total number of seconds for your interval
DECLARE #diff_in_seconds INT
-- gets the total number of seconds of your interval -> #diff_value
SELECT #diff_in_seconds = DATEPART(SECOND, #diff_value) + 60
* DATEPART(MINUTE, #diff_value) + 3600 * DATEPART(HOUR, #diff_value)
-- get the values that match the criteria
SELECT *
FROM #times
WHERE val = DATEADD(SECOND, #diff_in_seconds, #special_time)
OR val = DATEADD(SECOND, -( #diff_in_seconds ), #special_time)
DROP TABLE #times
Note that the WHERE clause filters the results by adding and subtracting the difference. The subtraction is achieved by making the #diff_in_seconds negative.

If we are understanding your question correctly, you want all the times that are bigger than 20 minutes from your given (special) time.
To achieve this, just do a select with a where clause that contains a clause looking like this: abs(datediff(minute, tableDate, #specialdate)) > 20
SQLFiddle sample and code example:
declare #specialDate datetime = '1900-01-01 13:25:00'
select *
from SampleData
where abs(datediff(minute, SomeDate, #specialDate)) > 20
Note that I set the dates of the Datetime columns to 1900-01-01 as an obscure reference, adjust according to your settings.
You will need the ABS in the line to make sure that both variants of the resulting datediff are checked (It can either bring back 0, > 0 or < 0)
References:
MSDN: DATEDIFF
MSDN: ABS

Here is a solution:
create table t(t time);
insert into t
values
('12:45:24'),
('13:05:00'),
('13:50:30'),
('14:50:32'),
('15:15:10')
declare #st time = '13:25:00'
declare #dt time = '00:20:00'
select * from t
where abs(datediff(ss, t, #st)) - datediff(ss, '00:00:00', #dt) = 0
abs(datediff(ss, t, #st) will hold difference in seconds between times in table and special time. You compare this difference to difference between 00:00:00 and interval datediff(ss, '00:00:00', #dt)
Output:
t
13:05:00.0000000
Fiddle http://sqlfiddle.com/#!6/05df4/1

Related

How to calculate average in date column

I don't know how to calculate the average age of a column of type date in SQL Server.
You can use datediff() and aggregation. Assuming that your date column is called dt in table mytable, and that you want the average age in years over the whole table, then you would do:
select avg(datediff(year, dt, getdate())) avg_age
from mytable
You can change the first argument to datediff() (which is called the date part), to any other supported value depending on what you actually mean by age; for example datediff(day, dt, getdate()) gives you the difference in days.
First, lets calculate the age in years correctly. See the comments in the code with the understanding that DATEDIFF does NOT calculate age. It only calculates the number of temporal boundaries that it crosses.
--===== Local obviously named variables defined and assigned
DECLARE #StartDT DATETIME = '2019-12-31 23:59:59.997'
,#EndDT DATETIME = '2020-01-01 00:00:00.000'
;
--===== Show the difference in milliseconds between the two date/times
-- Because of the rounding that DATETIME does on 3.3ms resolution, this will return 4ms,
-- which certainly does NOT depict an age of 1 year.
SELECT DATEDIFF(ms,#StartDT,#EndDT)
;
--===== This solution will mistakenly return an age of 1 year for the dates given,
-- which are only about 4ms apart according the SELECT above.
SELECT IncorrectAgeInYears = DATEDIFF(YEAR, #StartDT, #EndDT)
;
--===== This calulates the age in years correctly in T-SQL.
-- If the anniversary data has not yet occurred, 1 year is substracted.
SELECT CorrectAgeInYears = DATEDIFF(yy, #StartDT, #EndDT)
- IIF(DATEADD(yy, DATEDIFF(yy, #StartDT, #EndDT), #StartDT) > #EndDT, 1, 0)
;
Now, lets turn that correct calculation into a Table Valued Function that returns a single scalar value producing a really high speed "Inline Scalar Function".
CREATE FUNCTION [dbo].[AgeInYears]
(
#StartDT DATETIME, --Date of birth or date of manufacture or start date.
#EndDT DATETIME --Usually, GETDATE() or CURRENT_TIMESTAMP but
--can be any date source like a column that has an end date.
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
SELECT AgeInYears = DATEDIFF(yy, #StartDT, #EndDT)
- IIF(DATEADD(yy, DATEDIFF(yy, #StartDT, #EndDT), #StartDT) > #EndDT, 1, 0)
;
Then, to Dale's point, let's create a test table and populate it. This one is a little overkill for this problem but it's also useful for a lot of different examples. Don't let the million rows scare you... this runs in just over 2 seconds on my laptop including the Clustered Index creation.
--===== Create and populate a large test table on-the-fly.
-- "SomeInt" has a range of 1 to 50,000 numbers
-- "SomeLetters2" has a range of "AA" to "ZZ"
-- "SomeDecimal has a range of 10.00 to 100.00 numbers
-- "SomeDate" has a range of >=01/01/2000 & <01/01/2020 whole dates
-- "SomeDateTime" has a range of >=01/01/2000 & <01/01/2020 Date/Times
-- "SomeRand" contains the value of RAND just to show it can be done without a loop.
-- "SomeHex9" contains 9 hex digits from NEWID()
-- "SomeFluff" is a fixed width CHAR column just to give the table a little bulk.
SELECT TOP 1000000
SomeInt = ABS(CHECKSUM(NEWID())%50000) + 1
,SomeLetters2 = CHAR(ABS(CHECKSUM(NEWID())%26) + 65)
+ CHAR(ABS(CHECKSUM(NEWID())%26) + 65)
,SomeDecimal = CAST(RAND(CHECKSUM(NEWID())) * 90 + 10 AS DECIMAL(9,2))
,SomeDate = DATEADD(dd, ABS(CHECKSUM(NEWID())%DATEDIFF(dd,'2000','2020')), '2000')
,SomeDateTime = DATEADD(dd, DATEDIFF(dd,0,'2000'), RAND(CHECKSUM(NEWID())) * DATEDIFF(dd,'2000','2020'))
,SomeRand = RAND(CHECKSUM(NEWID())) --CHECKSUM produces an INT and is MUCH faster than conversion to VARBINARY.
,SomeHex9 = RIGHT(NEWID(),9)
,SomeFluff = CONVERT(CHAR(170),'170 CHARACTERS RESERVED') --Just to add a little bulk to the table.
INTO dbo.JBMTest
FROM sys.all_columns ac1 --Cross Join forms up to a 16 million rows
CROSS JOIN sys.all_columns ac2 --Pseudo Cursor
;
GO
--===== Add a non-unique Clustered Index to SomeDateTime for this demo.
CREATE CLUSTERED INDEX IXC_Test ON dbo.JBMTest (SomeDateTime ASC)
;
Now, lets find the average age of those million represented by the SomeDateTime column.
SELECT AvgAgeInYears = AVG(age.AgeInYears )
,RowsCounted = COUNT(*)
FROM dbo.JBMTest tst
CROSS APPLY dbo.AgeInYears(SomeDateTime,GETDATE()) age
;
Results:

CTE Recursive by Minutes

I'm trying to make a CTE Recursive code that tells me both the date and time but changes by the minute.
DECLARE #MinDate date = '02/10/18'
,#EndDate date = DATEADD(MINUTE, n, '00:00:00')
WITH MinuteData As
(
SELECT #MinDate AS TimeStamp
UNION ALL
SELECT DATEADD(MINUTE, 1, TimeStamp)
From MinuteData
Where TimeStamp < #EndDate
)
You did not state your RDBMS. This code is for SQL-Server which you might have to adapt for another product (for the next time: Always state your RDBMS with the actual version as question tag!)
If I get this correctly, you just want to get a list of DATETIME values, each increased by one minute between a given start and a given end.
Try something like this
DECLARE #StartDate DATETIME={d'2018-10-02'};
DECLARE #EndDate DATETIME={ts'2018-10-02 12:30:00'};
WITH Tally AS
( SELECT TOP(60*24) ROW_NUMBER() OVER(ORDER BY (SELECT NULL))-1 AS Nmbr
FROM master..spt_values)
SELECT DATEADD(MINUTE,Nmbr,#StartDate) AS YourChangingDateTime
FROM Tally
WHERE DATEADD(MINUTE,Nmbr,#StartDate)<=#EndDate;
The cte "Tally" will create a numbers table on the fly. You might also use a physical numbers table - very handsome for many issues!
TOP (60*24) is the count of minutes of 1 day, you can change this, if you need more.
master..spt_values is just a prefilled table with quite a lot of values. It's the easiest way to simulate a list with many rows.
How about using datetimes?
DECLARE #MinDateTime datetime = '2018-02-10',
#EndDateTime datetime = DATEADD(MINUTE, n, #MinDateTime);
WITH MinuteData As (
SELECT #MinDateTime AS TimeStamp
UNION ALL
SELECT DATEADD(MINUTE, 1, TimeStamp)
From MinuteData
Where TimeStamp < #EndDateTime
)

Correcting times

I have a table that has a datetime field called [avg time to answer]. For certain records the time is displaying incorrectly as shown below:
1899-12-30 01:04:00.000
1899-12-30 01:05:00.000
1899-12-30 01:30:00.000
The times shown here are wrong. Times shown above are as small sample but they need to be formatted in minutes seconds rather than hours and minutes
Is there a way to correct this with a SQL script as the alternative involves extracting the data again and that is going to be extremely difficult and time consuming to do.
You can use DATEPART:
Returns an integer that represents the specified datepart of the specified date.
Combined with DATEADD:
Returns a specified date with the specified number interval (signed integer) added to a specified datepart of that date.
Example:
DECLARE #val AS DATETIME = '1899-12-30 01:30:00.000';
SELECT #val; -- 1899-12-30 01:30:00.000
SET #val = DATEADD(SECOND, DATEPART(MINUTE, #val),
DATEADD(MINUTE, ( DATEPART(HOUR, #val) ),
CAST(CAST(#val AS DATE) AS DATETIME)));
SELECT #val; -- 1899-12-30 00:01:30.000
This casts the value to a date, to strip off the time portion and back to a datetime to set the time to 00:00:00. Then adds minutes based on the hour value and seconds based on the minute value.
You can use this with an UPDATE statement, but ensure you use a WHERE clause to avoid updating everything, unless that's what you intend.
Example Update Query:
CREATE TABLE #temp
(
AvgTimeToAnswer DATETIME
);
INSERT INTO #temp
( AvgTimeToAnswer )
VALUES ( '1899-12-30 01:30:00.000' );
SELECT *
FROM #temp; -- 1899-12-30 01:30:00.000
UPDATE #temp
SET AvgTimeToAnswer = DATEADD(SECOND, DATEPART(MINUTE, AvgTimeToAnswer),
DATEADD(MINUTE,
( DATEPART(HOUR, AvgTimeToAnswer) ),
CAST(CAST(AvgTimeToAnswer AS DATE) AS DATETIME)));
-- WHERE some condition
SELECT *
FROM #temp; -- 1899-12-30 00:01:30.000
DROP TABLE #temp;

Add dd.hh:mm:ss.nnnnn time format to Datetime in SQL Server

I have a value in dd.hh:mm:ss.nnnnn format. Some of the examples are 32.11:45:40.93877, 11:45:30.32012 which are in string format.
I want to add a perform addition of datetime value and above time span which is in string format. The value 30.12:43:10.98222 can be represented as 30 days 12 hours 43 minutes 10 seconds and 98222 milliseconds.
I wrote the following code (function) to get the result:
Pseudo code:
split the string split(5.11:45:40.90399, ':') and add into #temptable(id, value).
while(0 < #counter)
select #val = value from #tempTable where id = #counter
if (#counter = 0)
split '5.11' in 5 and 11 by using charindex, substring, left, Convert to Int function. Also handle availability of day value
datetimevalue = dateadd(day,5,datetimevalue)
datetimevalue = dateadd(hour, 11, datetimevalue)
if (#counter = 1)
datetimevalue = dateadd(minute, 45, datetimevalue)
if (##counter = 2)
split 40.90399 into 40 and 90399
datetimevalue = dateadd(second, 40, datetimevalue)
datetimevalue = dateadd(millisecond, 90399, datetimevalue)
End of while loop
return datetimevalue
Can we have an alternative solution or some change simplify the above process?
For now I was thinking of converting hh:mm:ss part to seconds and adding it as seconds thus reducing call of dateadd function.
First, to get that precision you can't use DateTime, you must use DateTime2.
Second, As Mayur Patil rightfully commented - the value you want to add is a time span, not a datetime value. There is no time span data type in Sql server, However, you can translate the string value into a time span using some string manipulations.
I came up with a suggested solution,
However, there is a problem with this solution that I couldn't solve: Your milliseconds part might be bigger then 1000 (and in fact it is), so the results seems to be a little off. I've tried treating it as nanoseconds, but that didn't give the results I was expecting. Perhaps someone else might refine it or come up with a better way to do it:
First, separate the days from the time,
Next, separate the time into it's components,
Then, use DateAdd to add it all to the original date:
DECLARE #Date datetime2 = GETDATE()
DECLARE #S as varchar(20) = '32.11:45:40.93877'
;WITH CTE1 As
(
SELECT LEFT(#S, CHARINDEX('.', #S)-1) As TheDay,
REPLACE(SUBSTRING(#S, CHARINDEX('.', #S) + 1, LEN(#S) - CHARINDEX('.', #S)), ':', '.') As TheTime
), CTE2 AS
(
SELECT CAST(TheDay As int) As TheDays,
CAST(PARSENAME(TheTime, 4) As Int) As TheHours,
CAST(PARSENAME(TheTime, 3) As Int) As TheMinutes,
CAST(PARSENAME(TheTime, 2) As Int) As TheSeconds,
CAST(PARSENAME(TheTime, 1) As Int) As TheNanoSeconds
FROM CTE1
)
SELECT #Date As OriginalDate,
#S As TimeSpan,
DATEADD(DAY, TheDays,
DATEADD(HOUR, TheHours,
DATEADD(MINUTE, TheMinutes,
DATEADD(SECOND, TheSeconds,
DATEADD(NANOSECOND, TheNanoSeconds, #Date)
)
)
)
) As Result
FROM CTE2
Results:
OriginalDate TimeSpan Result
--------------------------- -------------------- ---------------------------
2016-06-20 16:23:30.7470000 32.11:45:40.93877 2016-07-23 04:09:10.7470939

How can I group time by hour or by 10 minutes?

Like when I do
SELECT [Date]
FROM [FRIIB].[dbo].[ArchiveAnalog]
GROUP BY [Date]
How can I specify the group period? I'm using MS SQL 2008.
I've tried this, both with % 10 and / 10.
SELECT MIN([Date]) AS RecT, AVG(Value)
FROM [FRIIB].[dbo].[ArchiveAnalog]
GROUP BY (DATEPART(MINUTE, [Date]) / 10)
ORDER BY RecT
Is it possible to make Date output without milliseconds?
finally done with
GROUP BY
DATEPART(YEAR, DT.[Date]),
DATEPART(MONTH, DT.[Date]),
DATEPART(DAY, DT.[Date]),
DATEPART(HOUR, DT.[Date]),
(DATEPART(MINUTE, DT.[Date]) / 10)
Short and sweet
GROUP BY DATEDIFF(MINUTE, '2000', date_column) / 10
With heavy acknowledgements to Derek's answer, which forms the core of this one.
Practical usage
SELECT DATEADD(MINUTE, DATEDIFF(MINUTE, '2000', aa.[date]) / 10 * 10, '2000')
AS [date_truncated],
COUNT(*) AS [records_in_interval],
AVG(aa.[value]) AS [average_value]
FROM [friib].[dbo].[archive_analog] AS aa
-- WHERE aa.[date] > '1900-01-01'
GROUP BY DATEDIFF(MINUTE, '2000', aa.[date]) / 10
-- HAVING SUM(aa.[value]) > 1000
ORDER BY [date_truncated]
Details and commentary
The MINUTE and 10 terms can be changed to any DATEPART and integer,1 respectively, to group into different time intervals.
e.g. 10 with MINUTE is ten minute intervals; 6 with HOUR is
six hour intervals.
If you change the interval a lot, you might benefit from declaring it as a variable.
DECLARE #interval int = 10;
SELECT DATEADD(MINUTE, DATEDIFF(…) / #interval * #interval, '2000')
…
GROUP BY DATEDIFF(…) / #interval
Wrapping it with a DATEADD invocation with a multiplier will give you a DATETIME value, which means:
Data sources over long time intervals are fine. Some other answers have collision between years.
Including it in the SELECT statement will give your output a single column with the truncated timestamp.
In the SELECT, the division (/) operation after DATEDIFF truncates values to integers (a FLOOR shortcut), which yields the beginning of time intervals for each row.
If you want to label each row with the middle or end of its interval, you can tweak the division in the second term of DATEADD with the bold part below:
End of interval: …) / 10 * 10 + 10 , '2000'), credit to Daniel Elkington.
Middle of interval: …) / 10 * 10 + (10 / 2.0) , '2000').
Trivia
'2000' is an "anchor date" around which SQL will perform the date math. Most sample code uses 0 for the anchor, but JereonH discovered that you encounter an integer overflow when grouping more-recent dates by seconds or milliseconds.2
If your data spans centuries,3 using a single anchor date in the GROUP BY for seconds or milliseconds will still encounter the overflow. For those queries, you can ask each row to anchor the binning comparison to its own date's midnight:
Use DATEADD(DAY, DATEDIFF(DAY, 0, aa.[date]), 0) instead of '2000' wherever it appears above. Your query will be totally unreadable, but it will work.
An alternative might be CONVERT(DATETIME, CONVERT(DATE, aa.[date])) as the replacement.
1 If you want all :00 timestamps to be eligible for binning, use an integer that your DATEPART's maximum can evenly divide into.4 As a counterexample, grouping results into 13-minute or 37-hour bins will skip some :00s, but it should still work fine.
2 The math says 232 ≈ 4.29E+9. This means for a DATEPART of SECOND, you get 4.3 billion seconds on either side, which works out to "anchor date ± 136 years." Similarly, 232 milliseconds is ≈ 49.7 days.
3 If your data actually spans centuries or millenia and is still accurate to the second or millisecond… congratulations! Whatever you're doing, keep doing it.
4 If you ever wondered why our clocks have a 12 at the top, reflect on how 5 is the only integer from 6 (half of 12) or below that is not a factor of 12. Then note that 5 × 12 = 60. You have lots of choices for bin sizes with hours, minutes, and seconds.
In T-SQL you can:
SELECT [Date]
FROM [FRIIB].[dbo].[ArchiveAnalog]
GROUP BY [Date], DATEPART(hh, [Date])
or
by minute use DATEPART(mi, [Date])
or
by 10 minutes use DATEPART(mi, [Date]) / 10 (like Timothy suggested)
For a 10 minute interval, you would
GROUP BY (DATEPART(MINUTE, [Date]) / 10)
As was already mentioned by tzup and Pieter888... to do an hour interval, just
GROUP BY DATEPART(HOUR, [Date])
Should be something like
select timeslot, count(*)
from
(
select datepart('hh', date) timeslot
FROM [FRIIB].[dbo].[ArchiveAnalog]
)
group by timeslot
(Not 100% sure about the syntax - I'm more an Oracle kind of guy)
In Oracle:
SELECT timeslot, COUNT(*)
FROM
(
SELECT to_char(l_time, 'YYYY-MM-DD hh24') timeslot
FROM
(
SELECT l_time FROM mytab
)
) GROUP BY timeslot
The original answer the author gave works pretty well. Just to extend this idea, you can do something like
group by datediff(minute, 0, [Date])/10
which will allow you to group by a longer period then 60 minutes, say 720, which is half a day etc.
For MySql:
GROUP BY
DATE(`your_date_field`),
HOUR(`your_date_field`),
FLOOR( MINUTE(`your_date_field`) / 10);
If you want to actually display the date, have a variable grouping, and be able to specify larger time frames than 60 minutes:
DECLARE #minutes int
SET #minutes = 90
SELECT
DATEADD(MINUTE, DATEDIFF(MINUTE, 0, [Date]) / #minutes * #minutes, 0) as [Date],
AVG([Value]) as [Value]
FROM [FRIIB].[dbo].[ArchiveAnalog]
GROUP BY
DATEDIFF(MINUTE, 0, [Date]) / #minutes
declare #interval tinyint
set #interval = 30
select dateadd(minute,(datediff(minute,0,[DateInsert])/#interval)*#interval,0), sum(Value_Transaction)
from Transactions
group by dateadd(minute,(datediff(minute,0,[DateInsert])/#interval)*#interval,0)
In SQLite, in order to group by hour, you can do:
GROUP BY strftime('%H', [FRIIB].[dbo].[ArchiveAnalog].[Date]);
and to group by each 10 minutes:
GROUP BY strftime('%M', [FRIIB].[dbo].[ArchiveAnalog].[Date]) / 10;
My solution is to use a function to create a table with the date intervals and then join this table to the data I want to group using the date interval in the table.
The date interval can then be easily selected when presenting the data.
CREATE FUNCTION [dbo].[fn_MinuteIntervals]
(
#startDate SMALLDATETIME ,
#endDate SMALLDATETIME ,
#interval INT = 1
)
RETURNS #returnDates TABLE
(
[date] SMALLDATETIME PRIMARY KEY NOT NULL
)
AS
BEGIN
DECLARE #counter SMALLDATETIME
SET #counter = #startDate
WHILE #counter <= #endDate
BEGIN
INSERT INTO #returnDates VALUES ( #counter )
SET #counter = DATEADD(n, #interval, #counter)
END
RETURN
END
For SQL Server 2012, though I believe it would work in SQL Server 2008R2, I use the following approach to get time slicing down to the millisecond:
DATEADD(MILLISECOND, -DATEDIFF(MILLISECOND, CAST(time AS DATE), time) % #msPerSlice, time)
This works by:
Getting the number of milliseconds between a fixed point and target time:#ms = DATEDIFF(MILLISECOND, CAST(time AS DATE), time)
Taking the remainder of dividing those milliseconds into time slices:#rms = #ms % #msPerSlice
Adding the negative of that remainder to the target time to get the slice time:DATEADD(MILLISECOND, -#rms, time)
Unfortunately, as is this overflows with microseconds and smaller units, so larger, finer data sets would need to use a less convenient fixed point.
I have not rigorously benchmarked this and I am not in big data, so your mileage may vary, but performance was not noticeably worse than the other methods tried on our equipment and data sets, and the payout in developer convenience for arbitrary slicing makes it worthwhile for us.
select dateadd(minute, datediff(minute, 0, Date), 0),
sum(SnapShotValue)
FROM [FRIIB].[dbo].[ArchiveAnalog]
group by dateadd(minute, datediff(minute, 0, Date), 0)
select from_unixtime( 600 * ( unix_timestamp( [Date] ) % 600 ) ) AS RecT, avg(Value)
from [FRIIB].[dbo].[ArchiveAnalog]
group by RecT
order by RecT;
replace the two 600 by any number of seconds you want to group.
If you need this often and the table doesn't change, as the name Archive suggests, it would probably be a bit faster to convert and store the date (& time) as a unixtime in the table.
I know I am late to the show with this one, but I used this - pretty simple approach. This allows you to get the 60 minute slices without any rounding issues.
Select
CONCAT(
Format(endtime,'yyyy-MM-dd_HH:'),
LEFT(Format(endtime,'mm'),1),
'0'
) as [Time-Slice]
Try this query. It makes one column. (references #nobilist answer)
GROUP BY CAST(DATE(`your_date_field`) as varchar) || ' ' || CAST(HOUR(`your_date_field`) as varchar) || ':' || CAST(FLOOR(minute(`your_date_field`) / 10) AS varchar) || '0' AS date_format
Here is an option that provides a human readable start time of that interval (7:30, 7:40, etc).
In a temp table, it truncates seconds and milliseconds by using SMALLDATETIME, and then the main query subtracts any amount over the desired minute interval.
SELECT DATEADD(MINUTE, -(DATEDIFF(MINUTE, '2000', tmp.dt) % 10), tmp.dt)
FROM (
SELECT CAST(DateField AS SMALLDATETIME) AS dt
FROM MyDataTable
) tmp
It can also be done in a single line of code, but it is not as readable.
SELECT DATEADD(MINUTE, -(DATEDIFF(MINUTE, '2000', CAST(DateField AS SMALLDATETIME)) % 10), CAST(DateField AS SMALLDATETIME)) AS [interval] FROM MyDataTable