Melhor abordagem para remover parte do horário no datetime no SQL Server


514

Qual método fornece o melhor desempenho ao remover a parte da hora de um campo de data e hora no SQL Server?

a) select DATEADD(dd, DATEDIFF(dd, 0, getdate()), 0)

ou

b) select cast(convert(char(11), getdate(), 113) as datetime)

O segundo método envia mais alguns bytes de qualquer maneira, mas isso pode não ser tão importante quanto a velocidade da conversão.

Ambos também parecem ser muito rápidos, mas pode haver uma diferença de velocidade ao lidar com centenas de milhares ou mais de linhas?

Além disso, é possível que haja métodos ainda melhores para se livrar da parte do tempo de um datetime no SQL?


1
Eu tentei isso em um milhão de registros em uma das minhas tabelas de produção e não consegui obter uma leitura precisa do desempenho de qualquer maneira. Ambos os métodos retornaram exatamente a mesma quantidade de dados.
22615 Stephen Perelson

9
Em 18.000.000 de linhas, foi o que eu encontrei (SQL Server 2008): O método b é cerca de 24% mais lento que o método a. CAST (PISO (CAST (getdate () AS FLOAT)) AS DATETIME) é 3,5% mais lento que o método a. O método a parece ser um vencedor em relação ao desempenho. Obrigado a todos pelas ótimas respostas.
21915 Stephen Perelson

46
Por que diabos o SQL não tem uma função interna para fazer isso de qualquer maneira? !!
22715 Gary McGill

10
O novo tipo de dados DATE do SQL 2008 tratará disso.
Philip Kelley

Respostas:


558

Estritamente, o método aé o menos intensivo em recursos:

a) select DATEADD(dd, DATEDIFF(dd, 0, getdate()), 0)

Comprovou menos uso de CPU para a mesma duração total de um milhão de linhas por alguém com tempo demais em suas mãos: A maneira mais eficiente no SQL Server para obter data a partir de data e hora?

Eu vi um teste semelhante em outro lugar com resultados semelhantes também.

Prefiro o DATEADD / DATEDIFF porque:

Editar, outubro de 2011

Para o SQL Server 2008+, você pode CAST para dateie CAST(getdate() AS date). Ou apenas use o datetipo de dados para não ter tempo de remover.

Editar, janeiro de 2012

Um exemplo prático de como isso é flexível: Precisa calcular por hora ou data arredondadas no sql server

Editar, maio de 2012

Não use isso nas cláusulas WHERE e similares sem pensar: adicionar uma função ou CAST a uma coluna invalida o uso do índice. Veja o número 2 aqui: http://www.simple-talk.com/sql/t-sql-programming/ten-common-sql-programming-mistakes/

Agora, isso tem um exemplo de versões posteriores do otimizador do SQL Server que gerenciam o CAST até a data corretamente, mas geralmente será uma má idéia ...

Editar, set 2018, para datetime2

DECLARE @datetime2value datetime2 = '02180912 11:45' --this is deliberately within datetime2, year 0218
DECLARE @datetime2epoch datetime2 = '19000101'

select DATEADD(dd, DATEDIFF(dd, @datetime2epoch, @datetime2value), @datetime2epoch)

3
@ David Sopko para a edição de outubro de 2011, o código seria: selecione o elenco (GETDATE () como data) #
Choco Smith

1
Para versões mais recentes do SQL, usar data em vez de data e hora evita a necessidade de lidar com horas. Utilize o seguinte exemplo: Declare data notime = getdate (), withtime datetime = getdate () select @ notime, @ withtime
ozkary

1
o elenco como data é ótimo se você apenas precisar da data. No entanto, muitas vezes você precisa da data atual à meia-noite para poder fazer alguma manipulação adicional da data. o DATEhorário dos dados é irritantemente restritivo no que permitirá que você faça com relação a dados como data, data e hora e interação com outros tipos de dados de data / hora. Para esses casos, a DATEADD()abordagem reina.
Xedni

Isso não funciona para todas as datas. Eu havia entrado por engano em 0218vez de 2018como o ano e a DATEDIFFparte de sua declaração gera uma exceção The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range datetime valueTente:select DATEDIFF(dd, 0, convert(datetime2(0), '0218-09-12', 120))
Bernhard Döbler 12/12/18

