A conversão de um tipo de dados varchar para um tipo de dados datetime resultou em um valor fora do intervalo

8

Estou tentando executar uma consulta simples para obter todas as linhas criadas em novembro:

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

O SMSS retorna:

A conversão de um tipo de dados varchar para um tipo de dados datetime resultou em um valor fora do intervalo.

Não entendo por que os dados estão sendo convertidos de varchar para datetime quando 'Created' é definido como datetime:

Colunas Preciso informar ao servidor que 'Criado' é datetime? Caso contrário, por que estou recebendo essa mensagem varchar?

Editar: o valor no banco de dados era YYYY-MM-DD. A resposta do @SqlZim abaixo diz que preciso usar convert () para informar ao sql qual o formato da data no db - e substituir o caractere de espaço pela letra 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');`
jedluddley
fonte

Respostas:

8

Eu verifiquei seu perfil e vi que você está no Reino Unido. Se o seu servidor sql estiver configurado para usar o dateformat dmy, isso explica o seu problema. Sem usar o 'T' em vez do espaço na cadeia de data e hora, o Sql Server não o reconhecerá como formato ISO8601.

Tente o seguinte:

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');

Consultar usando datas e / ou datas pode ser complicado, para garantir que você esteja obtendo o que está procurando, recomendo a leitura:

editar: para esclarecer o valor fora do intervalo na sua mensagem de erro, seria interpretar o mês como 30 e o dia como 11.

SqlZim
fonte
8

Não entendo por que os dados estão sendo convertidos de varchar para datetime quando 'Created' está definido como datetime

Os literais que você está fornecendo para comparação com a Createdcoluna são cadeias de caracteres. Para comparar esses literais com a datetimecoluna, o SQL Server tenta converter as seqüências de caracteres em datetimetipos, de acordo com as regras de precedência do tipo de dados . Sem informações explícitas sobre o formato das cadeias, o SQL Server segue suas regras complicadas para interpretar cadeias de caracteres como data / hora.

Na minha opinião, a maneira mais clara de evitar esses tipos de problemas é ser explícito sobre os tipos. O SQL Server fornece as CAST and CONVERTfunções para esse fim. Ao trabalhar com seqüências de caracteres e tipos de data / hora, CONVERTé preferível, pois fornece um parâmetro de estilo para definir explicitamente o formato da sequência.

A pergunta usa cadeias de caracteres no formato canônico ODBC (com milissegundos) (estilo 121). Ser explícito sobre o tipo de dados e o estilo da sequência resulta no seguinte:

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);

Dito isto, há boas razões (como Aaron aponta em sua resposta ) para usar um intervalo semi-aberto em vez de BETWEEN(eu uso o estilo 120 abaixo apenas para variar ):

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);

Ser explícito sobre os tipos é um hábito muito bom, especialmente quando se lida com datas e horários.

Paul White 9
fonte
3

Outra alternativa, eu recomendo usar literais de data e hora ODBC . Apesar do nome, eles não exigem que você se conecte via ODBC. Eles ignoram as regras de conversão usuais no SQL Server e sempre são interpretadas como um 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'};


Os outros datetimeliterais ODBC suportados são De Tconforme documentados aqui nos Manuais Online. Ambos retornam datetime(não dateou time), mas a sintaxe ainda é compacta e inequívoca. Os formatos fixos para as strings são:

Formatos de sequência ODBC

Exemplo:

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

A Tvariante retorna a hora especificada no dia atual , conforme relatado pelo somente para uso interno {fn getdateODBC()}:

Plano de execução

Michael B
fonte
11
Bem, eu provavelmente faria CONVERT(DATE, '20141201')se sua necessidade de ser explícita substituísse todo o resto. Por outro lado, se a coluna subjacente for do tipo data / hora, isso não é realmente necessário. Você diz WHERE Active = CONVERT(BIT, 1)para evitar WHERE Active = 1ser interpretado como um INT?
Aaron Bertrand
3
@AaronBertrand Na verdade, sou conhecido por fazer exatamente isso :) E aqui está um exemplo do porquê .
Paul White 9
-1

o código abaixo, obtém a sessão atual dateformat, obtém um erro ao converter para datetime, depois define o formato da data como ymd e por último, mas não menos importante, teste a conversão (conversão) novamente e funciona

-- 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 de outubro de 2016 18:17:30 Direitos autorais (c) Microsoft Corporation Enterprise Edition: Licenciamento baseado em núcleo (64 bits) no Windows Server 2012 R2 Datacenter 6.3 (Compilação 9600:) (Hypervisor)

Marcello Miorelli
fonte
cuidado explicar o voto negativo ?, o código está funcionando bem aqui!
Marcello Miorelli