Преобразование SQL DateTime завершается ошибкой, когда преобразование не должно выполняться

Я изменяю существующий запрос для клиента и столкнулся с несколько непонятной проблемой.

Наш клиент использует SQL Server 2008 R2, и рассматриваемая база данных предоставляет пользователю возможность указывать настраиваемые поля для одной из своих таблиц, используя структуру EAV. Все значения, хранящиеся в этой структуре, varchar(255), а некоторые поля предназначены для хранения дат. Рассматриваемый запрос модифицируется, чтобы использовать два из этих полей и сравнивать их (одно — начало, другое — конец) с текущей датой, чтобы определить, какая строка является «текущей».

У меня проблема в том, что часть запроса выполняет CONVERT(DateTime, eav.Value), чтобы превратить varchar в DateTime. Сами преобразования прошли успешно, и я могу включить значение как часть предложения SELECT, но часть вопроса дает мне ошибку преобразования:

Conversion failed when converting date and/or time from character string.

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

Я попытался воссоздать базовую структуру в другой базе данных и включить образцы данных, но новая база данных ведет себя так, как ожидалось, поэтому я в растерянности.

EDIT Если это полезно, вот утверждение для представления:

create view Festival as 
select
    e.EntityId as FestivalId,
    e.LookupAs as FestivalName,
    convert(Date, nvs.Value) as ActivityStart,
    convert(Date, nve.Value) as ActivityEnd

from tblEntity e

left join CustomControl ccs on ccs.ShortName = 'Activity Start Date'
left join CustomControl cce on cce.ShortName = 'Activity End Date'
left join tblEntityNameValue nvs on nvs.CustomControlId = ccs.IdCustomControl and nvs.EntityId = e.EntityId
left join tblEntityNameValue nve on nve.CustomControlId = cce.IdCustomControl and nve.EntityId = e.EntityId

where e.EntityType = 'Festival'

Неудачный запрос таков:

select * 

from Festival f

join FestivalAttendeeAll fa on fa.FestivalId = f.FestivalId

where getdate() between f.ActivityStart and f.ActivityEnd

Тем не менее, это работает:

select * 

from Festival f

where getdate() between f.ActivityStart and f.ActivityEnd

(EntityId/FestivalId столбцы int)


person Adam Robinson    schedule 11.08.2011    source источник
comment
вероятно, из-за локали и дат, отформатированных в США, а не в США... Проверьте настройки локали... Можете ли вы опубликовать пример данных, демонстрирующих поведение?   -  person Mitch Wheat    schedule 11.08.2011
comment
Возможно, начните с первого запроса, который не работает, и опубликуйте его.   -  person paparazzo    schedule 11.08.2011
comment
@Mitch: Это была бы моя первая мысль, если бы я тоже отвечал, но это все даты в США, отформатированные либо в формате «1 января 2011 г., 00:00», либо в формате «1 января 2011 г., 00:00». . Запутанная часть заключается в том, что ошибка кажется связанной с чем-то, что (по моей логике, которая, очевидно, может быть неправильной) не должно иметь никакого влияния на то, происходит ли преобразование. Я бы предположил, что если данные на самом деле плохие, то выбор из самого представления вызовет ошибку, а это не так.   -  person Adam Robinson    schedule 11.08.2011
comment
@Balam: я буду через минуту; Я просто не уверен, насколько это будет полезно без контекста.   -  person Adam Robinson    schedule 11.08.2011
comment
Как указал @ spencer7593, проблема в том, что ваша таблица содержит НЕКОТОРЫЕ значения, не относящиеся к дате, и у вас мало контроля над тем, какие строки/значения будут преобразованы. Аналогичный недавний вопрос: stackoverflow.com /вопросы/6896299/   -  person Tao    schedule 11.08.2011


Ответы (1)


Я сталкивался с ошибкой такого типа раньше, это связано с «порядком операций», выполняемым планом выполнения.

Вы получаете это сообщение об ошибке, потому что план выполнения вашего оператора (сгенерированный оптимизатором) выполняет операцию CONVERT() для строк, содержащих строковые значения, которые не могут быть преобразованы в DATETIME.

По сути, вы не можете контролировать, над какими строками оптимизатор выполняет это преобразование. Вы знаете, что вам нужно, чтобы это преобразование выполнялось только для определенных строк, и у вас есть предикаты (предложения WHERE или ON), которые исключают эти строки (ограничивают строки теми, которые нуждаются в преобразовании), но ваш план выполнения выполняет операцию CONVERT() в строках ДО того, как эти строки будут исключены.

(Например, оптимизатор может выбрать сканирование таблицы и выполнить это преобразование для каждой строки до применения любого предиката.)

Я не могу дать конкретный ответ без конкретного вопроса и конкретного SQL, который генерирует ошибку.


Одним из простых подходов к решению этой проблемы было бы использование функции ISDATE() для проверки возможности преобразования строкового значения в дату.

То есть заменить:

CONVERT(DATETIME,eav.Value)

с:

CASE WHEN ISDATE(eav.Value) > 0 THEN CONVERT(DATETIME, eav.Value) ELSE NULL END

or:

CONVERT(DATETIME, CASE WHEN ISDATE(eav.Value) > 0 THEN eav.Value ELSE NULL END)

Обратите внимание, что функция ISDATE() имеет некоторые существенные ограничения, например, на нее влияют настройки DATEFORMAT и LANGUAGE сеанса.


Если в строке eav есть какое-то другое указание, вы можете использовать другой тест для условного выполнения преобразования.

CASE WHEN eav.ValueIsDateTime=1 THEN CONVERT(DATETIME, eav.Value) ELSE NULL END

Другой подход, который я использовал, состоит в том, чтобы попытаться получить некоторый контроль над порядком операций оптимизатора, используя встроенные представления или общие табличные выражения, с операциями, которые заставляют оптимизатор материализовать их и применять предикаты, так что это происходит ДО любое преобразование во внешнем запросе.

person spencer7593    schedule 11.08.2011
comment
Превосходно; кажется немного очевидным, что это возможно теперь, когда вы об этом упомянули; даже если кажется, что он должен материализовать значения как часть представления, он может решить этого не делать. Изменение вида для использования ISDATE как части проекции устранило проблему; Спасибо! - person Adam Robinson; 11.08.2011
comment
+1, но в последнем абзаце обратите внимание, что этот подход не всегда работает . - person Aaron Bertrand; 02.02.2014