1
@ BernhardDöbler em julho de 2009, quando eu respondi, "0218" teria sido uma data válida, então você não teria chegado tão longe. Além disso, o "0" não se converte em 19000101 para datetime2. Tente esta seleçãoSELECT DATEDIFF(dd, '19000101', convert(datetime2(0), '0218-09-12', 120))
gbn 13/09/18

69

No SQL Server 2008, você pode usar:

CONVERT(DATE, getdate(), 101)

13
O terceiro argumento não tem absolutamente nenhuma influência no resultado ao converter de a datetimepara a date, e assim sua solução se resume a apenas CONVERT(DATE,getdate()), o que já foi sugerido mais de uma vez.
precisa

Basta usar CAST(GETDATE() AS DATE)ou estritamente ANSI CAST(CURRENT_TIMESTAMP AS DATE)que eu acho que é inútil. Fique com o primeiro.
Ivanzinho 5/09

52

Claro que esse é um tópico antigo, mas para torná-lo completo.

No SQL 2008, você pode usar o tipo de dados DATE para fazer o seguinte:

SELECT CONVERT(DATE,GETDATE())

21
SELECT CAST(FLOOR(CAST(getdate() AS FLOAT)) AS DATETIME)

... não é uma boa solução, de acordo com os comentários abaixo.

Eu excluiria esta resposta, mas a deixarei aqui como um contra-exemplo, pois acho que a explicação dos comentaristas sobre por que não é uma boa ideia ainda é útil.


Veja a resposta da GBN, muitos investigaram isso. DATETIMEs NÃO são armazenados como carros alegóricos e, portanto, usar DATEADD / DATEDIFF evita a manipulação matemática necessária para CAST entre tipos.
21830 MatBailie

Posso aceitar que você queira evitar uma conversão de DATETIME para FLOAT pelo motivo descrito, mas nesse caso a conversão implícita de zero na opção OPs (a) também é um problema? Hmmm ... Suponho que, nesse caso, não seja um FLOAT e que o servidor provavelmente seja inteligente o suficiente para descartar as informações de horário. OK, reconheço :-)
Gary McGill

O 0 é de fato uma conversão implícita de um tipo numérico (INT, eu acho) para um DATETIME. Por ser uma expressão constante, no entanto, o otimizador pode fazer isso em tempo de compilação para Procedimentos armazenados e só precisa fazê-lo uma vez para executar SQL dinamicamente. Em resumo, há uma sobrecarga única para isso, a consulta baseada em FLOAT tem a sobrecarga equivalente para cada linha.
21815 MatBailie

Lançar para flutuar é terrivelmente impreciso. Esta resposta deve ser excluída. Ninguém deve usar esse código.
usr

3
Sem mencionar que não é seguro converter para flutuar e voltar para a data e hora - a flutuação não tem precisão suficiente. Portanto, acho que não pode ser recomendado. Veja este post para mais detalhes .
ErikE

17

No SQL Server 2008, há um tipo de dados DATE (também um tipo de dados TIME).

CAST(GetDate() as DATE)

ou

declare @Dt as DATE = GetDate()

Isto é o que eu usei e funcionou bem. Parece a resposta mais simples. Alguma desvantagem sobre o uso em conjunto com CONVERT?
Joelmdev 29/01

1
CAST e CONVERT são equivalentes em função. A diferença é que o CAST faz parte do padrão ANSI, enquanto o CONVERT é específico para o T-SQL. Portanto, use o CAST sempre que possível.
troy

@troy eu usar CAST porque eu pode salvar 3 letras de digitação e sintaxe é mais clara do que converter-se, a parte ANSI padrão é inútil
Ivanzinho

8

Aqui está mais uma resposta, de outra pergunta duplicada:

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

Esse método de número mágico executa um pouco mais rápido que o método DATEADD. (Parece ~ 10%)

O tempo de CPU em várias rodadas de um milhão de registros:

DATEADD   MAGIC FLOAT
500       453
453       360
375       375
406       360

