select *
from A
where posted_date >= '2015-07-27 00:00:00.000'
and posted_date <= '2015-07-27 23:59:59.999'
Mas o resultado contém um registro que postou_date hoje: 2015-07-28. Meu servidor de banco de dados não está no meu país. Qual é o problema ?
Como vários outros mencionaram em comentários e outras respostas à sua pergunta, o problema principal
2015-07-27 23:59:59.999
está sendo arredondado2015-07-28 00:00:00.000
pelo SQL Server. De acordo com a documentação para DATETIME:Observe que o intervalo de tempo nunca pode ser
.999
. Mais abaixo na documentação, ele 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 potenciais: "0", "3" ou "7".
Existem várias soluções/soluções alternativas para isso que você pode usar.
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 com clareza 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 deixar de usar o DATETIMEtipo de dados para um DATEtipo de dados para sua
posted_date
coluna.Em relação à opção 3, uma explicação muito boa sobre alguns problemas pode ser encontrada aqui: Cast to date é sargável, mas é uma boa ideia?
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 quaisBETWEEN
não é amplamente adotado, você pode querer conferir este post .Eu não gosto da opção 4 porque converter tipos de dados em uma string para fins de comparação parece sujo para mim. Um motivo mais qualitativo para evitá-lo no SQL Server é que ele afeta a capacidade de sargibilidade , pois você não pode executar uma busca de índice e isso frequentemente resultará em desempenho inferior.
Para obter mais informações sobre a maneira certa e a maneira errada de lidar com consultas de intervalo de datas, confira esta postagem de Aaron Bertrand .
Na parte, você poderia manter sua consulta original e se comportaria conforme desejado se você alterasse sua
posted_date
coluna de a DATETIMEpara aDATETIME2(3)
. Isso economizaria espaço de armazenamento no servidor, proporcionaria maior precisão com a mesma precisão, seria mais compatível/portátil com os padrões e permitiria ajustar facilmente a precisão/exatidão se suas necessidades mudarem no futuro. No entanto, esta é apenas uma opção se você estiver usando o SQL Server 2008 ou mais recente.Como um pouco de curiosidade, a
1/300
precisão de um segundo DATETIMEparece ser um atraso do UNIX por esta resposta do StackOverflow . O Sybase que tem uma herança compartilhada tem uma precisão semelhante1/300
a um segundo em seus tiposDATETIME
deTIME
dados, mas seus dígitos menos significativos são um pouco diferentes em "0", "3" e "6". Na minha opinião, a1/300
precisão de um segundo e/ou 3,33ms é uma decisão arquitetural infeliz, pois o bloco de 4 bytes para o tipo de dados do SQL Server DATETIMEpoderia facilmente suportar uma precisão de 1ms.Como você está usando o datetimetipo de dados, você precisa entender como o SQL Server arredonda os dados de data e hora.
Usando a consulta abaixo, você pode ver facilmente o problema de arredondamento que o sql server faz quando você usa
DATETIME
o tipo de dados.DATETIME2
existe desde o SQL Server 2008, então comece a usá-lo em vez doDATETIME
. Para sua situação, você pode usardatetime2
com precisão de 3 decimais , por exemplodatetime2(3)
.Benefícios do uso
datetime2
:datetime
suporta apenas 3 casas decimais .. e, portanto, você vê o problema de arredondamento, pois por padrãodatetime
arredonda o mais próximo.003 seconds
com incrementos de.000
ou.003
segundos.007
.datetime2
é muito mais preciso do quedatetime
edatetime2
dá a você o controle deDATE
eTIME
em oposição adatetime
.Referência:
Eu suponho que o tipo de dados post_date é Datetime. No entanto, não importa se o tipo do outro lado é Datetime, Datetime2 ou apenas Time porque a string (Varchar) será convertida implicitamente em Datetime.
Com data_postada declarada como Datetime2 (ou Time), a
posted_date <= '2015-07-27 23:59:59.99999'
cláusula where falha porque embora23:59:59.99999
seja um valor Datetime2 válido, este não é um valor Datetime válido:O intervalo de tempo de Datetime é 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 para o valor mais próximo.
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 com o valor
2015-07-27 23:59:59.999
que está dentro deste intervalo:2015-07-27 23:59:59.997
e2015-07-28 0:00:00.000
.Esse intervalo corresponde às opções anteriores e posteriores mais próximas, ambas terminando com .000, .003 ou .007.
Como está mais próximo de
2015-07-28 0:00:00.000
(+1 versus -2) do que2015-07-27 23:59:59.997
, a string é arredondada para cima e se torna este valor Datetime:2015-07-28 0:00:00.000
.Com um limite superior como
2015-07-27 23:59:59.998
(ou 0,995, 0,996, 0,997, 0,998), ele teria sido arredondado para baixo2015-07-27 23:59:59.997
e sua consulta teria funcionado conforme o esperado. No entanto, não teria sido uma solução, mas apenas um valor de sorte.Os intervalos de tempo Datetime2 e Time terminam
00:00:00.0000000
com23:59:59.9999999
uma precisão de 100 ns (o último dígito quando usado com uma precisão de 7 dígitos).No entanto, um intervalo Datetime(3) não é semelhante ao intervalo Datetime:
0:0:00.000
para23:59:59.997
0:0:00.000000000
para23:59:59.999
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 diferentes tipos de dados podem não ter o mesmo horário no final do dia:
< 2015-07-28 0:00:00.000
lhe dará resultados precisos e é a melhor opção<= 2015-07-27 23:59:59.xxx
pode retornar valores inesperados quando não for arredondado para o que você acha que deveria ser.Poderíamos pensar que alterar [posted_date] para Datetime2 e sua maior precisão poderia corrigir esse problema, mas 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á bemO Cast pode converter um valor com até 3 dígitos em Datetime ou com até 9 dígitos em Datetime2 ou Time e arredondar para a precisão correta.
Deve-se notar que Cast of Datetime2 e Time2 pode dar 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 meio que corrige o problema que o datetime está tendo com os incrementos 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
.998, .997, .996, .995 todos lançados / redondos para .997
Deveria usar
ou
Veja a precisão neste link
Sempre relatado como 0,000, 0,003, 0,007