Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Como remover a parte de hora de um valor de data e hora (SQL Server)?


SQL Server 2008 e superior

No SQL Server 2008 e superior, é claro que a maneira mais rápida é Convert(date, @date) . Isso pode ser convertido de volta para um datetime ou datetime2 se necessário.

O que é realmente melhor no SQL Server 2005 e versões anteriores?

Já vi afirmações inconsistentes sobre o que é mais rápido para truncar o tempo de uma data no SQL Server, e algumas pessoas até disseram que fizeram testes, mas minha experiência foi diferente. Então, vamos fazer alguns testes mais rigorosos e deixar que todos tenham o script para que, se eu cometer algum erro, as pessoas possam me corrigir.

As conversões flutuantes não são precisas

Primeiro, eu ficaria longe de converter datetime para float , porque não converte corretamente. Você pode se safar fazendo a remoção de tempo com precisão, mas acho que é uma má ideia usá-lo porque ele comunica implicitamente aos desenvolvedores que esta é uma operação segura e não é . Dê uma olhada:
declare @d datetime;
set @d = '2010-09-12 00:00:00.003';
select Convert(datetime, Convert(float, @d));
-- result: 2010-09-12 00:00:00.000 -- oops

Isso não é algo que devemos ensinar às pessoas em nosso código ou em nossos exemplos online.

Além disso, nem é o caminho mais rápido!

Prova – Teste de desempenho

Se você quiser realizar alguns testes para ver como os diferentes métodos realmente se acumulam, precisará deste script de configuração para executar os testes mais abaixo:
create table AllDay (Tm datetime NOT NULL CONSTRAINT PK_AllDay PRIMARY KEY CLUSTERED);
declare @d datetime;
set @d = DateDiff(Day, 0, GetDate());
insert AllDay select @d;
while @@ROWCOUNT != 0
   insert AllDay
   select * from (
      select Tm =
         DateAdd(ms, (select Max(DateDiff(ms, @d, Tm)) from AllDay) + 3, Tm)
      from AllDay
   ) X
   where Tm < DateAdd(Day, 1, @d);
exec sp_spaceused AllDay;  -- 25,920,000 rows

Observe que isso cria uma tabela de 427,57 MB em seu banco de dados e levará cerca de 15 a 30 minutos para ser executado. Se seu banco de dados for pequeno e estiver configurado para um crescimento de 10%, levará mais tempo do que se você fosse grande o suficiente primeiro.

Agora, para o script de teste de desempenho real. Observe que é proposital não retornar linhas de volta ao cliente, pois isso é muito caro em 26 milhões de linhas e ocultaria as diferenças de desempenho entre os métodos.

Resultados de desempenho
set statistics time on;
-- (All queries are the same on io: logical reads 54712)
GO
declare
    @dd date,
    @d datetime,
    @di int,
    @df float,
    @dv varchar(10);

-- Round trip back to datetime
select @d = CONVERT(date, Tm) from AllDay; -- CPU time = 21234 ms,  elapsed time = 22301 ms.
select @d = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 23031 ms, elapsed = 24091 ms.
select @d = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23782 ms, elapsed = 24818 ms.
select @d = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 36891 ms, elapsed = 38414 ms.
select @d = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 102984 ms, elapsed = 109897 ms.
select @d = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 103390 ms,  elapsed = 108236 ms.
select @d = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 123375 ms, elapsed = 135179 ms.

-- Only to another type but not back
select @dd = Tm from AllDay; -- CPU time = 19891 ms,  elapsed time = 20937 ms.
select @di = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 21453 ms, elapsed = 23079 ms.
select @di = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23218 ms, elapsed = 24700 ms
select @df = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 29312 ms, elapsed = 31101 ms.
select @dv = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 64016 ms, elapsed = 67815 ms.
select @dv = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 64297 ms,  elapsed = 67987 ms.
select @dv = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 65609 ms, elapsed = 68173 ms.
GO
set statistics time off;