Mas observe que esses números são possivelmente irrelevantes porque já são MUITO rápidos. A menos que eu tivesse conjuntos de registros de 100.000 ou mais, eu não conseguia nem obter o Tempo de CPU para ler acima de zero.

Considerando o fato de que o DateAdd se destina a essa finalidade e é mais robusto, eu diria que use o DateAdd.


1
Isso é horrível. Eu nunca colocaria meus dados em risco assim. Quem sabe se isso está correto para todas as datas, não apenas para as que você testou.
usr

@usr Oh, está correto, é apenas um número mágico e não deve ser usado por esse motivo. Se você quiser verificar se está correto, basta preencher todas as datas possíveis por um dia em uma tabela e verificar os resultados! Consulte também este post para obter mais informações.
ErikE

@ErikE bom ponto. Sua resposta fornece a possibilidade de usar o '12:00:00.003'que eu acho muito melhor, no entanto.
usr

6
SELECT CAST(CAST(GETDATE() AS DATE) AS DATETIME)

4
Uma opção válida, sim. Sugerido mais de uma vez neste tópico, no entanto.
precisa

Honestamente, esta solução é a mais fácil de ler. Meu goto
BelgoCanadian

5

Eu realmente gosto:

[date] = CONVERT(VARCHAR(10), GETDATE(), 120)

O 120código de formato coagirá a data ao padrão ISO 8601:

'YYYY-MM-DD' or '2017-01-09'

Super fácil de usar em dplyr ( R) e pandas ( Python)!


3

Cuidado!

O método a) eb) nem sempre tem a mesma saída!

select DATEADD(dd, DATEDIFF(dd, 0, '2013-12-31 23:59:59.999'), 0)

Resultado: 2014-01-01 00:00:00.000

select cast(convert(char(11), '2013-12-31 23:59:59.999', 113) as datetime)

Resultado: 2013-12-31 00:00:00.000

(Testado no MS SQL Server 2005 e 2008 R2)

EDIT: De acordo com o comentário de Adam, isso não pode acontecer se você ler o valor da data da tabela, mas pode acontecer se você fornecer o valor da data como um literal (exemplo: como um parâmetro de um procedimento armazenado chamado via ADO.NET).


1
.999 não pode ser armazenado no SQL Server em uma DATETIMEcoluna. O mais alto disponível é .997 De: msdn.microsoft.com/en-us/library/ms187819.aspx, você verá que os valores são arredondados para ter o milésimo lugar em 0, 3 ou 7. O OP não verá o valor do seu teste em suas tabelas.
Adam Wenger

Você está certo. Não pretendia postar isso como resposta à pergunta do OP, mas como um comentário para os outros verem, mas eu só tinha 11 pontos de reputação e 15 são necessários para comentar.
broslav

No seu primeiro trecho, a constante da string é implicitamente convertida em um datetime, no segundo ela permanece uma string (e o 113 é apenas ignorado).
Andriy M

2

Tire o tempo das inserções / atualizações em primeiro lugar. Quanto à conversão on-the-fly, nada pode superar uma função definida pelo usuário em termos de manutenção:

select date_only(dd)

A implementação de date_onlypode ser o que você quiser - agora é abstraída e o código de chamada é muito mais limpo.


Certa vez, inventei um gatilho para limpar os tempos das colunas selecionadas. Se os dados não podem estar ruins, você não precisa limpá-los.
Philip Kelley

2
Existe uma desvantagem na abordagem da UDF, que não é SARGable. Se usado nas cláusulas JOINs ou WHERE, o otimizador não pode usar INDEXes para melhorar o desempenho. O uso da abordagem DATEADD / DATEDIFF, no entanto, é SARGable e poderá se beneficiar dos índices. (Aparentemente, o método FLOAT é sargable também)
MatBailie

1
@ MatBailie Eu imploro para diferir! Definitivamente, os UDFs não são SARGable, mas nem o Dateadd nem o Convert to float! WHERE DateAdd(DateDiff(Column)) = @DateValuenão usará um índice. Por outro lado, WHERE Column >= dbo.UDF(@DateValue) AND Column < dbo.UDF(@DateValue + 1) é SARGable. Portanto, tenha cuidado com o que você coloca.
ErikE

2

Veja esta pergunta:
Como posso truncar uma data e hora no SQL Server?

