Respostas:
Como você está usando o datetimetipo de dados, é necessário entender como o sql server arredonda os dados da data e hora.
╔═══════════╦═════╦═════════════════════════════╦═════════════════════════════╦══════════╦═══════════╗
║ Name ║ sn ║ Minimum value ║ Maximum value ║ Accuracy ║ Storage ║
╠═══════════╬═════╬═════════════════════════════╬═════════════════════════════╬══════════╬═══════════╣
║ datetime ║ dt ║ 1753-01-01 00:00:00.000 ║ 9999-12-31 23:59:59.997 ║ 3.33 ms ║ 8 bytes ║
║ datetime2 ║ dt2 ║ 0001-01-01 00:00:00.0000000 ║ 9999-12-31 23:59:59.9999999 ║ 100ns ║ 6-8 bytes ║
╚═══════════╩═════╩═════════════════════════════╩═════════════════════════════╩══════════╩═══════════╝
Usando a consulta abaixo, você pode ver facilmente o problema de arredondar o servidor sql ao usar o DATETIME
tipo de dados.
select '2015-07-27 00:00:00.000' as Original_startDateTime,
convert(datetime ,'2015-07-27 00:00:00.000') as startDateTime,
'2015-07-27 23:59:59.999' as Original_endDateTime,
convert(datetime ,'2015-07-27 23:59:59.999') as endDateTime,
'2015-07-27 00:00:00.000' as Original_startDateTime2,
convert(datetime2 ,'2015-07-27 00:00:00.000') as startDateTime2, -- default precision is 7
'2015-07-27 23:59:59.999' as Original_endDateTime2,
convert(datetime2 ,'2015-07-27 23:59:59.999') as endDateTime2 -- default precision is 7
DATETIME2
existe desde o SQL Server 2008, então comece a usá-lo em vez de DATETIME
. Para sua situação, você pode usar datetime2
com precisão de 3 casas decimais, por exemplo datetime2(3)
.
Benefícios do uso datetime2
:
datetime
apoiar apenas 3 casas decimais .. e, portanto, você vê a questão de arredondamento já que por padrão datetime
rodadas a mais próxima .003 seconds
com incrementos de .000
, .003
ou .007
segundos.datetime2
é muito mais preciso do que datetime
e datetime2
dá a você o controle DATE
e TIME
o contrário datetime
.Referência:
DateTime2
vs DateTime
.: a. Para a vasta maioria dos casos de uso do mundo real , benefícios de DateTime2
muito <custos. Consulte: stackoverflow.com/questions/1334143/… b. Esse não é o problema raiz aqui. Veja o próximo comentário.
datetime3
70 (vs. 7) dígitos de precisão?). A melhor prática é usar um valor em que a precisão não importa, isto é, <o início do próximo segundo, minuto, hora ou dia vs. <= o final do segundo, minuto, hora ou dia anterior.
Como várias outras pessoas mencionaram nos comentários e outras respostas à sua pergunta, o principal problema 2015-07-27 23:59:59.999
está sendo arredondado 2015-07-28 00:00:00.000
pelo SQL Server. De acordo com a documentação para DATETIME:
Intervalo de tempo - 00:00:00 a 23: 59: 59.997
Observe que o intervalo de tempo nunca pode ser .999
. Mais abaixo na documentação, especifica as regras de arredondamento que o SQL Server usa para o dígito menos significativo.
Observe que o dígito menos significativo pode ter apenas um dos três valores possíveis: "0", "3" ou "7".
Existem várias soluções / soluções alternativas para isso que você pode usar.
-- Option 1
SELECT
*
FROM A
WHERE posted_date >= '2015-07-27 00:00:00.000'
AND posted_date < '2015-07-28 00:00:00.000' --Round up and remove equality
-- Option 2
SELECT
*
FROM A
WHERE posted_date >= '2015-07-27 00:00:00.000'
AND posted_date <= '2015-07-27 23:59:59.997' --Round down and keep equality
-- Option 3
SELECT
*
FROM A
WHERE CAST(posted_date AS DATE) = '2015-07-27' -- Use different data type
-- Option 4
SELECT
*
FROM A
WHERE CONVERT(CHAR(8), DateColumn, 112) = '20150727' -- Cast to string stripping off time
-- Option 5
SELECT
*
FROM A
WHERE posted_date BETWEEN '2015-07-27 00:00:00.000'
AND '2015-07-27 23:59:59.997' --Use between
Das cinco opções que apresentei acima, consideraria as opções 1 e 3 as únicas opções viáveis. Eles transmitem sua intenção claramente e não serão interrompidos se você atualizar os tipos de dados. Se você estiver usando o SQL Server 2008 ou mais recente, acho que a opção 3 deve ser sua abordagem preferida. Isso é especialmente verdadeiro se você puder DATETIMEdeixar de usar o tipo de dados para um DATEtipo de dados para o seuposted_date
coluna.
Em relação à opção 3, uma explicação muito boa sobre alguns problemas pode ser encontrada aqui: A conversão até a data é sargável, mas é uma boa idéia?
Não gosto das opções 2 e 5 porque os .997
segundos fracionários serão apenas mais um número mágico que as pessoas vão querer "consertar". Por mais algumas razões pelas quais BETWEEN
não é amplamente aceito, convém verificar esta postagem .
Não gosto da opção 4 porque a conversão de tipos de dados em uma string para fins de comparação parece suja para mim. Um motivo mais qualitativo para evitá-lo no SQL Server é o impacto na sargabilidade ou seja, você não pode executar uma pesquisa de índice e isso frequentemente resulta em desempenho .
Para obter mais informações sobre o caminho certo e o caminho errado para lidar com consultas de período, confira este post por Aaron Bertrand .
Ao se separar, você poderá manter sua consulta original e ela se comportaria como desejado se você alterar sua posted_date
coluna de uma DATETIMEpara umaDATETIME2(3)
. Isso economizaria espaço de armazenamento no servidor, proporcionaria maior precisão com a mesma precisão, seria mais compatível com os padrões / portátil e permitiria ajustar facilmente a exatidão / precisão se suas necessidades mudarem no futuro. No entanto, essa é apenas uma opção se você estiver usando o SQL Server 2008 ou mais recente.
Como um pouco de trivialidade, a 1/300
precisão de um segundo com DATETIMEparece ser uma retenção do UNIX por esta resposta do StackOverflow . A Sybase, que tem uma herança compartilhada, tem 1/300
uma segunda precisão semelhante em suaDATETIME
TIME
tipos e de dados, mas seus dígitos menos significativos são diferentes em "0", "3" e "6". Na minha opinião, a 1/300
precisão de um segundo e / ou 3,33ms é uma decisão arquitetônica infeliz, já que o bloco de 4 bytes para o tempo no DATETIMEtipo de dados do SQL Server poderia facilmente suportar a precisão de 1ms.
datetime3
com 70 (vs. 7) dígitos de precisão adicionados? A melhor prática é usar um valor em que a precisão não importa, isto é, <o início do próximo segundo, minuto, hora ou dia vs. <= o final do segundo, minuto, hora ou dia anterior.
Conversão implícita
Eu deveria ter postado o tipo de dados date_datetime. No entanto, não importa se o tipo do outro lado é Datetime, Datetime2 ou apenas Time, porque a sequência (Varchar) será implicitamente convertida em Datetime.
Com o posted_date declarado como Datetime2 (ou Time), a posted_date <= '2015-07-27 23:59:59.99999'
cláusula where falha porque embora 23:59:59.99999
seja um valor Datetime2 válido, esse não é um valor Datetime válido:
Conversion failed when converting date and/or time from character string.
Intervalo de tempo para data e hora
O período de data e hora é de 00:00:00 a 23: 59: 59.997. Portanto, 23: 59: 59.999 está fora do intervalo e deve ser arredondado para cima ou para baixo até o valor mais próximo.
Precisão
Além disso, os valores de data e hora são arredondados por incrementos de 0,000, 0,003 ou 0,007 segundos. (ou seja, 000, 003, 007, 010, 013, 017, 020, ..., 997)
Este não é o caso do valor 2015-07-27 23:59:59.999
que está dentro desse intervalo: 2015-07-27 23:59:59.997
e 2015-07-28 0:00:00.000
.
Esse intervalo corresponde às opções anteriores e seguintes mais próximas, ambas terminando com .000, .003 ou .007.
Arredondando para cima ou para baixo ?
Porque é mais perto 2015-07-28 0:00:00.000
(+1 contra -2) do que 2015-07-27 23:59:59.997
, a string é arredondado para cima e se torna esse valor de data e hora: 2015-07-28 0:00:00.000
.
Com um limite superior como 2015-07-27 23:59:59.998
(ou .995, .996, .997, .998), teria sido arredondado para baixo 2015-07-27 23:59:59.997
e sua consulta funcionaria conforme o esperado. No entanto, não teria sido uma solução, mas apenas um valor de sorte.
Tipos de data e hora2 ou hora
Datetime2 e intervalos de tempo são 00:00:00.0000000
através23:59:59.9999999
com uma precisão de 100 ns (o último dígito quando usado com uma precisão de 7 dígitos).
No entanto, um intervalo de data e hora (3) não é semelhante ao intervalo de data e hora:
0:0:00.000
e para23:59:59.997
0:0:00.000000000
para23:59:59.999
Solução
No final, é mais seguro procurar datas abaixo do dia seguinte do que datas abaixo ou iguais ao que você acha que é o último fragmento de hora do dia. Isso ocorre principalmente porque você sabe que o dia seguinte sempre começa às 0: 00: 00.000, mas tipos de dados diferentes podem não ter a mesma hora no final do dia:
Datetime `0:0:00.000` to `23:59:59.997`
Datetime2 `0:0:00.000000000` to `23:59:59.999-999-900`
Time2 `0:0:00.000000000` to `23:59:59.999-999-900`
< 2015-07-28 0:00:00.000
fornecerá resultados precisos e é a melhor opção<= 2015-07-27 23:59:59.xxx
pode retornar valores inesperados quando não estiver arredondado para o que você pensa que deveria ser.Poderíamos pensar que mudar [data_ postada] para Datetime2 e sua precisão mais alta poderiam corrigir esse problema, mas isso não ajudará porque a string ainda é convertida em Datetime. No entanto, se um elenco for adicionado cast(2015-07-27 23:59:59.999' as datetime2)
, isso funcionará bem
Transmitir e converter
A conversão pode converter um valor com até 3 dígitos em Datetime ou com até 9 dígitos em Datetime2 ou Time e arredondá-lo para a precisão correta.
Deve-se notar que Cast of Datetime2 e Time2 podem fornecer resultados diferentes:
select cast('20150101 23:59:59.999999999' as datetime2(7))
é arredondado para cima 2015-05-03 00: 00: 00.0000000 (para valor maior que 999999949)select cast('23:59:59.999999999' as time(7))
=> 23: 59: 59.9999999Ele corrige o problema que a data e hora está tendo com os incrementos de 0, 3 e 7, embora ainda seja sempre melhor procurar datas antes do 1º nano segundo do dia seguinte (sempre 0: 00: 00.000).
MSDN de origem: datetime (Transact-SQL)
Está arredondando
select cast('2015-07-27 23:59:59.999' as datetime)
returns 2015-07-28 00:00:00.000
.998, .997, .996, .995 todos convertidos / arredondados para .997
Deveria usar
select *
from A
where posted_date >= '2015-07-27 00:00:00.000'
and posted_date < '2015-07-28 00:00:00.000'
ou
where cast(posted_date as date) = '2015-07-27'
Veja a precisão neste link
Sempre relatado como .000, .003, .007
select * from A where date(posted_date) = '2015-07-27'
'DATE' is not a recognized built-in function name.
gives you control of DATE and TIME as opposed to datetime.
o que isso significa?