Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

DATEDIFF() gibt falsche Ergebnisse in SQL Server zurück? Lesen Sie dies.

Wenn Sie bei der Verwendung von DATEDIFF() einige wirklich seltsame Ergebnisse erhalten Funktion in SQL Server, und Sie sind überzeugt, dass die Funktion einen Fehler enthält, reißen Sie sich jetzt noch nicht die Haare. Es ist wahrscheinlich kein Fehler.

Es gibt Szenarien, in denen die von dieser Funktion erzeugten Ergebnisse ziemlich verrückt sein können. Und wenn Sie nicht verstehen, wie die Funktion tatsächlich funktioniert, sehen die Ergebnisse völlig falsch aus.

Hoffentlich kann dieser Artikel helfen zu klären, wie DATEDIFF() Die Funktion wurde entwickelt, um zu funktionieren, und bietet einige Beispielszenarien, in denen Ihre Ergebnisse möglicherweise nicht Ihren Erwartungen entsprechen.

Beispiel 1 – 365 Tage sind nicht immer ein Jahr

Frage: Wann ist 365 Tage nicht ein Jahr?

Antwort: Bei Verwendung von DATEDIFF() natürlich!

Hier ist ein Beispiel, in dem ich DATEDIFF() verwende um die Anzahl der Tage zwischen zwei Daten und dann die Anzahl der Jahre zwischen denselben beiden Daten zurückzugeben.

DECLARE @startdate datetime2 ='2016-01-01 00:00:00.0000000', @enddate datetime2 ='2016-12-31 23:59:59.9999999';SELECT DATEDIFF(day, @startdate, @enddate) Tage , DATEDIFF(year, @startdate, @enddate) Jahre;

Ergebnis:

+--------+---------+| Tage | Jahre ||--------+---------|| 365 | 0 |+--------+---------+

Wenn Sie denken, dass dieses Ergebnis falsch ist, dann DATEDIFF() hat offensichtlich einen Fehler, lesen Sie weiter – nicht alles ist so, wie es scheint.

Ob Sie es glauben oder nicht, das ist tatsächlich das erwartete Ergebnis. Dieses Ergebnis stimmt genau mit DATEDIFF() überein ist so konzipiert, dass es funktioniert.

Beispiel 2 – 100 Nanosekunden =1 Jahr?

Nehmen wir es anders.

DECLARE @startdate datetime2 ='2016-12-31 23:59:59.9999999', @enddate datetime2 ='2017-01-01 00:00:00.0000000';SELECT DATEDIFF(year, @startdate, @enddate) Jahr , DATEDIFF(quartal, @startdate, @enddate) Quartal, DATEDIFF(month, @startdate, @enddate) Monat, DATEDIFF(dayofyear, @startdate, @enddate) DOY, DATEDIFF(day, @startdate, @enddate) Tag, DATEDIFF (week, @startdate, @enddate) Week, DATEDIFF(hour, @startdate, @enddate) Hour, DATEDIFF(minute, @startdate, @enddate) Minute, DATEDIFF(second, @startdate, @enddate) Second, DATEDIFF(millisecond , @startdate, @enddate) Millisekunde, DATEDIFF(microsecond, @startdate, @enddate) Mikrosekunde, DATEDIFF(nanosecond, @startdate, @enddate) Nanosekunde;

Ergebnisse (dargestellt mit vertikaler Ausgabe):

Jahr | 1Quartal | 1Monat | 1DOY | 1 Tag | 1Woche | 1Stunde | 1 Minute | 1Sekunde | 1 Millisekunde | 1 Mikrosekunde | 1 Nanosekunde | 100

Es gibt nur einen Unterschied von hundert Nanosekunden (0,0000001 Sekunden) zwischen den beiden Daten/Zeiten, aber wir erhalten für jeden Datumsteil genau das gleiche Ergebnis, außer Nanosekunden.

Wie kann das passieren? Wie kann es gleichzeitig 1 Mikrosekunde Unterschied und 1 Jahr Unterschied sein? Ganz zu schweigen von all den Datumsteilen dazwischen?

Es mag verrückt erscheinen, aber das ist auch kein Fehler. Diese Ergebnisse stimmen genau mit DATEDIFF() überein soll funktionieren.

Und um die Sache noch verwirrender zu machen, könnten wir je nach Datentyp unterschiedliche Ergebnisse erhalten. Aber dazu kommen wir gleich. Schauen wir uns zuerst an, wie die DATEDIFF() Funktion funktioniert tatsächlich.