Faça o que fizer, não use o método string . Essa é a pior maneira de fazer isso.


Obrigado, achei que isso deveria ter sido solicitado antes. Por mais estranho que meus experimentos tenham apontado que o método float é realmente mais lento em 3,5% no SQL Server 2008 do que o método dateadd (dd, 0, datediff (dd, 0, getDate ())). Eu executei meus testes várias vezes para cada método e o servidor de banco de dados não foi utilizado para mais nada no momento.
21915 Stephen Perelson

Digamos que sou cético em relação aos benchmarks feitos por qualquer pessoa que não tenha demonstrado que eles fazem benchmarks regularmente e de uma maneira muito científica como parte de seu trabalho. Até o benchmark de Thomas no link por gbn tem alguns problemas óbvios quando você olha para ele. Isso não torna errado necessariamente, apenas não definitivo. O método cast / floor / cast foi o caminho mais rápido aceito por um período muito longo, e eu suspeito que isso já foi indiscutivelmente verdadeiro. Dito isto, estou começando a reconsiderar; especialmente para o sql server 2008, onde é completamente desnecessário.
Joel Coehoorn

1
O método string é extremamente fácil de usar, ler e lembrar. Esses são fatores muito importantes que eu acho que você está subestimando!
Ben

1
@ JoelCoehoorn, o estilo de conversão 121 é chamado "ODBC Canonical". Não varia com agrupamento ou localidade. O truque das cordas também é fácil de generalizar para ano, ano + mês, dia, hora ou minuto.
Ben

1
@ Ben O truque de strings ensina os desenvolvedores a usar conversões de strings. Eles funcionam , mas a matemática da data é muito, muito superior, por muitas razões, a menor delas é a velocidade - mas ainda mais, para o que aprender a trabalhar com as datas como números confere ao desenvolvedor e suas habilidades mentais para seja fluido com a manipulação de números no código.
ErikE

2

Já respondi, mas vou jogar isso lá fora também ... isso supostamente também se forma bem, mas funciona jogando fora o decimal (que armazena tempo) do flutuador e retornando apenas uma parte inteira (que é a data)

 CAST(
FLOOR( CAST( GETDATE() AS FLOAT ) )
AS DATETIME
)

segunda vez que encontrei esta solução ... peguei esse código


1
Converter para flutuar não é seguro .
ErikE

2
CAST(round(cast(getdate()as real),0,1) AS datetime)

Este método não usa a função string. Dateé basicamente um tipo de dados real com dígitos antes do decimal e fração do dia.

acho que isso será mais rápido do que muito.


1
Moldar como flutuador não é seguro .
ErikE


2

selecione CONVERT (char (10), GetDate (), 126)


Qual é a principal diferença da sua sugestão do método mencionado na resposta de @ broslav ou do método que foi determinado como o mais lento em esta discussão (o mesmo destino como na resposta aceita)?
Andriy M

1

Eu acho que você quer dizer cast(floor(cast(getdate()as float))as datetime)

real é de apenas 32 bits e pode perder algumas informações

Isso é mais rápido cast(cast(getdate()+x-0.5 as int)as datetime)

... embora apenas 10% mais rápido(about 0.49 microseconds CPU vs. 0.58)

Isso foi recomendado e leva o mesmo tempo no meu teste agora: DATEADD(dd, DATEDIFF(dd, 0, getdate()), 0)

No SQL 2008, a função SQL CLR é cerca de 5 vezes mais rápida do que o uso de uma função SQL, a 1,35 microssegundos versus 6,5 microsecções, indicando uma sobrecarga de chamada de função muito mais baixa para uma função CLR do SQL em comparação com uma simples UDF do SQL.

No SQL 2005, a função SQL CLR é 16 vezes mais rápida, por meu teste, em comparação com essa função lenta:

create function dateonly (  @dt datetime )
returns datetime
as
begin
return cast(floor(cast(@dt as float))as int)
end

1

Que tal select cast(cast my_datetime_field as date) as datetime)? Isso resulta na mesma data, com o horário definido para 00:00, mas evita qualquer conversão em texto e também evita qualquer arredondamento numérico explícito.



