Por que minha data e hora da pesquisa de consulta não coincidem?


20
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_data hoje: 28-07-2015. Meu servidor de banco de dados não está no meu país. Qual é o problema ?

Respostas:


16

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 
╚═══════════╩═════╩═════════════════════════════╩═════════════════════════════╩══════════╩═══════════╝

insira a descrição da imagem aqui

Usando a consulta abaixo, você pode ver facilmente o problema de arredondar o servidor sql ao usar o DATETIMEtipo 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

insira a descrição da imagem aqui Clique para ampliar

DATETIME2existe desde o SQL Server 2008, então comece a usá-lo em vez de DATETIME. Para sua situação, você pode usar datetime2com precisão de 3 casas decimais, por exemplo datetime2(3).

Benefícios do uso datetime2:

  • Suporta até 7 casas decimais para o componente tempo vs datetimeapoiar apenas 3 casas decimais .. e, portanto, você vê a questão de arredondamento já que por padrão datetimerodadas a mais próxima .003 secondscom incrementos de .000, .003ou .007segundos.
  • datetime2é muito mais preciso do que datetimee datetime2dá a você o controle DATEe TIMEo contrário datetime.

Referência:


1
gives you control of DATE and TIME as opposed to datetime.o que isso significa?
Nurettin

Ré. usando DateTime2vs DateTime.: a. Para a vasta maioria dos casos de uso do mundo real , benefícios de DateTime2muito <custos. Consulte: stackoverflow.com/questions/1334143/… b. Esse não é o problema raiz aqui. Veja o próximo comentário.
Tom

O problema raiz aqui (como aposto que a maioria dos desenvolvedores seniores concorda) não é uma precisão insuficiente na data e hora final inclusivas de uma comparação de intervalo de datas e datas, mas sim no uso de um período inclusivo (vs. exclusivo). É como verificar a igualdade com o Pi, sempre existe a possibilidade de um dos #s ter> ou <precisão (ou seja, e se forem adicionados datetime370 (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.
Tom

18

Como várias outras pessoas mencionaram nos comentários e outras respostas à sua pergunta, o principal problema 2015-07-27 23:59:59.999está sendo arredondado 2015-07-28 00:00:00.000pelo 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.

Tabela mostrando regras de arredondamento

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 .997segundos 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 BETWEENnã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_datecoluna 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/300precisã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/300uma segunda precisão semelhante em suaDATETIMETIME tipos e de dados, mas seus dígitos menos significativos são diferentes em "0", "3" e "6". Na minha opinião, a 1/300precisã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.


Sim, mas o principal "problema central" não está usando a opção 1 (por exemplo, usando qualquer valor final de intervalo inclusivo (vs. exclusivo) em que a precisão de tipos de dados passados ​​ou futuros possa afetar os resultados). É como verificar a igualdade com o Pi, é sempre possível que um # tenha> ou <precisão (a menos que ambos sejam pré-arredondados para a menor precisão comum). E se datetime3com 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.
Tom

9

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.99999seja 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.999que está dentro desse intervalo: 2015-07-27 23:59:59.997e 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.997e 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.0000000atravé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:

  • Data hora 0:0:00.000 e para23:59:59.997
  • Datetime2 0:0:00.000000000para23: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.000fornecerá 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.
  • A conversão para Data e o uso da função devem ser evitados, pois limitam o uso de índices

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.9999999

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


6

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


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.