Die eigentliche Definition von DATEDIFF()

Der Grund, warum wir die Ergebnisse erhalten, ist, weil DATEDIFF() Funktion ist wie folgt definiert:

Diese Funktion gibt die Anzahl (als vorzeichenbehafteter ganzzahliger Wert) der angegebenen Datumsteilgrenzen zurück, die zwischen dem angegebenen Startdatum überschritten wurden und Enddatum .

Achten Sie besonders auf die Worte „Datumsbereichsgrenzen überschritten“. Aus diesem Grund erhalten wir die Ergebnisse wie in den vorherigen Beispielen. Es ist leicht anzunehmen, dass DATEDIFF() verwendet die verstrichene Zeit für seine Berechnungen, tut es aber nicht. Es verwendet die Anzahl der überschrittenen Datepart-Grenzen.

Im ersten Beispiel haben die Datumsangaben keine Jahresteilgrenzen überschritten. Das Jahr des ersten Datums war genau dasselbe wie das Jahr des zweiten Datums. Es wurden keine Grenzen überschritten.

Im zweiten Beispiel hatten wir das gegenteilige Szenario. Die Datumsangaben haben jede Datumsteilgrenze mindestens einmal überschritten (100 Mal für Nanosekunden).

Beispiel 3 – Ein anderes Ergebnis für eine Woche

Stellen wir uns nun vor, ein ganzes Jahr wäre vergangen. Und hier sind wir genau ein Jahr später mit den Datums-/Uhrzeitwerten, außer dass die Jahreswerte um eins erhöht wurden.

Wir sollten die gleichen Ergebnisse erzielen, richtig?

DECLARE @startdate datetime2 ='2017-12-31 23:59:59.9999999', @enddate datetime2 ='2018-01-01 00:00:00.0000000';SELECT DATEDIFF(year, @startdate, @enddate) Jahr , DATEDIFF(quartal, @startdate, @enddate) Quartal, DATEDIFF(month, @startdate, @enddate) Monat, DATEDIFF(dayofyear, @startdate, @enddate) DOY, DATEDIFF(day, @startdate, @enddate) Tag, DATEDIFF (week, @startdate, @enddate) Week, DATEDIFF(hour, @startdate, @enddate) Hour, DATEDIFF(minute, @startdate, @enddate) Minute, DATEDIFF(second, @startdate, @enddate) Second, DATEDIFF(millisecond , @startdate, @enddate) Millisekunde, DATEDIFF(microsecond, @startdate, @enddate) Mikrosekunde, DATEDIFF(nanosecond, @startdate, @enddate) Nanosekunde;

Ergebnisse:

Jahr | 1Quartal | 1Monat | 1DOY | 1Tag | 1Woche | 0Stunde | 1 Minute | 1Sekunde | 1 Millisekunde | 1 Mikrosekunde | 1 Nanosekunde | 100

Falsch.

Die meisten von ihnen sind gleich, aber dieses Mal hat die Woche 0 zurückgegeben .

Häh?

Dies geschah, weil die Eingabedaten dieselbe Woche im Kalender haben Werte. Es ist einfach so, dass die ausgewählten Daten für Beispiel 2 unterschiedliche Kalenderwochenwerte hatten.

Genauer gesagt, Beispiel 2 hat Wochenabschnittsgrenzen von „2016-12-31“ bis „2017-01-01“ überschritten. Dies liegt daran, dass die letzte Woche des Jahres 2016 am 31.12.2016 endete und die erste Woche des Jahres 2017 am 01.01.2017 (Sonntag) begann.

Aber in Beispiel 3 begann die erste Woche des Jahres 2018 tatsächlich an unserem Startdatum 2017-12-31 (Sonntag). Unser Enddatum, das der nächste Tag war, fiel in dieselbe Woche. Daher wurden keine Wochenabschnittsgrenzen überschritten.

Dies setzt natürlich voraus, dass der Sonntag der erste Tag jeder Woche ist. Wie sich herausstellt, ist DATEDIFF() Funktion macht Gehen Sie davon aus, dass Sonntag der erste Tag der Woche ist. Es ignoriert sogar Ihr SET DATEFIRST (mit dieser Einstellung können Sie explizit festlegen, welcher Tag als erster Wochentag gilt). Microsofts Begründung für das Ignorieren von SET DATEFIRST ist, dass es das DATEDIFF() sicherstellt Funktion ist deterministisch. Hier ist eine Problemumgehung, wenn dies ein Problem für Sie ist.

