Динамический SQL-запрос, возвращающий неожиданные результаты при сравнении дат

Я создаю динамический запрос как хранимую процедуру в SQL Server 2008 и получаю неожиданные результаты при сравнении даты (предоставленной в качестве параметра) и даты и времени (хранящейся в базе данных). Я искал несколько способов сравнить их, не принимая во внимание временную часть, и нашел это:

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

Что я делаю с этим, так это пытаюсь найти записи, в которых предоставленный параметр даты и сохраненная ScheduledDate совпадают (с точки зрения дней, например, 05.02.2011 и 05.02.2011 11 :26:19.157). Это запрос, который я написал для этого:

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)

Я ожидаю, что этот запрос приведет ко всем запланированным датам для любого сотрудника, визит которого запланирован на этот день. Другими словами, если у меня есть два сотрудника с идентификаторами 5 и 7, а в таблице посещений есть две записи ScheduleDate от 05.02.2011 для сотрудников с идентификаторами 5 и 7, я ожидаю, что оба этих сотрудника вернутся, когда я запустите этот запрос. Однако кажется, что когда я запускаю его, я возвращаю только одну строку. (В качестве примечания: две записи ScheduledDate, с которыми я работаю, относятся к одному и тому же дню, но с разницей примерно в 3 часа. Я думаю, что функция DATEDIFF будет учитывать это, поскольку несколько часов, безусловно, находятся в пределах временные рамки дня.) Если я изменяю = в запросе на >= 0 или ‹= 0, я получаю больше строк, как и ожидалось, но, как ни странно, по-прежнему получаю только одну запись для этой конкретной даты. В таблице есть другие записи, в которых один и тот же сотрудник имеет несколько посещений в разные даты, и они возвращаются соответственно, когда я использую >= 0 или ‹= 0. Например, сотрудник с идентификатором 41 имеет 3 посещения 29.10.2011. , 11-24-2011 и 12-28-2011, и все 3 из них возвращаются, когда я меняю DATEDIFF на >= 0. Я все еще не понимаю, почему я получаю только одну запись, когда два разных сотрудника визит запланирован на тот же день. Может ли кто-нибудь дать некоторое представление о том, где моя логика идет не так, как надо? Обратите внимание, что когда я тестирую это, я задаю только параметр ScheduledDate. Все остальные операторы IF IS NOT NULL просто не выполняются, поскольку все остальные параметры вставляются как NULL.


person Zajn    schedule 23.12.2011    source источник
comment
На заметку: поиск sp_executesql, чтобы вы могли параметризовать свои запросы. (Кэшированные планы, безопасность типов, упрощение отладки и т. д.)   -  person MatBailie    schedule 23.12.2011
comment
Спасибо Демс. Я рассматривал возможность использования этого, но я просто не удосужился переписать свой запрос. Я все еще пытаюсь заставить его работать правильно!   -  person Zajn    schedule 23.12.2011
comment
Это одно из преимуществ sp_executesql, оно позволяет делать вещи более аккуратными и НЕ требует приведения дат к строкам (неявно или иным образом) для создания запроса. Это сужает круг возможных источников ошибок.   -  person MatBailie    schedule 23.12.2011
comment
О, я не знал об этом. Я думаю, что попробую использовать sp_executesql. Если я правильно понимаю, устранит ли это необходимость объявлять мой параметр @d как VARCHAR?   -  person Zajn    schedule 23.12.2011
comment
это лучший способ установить дату и время: DATEADD(day,DATEDIFF(day,0,@datetime),0) чтобы удалить время   -  person KM.    schedule 23.12.2011
comment
@KM - А как насчет CAST(@d AS DATE), поскольку это SQL Server 2008?   -  person MatBailie    schedule 23.12.2011
comment
Можете ли вы опубликовать весь sproc?   -  person UnhandledExcepSean    schedule 23.12.2011
comment
@SpectralGhost Я немного опубликую здесь sproc. Сначала попробуйте решение Дема прямо сейчас.   -  person Zajn    schedule 23.12.2011
comment
Хорошо, я решил это с помощью коллеги. Ошибка была комбинацией мусора в БД, а также небольшой ошибкой в ​​моем запросе. Был Сотрудник, который был удален из таблицы Сотрудников, но его посещения все еще существовали в таблице Посещения. Мы сделали запрос LEFT JOIN Visit v on e.Id = v.EmpId и получили только одну запись, когда ожидали 2. Эта ошибка возникла из-за отсутствия сотрудника в таблице сотрудников, чей визит все еще существовал. Спасибо за вашу помощь всем!   -  person Zajn    schedule 23.12.2011


Ответы (4)


Это подробно, но очень информативно: 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
person MatBailie    schedule 23.12.2011
comment
Это выглядит многообещающе. Я собираюсь попытаться соответствующим образом переписать свой запрос и дам вам знать, как он работает. Спасибо! - person Zajn; 23.12.2011
comment
SQL Server, похоже, не любит строку @date = CAST(@d AS DATE). Может ли это быть из-за того, что @d объявлен как VARCHAR? Это дает мне «неправильный синтаксис рядом с @d - Ожидание SELECT или '(' ' - person Zajn; 23.12.2011
comment
@zajn: возможно, измените его на тип данных DATETIME или DATE и посмотрите, что вы получите. Также возможно, что вы не можете использовать выражения там, поэтому вам может понадобиться SET @d = CAST(@d AS DATE) перед SP_EXECUTE, тогда вы можете просто иметь @date = @d, там. - person MatBailie; 23.12.2011
comment
Спасибо, это устранило эту ошибку. Запрос выполняется сейчас, но я все еще получаю гораздо больше записей, чем должен. Похоже, что он возвращает каждую запись в таблице «Сотрудники», а затем объединяет в них две запланированные даты. Таким образом, на самом деле он возвращает правильные запланированные даты, но действует так, как будто у каждого сотрудника есть запланированные визиты, а не только у двух, которые на самом деле есть. - person Zajn; 23.12.2011
comment
Вместо CAST вам, возможно, придется использовать CONVERT с типами дат. - person JeffO; 23.12.2011
comment
Я отмечаю это как принятый ответ, потому что он значительно улучшил мой запрос и помог найти решение. - person Zajn; 23.12.2011

