1

I am building a dynamic query as a stored procedure in SQL Server 2008 and I'm getting some unexpected results when I compare a date (supplied as a parameter) and a datetime (stored in the database). I searched around for some ways to compare the two without taking the time portion into consideration and found this:

DATEDIFF(day, @d, v.ScheduledDate) = 0

What I'm doing with this is trying to find records where the supplied date parameter and the stored ScheduledDate are the same (in terms of days, e.g. 02/05/2011 and 02/05/2011 11:26:19.157). This is query that I wrote to do this:

SET @sql = 'SELECT e.Id, e.FirstName, e.LastName, v.ScheduledDate
            FROM Employee e, Visit v
            WHERE 1=1'
-- Several IF IS NOT NULL statements here

IF @d IS NOT NULL
BEGIN
  SET @sql = @sql + ' AND DATEDIFF(day, ''' + @d + ''',' + 'v.ScheduledDate) = 0
  AND v.EmpId = e.Id '
END
EXEC (@sql)

I would expect that this query would result in the all of the ScheduledDates for any employee who has a visit scheduled for that day. In other words, if I have two employees with Ids 5 and 7, and there exists in the Visit table two ScheduleDate entries on 02/05/2011 for employees with Ids 5 and 7, I would expect to get both those employees back when I run this query. It seems, however, that when I run it, I only get one row back. (As a side note, the two ScheduledDate entries I'm working with are on the same day, but about 3 hours apart from each other. I would think that the DATEDIFF function would account for this, since a few hours is certainly within the time frame of a day.) If I change the = in the query to a >= 0 or a <= 0, I get more rows as expected, but oddly enough still only get the single entry for that specific date. There are other records in the table where the same Employee has multiple visits on different dates, and those are returned accordingly when I use >= 0 or <= 0. For example, Employee with Id 41 has 3 visits on 10-29-2011, 11-24-2011, and 12-28-2011 and all 3 of those are returned when I change DATEDIFF to >= 0. I'm still confused as to why I'm only getting back the single record when two different Employees have a visit scheduled on the same date. Could anyone provide some insight as to where my logic is going wrong here? Note that when I'm testing this, I'm only supplying the ScheduledDate parameter. All of the other IF IS NOT NULL statements just fall through since all other params are inserted as NULL.

Zajn
  • 4,078
  • 24
  • 39
  • On a side note : Lookup `sp_executesql` so that you can parameterise your queries. (Cached plans, type safety, easier to debug, etc.) – MatBailie Dec 23 '11 at 16:19
  • Thanks Dems. I had been considering using that, but I just haven't gotten around to re-writing my query. I'm still trying to get it to work properly! – Zajn Dec 23 '11 at 16:22
  • That's one benefit of `sp_executesql`, it lets you make things neater and NOT need to cast dates to strings (implicitly or otherwise) to create a query. It narrows the range of possible sources of error. – MatBailie Dec 23 '11 at 16:25
  • Oh, I wasn't aware of that. I think I'll try and look into using `sp_executesql`. If I understand correctly, would that eliminate the need for me to declare my @d param as a VARCHAR? – Zajn Dec 23 '11 at 16:31
  • this is the best way to floor a datetime: `DATEADD(day,DATEDIFF(day,0,@datetime),0)` to remove the time – KM. Dec 23 '11 at 16:37
  • @KM - What about `CAST(@d AS DATE)` as this is SQL Server 2008? – MatBailie Dec 23 '11 at 16:38
  • Can you post the entire sproc? – UnhandledExcepSean Dec 23 '11 at 16:42
  • @SpectralGhost I'll post the sproc here in a little bit. Trying Dem's solution right now first. – Zajn Dec 23 '11 at 16:57
  • Okay, I solved this with the aid of a co-worker. The error was a combination of garbage in the DB and also a slight error in my query. There was an Employee who had been deleted from the Employee table, but his visits still existed in the Visit table. We made the query `LEFT JOIN Visit v on e.Id = v.EmpId` and only got the one record back when we expected 2. That error stemmed from the missing Employee in the Employee table whose Visit still existed. Thanks for all your help everyone! – Zajn Dec 23 '11 at 17:58

4 Answers4

1

This is in depth, but it's very informative: http://www.sommarskog.se/dyn-search.html

SET @sql = N'SELECT e.Id, e.FirstName, e.LastName, v.ScheduledDate
             FROM Employee e, Visit v
             WHERE v.EmpId = e.Id'

-- Several IF IS NOT NULL statements here
IF @d IS NOT NULL
  SET @sql = @sql + N' AND (v.ScheduledDate >= @date AND v.ScheduledDate < @date + 1)'

-- This stays the same, EVEN if the parameter is NULL and not used
-- This ensures execution plan re-use is available
SET @param_definition = '@date DATETIME,    -- Or whatever type v.ScheduledDate is
                         @smeg INT,
                         @head WHATEVER'

SP_EXECUTESQL
    @sql,
    @param_definition,
    @date = CAST(@d AS DATE),
    @smeg = 0,
    @head = NULL
MatBailie
  • 83,401
  • 18
  • 103
  • 137
  • This looks promising. I'm going to try to re-write my query accordingly and I'll let you know how it works. Thanks! – Zajn Dec 23 '11 at 16:45
  • SQL Server doesn't seem to like the @date = CAST(@d AS DATE) line. Could this be because I have @d declared as a VARCHAR? It gives me an 'incorrect syntax near @d - Expecting SELECT or '(' ' – Zajn Dec 23 '11 at 16:48
  • @zajn : Possibly, change it to a DATETIME or DATE data type and see what you get. It's also possible that you can't use expressions there to, so you may need `SET @d = CAST(@d AS DATE)` prior to the `SP_EXECUTE`, you can then just have `@date = @d,` there. – MatBailie Dec 23 '11 at 16:50
  • Thanks, that cleared up that error. The query executes now, but I'm still getting back way many more records than I should. What it appears to be doing is returning every record in the Employees table, and then concatenating the two scheduled dates onto them. So it IS in fact returning the proper scheduled dates, but it's acting as if every employee has visits scheduled then, not just the two that actually do. – Zajn Dec 23 '11 at 16:59
  • Instead of CAST you may have to use CONVERT with date types. – JeffO Dec 23 '11 at 17:27
  • I'm marking this as the accepted answer because it greatly improved my query and helped lead me to the solution. – Zajn Dec 23 '11 at 17:59
0

I think the best practice is to remove the time portion of the date do that like so

DECLARE @dt DATETIME
SELECT @dt = GETDATE()
SELECT CAST(FLOOR(CAST(@dt AS FLOAT)) AS DATETIME)

then you can just use plain old = to compare

bebonham
  • 135
  • 1
  • 12
  • I've read that casting the DATETIME like that can be really inefficient, but I'll give it a go. Thanks for the input! – Zajn Dec 23 '11 at 16:11
  • 1
    It's not really inefficient. `DATEADD(DAY, DATEDIFF(DAY, 0, @date), 0)` is faster, but this is also very close. `CAST(@date AS DATE)` is even quicker in SQL 2008 afaik. I would use `WHERE v.ScheduledDate >= CAST(@d AS DATE) AND v.ScheduledDate < (CAST(@d AS DATE) + 1)` – MatBailie Dec 23 '11 at 16:22
  • Hmm, okay. I'm not very well-versed in SQL so I'm just saying what I've read before. I'll have to give it a try and see for myself. Thanks again! – Zajn Dec 23 '11 at 16:26
  • @Zajn: see http://sqlinthewild.co.za/index.php/2008/09/04/comparing-date-truncations/ for a nice performance comparison of time-truncation techniques – Tao Dec 23 '11 at 17:24
0

I just did the SAME thing today (dynamic query with that line of code). This was my line of code:

DATEDIFF(day,SchDate,GetDate()) = 0

It worked perfectly. Sorry I can't help you any further but I read that like 4 times and I can't find any mistakes in the datediff portion. I did the same thing as you did and it worked flawlessly. However, if you want to you can use something like:

(AND day(@d) = day (v.ScheduledDate)) AND (month(@d) = month(v.ScheduledDate)) AND (year(@d) = year(v.ScheduledDate))

Maybe that will work (I just tested it with random dates and it did). Ofc you need to add the +@d+ and stuff because the dynamic query doesn't have the @d variable, but you already know that =-)

Hope this helps!

Gaspa79
  • 5,488
  • 4
  • 40
  • 63
  • I'm not sure that this would be SARGable and so not make adequate use of indexes. Could try it and check the execution plan to see though. – MatBailie Dec 23 '11 at 16:33
  • @Dems: because the column (v.ScheduledDate) is wrapped in a function call, it will *never* make decent use of indexes anyway (neither this answer nor the original posted code). The only way to make good use of indexes here is to rewrite the condition to search for a range of v.ScheduledDate values, as in *your* answer. Sorry if that's what you intended to convey, it's not clear to me from your comment. – Tao Dec 23 '11 at 17:05
  • @Tao - Actually, `DATEADD(DAY, DATEDIFF(DAY, 0, v.ScheduledDate), 0)` is SARGable. Some functions are, some functions are not. – MatBailie Dec 23 '11 at 17:47
  • 1
    @Dems: can you confirm that in some way? I just whipped up a quick test run, it seems to disprove your claim (tested on SQL 2008 R2, but my experience of 2000 and 2005 matches): http://pastebin.com/vQhLp4H4. Either way, do you know where one can find out what functions are in fact SARGable? I thought none were. – Tao Dec 23 '11 at 18:18
  • @Tao - Yes, apparently I have plenty of evidence. Evidence that I'm wrong. Oops. – MatBailie Dec 23 '11 at 18:39
  • (related: popular answer here claims all functions are non-SARGable: http://stackoverflow.com/questions/799584/what-makes-a-sql-statement-sargable - should be updated if we find that there are in fact SARGable functions) – Tao Dec 23 '11 at 18:40
0

This could be way off, but my best guess from everything you've described so far (including your comments on Dems' answer) is that the relation between Employee and Visit is not defined solely by v.EmpId = e.Id, but rather a compound key, and you're missing part of it in your join/where condition. Something like v.EmpId = e.Id AND v.CompanyID = e.CompanyID.

Tao
  • 13,457
  • 7
  • 65
  • 76
  • I think you're on to something with that, but I've looked at the table design in SQL Server Studio, and there aren't any of relationships aside from v.EmpId = e.Id. I don't know if this is some oversight on my part, or is poor database design courtesy of whoever created this. – Zajn Dec 23 '11 at 17:25
  • You were right in your thinking Tao! There wasn't an additional key that I was missing, but there was a record missing in the table which led me to some incorrect results. Thanks for the insight! – Zajn Dec 23 '11 at 18:01