Kurz gesagt, Ihre Ergebnisse könnten je nach Datum/Uhrzeit für jeden Datumsteil „falsch“ aussehen. Ihre Ergebnisse können besonders falsch aussehen, wenn Sie den Wochenteil verwenden. Und sie könnten sogar noch falscher aussehen, wenn Sie ein SET DATEFIRST verwenden Wert anders als 7 (für Sonntag) und Sie erwarten DATEDIFF() das zu ehren.

Aber die Ergebnisse sind nicht falsch, und es ist kein Fehler. Es ist eher ein „Fallfang“ für diejenigen, die nicht wissen, wie die Funktion tatsächlich funktioniert.

All diese Fallstricke gelten auch für DATEDIFF_BIG() Funktion. Es funktioniert genauso wie DATEDIFF() mit der Ausnahme, dass es das Ergebnis als signiertes bigint zurückgibt (im Gegensatz zu einem int für DATEDIFF() ).

Beispiel 4 – Ergebnisse hängen vom Datentyp ab

Aufgrund des Datentyps, den Sie für Ihre Eingabedaten verwenden, könnten Sie auch unerwartete Ergebnisse erhalten. Die Ergebnisse unterscheiden sich oft je nach Datentyp der Eingabedaten. Aber Sie können DATEDIFF() nicht beschuldigen dafür, da dies allein auf die Möglichkeiten und Einschränkungen der verschiedenen Datentypen zurückzuführen ist. Sie können nicht erwarten, hochpräzise Ergebnisse von einem Eingabewert mit niedriger Genauigkeit zu erhalten.

Zum Beispiel immer dann, wenn das Start- oder Enddatum ein smalldatetime hat -Wert, die Sekunden und Millisekunden geben immer 0 zurück. Das liegt daran, dass smalldatetime Datentyp ist nur auf die Minute genau.

Folgendes passiert, wenn wir Beispiel 2 auf die Verwendung von smalldatetime umstellen statt datetime2 :

DECLARE @startdate smalldatetime ='2016-12-31 23:59:59', @enddate smalldatetime ='2017-01-01 00:00:00';SELECT DATEDIFF(year, @startdate, @enddate) Jahr , DATEDIFF(quartal, @startdate, @enddate) Quartal, DATEDIFF(month, @startdate, @enddate) Monat, DATEDIFF(dayofyear, @startdate, @enddate) DOY, DATEDIFF(day, @startdate, @enddate) Tag, DATEDIFF (week, @startdate, @enddate) Week, DATEDIFF(hour, @startdate, @enddate) Hour, DATEDIFF(minute, @startdate, @enddate) Minute, DATEDIFF(second, @startdate, @enddate) Second, DATEDIFF(millisecond , @startdate, @enddate) Millisekunde, DATEDIFF(microsecond, @startdate, @enddate) Mikrosekunde, DATEDIFF(nanosecond, @startdate, @enddate) Nanosekunde;

Ergebnis:

Jahr | 0Quartal | 0Monat | 0DOY | 0Tag | 0Woche | 0Stunde | 0 Minute | 0Sekunde | 0Millisekunde | 0Mikrosekunde | 0 Nanosekunde | 0

Der Grund, warum diese alle Null sind, liegt darin, dass beide Eingabedaten tatsächlich identisch sind:

DECLARE @startdate smalldatetime ='2016-12-31 23:59:59', @enddate smalldatetime ='2017-01-01 00:00:00';SELECT @startdate 'Start Date', @enddate 'End Datum';

Ergebnis:

+---------------------+---------------------+| Startdatum | Enddatum ||---------------------+---------------------|| 01.01.2017 00:00:00 | 2017-01-01 00:00:00 |+---------------------+---------------- -----+

Die Einschränkungen der smalldatetime Der Datentyp bewirkte, dass die Sekunden aufgerundet wurden, was dann zu einem Fluss führte und alles aufgerundet wurde. Selbst wenn Sie am Ende keine identischen Eingabewerte erhalten, könnten Sie dennoch ein unerwartetes Ergebnis erhalten, da der Datentyp nicht die erforderliche Genauigkeit bietet.