Я думаю, что лучше всего удалить временную часть даты, сделать это так

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

тогда вы можете просто использовать обычный старый = для сравнения

person bebonham    schedule 23.12.2011
comment
Я читал, что такое приведение DATETIME может быть очень неэффективным, но я попробую. Спасибо за отзыв! - person Zajn; 23.12.2011
comment
Это не совсем неэффективно. DATEADD(DAY, DATEDIFF(DAY, 0, @date), 0) быстрее, но это тоже очень близко. CAST(@date AS DATE) еще быстрее в SQL 2008 afaik. я бы использовал WHERE v.ScheduledDate >= CAST(@d AS DATE) AND v.ScheduledDate < (CAST(@d AS DATE) + 1) - person MatBailie; 23.12.2011
comment
Хм, хорошо. Я не очень хорошо разбираюсь в SQL, поэтому просто говорю то, что читал раньше. Я должен дать ему попробовать и посмотреть на себя. Спасибо еще раз! - person Zajn; 23.12.2011
comment
@Zajn: см. sqlinthewild.co.za/index .php/2008/09/04/ для хорошего сравнения производительности методов сокращения времени. - person Tao; 23.12.2011

Я только что сделал сегодня ТО ЖЕ ЖЕ (динамический запрос с этой строкой кода). Это была моя строка кода:

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

Это сработало отлично. Извините, я больше ничем не могу вам помочь, но я прочитал это примерно 4 раза и не могу найти ошибок в датированной части. Я сделал то же самое, что и вы, и это сработало без нареканий. Однако, если вы хотите, вы можете использовать что-то вроде:

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

Возможно, это сработает (я только что проверил это со случайными датами, и это сработало). Конечно, вам нужно добавить +@d+ и прочее, потому что в динамическом запросе нет переменной @d, но вы уже знаете это =-)

Надеюсь это поможет!

person Gaspa79    schedule 23.12.2011
comment
Я не уверен, что это будет SARGable и поэтому не будет адекватно использовать индексы. Можно попробовать и проверить план выполнения, чтобы увидеть. - person MatBailie; 23.12.2011
comment
@Dems: поскольку столбец (v.ScheduledDate) заключен в вызов функции, он в любом случае никогда не будет прилично использовать индексы (ни этот ответ, ни исходный опубликованный код). Единственный способ эффективно использовать индексы здесь — переписать условие для поиска диапазона значений v.ScheduledDate, как в вашем ответе. Извините, если это то, что вы хотели донести, мне непонятно из вашего комментария. - person Tao; 23.12.2011
comment
@Tao - На самом деле, DATEADD(DAY, DATEDIFF(DAY, 0, v.ScheduledDate), 0) можно использовать SARG. Какие-то функции есть, какие-то нет. - person MatBailie; 23.12.2011
comment
@Dems: ты можешь это как-то подтвердить? Я только что провел быстрый тестовый прогон, похоже, он опровергает ваше утверждение (проверено на SQL 2008 R2, но мой опыт с 2000 и 2005 годами совпадает): pastebin.com/vQhLp4H4. В любом случае, знаете ли вы, где можно узнать, какие функции на самом деле являются SARGable? Я думал, что нет. - person Tao; 23.12.2011
comment
@Tao - Да, видимо, у меня много доказательств. Доказательство того, что я не прав. Упс. - person MatBailie; 23.12.2011
comment
(связанный: популярный ответ здесь утверждает, что все функции не поддерживаются SARG: stackoverflow.com/questions/799584/ - следует обновить, если мы обнаружим, что на самом деле существуют функции SARGable) - person Tao; 23.12.2011

Это может быть далеко, но мое лучшее предположение из всего, что вы описали до сих пор (включая ваши комментарии к ответу Демса), заключается в том, что отношение между Employee и Visit определяется не только v.EmpId = e.Id, а скорее составным ключом, и вы вам не хватает части в вашем условии соединения/где. Что-то вроде v.EmpId = e.Id AND v.CompanyID = e.CompanyID.

person Tao    schedule 23.12.2011
comment
Я думаю, вы что-то понимаете в этом, но я просмотрел дизайн таблицы в SQL Server Studio и не обнаружил никаких отношений, кроме v.EmpId = e.Id. Я не знаю, является ли это какой-то оплошностью с моей стороны или плохой дизайн базы данных, любезно предоставленный тем, кто это создал. - person Zajn; 23.12.2011
comment
Вы были правы в своем мышлении Дао! Не было дополнительного ключа, которого мне не хватало, но в таблице отсутствовала запись, что привело к некоторым неправильным результатам. Спасибо за понимание! - person Zajn; 23.12.2011