views:

854

answers:

5

I have a table in SQL Server 2005 which has three columns:

id (int), 
message (text), 
timestamp (datetime)

There is an index on timestamp and id.

I am interested in doing a query which retrieves all messages for a given date, say '12/20/2008'. However I know that simply doing where timestamp='12/20/2008' won't give me the correct result because the field is a datetime field.

Somebody had recommended using the DATEPART function and pulling the year, month, and day out of timestamp and verifying that these are equal to 2008, 12, and 20, respectively. It looks like this would not use the index I have on timestamp and would end up doing a full table scan.

So what is the best way to construct my query so that I am taking advantage of the index that I have created?

+1  A: 
-- avoid re-calculating @MyDate +1 for every row
DECLARE @NextDay DateTime
Set @NextDay = @MyDate + 1

SELECT 
    -- ...
WHERE [timestamp] >= @MyDate AND [timestamp] < @NextDay
Joel Coehoorn
Would SQL server not be smart enough to understand that it needs only calculate @MyDate + 1 once for the statement?
erikkallen
Probably. I know for a fact that it won't do it for getdate(), though. Of course that's a function call, which is a little different. But I'm paranoid about leaving anything I don't need to in a where clause.
Joel Coehoorn
this will not work if @MyDate has time portion. To fix that use:Where [timestamp] >= DateAdd(day, DateDiff(day, 0, @Date), 0) And [timestamp] < DateAdd(day, DateDiff(day, 0, @Date), 1)
Charles Bretana
lol, Don't call DateAdd() in the where clause if you can help it either. What I posted will give you an 24-hour range. If you also need to truncate the time away from @MyDate the fastest way is like this: Set @MyDate = Cast(Floor(Cast(@MyDate As Int)) AS DateTime)
Joel Coehoorn
@Joel, No... DateAdd is the fastest possible way to strip times off of DateTime values, and as long as you apply it only on the input parameter side of a wheree clause predicate there is absolutely no impact on the SARGability of the query
Charles Bretana
.. faster than casting... and your inner cast(@MyDate as Integer) will round UP to the next higher integfer for all after noon datetimes... Demonstrated by Select Cast(Cast(Cast('26 Dec 2008 12:01:00' As Datetime) as Int) as DateTime)this produces 12/27/2008
Charles Bretana
@Joel, another thing you might want to re-examine, is your assertion that getDate() is recalculated for every row. It most definitely is not. The query optimizer is much smarter than that. It will also calculate @MyDate + 1 only once. It can recognize when an expression is not row-dependant.
Charles Bretana
A: 

The BETWEEN statement can help you.

SELECT *
FROM MyTable
WHERE TimeStamp BETWEEN @Start AND @End;

Start would need to be something like 12:01 am for the day you want messages for, and end would be like 11:59pm for the end of the same day.

Stephen Wrighton
The 12:00:00.000 vs 11:59:59.999 thing makes it simpler in my mind to just use two compare and the appropriate operators, like I showed earlier.
Joel Coehoorn
@Joel - Between does the same things as your operators but is easier to read (at least IMO).
Stephen Wrighton
No, it doesn't. I created another answer to demonstrate, since there's not space here.
Joel Coehoorn
The SQL Server documentation saysBETWEEN returns TRUE if the value of test_expression is greater than or equal to the value of begin_expression and less than or equal to the value of end_expression.
le dorfier
@Joel, Between (with a construct as Stephen specified, 11:59:59:999 pm), DOES do the exact same thing as what you suggested...
Charles Bretana
@Charles - while my structure performs the same result, Joel is correct in stating that they are not the same operation.
Stephen Wrighton
+2  A: 

The use of two datetime variables has always worked infallibly in my experience. The issue of the resolution seems highly unlikely. The important fact to remember, however, is that a range (of any type) includes both end points. So you can't test using BETWEEN on two dates, because it will include both. Rather use something like

datefield >= @startdate AND datefield < @enddate

The Manual.

C'mon folks - the documentation for this isn't that hard to find. :D

le dorfier
A: 

BETWEEN does NOT do >=, <. It does >=, <=, as this code proves:

declare @low datetime
declare @high datetime
set @low = getdate()
set @high = @low+1

select case when @low between @low and @high then 1 else 0 end, 
    case when @high between @low and @high then 1 else 0 end

The result will be 1,1, showing that the = is applied to both bounds.

Joel Coehoorn
A: 

Assuming @Date is a datetime value for any datetime on the day you want all the messages for, use this

Where [timestamp] >= DateAdd(day, DateDiff(day, 0, @Date), 0) 
  And [timestamp] <  DateAdd(day, DateDiff(day, 0, @Date), 1)

This is much faster than using a CAST, not to mention that when using CAST on datetimes, if you Cast a datetime value which is after noon to an integer,

Declare @MyDate as Datetime
Set @MyDate = '12/25/2008 12:01:00'
Declare @IntVal Integer
Set @IntVal = Cast(@MyDate as Integer) 
Select Cast(@IntVal as DateTime)

it will round UP to the integer representing tne NEXT day's date. The above script will output 12/26/2008

Charles Bretana