Wie entferne ich den Zeitanteil eines Datums- / Uhrzeitwerts (SQL Server)?


83

Folgendes verwende ich:

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

Ich denke, es gibt vielleicht einen besseren und eleganteren Weg.

Bedarf:

  • Es muss so schnell wie möglich sein (je weniger Casting, desto besser).
  • Das Endergebnis muss ein datetimeTyp sein, keine Zeichenfolge.

Antworten:


115

SQL Server 2008 und höher

In SQL Server 2008 und höher ist natürlich der schnellste Weg Convert(date, @date). Dies kann auf a datetimeoder datetime2bei Bedarf zurückgesetzt werden.

Was ist in SQL Server 2005 und älter wirklich am besten?

Ich habe inkonsistente Behauptungen darüber gesehen, was am schnellsten ist, um die Zeit von einem Datum in SQL Server abzuschneiden, und einige Leute sagten sogar, sie hätten Tests durchgeführt, aber meine Erfahrung war anders. Lassen Sie uns also einige strengere Tests durchführen und jedem das Skript geben, damit mich die Leute korrigieren können, wenn ich Fehler mache.

Float-Konvertierungen sind nicht genau

Erstens würde ich aus der Umwandlung bleibt weg datetimezu float, weil es nicht richtig konvertieren. Sie werden vielleicht damit durchkommen, die Zeitentfernung genau durchzuführen, aber ich denke, es ist eine schlechte Idee, sie zu verwenden, da sie den Entwicklern implizit mitteilt, dass dies ein sicherer Vorgang ist und nicht . Schau mal:

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

Dies sollten wir den Menschen nicht in unserem Code oder in unseren Online-Beispielen beibringen.

Auch ist es nicht einmal der schnellste Weg!

Proof - Leistungstests

Wenn Sie einige Tests selbst durchführen möchten, um zu sehen, wie sich die verschiedenen Methoden wirklich stapeln, benötigen Sie dieses Setup-Skript, um die Tests weiter unten auszuführen:

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

Bitte beachten Sie, dass dadurch eine Tabelle mit 427,57 MB in Ihrer Datenbank erstellt wird und die Ausführung etwa 15 bis 30 Minuten dauert. Wenn Ihre Datenbank klein ist und auf 10% Wachstum eingestellt ist, dauert es länger, als wenn Sie zuerst groß genug sind.

Nun zum eigentlichen Leistungstestskript. Bitte beachten Sie, dass es sinnvoll ist, keine Zeilen an den Client zurückzugeben, da dies bei 26 Millionen Zeilen verrückt teuer ist und die Leistungsunterschiede zwischen den Methoden verbergen würde.

Leistungsergebnisse

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;

Einige Rambling-Analysen

Einige Anmerkungen dazu. Wenn Sie nur ein GROUP BY oder einen Vergleich durchführen, müssen Sie zunächst nicht zurück konvertieren datetime. Sie können also CPU sparen, indem Sie dies vermeiden, es sei denn, Sie benötigen den endgültigen Wert für Anzeigezwecke. Sie können sogar nach dem nicht konvertierten Wert gruppieren und die Konvertierung nur in die SELECT-Klausel einfügen:

select Convert(datetime, DateDiff(dd, 0, Tm))
from (select '2010-09-12 00:00:00.003') X (Tm)
group by DateDiff(dd, 0, Tm)

Sehen Sie auch, wie die numerische Konvertierung nur geringfügig länger dauert, bis die Konvertierung wieder datetimeerfolgt, sich die varcharKonvertierung jedoch fast verdoppelt. Dies zeigt den Teil der CPU, der der Datumsberechnung in den Abfragen gewidmet ist. Es gibt Teile der CPU-Auslastung, die keine Datumsberechnung beinhalten, und dies scheint in den obigen Abfragen ungefähr 19875 ms zu sein. Dann erfordert die Konvertierung einen zusätzlichen Betrag. Wenn also zwei Konvertierungen vorhanden sind, wird dieser Betrag ungefähr zweimal verbraucht.

Weitere Untersuchungen haben ergeben, dass Convert(, 112)die Convert(, 101)Abfrage im Vergleich zu einigen zusätzlichen CPU-Kosten verursacht (da sie länger dauert varchar?), Da die zweite Konvertierung zurück zu datenicht so viel kostet wie die anfängliche Konvertierung nach varchar, aber Convert(, 112)damit näher an den gleichen 20000 liegt ms CPU-Grundkosten.

Hier sind die Berechnungen zur CPU-Zeit, die ich für die obige Analyse verwendet habe:

     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 ist die CPU-Zeit für einen Roundtrip zurück zu datetime.

  • single ist die CPU-Zeit für eine einzelne Konvertierung in den alternativen Datentyp (derjenige, der den Nebeneffekt hat, den Zeitanteil zu entfernen).

  • Basis ist die Berechnung des Subtrahierens von singleder Differenz zwischen den beiden Aufrufen : single - (round - single). Es handelt sich um eine Standardfigur, die die Konvertierung zu und von diesem Datentyp annimmt und datetimein beiden Richtungen ungefähr gleich ist. Es scheint, dass diese Annahme nicht perfekt ist, aber nahe beieinander liegt, da die Werte mit nur einer Ausnahme alle nahe bei 20000 ms liegen.

Eine weitere interessante Sache ist, dass die Grundkosten fast gleich der Einzelmethode sind Convert(date)(die fast 0 Kosten betragen muss, da der Server den ganzzahligen Tagesanteil intern direkt aus den ersten vier Bytes des datetimeDatentyps extrahieren kann ).

