4

This was originally going to be a question about how to implement this because I was stuck at a certain part but I am now curious as to why this was happening in the first place. I needed to compare only the dates not the time which wouldn't have been a problem if the times didn't differ. The code below shows the query I was originally trying

SELECT *
FROM Employee e
inner join OT_Hours o on o.Emp_ID=e.Emp_ID
inner join Position p on p.Position_ID=e.Position_ID
inner join Signup_Sheet s on s.Employee_ID=e.Emp_ID
WHERE e.Eligible_OT=1 and s.Day_Shift = 1 
and p.Position_Name = 'Controller' 
and Convert(Varchar(20),s.Date,101) = '07/26/2010'
and Convert(Varchar(20),o.Date,101) <='07/26/2010'
and Convert(Varchar(20),o.Date,101) > '07/26/2009'
and o.Quantity NOT IN(0.3) order by o.Date DESC

I would get no result when I ran that query, but when I removed the second last line it would return 12 results (<=) and when I removed the 3rd last line but kept the second last it would return 6 results (>). After reviewing the data I could see that 4 of those results should have been returned. Now for the wierd part. Below is the code I am currently using.

SELECT DISTINCT o.Date, e.Emp_ID as Emp_ID, e.First_Name+ ' ' +e.Last_Name as Name, o.Quantity as Sum
FROM Employee e
left join OT_Hours o on o.Emp_ID=e.Emp_ID
left join Position p on p.Position_ID=e.Position_ID
left join Signup_Sheet s on s.Employee_ID=e.Emp_ID
WHERE e.Eligible_OT=1 and s.Day_Shift = 1 
and p.Position_Name = 'Controller' 
and Convert(Varchar(20),s.Date,101) = '07/26/2010'
and o.Date between '07/26/2009' and '07/26/2010'
and o.Quantity NOT IN(0.3) order by o.Date DESC

This query will return results but I also tested it like I did the other one when o.Date was above and below the date specified. When the date was <= 16 results were returned, when > 8 results were returned. The final query produced 6 results. Now this is not a production database I'm querying against and I'm the only one using it so the data did not change. Any explanation on why this was happening? I'm assuming it had something to do with converting it to varchar and it couldn't compare properly but that doesn't explain why I would get 12 <=, 6 > and then no results in the end. Also if anyone knows a better way to implement this please let me know.

OMG Ponies
  • 325,700
  • 82
  • 523
  • 502
Gage
  • 7,365
  • 9
  • 47
  • 77
  • 2
    This has already been asked(in a way) Check the accepted answer here... http://stackoverflow.com/questions/2775/whats-the-best-way-to-remove-the-time-portion-of-a-datetime-value-sql-server – Wil P Jul 27 '10 at 16:31

6 Answers6

7

The two queries aren't the same - this:

and o.Date between '07/26/2009' and '07/26/2010'

...is the equivalent of:

and o.Date >= '07/26/2009' 
and o.Date <= '07/26/2010'

BETWEEN is ANSI standard, and inclusive on every database I've ever encountered.

Mind that if you don't specify a time portion for DATETIMEs, the value defaults to starting at midnight of the day - 00:00:00.

OMG Ponies
  • 325,700
  • 82
  • 523
  • 502
5

I learned this technique from SQL Server Magazine February 2007 (Datetime Calculations by Itzik Ben-Gan). This way, your 'between' will work regardless of whether the row's date is after midnight, as everything has been normalized to be at midnight with this comparison:

select *
from someTable
where dateadd(day, datediff(day, 0, somedate), 0) between '07/26/2009' and '07/26/2010' 

The datediff and dateadd work together to strip off the time and leave the date. You can then compare it to string literals, or other dates that have had the same modification done. I'd recommend putting this in a function.

EDIT: Based on OMG Ponies' comments. This will not take advantage of an index on the date column. An alternative might be to use the time stripping technique in addition to the technique others have mentioned. So instead of doing it on the table's column, do it on the last argument to 'between'. You could have a function like so:

CREATE FUNCTION [dbo].[fn_enddate](@enddate datetime)
RETURNS datetime AS  
BEGIN
    DECLARE @endOfDay datetime
    set @endOfDay = dateadd(millisecond, -2, dateadd(day, datediff(day, 0, @enddate) + 1, 0))
    return @endOfDay
END

This takes the argument date, sets it to midnight the next day, then subtracts two milliseconds, giving the end of the day of the given datetime. So then you could do:

select *
from someTable
where somedate between '07/26/2009' and dbo.fn_enddate('07/26/2010')
Ryan Ische
  • 3,536
  • 3
  • 21
  • 20
  • I haven't seen this one before. Nice to know. Did they talk about `'CAST(FLOOR(CAST('` method as well? – Wil P Jul 27 '10 at 16:18
  • I can't remember. He compared a few, and this one seemed to be the best. – Ryan Ische Jul 27 '10 at 16:20
  • 1
    I don't recommend using this - using a function on a column renders an index, if in place, useless. – OMG Ponies Jul 27 '10 at 16:25
  • @OMG Ponies - Interesting - definitely a consideration. So if left "inline", the index would be used? – Ryan Ische Jul 27 '10 at 16:29
  • @Ryan: Sorry, I don't understand "left inline"? – OMG Ponies Jul 27 '10 at 16:29
  • @OMG Ponies - I mean, if left as I have it in my example. – Ryan Ische Jul 27 '10 at 16:30
  • @Ryan: Sorry - the opposite. Using `DATEADD(...somedate)` means that if an index exists (or includes) the `somedate` column, the optimizer can't use the index because the data has changed - there's no relation between the index values and the value being evaluated. – OMG Ponies Jul 27 '10 at 16:33
  • @OMG Ponies - nm, I get what you're saying, no index scanning because of the two date functions it's going through. I was thinking of the last line I put in about putting the whole manipulation in a function. Very good point. – Ryan Ische Jul 27 '10 at 16:33
4

In the third from last line in your first query you are comparing two strings.

As such 01/02/2009 is greater than 01/01/2010

I usually do date BETWEEN '01/02/2009 00:00:00.000' AND '01/01/2010 23:59:59.997' but it will be interesting to see a better solution.

Chris Diver
  • 19,362
  • 4
  • 47
  • 58
  • SQL Server performs implicit data type conversion, going from string to datetime providing the format matches. – OMG Ponies Jul 27 '10 at 16:08
  • 1
    `SELECT CASE WHEN '01/02/2009' > '01/01/2010' THEN 1 ELSE 0 END` returns 1 on my SQL box. `o.Date > '07/26/2009'` would be implicitly converted, but not two varchars as per the first query. – Chris Diver Jul 27 '10 at 16:10
  • Not an alternative but you answered my question as to why it wasn't comparing the dates properly. Didn't realize 1/02/2009 is greater than 01/01/2010 lol – Gage Jul 27 '10 at 17:27
  • use datediff to compare the day part only: DATEDIFF(day, FieldDate, GETDATE()) = 0 src: [link](http://stackoverflow.com/questions/1293154/tsql-case-date-compare) – Michael Bahig Feb 23 '12 at 12:33
1

If your database is SQL Server what I have done that workS pretty well to strip off the time is something like the following....

SELECT DISTINCT o.Date, e.Emp_ID as Emp_ID, e.First_Name+ ' ' +e.Last_Name as Name, o.Quantity as Sum 
FROM Employee e 
left join OT_Hours o on o.Emp_ID=e.Emp_ID 
left join Position p on p.Position_ID=e.Position_ID 
left join Signup_Sheet s on s.Employee_ID=e.Emp_ID 
WHERE e.Eligible_OT=1 and s.Day_Shift = 1  
and p.Position_Name = 'Controller'  
and CAST(FLOOR(CAST(s.Date AS FLOAT)) AS DATETIME) = '07/26/2010' 
and CAST(FLOOR(CAST(o.Date AS FLOAT)) AS DATETIME) between '07/26/2009' and '07/26/2010' 
and o.Quantity NOT IN(0.3) order by o.Date DESC 

Depending on how your parameters are set '07/26/2010', '07/26/2009' you could store them in datetime variables and perform the same cast(floor(cast(@datevar as float)) as datetime) operation.

This seems to be a repost. Check the accepted answer here... How to remove the time portion of a datetime value (SQL Server)?

Community
  • 1
  • 1
Wil P
  • 3,341
  • 1
  • 20
  • 20
1
...
AND s.Date BETWEEN '2010-07-26 00:00:00.000' AND '2010-07-26 23:59:59.997'
AND o.Date BETWEEN '2009-07-26 00:00:00.000' AND '2010-07-26 23:59:59.997'
...
LukeH
  • 263,068
  • 57
  • 365
  • 409
1

My suggestion would be to adjust your criteria to cover times, not the dates themselves. If you convert or otherwise manipulate the column(s) for the compare you can destroy the use of indexes in the query.

Also, always compare dates to dates. When you convert them to strings and try to compare you run into problems as Chris Diver points out.

In your case, I would try:

SELECT
    o.Date,
    e.Emp_ID as Emp_ID,
    e.First_Name+ ' ' +e.Last_Name as Name,
    o.Quantity as Sum
FROM
    Employee e
LEFT JOIN OT_Hours o ON o.Emp_ID = e.Emp_ID
LEFT JOIN Position p ON p.Position_ID = e.Position_ID
LEFT JOIN Signup_Sheet s ON s.Employee_ID = e.Emp_ID
WHERE
    e.Eligible_OT = 1 AND
    s.Day_Shift = 1 AND
    p.Position_Name = 'Controller' AND
    (s.Date >= @signup_date AND s.Date < DATEADD(dy, 1, @signup_date)) AND
    (o.Date >= @order_start_date AND o.Date < DATEADD(dy, 1, @order_end_date)) AND
    o.Quantity NOT IN (0.3)
ORDER BY
    o.Date DESC

You'll need to make sure that the parameters or variables are always without a time portion.

Tom H
  • 46,766
  • 14
  • 87
  • 128