Best practice: Searching table against day, month or year - sql

I've got a table with a "date" column, where a user input will be queried against (using stored procedure)..and results will be shown on a datagrid..
now a user can either enter a year, Year/month , Year/month/day.. (from drop down lists)
i know there r many possible ways to handle the different queries.. however i am trying to figure out which would be best practice:
Solution 1: having 3 different stored procedures , one for every case.
Solution 2: having 1 stored procedure, with 1 extra parameter as searchlvl , then using IF ELSE statements to decide what lvl of search should be applied.
Solution 3: having 1 stored procedure, and sending the datetime as 3 different parameters , then checking IF parameter is null , and using that to decide search lvl
Solution 4: your suggestions :)
NOTE: i know how to do partial search(using datepart), my question is about best practice among the 3 solutions i offered or any other solution offered in the answers..
Like which would be faster, lighter on database and such..
and which would be slower, heavier..

There are no levels.
When user selects year 2009, you search rows where date >= '2009.01.01 00:00' and < '2010.01.01 00:00'.
When he selects month 01 of year 2009 you search where date >= '2009.01.01 00:00' and < '2009.02.01 00:00'.
Of course you don't pass dates as strings, you should use CONVERT() or pass dates as DATETIME type. This is universal solution and will be fast, because it will use indexes. You can create stored procedure that takes two dates, it will allow to search by every date range, not only year/month/day.

I'd do none of the above.
You should design you stored procedure to take three different ints, one for day, one for month and one for year. Leave the parameters nullable, but establish a convention so only meaningful parameter combinations are used. Then you construct a MINDATE and MAXDATE from the parameters.
Searching Datetime columns based on day/year/month requires a query like:
SELECT * FROM table WHERE date > MINDATE AND date < MAXDATE
which is pretty inefficient but not a definite problem.
Another approach (if the table is huge) would be to create an indexed view with year/month/day integer columns and search for exact matches there. To create such a view use DATEPART().

