Como remover a parte do tempo de um valor datetime (SQL Server)?

84

Aqui está o que eu uso:

SELECT CAST(FLOOR(CAST(getdate() as FLOAT)) as DATETIME)

Estou pensando que pode haver uma maneira melhor e mais elegante.

Requisitos:

  • Tem que ser o mais rápido possível (quanto menos fundição, melhor).
  • O resultado final deve ser um datetimetipo, não uma string.
Nathan Bedford
fonte

Respostas:

116

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 datetimeou datetime2se necessário.

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

Tenho visto 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 tem sido 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 evitaria a conversão datetimepara float, porque ele não converte corretamente. Você pode se safar fazendo a coisa de remoção de tempo com precisão, mas acho uma má ideia usá-lo porque 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 deveríamos ensinar às pessoas em nosso código ou em nossos exemplos online.

Além disso, não é a maneira mais rápida!

Prova - Teste de Desempenho

Se você deseja realizar alguns testes para ver como os diferentes métodos realmente se acumulam, você precisará deste script de configuração para executar os testes mais adiante:

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-30 minutos para ser executado. Se seu banco de dados for pequeno e definido para 10% de crescimento, demorará mais do que se você dimensionar primeiro o suficiente.

Agora, para o script de teste de desempenho real. Observe que é útil não retornar linhas de volta ao cliente, pois isso é extremamente 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 errantes

Algumas notas sobre isso. Em primeiro lugar, se apenas executar um GROUP BY ou uma comparação, não há necessidade de converter de volta para datetime. Portanto, você pode economizar um pouco de CPU evitando isso, a menos que precise do valor final para fins de exibição. Você pode até mesmo 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 apenas um pouco mais de tempo para serem convertidas datetime, mas a varcharconversão quase dobra? Isso revela a parte da CPU que é dedicada ao cálculo da data nas consultas. Existem partes do uso da CPU que não envolvem cálculo de data e isso parece ser algo próximo a 19875 ms nas consultas acima. Em seguida, a conversão leva algum valor adicional, portanto, se houver duas conversões, esse valor é usado cerca de duas vezes.

Um exame mais revela que comparada a Convert(, 112), a Convert(, 101)consulta tem algum gasto adicional de CPU (já que usa um mais longo varchar?), Porque a segunda conversão de volta para datenão custa tanto quanto a conversão inicial para varchar, mas com Convert(, 112)ela está mais perto do mesmo 20000 Custo base de CPU de ms.

Aqui estão os cálculos do 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
  • round é o tempo de CPU para uma viagem de ida e volta para datetime.

  • single é 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).

  • de base é o cálculo de subtraindo singlea 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 ambas as direções. Parece que essa suposição não é perfeita, mas está próxima porque os valores estão todos próximos a 20000 ms, com apenas uma exceção.

Outra coisa interessante é que o custo base é quase igual ao Convert(date)método único (que deve ser quase custo 0, pois o servidor pode extrair internamente a parte do dia inteiro diretamente dos primeiros quatro bytes do datetimetipo de dados).

Conclusão

Portanto, o que parece é que o varcharmétodo de conversão de direção única leva cerca de 1,8 μs e o DateDiffmétodo de direção única leva cerca de 0,18 μs. Estou baseando isso no tempo de "CPU base" mais conservador em meu teste de 18458 ms no total para 25.920.000 linhas, então 23218 ms / 25920000 = 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 (617 mil linhas = economia de 1 segundo).

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

Notas Adicionais

Quando eu tiver algum tempo, vou mudar 0.50000004para '12:00:00.003'e ver como fica. É convertido para o mesmo datetimevalor 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)

ErikE
fonte
1
+1 Em qual versão do SQL Server você testou isso?
Martin Smith
1
Parece que você tem versos simples e arredondados em sua mesa. Além disso, há alguma diferença de tempo se você usar em charvez de varchar?
Gabe
1
@Gabe obrigado, corrigido. Char parece ser exatamente o mesmo que varchar.
ErikE
No Oracle existe select round(sysdate) from duale definitivamente precisamos disso no Sql Server.
Denis Valeev
3
@Roman Se você estiver trabalhando com o SQL Server 2008 e superior, sim, a conversão para o datetipo de dados é mais rápida, conforme mostrado em meus testes acima.
ErikE
30

O SQL Server 2008 tem um novo tipo de dados de data e isso simplifica o problema para:

SELECT CAST(CAST(GETDATE() AS date) AS datetime)
Marek Grzenkowicz
fonte
1
Eu havia inserido por engano 0218 em vez de 2018 como o ano e o DATEADD(DATEDIFF())método para reduzir a parte do tempo lança uma exceção. Quando datetime2select cast(CAST(convert(datetime2(0), '0218-09-12', 120) AS date) as datetime2)
lanço
18

Itzik Ben-Gan em DATETIME Calculations, Part 1 (SQL Server Magazine, fevereiro de 2007) mostra três métodos de realizar essa conversão (do mais lento para o mais rápido ; a diferença entre o segundo e o terceiro método é pequena):

SELECT CAST(CONVERT(char(8), GETDATE(), 112) AS datetime)

SELECT DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0)

SELECT CAST(CAST(GETDATE() - 0.50000004 AS int) AS datetime)

Sua técnica (casting to float ) é sugerida por um leitor na edição de abril da revista. Segundo ele, tem desempenho comparável ao da segunda técnica apresentada acima.

Marek Grzenkowicz
fonte
1
Na minha opinião, lançar para flutuar não é o melhor. Por favor, veja minha resposta
ErikE
1
@Emtucifor Concordo que o terceiro método é muito obscuro por causa do valor de 0,50000004 , mas é o mais rápido e seus testes confirmam isso . Assim, satisfaz o requisito o mais rápido possível .
Marek Grzenkowicz
1
@Emtucifor Além disso, aqui está o que o artigo que vinculei diz sobre o valor 0,50000004 : Embora essa expressão seja curta (e eficiente, como demonstrarei em breve), devo dizer que me sinto desconfortável com ela . Não tenho certeza se posso identificar exatamente o porquê - talvez porque seja muito técnico e você não consiga ver a lógica relacionada a data e hora nele.
Marek Grzenkowicz
2
Se vamos usar este método, eu preferiria SELECT CAST(CAST(GETDATE() - '12:00:00.003' AS int) AS datetime) , porque significa algo para mim e é muito mais fácil de lembrar.
ErikE
6
Este é agora o mais rápido no SQL 2008: Convert(date, GetDate()).
ErikE
12

Seu CAST- FLOOR- CASTjá parece ser o caminho ideal, pelo menos no MS SQL Server 2005.

Algumas outras soluções que vi têm uma conversão de string, como Select Convert(varchar(11), getdate(),101)nelas, que é mais lenta por um fator de 10.

Michael Stum
fonte
1
Usamos o método sugerido por Michael Stum em um de nossos produtos e funciona perfeitamente.
Chris Roberts
3
Esta não é a maneira ideal, de modo algum. Por favor, veja minha resposta nesta mesma página.
ErikE
4

Tente por favor:

SELECT CONVERT(VARCHAR(10),[YOUR COLUMN NAME],105) [YOURTABLENAME]
Srihari
fonte
1

SQL2005: Eu recomendo cast em vez de dateadd. Por exemplo,

select cast(DATEDIFF(DAY, 0, datetimefield) as datetime)

em média cerca de 10% mais rápido no meu conjunto de dados, do que

select DATEADD(DAY, DATEDIFF(DAY, 0, datetimefield), 0)

(e lançar em smalldatetime foi ainda mais rápido)

user4217069
fonte