Преобразование SQL DateTime завершается неудачно, если преобразование не должно выполняться - PullRequest
5 голосов
/ 11 августа 2011

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

Наш клиент использует 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(), тогда он работает правильно, но не работает, если я добавляю объединение во вторую таблицу, используя одно из полей (без даты) в представлении. Я понимаю, что это может быть довольно сложно, поэтому я могу опубликовать пример запроса, но этот вопрос уже немного затянулся.

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

РЕДАКТИРОВАТЬ В случае, если это полезно, вот утверждение для представления:

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)

1 Ответ

11 голосов
/ 11 августа 2011

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

Вы получаете это сообщение об ошибке, потому что план выполнения для вашего оператора (сгенерированныйоптимизатор) выполняет операцию 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

или:

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...