You can use datepart to get the parts of you date you want to filter against as
declare #table table(
DateVal DATETIME
)
INSERT INTO #table SELECT GETDATE()
DECLARE #Year INT,
#Month INT,
#Day INT
SELECT #Year = 2009
SELECT DATEPART(YY, DateVal) DateYear,
DATEPART(MM, DateVal) DateMonth,
DATEPART(DD, DateVal) DateDay,
*
FROM #table
WHERE (DATEPART(YY, DateVal) = #Year OR #Year IS NULL)
AND (DATEPART(MM, DateVal) = #Month OR #Month IS NULL)
AND (DATEPART(DD, DateVal) = #Day OR #Day IS NULL)

I'd pass in year/month/date as separate parameters into one stored proc, say default of NULL.
Then, I'd use DATEADD to build up from/to datetimes and use that
...
SELECT
#ToYear = ISNULL(#ToYear, DATEPART(year, GETDATE()), --or some base value, such as "1900"
#ToMonth = ...
...
SELECT
#DateTo = DATEADD(year, #ToYear, DATEADD(month, #ToMonth, DATEADD(day, #ToDay, 0), 0), 0)
....
SELECT * FROM myTable WHERE DateColumn >= #DateFrom AND DateColumn <= #DateTo
I would not use any functions on columns or conditional logic to switch between selects

Related

SQL query Performance when using Datetime in where / On clause

I have the 5l record in table. need to query...Which is one is query fast,Input parameter #RegistrationFrom DATE,#RegistrationTo DATE
Approach #1:
WHERE
CAST(Act.RegistrationOn AS DATE) BETWEEN CAST(#RegistrationFrom AS DATE)
AND CAST(#RegistrationTo AS DATE)
Approach #2 - convert into datetime:
DECLARE #From DATETIME, #Todate DATETIME;
SELECT #From = #RegistrationFrom;
SELECT #Todate = DATEADD(day, 1, #RegistrationTo);
WHERE
Act.RegistrationOn BETWEEN #From AND #Todate
Approach #3:
WHERE CONVERT(VARCHAR, Act.RegistrationOn,101) BETWEEN #From AND #Todate
I ma getting fast response approach 3 than above 1,2.
How it is working?
I would write the query as:
SELECT #Todate = DATEADD(day, 1, #RegistrationTo);
. . .
WHERE Act.RegistrationOn >= #From AND
Act.RegistrationOn < #Todate
Whether you use variables or a cast on the variable should have no or little affect on performance. More important considerations are the use of indexes and interpretability (does the code do what you intend).
Aaron Bertrand has a very good blog on why you shouldn't use BETWEEN for dates.
As for the first version . . . it is actually more reasonable than you might think. In general, function calls prevent the use of indexes on columns. However, SQL Server makes an exception for conversion of a datetime to date. So, it will still use an index.
That said, I would still go with the above version.
Try it like this:
WITH MyPrms AS
(
SELECT CAST(#RegistrationFrom AS DATE) AS fromD
,CAST(#RegistrationTo AS DATE) + 1 AS toD
)
SELECT *
FROM MyPrms
CROSS JOIN Act
WHERE Act.Registration>=fromD AND Act.RegistrationOn<toD;
It is fully inlineable (in VIEWS, functions...) and sargable (optimizer can use indexes)
EDIT just for clearity
By adding +1 to your upper border date (which was casted to a timeless DATE before, the upper border is midnight after the last day. By using a < you will get all data from the full day. BETWEEN includes the border which can lead to unexpected errors...
Both options are equivalent from performance point of view:
Approach 1
WHERE Act.RegistrationOn AS DATE BETWEEN CAST(#RegistrationFrom AS DATE) AND CAST(#RegistrationTo AS DATE)
Approach 2
WHERE RegistrationOn BETWEEN #From AND #Todate
Each casting in Approach 1 is executed only once and not for each row as you might expected.
For clarity, I would take option 2 or even better send the parameter with the right type to this query.
Also, an index on RegistrationOn column would definitively help. Please note a CAST on RegistrationOn column has been removed from Approach 1 as this would prevent SQL Server to use this index.
WHERE CONVERT(VARCHAR, Act.RegistrationOn,101) BETWEEN #From AND #Todate

SQL Server: use parameter instead of GETDATE()

I have a stored procedure that uses selects like the following which works fine so far.
In this case for example it selects all records with a date from the previous month, i.e. March 2014 (column: dateEsc, formatted as nvarchar(20), example date: 2014-03-25).
My Select (example):
SELECT COUNT(*) AS groupCount
FROM Log_Esc
WHERE
CONVERT(DATE, dateEsc, 120) >= CONVERT(DATE, CONVERT(VARCHAR(6), DATEADD(month, -1, GETDATE()), 112) + '01', 112)
How do I have to change this if instead of the current Date (GETDATE()) I want to use a variable date input as the reference.
This input would be any date and is formatted as nvarchar(20) as well, example: 2014-04-03.
So instead of calculating the previous month compared to the current month from GETDATE() I would like to calculate the same from the variable date input.
Many thanks for any help with this, Tim.
First of all I think this query is better than the one you have:
SELECT COUNT(*) AS groupCount
FROM Log_Esc
WHERE DATE >= dateadd(month,datediff(month,0,dateadd(month,GETDATE(),-1)),0)
AND DATE < dateadd(month,datediff(month,0,GETDATE()),0)
If there is an index on the DATE field this can do a seek.
If you have a parameter #indate defined as date or datetime then this will work
SELECT COUNT(*) AS groupCount
FROM Log_Esc
WHERE DATE >= dateadd(month,datediff(month,0,dateadd(month,#indate,-1)),0)
AND DATE < dateadd(month,datediff(month,0,#indate),0)
See this question for more information on flooring a date to a month: Floor a date in SQL server
So what you want is a parameter:
Specifying Parameters in a Stored Procedure
Parameters allow you to pass user input to modify output.
An example
CREATE PROCEDURE dbo.Param1
#param int
AS
BEGIN
select 7 *#param as Value
END
EXEC dbo.Param1 5 -- 7 *5
EXEC dbo.Param1 -10 -- 7 * -10
Perhaps this'll give you some creative ideas for how you might implement parameters to accomplish your group count.

Compare multiple columns in sql

I have 3 date columns in a table, when created, last modified, and date an action was taken. I am trying to write a query that would compare the dates of all of these without having to write it out 3 different times.
Something like this is what I thought it would be and I have tried some variations but with no luck with my efforts and looking it up online(maybe because of the wording being to generic).
select * from table
where (DateTimeInspected and DateTimeModified and DateTimeCreated) between '11/01/2012' and '11/01/2013'
You probably want something like the following:
If you want one of them between the dates:
SELECT YourField
FROM TABLE
WHERE (DateTimeInspected BETWEEN '11/01/2012' AND '11/01/2013')
OR (DateTimeModified BETWEEN '11/01/2012' AND '11/01/2013')
OR (DateTimeCreated BETWEEN '11/01/2012' AND '11/01/2013')
Be careful with DateTime fields and BETWEEN though, because you do not specify a time in '11/01/2013', SQL will assume you mean '11/01/2013 00:00:00'. If the start and end dates are parameters you could add one day if necessary to solve this issue.
OR (DateTimeCreated BETWEEN '11/01/2012' AND DATEADD(day, 1, '11/01/2013') )
By comparing dates you should be aware about the upper bound. If you write <= "2013-11-20" you will not get any data that has the date 2013-11-20. Therefore you have to write <= "2013-11-20 23:59:59" or even better < "2013-11-21".
Next you have to expand the where and use between for every column you want to check:
Therefore the SQL-query should look like:
SELECT * FROM table
WHERE DateTimeInspected BETWEEN '11/01/2012' AND '11/01/2013'
AND DateTimeModified BETWEEN '11/01/2012' AND '11/01/2013'
AND DateTimeCreated BETWEEN '11/01/2012' AND '11/01/2013'
Note: column BETWEEN value1 AND value2 is equals to column >= value1 AND column <= value2
One way
SELECT *
FROM YourTable
WHERE EXISTS (SELECT *
FROM (VALUES(DateTimeInspected),
(DateTimeModified),
(DateTimeCreated)) V(D)
WHERE V.D >= '20121101'
AND V.D < '20131101')
You should use an unambiguous datetime format such as yyyymmdd and BETWEEN probably doesn't do what you need.
I guess the question was about how to optimize checking boundaries for 3 dates at once.
In this case there is sense to create your own specific inline function to validate required conditions. It will allow you to reuse the functionality and have possibility to change it in a single place:
CREATE FUNCTION [dbo].[fnDateTimeBetween](
#startDate datetime,
#endDate datetime,
#datetime1 datetime,
#datetime2 datetime,
#datetime3 datetime)
RETURNS bit
AS
BEGIN
DECLARE #result bit = 0
SELECT #result = 1
WHERE #datetime1 BETWEEN #startDate AND #endDate
AND #datetime2 BETWEEN #startDate AND #endDate
AND #datetime3 BETWEEN #startDate AND #endDate
RETURN #result
END
Then you'll be able to use it like:
SELECT dbo.fnDateTimeBetween('11/01/2012', '11/01/2013', '11/02/2012', '11/02/2012', '11/02/2012')

A better way? Have date in query always use a date in the current year without maintenance

SELECT Date_Received, DateAdd(Year, DateDiff(year, Cast('3/01/2010 12:00:00AM' as DateTime) ,
GetDate())-1, Cast('3/01/2010 12:00:00AM' as DateTime)) as minimum_date
FROM [Volunteers].[dbo].[Applications]
WHERE Date_received >= DateAdd(Year, DateDiff(year, Cast('3/01/2010 12:00:00AM' as DateTime),
GetDate())-1, Cast('3/01/2010 12:00:00AM' as DateTime))
In several subqueries where I need to check that a date is within an acceptable range. I need to avoid using a simple constant as I really don't want to update it or a config file each new school year.
My current solution is to enter the date into the query and use some complicated DATEADD tricks to get the current year(or previous year) into the date I am using in the comparison. The exact code is above. Is there a cleaner way for me to do this?
Thanks
Edit
The business requirement is to find applications submitted between 3/01 and 7/31.
We are running background checks and it costs us money for each check we do. Identifying applications submitted during this time period helps us determine if we should do a full, partial or no background check. I will also need to check if dates concerning the previous year.
We will be doing this every year and we need to know if they were in the current year. Maintaining the queries each year to update the dates is not something I want to do.
So I am looking for a good technique to keep the year parts of the dates relevant without having to update the query or a config file.
Old TSQL trick: cast the date to a string in a format that starts with the four-digit year, using substring to take the first four characters of that, cast it back to a date.
Actually, the reason that it's an old TSQL trick is that, if I recall correctly, there wasn't a year() function back then. Given that there's one now, using year( getdate() ) , as others' have answered, is probably the better answer.
SELECT YEAR(GETDATE())
will give you the current year.
If you need to query by month and year a lot, you should also consider making those properties into persisted, computed fields:
ALTER TABLE dbo.Applications
ADD DateReceivedMonth AS MONTH(Date_Received) PERSISTED
ALTER TABLE dbo.Applications
ADD DateReceivedYear AS YEAR(Date_Received) PERSISTED
SQL Server will now extract the MONTH and YEAR part of your Date_Received and place them into two new columns. Those are persisted, e.g. stored along side with your table data. SQL Server will make sure to keep them up to date automatically, e.g. if you change Date_Received, those two new columns will be recomputed (but not on every SELECT).
Now, your queries might be a lot easier:
SELECT (list of fields)
FROM dbo.Applications
WHERE DateReceivedYear = 2010 AND DateReceivedMonth BETWEEN 3 AND 7
Since these are persisted fields, you can even put an index on them to speed up queries against them!
Is there any reason you cannot simply use the Year function?
Select Date_Received
, Year(GetDate())
- Year('3/01/2010 12:00:00AM') - 1
+ Year('3/01/2010 12:00:00AM')
From [Volunteers].[dbo].[Applications]
Where Date_received >= ( Year(GetDate())
- Year('3/01/2010 12:00:00AM') - 1
+ Year('3/01/2080 12:00:00AM') )
Another way would be to use a common-table expression
With Years As
(
Select Year(GetDate()) As CurrentYear
, Year('3/01/2010 12:00:00AM') As ParamYear
, Year('3/01/2080 12:00:00AM') As BoundaryYear
)
Select Date_Received
, CurrentYear - Years.ParamYear - 1 + Years.ParamYear
From [Volunteers].[dbo].[Applications]
Cross Join Years
Where Date_received >= ( Years.CurrentYear
- Years.ParamYear - 1 + Years.BoundaryYear )
TSQL Function returns four digit year dependent on year. This behaves much like the standard SQL YEAR functions [Thomas - nod] which 'CAN' be tweaked using sp_configure on the advanced options, however, the code below is provided as a framework for CUSTOM requirements and can be modified as required. e.g. return as int, use with standard DATETIME functions in SQL to achieve what is needed. e.g. When working with "dirty" data I had to migrate, I used it with the PATINDEX() function to strip non-numeric values etc.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Andrew McLintock
-- Create date: 13 July 2016
-- Description: Return 4-digit YEAR
-- =============================================
/*
SELECT Staging.fn_4year('06')
SELECT Staging.fn_4year('56')
SELECT Staging.fn_4year('99')
SELECT Staging.fn_4year('1906')
SELECT Staging.fn_4year('2025')
*/
CREATE FUNCTION Staging.fn_4year
(
#year_in varchar (4)
)
RETURNS varchar(4)
AS
BEGIN
DECLARE #yeartmp int, #Retval varchar(4)
SET #yeartmp = CAST(REPLACE(#year_in,' ','') AS INT)
IF LEN(CAST(#yeartmp AS Varchar)) = 4
BEGIN
Return cast(#yeartmp as varchar(4))
END
IF LEN(#year_in) = 2
BEGIN
SET #Retval = CAST(iif(#yeartmp > 49, #yeartmp + 1900, #yeartmp + 2000) AS varchar(4))
END
RETURN #Retval
END
GO
Consider keeping a set of datetime variables help readability and maintainability. I'm not sure I've captured all your requirements, especially with reference to 'previous year'. If it's as simple as finding applications submitted between 3/01 and 7/31, then this should work. If you need to determine those that were submitted Aug 1 (last year) through Feb 28 (current year), this solution could be modified to suit.
DECLARE #Start smalldatetime, #End smalldatetime, #CurrYear char(4)
SELECT #CurrYear = YEAR(getdate())
SELECT #Start = CAST( 'mar 1 ' + #CurrYear as smalldatetime),
#End = CAST( 'jul 31 ' + #CurrYear as smalldatetime)
SELECT *
FROM Applications
WHERE Date_Received
BETWEEN #Start AND #End

How can I compare time in SQL Server?

I'm trying to compare time in a datetime field in a SQL query, but I don't know if it's right. I don't want to compare the date part, just the time part.
I'm doing this:
SELECT timeEvent
FROM tbEvents
WHERE convert(datetime, startHour, 8) >= convert(datetime, #startHour, 8)
Is it correct?
I'm asking this because I need to know if 08:00:00 is less or greater than 07:30:00 and I don't want to compare the date, just the time part.
Thanks!
Your compare will work, but it will be slow because the dates are converted to a string for each row. To efficiently compare two time parts, try:
declare #first datetime
set #first = '2009-04-30 19:47:16.123'
declare #second datetime
set #second = '2009-04-10 19:47:16.123'
select (cast(#first as float) - floor(cast(#first as float))) -
(cast(#second as float) - floor(cast(#second as float)))
as Difference
Long explanation: a date in SQL server is stored as a floating point number. The digits before the decimal point represent the date. The digits after the decimal point represent the time.
So here's an example date:
declare #mydate datetime
set #mydate = '2009-04-30 19:47:16.123'
Let's convert it to a float:
declare #myfloat float
set #myfloat = cast(#mydate as float)
select #myfloat
-- Shows 39931,8244921682
Now take the part after the comma character, i.e. the time:
set #myfloat = #myfloat - floor(#myfloat)
select #myfloat
-- Shows 0,824492168212601
Convert it back to a datetime:
declare #mytime datetime
set #mytime = convert(datetime,#myfloat)
select #mytime
-- Shows 1900-01-01 19:47:16.123
The 1900-01-01 is just the "zero" date; you can display the time part with convert, specifying for example format 108, which is just the time:
select convert(varchar(32),#mytime,108)
-- Shows 19:47:16
Conversions between datetime and float are pretty fast, because they're basically stored in the same way.
convert(varchar(5), thedate, 108) between #leftTime and #rightTime
Explanation:
if you have varchar(5) you will obtain HH:mm
if you have varchar(8) you obtain HH:mm ss
108 obtains only the time from the SQL date
#leftTime and #rightTime are two variables to compare
If you're using SQL Server 2008, you can do this:
WHERE CONVERT(time(0), startHour) >= CONVERT(time(0), #startTime)
Here's a full test:
DECLARE #tbEvents TABLE (
timeEvent int IDENTITY,
startHour datetime
)
INSERT INTO #tbEvents (startHour) SELECT DATEADD(hh, 0, GETDATE())
INSERT INTO #tbEvents (startHour) SELECT DATEADD(hh, 1, GETDATE())
INSERT INTO #tbEvents (startHour) SELECT DATEADD(hh, 2, GETDATE())
INSERT INTO #tbEvents (startHour) SELECT DATEADD(hh, 3, GETDATE())
INSERT INTO #tbEvents (startHour) SELECT DATEADD(hh, 4, GETDATE())
INSERT INTO #tbEvents (startHour) SELECT DATEADD(hh, 5, GETDATE())
--SELECT * FROM #tbEvents
DECLARE #startTime datetime
SET #startTime = DATEADD(mi, 65, GETDATE())
SELECT
timeEvent,
CONVERT(time(0), startHour) AS 'startHour',
CONVERT(time(0), #startTime) AS '#startTime'
FROM #tbEvents
WHERE CONVERT(time(0), startHour) >= CONVERT(time(0), #startTime)
Just change convert datetime to time that should do the trick:
SELECT timeEvent
FROM tbEvents
WHERE convert(time, startHour) >= convert(time, #startHour)
if (cast('2012-06-20 23:49:14.363' as time) between
cast('2012-06-20 23:49:14.363' as time) and
cast('2012-06-20 23:49:14.363' as time))
One (possibly small) issue I have noted with the solutions so far is that they all seem to require a function call to process the comparison. This means that the query engine will need to do a full table scan to seek the rows you are after - and be unable to use an index. If the table is not going to get particularly large, this probably won't have any adverse affects (and you can happily ignore this answer).
If, on the other hand, the table could get quite large, the performance of the query could suffer.
I know you stated that you do not wish to compare the date part - but is there an actual date being stored in the datetime column, or are you using it to store only the time? If the latter, you can use a simple comparison operator, and this will reduce both CPU usage, and allow the query engine to use statistics and indexes (if present) to optimise the query.
If, however, the datetime column is being used to store both the date and time of the event, this obviously won't work. In this case if you can modify the app and the table structure, separate the date and time into two separate datetime columns, or create a indexed view that selects all the (relevant) columns of the source table, and a further column that contains the time element you wish to search for (use any of the previous answers to compute this) - and alter the app to query the view instead.
Using float does not work.
DECLARE #t1 datetime, #t2 datetime
SELECT #t1 = '19000101 23:55:00', #t2 = '20001102 23:55:00'
SELECT CAST(#t1 as float) - floor(CAST(#t1 as float)), CAST(#t2 as float) - floor(CAST(#t2 as float))
You'll see that the values are not the same (SQL Server 2005). I wanted to use this method to check for times around midnight (the full method has more detail) in which I was comparing the current time for being between 23:55:00 and 00:05:00.
Adding to the other answers:
you can create a function for trimming the date from a datetime
CREATE FUNCTION dbo.f_trimdate (#dat datetime) RETURNS DATETIME AS BEGIN
RETURN CONVERT(DATETIME, CONVERT(FLOAT, #dat) - CONVERT(INT, #dat))
END
So this:
DECLARE #dat DATETIME
SELECT #dat = '20080201 02:25:46.000'
SELECT dbo.f_trimdate(#dat)
Will return
1900-01-01 02:25:46.000
Use Datepart function: DATEPART(datepart, date)
E.g#
SELECT DatePart(#YourVar, hh)*60) +
DatePart(#YourVar, mi)*60)
This will give you total time of day in minutes allowing you to compare more easily.
You can use DateDiff if your dates are going to be the same, otherwise you'll need to strip out the date as above
You can create a two variables of datetime, and set only hour of date that your need to compare.
declare #date1 datetime;
declare #date2 datetime;
select #date1 = CONVERT(varchar(20),CONVERT(datetime, '2011-02-11 08:00:00'), 114)
select #date2 = CONVERT(varchar(20),GETDATE(), 114)
The date will be "1900-01-01" you can compare it
if #date1 <= #date2
print '#date1 less then #date2'
else
print '#date1 more then #date2'
SELECT timeEvent
FROM tbEvents
WHERE CONVERT(VARCHAR,startHour,108) >= '01:01:01'
This tells SQL Server to convert the current date/time into a varchar using style 108, which is "hh:mm:ss". You can also replace '01:01:01' which another convert if necessary.
I believe you want to use DATEPART('hour', datetime).
Reference is here:
http://msdn.microsoft.com/en-us/library/ms174420.aspx
I don't love relying on storage internals (that datetime is a float with whole number = day and fractional = time), but I do the same thing as the answer Jhonny D. Cano. This is the way all of the db devs I know do it. Definitely do not convert to string. If you must avoid processing as float/int, then the best option is to pull out hour/minute/second/milliseconds with DatePart()
I am assuming your startHour column and #startHour variable are both DATETIME; In that case, you should be converting to a string:
SELECT timeEvent
FROM tbEvents
WHERE convert(VARCHAR(8), startHour, 8) >= convert(VARCHAR(8), #startHour, 8)
below query gives you time of the date
select DateAdd(day,-DateDiff(day,0,YourDateTime),YourDateTime) As NewTime from Table
#ronmurp raises a valid concern - the cast/floor approach returns different values for the same time. Along the lines of the answer by #littlechris and for a more general solution that solves for times that have a minute, seconds, milliseconds component, you could use this function to count the number of milliseconds from the start of the day.
Create Function [dbo].[MsFromStartOfDay] ( #DateTime datetime )
Returns int
As
Begin
Return (
( Datepart( ms , #DateTime ) ) +
( Datepart( ss , #DateTime ) * 1000 ) +
( Datepart( mi , #DateTime ) * 1000 * 60 ) +
( Datepart( hh , #DateTime ) * 1000 * 60 * 60 )
)
End
I've verified that it returns the same int for two different dates with the same time
declare #first datetime
set #first = '1900-01-01 23:59:39.090'
declare #second datetime
set #second = '2000-11-02 23:56:39.090'
Select dbo.MsFromStartOfDay( #first )
Select dbo.MsFromStartOfDay( #second )
This solution doesn't always return the int you would expect. For example, try the below in SQL 2005, it returns an int ending in '557' instead of '556'.
set #first = '1900-01-01 23:59:39.556'
set #second = '2000-11-02 23:56:39.556'
I think this has to do with the nature of DateTime stored as float. You can still compare the two number, though. And when I used this approach on a "real" dataset of DateTime captured in .NET using DateTime.Now() and stored in SQL, I found that the calculations were accurate.
TL;DR
Separate the time value from the date value if you want to use indexes in your search (you probably should, for performance). You can: (1) use function-based indexes or (2) create a new column for time only, index this column and use it in you SELECT clause.
Keep in mind you will lose any index performance boost if you use functions in a SQL's WHERE clause, the engine has to do a scan search. Just run your query with EXPLAIN SELECT... to confirm this. This happens because the engine has to process EVERY value in the field for EACH comparison, and the converted value is not indexed.
Most answers say to use float(), convert(), cast(), addtime(), etc.. Again, your database won't use indexes if you do this. For small tables that may be OK.
It is OK to use functions in WHERE params though (where field = func(value)), because you won't be changing EACH field's value in the table.
In case you want to keep use of indexes, you can create a function-based index for the time value. The proper way to do this (and support for it) may depend on your database engine. Another option is adding a column to store only the time value and index this column, but try the former approach first.
Edit 06-02
Do some performance tests before updating your database to have a new time column or whatever to make use of indexes. In my tests, I found out the performance boost was minimal (when I could see some improvement) and wouldn't be worth the trouble and overhead of adding a new index.