Algumas análises desconexas

Algumas notas sobre isso. Em primeiro lugar, se estiver apenas realizando um GROUP BY ou uma comparação, não há necessidade de converter novamente para datetime . Assim, você pode economizar um pouco de CPU evitando isso, a menos que precise do valor final para fins de exibição. Você pode até GROUP BY o valor não convertido e colocar a conversão apenas na cláusula SELECT:
select Convert(datetime, DateDiff(dd, 0, Tm))
from (select '2010-09-12 00:00:00.003') X (Tm)
group by DateDiff(dd, 0, Tm)

Além disso, veja como as conversões numéricas levam um pouco mais de tempo para serem convertidas de volta para datetime , mas o varchar conversão quase dobra? Isso revela a parte da CPU que é dedicada ao cálculo de data nas consultas. Há partes do uso da CPU que não envolvem cálculo de data, e isso parece ser algo próximo a 19.875 ms nas consultas acima. Em seguida, a conversão leva algum valor adicional, portanto, se houver duas conversões, esse valor será usado aproximadamente duas vezes.

Mais exames revelam que, em comparação com Convert(, 112) , o Convert(, 101) consulta tem alguma despesa de CPU adicional (uma vez que usa um varchar mais longo ?), porque a segunda conversão volta para date não custa tanto quanto a conversão inicial para varchar , mas com Convert(, 112) está mais próximo do mesmo custo básico de CPU de 20.000 ms.

Aqui estão os cálculos sobre o tempo de CPU que usei para a análise acima:
     method   round  single   base
-----------  ------  ------  -----
       date   21324   19891  18458
        int   23031   21453  19875
   datediff   23782   23218  22654
      float   36891   29312  21733
varchar-112  102984   64016  25048
varchar-101  123375   65609   7843

  • rodada é o tempo de CPU para uma viagem de ida e volta para datetime .

  • único é o tempo de CPU para uma única conversão para o tipo de dados alternativo (aquele que tem o efeito colateral de remover a parte do tempo).

  • base é o cálculo da subtração de single a diferença entre as duas invocações:single - (round - single) . É um valor aproximado que assume a conversão de e para esse tipo de dados e datetime é aproximadamente o mesmo em qualquer direção. Parece que essa suposição não é perfeita, mas é próxima porque os valores estão todos próximos de 20.000 ms, com apenas uma exceção.

Mais uma coisa interessante é que o custo base é quase igual ao único Convert(date) (que tem que ser quase 0 de custo, pois o servidor pode extrair internamente a parte do dia inteiro dos primeiros quatro bytes do datetime tipo de dados).

Conclusão

Então, o que parece é que o varchar de direção única o método de conversão leva cerca de 1,8 μs e o DateDiff de direção única método leva cerca de 0,18 μs. Estou baseando isso no tempo de "CPU base" mais conservador em meus testes de 18.458 ms no total para 25.920.000 linhas, portanto, 23.218 ms / 25920.000 =0,18 μs. A aparente melhoria de 10x parece muito, mas é francamente muito pequena até que você esteja lidando com centenas de milhares de linhas (617k linhas =1 segundo de economia).

Mesmo com essa pequena melhoria absoluta, na minha opinião, o DateAdd método ganha porque é a melhor combinação de desempenho e clareza. A resposta que requer um "número mágico" de 0.50000004 vai morder alguém algum dia (cinco zeros ou seis???), além de ser mais difícil de entender.

Observações adicionais

Quando eu tiver algum tempo vou mudar 0.50000004 para '12:00:00.003' e veja como fica. Ele é convertido para o mesmo datetime valor e acho muito mais fácil de lembrar.

Para os interessados, os testes acima foram executados em um servidor onde @@Version retorna o seguinte:

Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (Intel X86) 9 de julho de 2008 14:43:34 Copyright (c) 1988-2008 Microsoft Corporation Standard Edition no Windows NT 5.2 (Build 3790:Service Pack 2)