Fazit

Es sieht also so aus, als ob das varcharEinrichtungsumwandlungsverfahren etwa 1,8 μs und das DateDiffEinrichtungsumwandlungsverfahren etwa 0,18 μs benötigt. Ich stütze mich dabei auf die konservativste "Basis-CPU" -Zeit in meinem Test von insgesamt 18458 ms für 25.920.000 Zeilen, also 23218 ms / 25920000 = 0,18 μs. Die offensichtliche 10-fache Verbesserung scheint viel zu sein, ist aber ehrlich gesagt ziemlich klein, bis Sie mit Hunderttausenden von Zeilen zu tun haben (617.000 Zeilen = 1 Sekunde Einsparung).

Selbst angesichts dieser kleinen absoluten Verbesserung DateAddgewinnt die Methode meiner Meinung nach, weil sie die beste Kombination aus Leistung und Klarheit ist. Die Antwort, die eine "magische Zahl" von erfordert, wird eines 0.50000004Tages jemanden beißen (fünf Nullen oder sechs ???), und es ist schwieriger zu verstehen.

Zusätzliche Bemerkungen

Wenn ich einige Zeit erreiche ich ändern werde , 0.50000004um '12:00:00.003'zu sehen , wie es funktioniert. Es wird in den gleichen datetimeWert konvertiert und ich finde es viel einfacher, mich daran zu erinnern.

Für Interessenten wurden die oben genannten Tests auf einem Server ausgeführt, auf dem @@ Version Folgendes zurückgibt:

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


1
+1 Auf welcher Version von SQL Server haben Sie das übrigens getestet?
Martin Smith

1
Es sieht so aus, als hätten Sie einzelne und runde rückwärts in Ihrem Tisch. Gibt es auch einen Zeitunterschied, wenn Sie charanstelle von verwenden varchar?
Gabe

1
@ Gabe danke, behoben. Char scheint genau das gleiche zu sein wie varchar.
ErikE

In Oracle gibt es select round(sysdate) from dualund wir brauchen das definitiv in SQL Server.
Denis Valeev

3
@Roman Wenn Sie mit SQL Server 2008 und höher arbeiten, ist die Konvertierung in den dateDatentyp am schnellsten, wie in meinen obigen Tests gezeigt.
ErikE

30

SQL Server 2008 hat einen neuen Datum Datentyp und dies vereinfacht dieses Problem:

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

1
Ich hatte fälschlicherweise 0218 anstelle von 2018 als Jahr eingegeben, und die DATEADD(DATEDIFF())Methode zum Reduzieren des Zeitteils löst eine Ausnahme aus. Wenn ich das Ergebnis auf datetime2Ihre Methode zurückgreife, funktioniert das gutselect cast(CAST(convert(datetime2(0), '0218-09-12', 120) AS date) as datetime2)
Bernhard Döbler

18

Itzik Ben-Gan zeigt in DATETIME Calculations, Teil 1 (SQL Server Magazine, Februar 2007) drei Methoden zur Durchführung einer solchen Konvertierung ( langsamste bis schnellste ; der Unterschied zwischen der zweiten und dritten Methode ist gering):

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)

Ihre Technik (Casting to Float ) wird von einem Leser in der April-Ausgabe des Magazins vorgeschlagen. Ihm zufolge hat es eine Leistung, die mit der der oben vorgestellten zweiten Technik vergleichbar ist.


1
Meiner Meinung nach ist Casting to Float nicht das Beste. Bitte sehen Sie meine Antwort
ErikE

1
@Emtucifor Ich stimme zu, dass die 3. Methode aufgrund des Werts von 0,50000004 sehr dunkel ist, aber sie ist die schnellste und Ihre Tests bestätigen dies . Somit erfüllt es die möglichst schnelle Anforderung.
Marek Grzenkowicz

1
@Emtucifor Außerdem sagt der Artikel, den ich verlinkt habe, Folgendes über den Wert von 0,50000004 : Obwohl dieser Ausdruck kurz (und effizient, wie ich gleich zeigen werde) ist, muss ich sagen, dass ich mich damit unwohl fühle . Ich bin mir nicht sicher, ob ich genau sagen kann, warum - vielleicht weil es zu technisch ist und Sie darin keine datetime-bezogene Logik sehen können.
Marek Grzenkowicz

2
Wenn wir diese Methode verwenden wollen, würde ich es vorziehen SELECT CAST(CAST(GETDATE() - '12:00:00.003' AS int) AS datetime), da sie mir etwas bedeutet und imo viel einfacher zu merken ist.
ErikE

6
Dies ist jetzt in SQL 2008 am schnellsten : Convert(date, GetDate()).
ErikE

12

Ihr CAST- FLOOR- CASTscheint zumindest unter MS SQL Server 2005 bereits der optimale Weg zu sein.

Einige andere Lösungen, die ich gesehen habe, haben eine String-Konvertierung, wie Select Convert(varchar(11), getdate(),101)in ihnen, die um den Faktor 10 langsamer ist.


1
Wir verwenden die von Michael Stum vorgeschlagene Methode in einem unserer Produkte und es funktioniert wie ein Zauber.
Chris Roberts

3
Dies ist bei weitem nicht der optimale Weg. Bitte sehen Sie meine Antwort auf derselben Seite.
ErikE

4

Bitte versuche:

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

1

SQL2005: Ich empfehle Cast anstelle von Dateadd. Beispielsweise,

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

durchschnittlich rund 10% schneller in meinem Datensatz als

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

(und das Casting in die Smalldatetime war noch schneller)

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.