Crie um site como este com o WordPress.com
Comece agora

Para onde foi o 23:59:59.999?!

Em fórum de SQL Server o autor do tópico Update em Campo DATETIME questiona “Poderiam me dizer/explicar porque no SQL não está aceitando fazer um update de um campo datetime para ‘2019-01-01 23:59:59:999’”?

Como resposta, foi postada a seguinte informação

p025_x997

Na documentação do tipo de dados datetime consta que o horário pode ir das 00:00:00 às 23:59:59.997 e também que o valor de horário é arredondado para incrementos de .000, .003 e .007 mas não explica o motivo desse comportamento peculiar. Porque não é possível armazenar de 000 a 999 como milésimos de segundos em colunas com tipo de dados datetime?

Antes de continuarmos, rode o seguinte código SQL:

-- código #1
set dateformat dmy;

SELECT cast ('31/12/2019 23:59:59.990' as datetime) as [23:59:59.990],
       cast ('31/12/2019 23:59:59.991' as datetime) as [23:59:59.991];
SELECT cast ('31/12/2019 23:59:59.992' as datetime) as [23:59:59.992],
       cast ('31/12/2019 23:59:59.993' as datetime) as [23:59:59.993],
       cast ('31/12/2019 23:59:59.994' as datetime) as [23:59:59.994];
SELECT cast ('31/12/2019 23:59:59.995' as datetime) as [23:59:59.995],
       cast ('31/12/2019 23:59:59.996' as datetime) as [23:59:59.996],
       cast ('31/12/2019 23:59:59.997' as datetime) as [23:59:59.997],
       cast ('31/12/2019 23:59:59.998' as datetime) as [23:59:59.998];
SELECT cast ('31/12/2019 23:59:59.999' as datetime) as [23:59:59.999];

E então, quais são as suas observações sobre o resultado do código SQL acima? Se você quiser pode postá-las ao final deste artigo, na parte de comentários.

Armazenamento. No banco de dados, o tipo de dados datetime ocupa 8 bytes, sendo que os 4 bytes iniciais armazenam a data e os 4 bytes finais armazenam o horário. Cada unidade de horário armazenada em coluna do tipo datetime é denominada de tick e é equivalente a 1 / 300 de segundo:

p025_quadro_tick

Na prática, 1 / 300 é algo próximo de 3 milissegundos.

Comparação de data e horário. Por causa dessa particularidade do tipo de dados datetime, quando é necessário comparar se coluna do banco de dados está em determinado período, às vezes nos deparamos com algo parecido a

-- código #2
SELECT V.Id_Venda, V.Data_Venda, V.Total_Venda
  from tbVenda as V
  where V.Data_Venda between '20180201 00:00:00.000' 
                             and '20180228 23:59:59.997';

para casos em que a coluna V.Data_Venda foi declarada como datetime. A princípio parece correto, pois o maior valor que pode ser armazenado nessa coluna é “23:59:59.997”. Entretanto, é necessário ficar atento que se hoje a coluna Data_Venda está como datetime, posteriormente ela pode ser alterada para datetime2. Nesse caso, o código SQL acima fica errado pois se houver alguma venda posterior no horário das “23:59:59.9980000” ela será rejeitada.

Algo também não recomendado é subtrair 3 milissegundos de uma expressão do tipo datetime para obter o “.997”. Por exemplo:

-- código #3
declare @Datainicial datetime, @DataFinal datetime;
set @DataInicial= convert (datetime, '1/2/2018', 103);
set @DataFinal= dateadd (ms, -3, dateadd (month, +1, @DataInicial));

SELECT V.Id_Venda, V.Data_Venda, V.Total_Venda
  from tbVenda as V
  where V.Data_Venda between @DataInicial and @DataFinal;

Já programei utilizando as duas formas e somente com o tempo aprendi que não era uma boa prática. Se utilizar “23:59:59.997” é uma prática ruim, então qual é a boa prática, nesse caso? Uma solução é substituir

  where coluna_data between A and B

por

  where coluna_data >= A
        and coluna_data < C

onde C é o dia seguinte a B e com o horário zerado. Observe que a comparação com o limite final passou a ser “menor do que”.

No caso do código #3, uma forma de reescrevê-lo é a seguinte:

-- código #4
declare @Data_Inicio datetime, @Data_Final datetime;
set @Data_Inicio= convert (datetime, '1/2/2019', 103);
set @Data_Final= convert (datetime, '1/3/2019', 103);

SELECT V.Id_Venda, V.Data_Venda, V.Total_Venda
  from tbVenda as V
  where V.Data_Venda >= @Data_Inicio 
        and V.Data_Venda < @Data_Final;

Dessa forma o código SQL fica robusto, sem utilizar particularidades da implementação do tipo de dados. Caso o tipo de dados da coluna V.Data_Venda seja alterado posteriormente, basta alterar as declarações @Data_Inicio e @Data_Final para o mesmo tipo de dados da coluna V.Data_Venda. E, é claro, o tipo de dados na função CONVERT().

Para quem não testou o código #1, eis aqui o resultado dele:

p025_c0 borda

Lembra-se de que este post iniciou com alguém perguntando sobre 23:59:59.999? Na figura acima você verifica o que estava a ocorrer.

Publicidade

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.

%d blogueiros gostam disto: