Преобразование в datetime завершается неудачно только в предложении WHERE? - PullRequest
10 голосов
/ 01 сентября 2011

У меня проблема с некоторыми запросами к серверу SQL.Оказывается, у меня есть таблица с полями «Attibute_Name» и «Attibute_Value», которые могут быть любого типа, хранящиеся в varchar.(Да ... Я знаю.)

Кажется, что все даты для определенного атрибута хранятся в формате "ГГГГ-ММ-ДД чч: мм: сс" (в этом нет уверенности на 100%,здесь миллионы записей), поэтому я могу выполнить этот код без проблем:

select /*...*/ CONVERT(DATETIME, pa.Attribute_Value)
from 
    ProductAttributes pa
    inner join Attributes a on a.Attribute_ID = pa.Attribute_ID
where 
    a.Attribute_Name = 'SomeDate'

Однако, если я выполню следующий код:

select /*...*/ CONVERT(DATETIME, pa.Attribute_Value)
from 
    ProductAttributes pa
    inner join Attributes a on a.Attribute_ID = pa.Attribute_ID
where 
    a.Attribute_Name = 'SomeDate'
    and CONVERT(DATETIME, pa.Attribute_Value) < GETDATE()

, я получу следующую ошибку: Преобразование не удалось при преобразовании даты и / или времени из символьной строки.

Почему происходит сбой в предложении where, а не в предложении select?

Другая подсказка:

Если вместо фильтрации по Attribute_Name я использую фактический Attribute_ID, хранящийся в базе данных (PK), он будет работать без проблем.

select /*...*/ CONVERT(DATETIME, pa.Attribute_Value)
from 
    ProductAttributes pa
    inner join Attributes a on a.Attribute_ID = pa.Attribute_ID
where 
    a.Attribute_ID = 15
    and CONVERT(DATETIME, pa.Attribute_Value) < GETDATE()

Обновление Спасибо всем за ответы.Мне было трудно действительно выбрать правильный ответ, потому что все указали на то, что было полезно для понимания проблемы.Это было определенно связано с порядком исполнения.Оказывается, мой первый запрос работал правильно, потому что сначала было выполнено предложение WHERE, а затем SELECT.Мой второй запрос не прошел по той же причине (поскольку атрибуты не были отфильтрованы, преобразование не выполнено при выполнении того же предложения WHERE).Мой третий запрос работал, потому что идентификатор был частью индекса (PK), поэтому он имел приоритет и сначала детализировал результаты по этому условию.

Спасибо!

Ответы [ 6 ]

9 голосов
/ 01 сентября 2011

Похоже, вы предполагаете что-то вроде оценки короткого замыкания или гарантированного упорядочения предикатов в предложении WHERE.Это не гарантировано.Если в столбце есть смешанные типы данных, то единственный безопасный способ их обработки - выражение CASE.

Использование (например)

CONVERT(DATETIME, 
      CASE WHEN ISDATE(pa.Attribute_Value) = 1 THEN pa.Attribute_Value END)

Не

CONVERT(DATETIME, pa.Attribute_Value)
2 голосов
/ 01 сентября 2011

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

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

Обновление

После более пристального взгляда (извините, я @VLDB и только смотрю SO между сеансами), я понимаю, что у вас есть хранилище EAV с присущей семантикой без типов (attribute_value может быть строкой, датой, int и т. Д.) ). Мое мнение таково, что лучше всего использовать sql_variant в хранилище и вплоть до клиента (т.е. проект sql_variant). Вы можете связать тип в клиенте, все клиентские API имеют методы для извлечения внутреннего типа из sql_variant, см. Использование данных sql_variant (ну, почти все клиентские API ... Использование Тип данных sql_variant в CLR ). С sql_variant вы можете хранить несколько типов без проблем прохождения строковых представлений, вы можете использовать SQL_VARIANT_PROPERTY для проверки таких вещей, как BaseType в сохраненных значениях, и вы можете даже do думает как проверка ограничений для обеспечения корректности типа данных.

1 голос
/ 01 сентября 2011

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

SELECT /* ... */
FROM
(
  SELECT /* ... */
    FROM ProductAttributes AS pa
    INNER JOIN dbo.Attributes AS a
    ON a.Attribute_ID = pa.Attribute_ID
    WHERE a.Attribute_Name = 'SomeDate'
    AND ISDATE (pa.Attribute_Value) = 1
) AS z
WHERE CONVERT(CHAR(8), AttributeValue, 112) < CONVERT(CHAR(8), GETDATE(), 112);

Но лучшим ответом, вероятно, будет использование клавиши Attribute_ID вместо имени, если это возможно.

0 голосов
/ 01 сентября 2011

Вы можете проверить планы выполнения. Может случиться так, что при первом запросе второй критерий (CONVERT(DATETIME, pa.Attribute_Value) < GETDATE()) будет оцениваться первым по всем строкам, включая строки с недопустимыми данными (не датой), тогда как в случае второго - a.Attribute_ID = 15 оценивается первым. Таким образом, исключая строки со значениями без даты.

Кстати, второй также может быть быстрее, и если у вас нет ничего из Attributes в списке выбора, вы можете избавиться от inner join Attributes a on a.Attribute_ID = pa.Attribute_ID.

На этой ноте было бы желательно избавиться от EAV, пока не слишком поздно :)

0 голосов
/ 01 сентября 2011

Я думаю, проблема в том, что у вас неверная дата в вашей базе данных (очевидно).

В первом примере, когда вы не проверяете дату в предложении WHERE, все датыгде a.attribute.Name = 'SomeDate' допустимы, поэтому он никогда не пытается преобразовать неверную дату.

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

В третьем примере изменение на Attribute_Id, вероятно, изменит план запроса так, что он будет искать только те, где id = 15 First,и затем проверяет, есть ли у этих записей действительная дата, что они и делают.(Возможно, Attribute_Id проиндексирован, а Attribute_name нет)

Итак, у вас где-то плохая дата, но ее нет ни в одной записи с Arttribute_id = 15.

0 голосов
/ 01 сентября 2011

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

...