Преобразование типа данных varchar в тип данных datetime привело к значению вне допустимого диапазона


8

Я пытаюсь выполнить простой запрос, чтобы получить все строки, созданные в ноябре:

SELECT COUNT(*)
FROM dbo.profile 
WHERE [Created] BETWEEN '2014-11-01 00:00:00.000' 
AND '2014-11-30 23:59:59.997';

SMSS возвращает:

Преобразование типа данных varchar в тип данных datetime привело к значению вне допустимого диапазона.

Я не понимаю, почему данные преобразуются из varchar в datetime, когда для параметра «Created» установлено значение datetime:

Колонны Нужно ли сообщать серверу, что «Создано» - это время и дата? Если нет, почему я получаю это сообщение varchar?

Изменить: значение в базе данных было YYYY-MM-DD. Ответ от @SqlZim ниже говорит о том, что мне нужно использовать convert (), чтобы сообщить sql, в каком формате находится дата в БД, - и заменить символ пробела на букву T:

select count(*) 
from dbo.profile 
where [created] between convert(datetime,'2014-11-01T00:00:00.000') 
and convert(datetime,'2014-11-30T23:59:59.997');`

Ответы:


8

Я проверил ваш профиль и увидел, что вы находитесь в Великобритании. Если ваш sql сервер настроен на использование формата даты dmy, то это объясняет вашу проблему. Без использования буквы «Т» вместо пробела в строке даты и времени Sql Server не распознает его как формат ISO8601.

Попробуй это:

select count(*) 
  from dbo.profile 
  where [created] between convert(datetime,'2014-11-01T00:00:00.000') 
                      and convert(datetime,'2014-11-30T23:59:59.997');

Запросы с использованием даты и / или даты и времени могут быть сложными, чтобы убедиться, что вы получаете то, что ищете, я рекомендую прочитать:

изменить: чтобы уточнить значение выхода за пределы диапазона в вашем сообщении об ошибке, можно интерпретировать месяц как 30, а день как 11.


8

Я не понимаю, почему данные преобразуются из varchar в datetime, когда для параметра «Created» установлено значение datetime

Литералы, которые вы предоставляете для сравнения со Createdстолбцом, являются строками. Чтобы сравнить эти литералы со datetimeстолбцом, SQL Server пытается преобразовать строки в datetimeтипы в соответствии с правилами приоритета типов данных . Без явной информации о формате строк SQL Server следует своим запутанным правилам интерпретации строк как даты и времени.

На мой взгляд, самый лучший способ избежать подобных проблем - это четко указать типы. SQL Server предоставляет CAST and CONVERTфункции для этой цели. При работе со строками и типами даты / времени CONVERTпредпочтительнее, поскольку он предоставляет параметр стиля для явного определения формата строки.

В вопросе используются строки в каноническом (с миллисекундами) формате ODBC (стиль 121). Явное понимание типа данных и стиля строки приводит к следующему:

SELECT COUNT(*)
FROM dbo.profile 
WHERE [Created] BETWEEN 
    CONVERT(datetime, '2014-11-01 00:00:00.000', 121)
    AND 
    CONVERT(datetime, '2014-11-30 23:59:59.997', 121);

Тем не менее, есть веские причины (как Аарон указывает в своем ответе ) использовать полуоткрытый диапазон вместо BETWEEN(я использую стиль 120 ниже только для разнообразия):

SELECT COUNT(*)
FROM dbo.profile 
WHERE
    [Created] >= CONVERT(datetime, '2014-11-01 00:00:00', 120)
    AND [Created] < CONVERT(datetime, '2014-12-01 00:00:00', 120);

Быть открытым о типах - очень хорошая привычка, особенно когда дело касается дат и времени.


7

Так как BETWEENэто очень проблематично из-за округления различных типов даты / времени и других проблем , и поскольку YYYY-MM-DDэто не безопасный формат без неудобногоT , открытый диапазон, использующий стандартные полные даты ISO без разделителей, является гораздо лучшим подходом:

WHERE Created >= '20141101' AND Created < '20141201';

3

Другой вариант, я рекомендую использовать литералы даты и времени ODBC . Несмотря на их имя, они не требуют подключения через ODBC. Они обходят обычные правила преобразования в SQL Server и всегда интерпретируются как datetime.

SELECT COUNT(*)
FROM dbo.profile 
WHERE [Created] BETWEEN 
    {TS '2014-11-01 00:00:00.000'}
    AND 
    {TS '2014-11-30 23:59:59.997'};


Другие поддерживаемые ODBC datetimeлитералы Dи , Tкак документированы в Books Online. Оба возвращают datetime(не dateили time), но синтаксис все еще компактен и однозначен. Фиксированные форматы для строк:

ODBC строковые форматы

Пример:

SELECT TOP (1)
    D = {D '2014-12-27'},
    T = {T '14:49:23.789'},
    TS = {TS '2014-12-27 14:49:23.789'};

TВариант возвращает время , указанное на текущий день , как сообщает внутреннее использование только {fn getdateODBC()}:

План выполнения


1
Что ж, я бы, наверное, просто сделал, CONVERT(DATE, '20141201')если бы ваша потребность быть явным перевесила все остальное С другой стороны, если базовый столбец имеет тип даты / времени, это на самом деле не обязательно. Вы говорите, WHERE Active = CONVERT(BIT, 1)чтобы избежать WHERE Active = 1интерпретации как INT?
Аарон Бертран

3
@AaronBertrand На самом деле, я знаю, что именно это и делает :) И вот один пример того, почему .
Пол Уайт 9

-1

приведенный ниже код, получить текущий сеанс dateformat, получает ошибку при преобразовании в datetime, затем устанавливает dateformat в ymd и last, но, что не менее важно, снова проверяет преобразование (приведение), и это работает

-- set the dateformat for the current session
-- if you use this date format you get the following error message:
--Msg 242, Level 16, State 3, Line 9
--The conversion of a varchar data type to a datetime data type resulted in an out-of-range value.
set dateformat dmy


-- set the dateformat for the current session
--this one does not give an error message
set dateformat ymd

-- The conversion of a varchar data type 
-- to a datetime data type resulted in an out-of-range value.
select cast('2017-08-13 16:31:31'  as datetime)

-- get the current session date_format
select date_format
from sys.dm_exec_sessions
where session_id = @@spid

-- set the dateformat for the current session
set dateformat ymd

-- this should work
select cast('2017-08-13 16:31:31'  as datetime)



select @@version

Microsoft SQL Server 2016 (SP1) (KB3182545) - 13.0.4001.0 (X64) 28 октября 2016 г. 18:17:30 Авторские права (c) Microsoft Corporation Enterprise Edition: 64-разрядное лицензирование на основе ядра в Windows Server 2012 R2 Datacenter 6.3 (Сборка 9600 :) (гипервизор)


не хотите объяснить, почему голосуете? код здесь работает нормально!
Марчелло Миорелли
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.