Pergunta

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 casting, melhor).
  • O resultado final deve ser um datetime tipo, não uma string.
Foi útil?

Solução

SQL Server 2008 e superior

No SQL Server 2008 e superior, é claro que a maneira mais rápida é Convert(date, @date).Isso pode ser revertido 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 a hora 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 ficaria longe de converter datetime para float, porque não converte corretamente.Você pode conseguir fazer a remoção de tempo com precisão, mas acho que é uma má ideia usá-la porque ela 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, 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 comparam, você 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á de 15 a 30 minutos para ser executada.Se o seu banco de dados for pequeno e definido para um crescimento de 10%, levará mais tempo do que se você dimensionar grande o suficiente primeiro.

Agora, para o script de teste de desempenho real.Observe que é proposital não retornar linhas 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 divagantes

Algumas notas sobre isso.Primeiro de tudo, se estiver apenas realizando um GROUP BY ou uma comparação, não há necessidade de converter novamente 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é 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 de volta para datetime, mas o varchar a conversão quase dobra?Isso revela a parte da CPU dedicada ao cálculo de datas nas consultas.Há partes do uso da CPU que não envolvem cálculo de data, e isso parece ser algo próximo de 19.875 ms nas consultas acima.Em seguida, a conversão exige um valor adicional; portanto, se houver duas conversões, esse valor será gasto aproximadamente duas vezes.

Mais exames revelam que, em comparação com Convert(, 112), o Convert(, 101) consulta tem alguma despesa adicional de CPU (uma vez que usa um tempo mais longo varchar?), porque a segunda conversão de 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 base de CPU de 20.000 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
  • redondo é o tempo de CPU para uma viagem de ida e volta para datetime.

  • solteiro é 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 pressupõe 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 está 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 custo único Convert(date) método (que deve ter custo quase 0, já que o servidor pode extrair internamente a parte inteira do dia diretamente dos primeiros quatro bytes do datetime tipo de dados).

Conclusão

Então o que parece é que a direção única varchar o método de conversão leva cerca de 1,8 μs e o método de direção única DateDiff 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/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 tendo em conta esta pequena melhoria absoluta, na minha opinião, o DateAdd o método vence porque é a melhor combinação de desempenho e clareza.A resposta que requer um “número mágico” de 0.50000004 algum dia vai morder alguém (cinco zeros ou seis???), além de ser mais difícil de entender.

Notas Adicionais

Quando eu tiver algum tempo vou mudar 0.50000004 para '12:00:00.003' e veja como funciona.É 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:Pacote de serviços 2)

Outras dicas

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

SELECT CAST(CAST(GETDATE() AS date) AS datetime)

Itzik Ben-Gan em Cálculos DATETIME, Parte 1 (SQL Server Magazine, fevereiro de 2007) mostra três métodos para 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 (lançar para flutuador) é sugerido por um leitor na edição de abril da revista.Segundo ele, tem desempenho comparável ao da segunda técnica apresentada acima.

Seu CAST-FLOOR-CAST já 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) neles, o que é mais lento por um fator de 10.

Tente por favor:

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

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

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

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

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

(e a conversão para smalldatetime foi ainda mais rápida)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top