Eles não são os mesmos. As outras respostas sugeriram convertê-lo para uma data sem componente de horário e deixá-lo assim. Minha postagem o define para um horário datado com a hora da meia-noite. Há uma grande diferença; tente exportar para o MS Excel e verá que ele lida com a data e hora muito melhor que a data.
Dr. Drew

O primeiro é exatamente o mesmo.
Mikael Eriksson

Ok, sim, eu vejo esse agora. Ficarei feliz em remover minha resposta como duplicada, se necessário.
Dr. Drew

1

Eu acho que se você se mantiver estritamente com TSQLisso, esta é a maneira mais rápida de interromper o tempo:

 select convert(datetime,convert(int,convert(float,[Modified])))

Eu achei esse método de truncamento cerca de 5% mais rápido que o DateAddmétodo. E isso pode ser facilmente modificado para arredondar para o dia mais próximo assim:

select convert(datetime,ROUND(convert(float,[Modified]),0))

Converter para flutuar não é seguro .
ErikE

1

Aqui, criei uma função para remover algumas partes de um datetime para o SQL Server. Uso:

  • O primeiro parâmetro é a data e hora a serem removidas.
  • O segundo parâmetro é um caractere:
    • s: arredonda para segundos; remove milissegundos
    • m: arredonda para minutos; remove segundos e milissegundos
    • h: arredonda para horas; remove minutos, segundos e milissegundos.
    • d: arredondamentos para dias; remove horas, minutos, segundos e milissegundos.
  • Retorna a nova data e hora

create function dbo.uf_RoundDateTime(@dt as datetime, @part as char) returns datetime as begin if CHARINDEX( @part, 'smhd',0) = 0 return @dt; return cast( Case @part when 's' then convert(varchar(19), @dt, 126) when 'm' then convert(varchar(17), @dt, 126) + '00' when 'h' then convert(varchar(14), @dt, 126) + '00:00' when 'd' then convert(varchar(14), @dt, 112) end as datetime ) end


Obrigado Andriy! Eu não sabia que minha recomendação não era tão eficiente. Pelo menos funciona, mas você está certo.
Max Vargas

1

Caso alguém esteja procurando aqui uma versão do Sybase, pois várias das versões acima não funcionaram

CAST(CONVERT(DATE,GETDATE(),103) AS DATETIME)
  • Testado no I SQL v11 em execução no Adaptive Server 15.7

Isso se encaixa melhor como uma edição na resposta aceita. Com outras 20 respostas, isso será enterrado e quase impossível de ser desligado. Além disso, a resposta aceita faz menção ao uso cast: No SQL Server 2008+, você pode CAST até o momento. Ou apenas use a data para que não haja tempo para remover.
EWit

Seria melhor postar isso como resposta a uma pergunta equivalente da Sybase. Se não houver essa pergunta, você poderá criar uma (e responder você mesmo).
Andriy H

Além disso, não faz sentido especificar um terceiro parâmetro para CONVERT ao converter um datetimepara date: nenhum deles possui um formato inerente.
Andriy H

0

Se possível, para coisas especiais como essa, eu gosto de usar as funções CLR.

Nesse caso:

[Microsoft.SqlServer.Server.SqlFunction]
    public static SqlDateTime DateOnly(SqlDateTime input)
    {
        if (!input.IsNull)
        {
            SqlDateTime dt = new SqlDateTime(input.Value.Year, input.Value.Month, input.Value.Day, 0, 0, 0);

            return dt;
        }
        else
            return SqlDateTime.Null;
    }

0

Pessoalmente, quase sempre uso funções definidas pelo usuário para isso se estiver lidando com o SQL Server 2005 (ou versão inferior), no entanto, deve-se notar que existem desvantagens específicas no uso de UDFs, especialmente se você for aplicá-las às cláusulas WHERE (veja abaixo e os comentários sobre esta resposta para obter mais detalhes). Se estiver usando o SQL Server 2008 (ou superior) - veja abaixo.

De fato, para a maioria dos bancos de dados criados, eu adiciono esses UDFs logo no início, pois sei que há uma chance de 99% de precisar deles mais cedo ou mais tarde.

Eu crio um para "somente data" e "somente horário" (embora o "somente data" seja de longe o mais usado dos dois).

Aqui estão alguns links para uma variedade de UDFs relacionadas à data:

Essencial SQL Server Data, hora e de data e hora Funções
Obter Data única função

Esse último link mostra nada menos que três maneiras diferentes de obter a data apenas como parte de um campo de data e hora e menciona alguns prós e contras de cada abordagem.

Se você estiver usando um UDF, deve-se observar que você deve evitar o uso do UDF como parte de uma cláusula WHERE em uma consulta, pois isso prejudicará bastante o desempenho da consulta. A principal razão para isso é que o uso de um UDF em uma cláusula WHERE torna essa cláusula não sargável , o que significa que o SQL Server não pode mais usar um índice com essa cláusula para melhorar a velocidade da execução da consulta. Com referência ao meu próprio uso de UDF, usarei frequentemente a coluna de data "bruta" na cláusula WHERE, mas aplicarei o UDF à coluna SELECTed. Dessa maneira, o UDF é aplicado apenas ao conjunto de resultados filtrado e nem a todas as linhas da tabela como parte do filtro.

Obviamente, a melhor abordagem absoluta para isso é usar o SQL Server 2008 (ou superior) e separar suas datas e horas , pois o mecanismo de banco de dados do SQL Server fornece nativamente os componentes individuais de data e hora e pode consultá-los de maneira eficiente. sem a necessidade de um UDF ou outro mecanismo para extrair a parte da data ou hora de um tipo de data e hora composto.


O uso de um UDF pode ser bom em algumas situações (como na limpeza de parâmetros). Mas na maioria das situações, é uma solução terrível - executar uma UDF uma vez para cada linha é uma maneira de reduzir o desempenho de uma consulta, sem a necessidade!
ErikE

@ErikE - Não discordo, Erik, as UDFs são prejudiciais ao desempenho, e é por isso que digo que, se você pode usar o SQL Server 2008 ou superior e usar um tipo de dados interno que faça isso por você, essa será a melhor solução (tanto em termos de cumprimento do necessário quanto em termos de desempenho). Se você está preso a uma versão mais antiga do SQL Server que não oferece suporte nativo a isso, desistirá de algo para atingir seus requisitos.
CraigTP

Verdade. Seria bom se o mecanismo de banco de dados nos desse algo SARGable, mas mais fácil de expressar. Enquanto isso, se você está procurando um valor que é qualquer momento durante um dia inteiro, este ainda é a melhor solução (pelo menos para versões mais antigas do SQL): WHERE DateColumn >= {TimeTruncatingExpression}(@DateValue) AND DateColumn < {TimeTruncatingExpression}(@DateValue + 1). Eu senti que tinha que dizer alguma coisa, já que você disse que "quase sempre uso UDFs" não explica nenhuma das desvantagens, nem a maneira de fazer uma consulta SARGable apenas com data.
ErikE

@ErikE - Não se preocupe, Erik. Quando usei UDFs, trabalhei com pequenos conjuntos de dados em que o desempenho não é fundamental ou, mais provavelmente, tenho filtrado a consulta no campo de data "bruto" (para garantir a capacidade de sargabilidade), mas selecionando a coluna com o UDF aplicado. Como esses geralmente são pequenos conjuntos de dados depois de filtrados, a execução do UDF nesse pequeno número de registros não é um problema de desempenho. Dito isto, você levanta um ponto muito bom e eu atualizei minha resposta para refletir isso.
CraigTP

-4

Eu usaria:

CAST
(
CAST(YEAR(DATEFIELD) as varchar(4)) + '/' CAST(MM(DATEFIELD) as varchar(2)) + '/' CAST(DD(DATEFIELD) as varchar(2)) as datetime
) 

Assim, criando efetivamente um novo campo a partir do campo de data que você já possui.


2
Por que você faria isso? Você acha que extrair bits de um datetimevalor, convertê-los em seqüências de caracteres, concatená-los e, finalmente, converter o resultado novamente datetimeé melhor do que, por exemplo, executar cálculos diretos no original datetime(o método DATEADD/ DATEDIFF)?
Andriy H

Além disso, o que são MMe DD? Não existem funções no SQL Server.
Andriy H
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.