Warum stimmt meine Abfragesuchzeit nicht überein?


20
select * 
from A 
where posted_date >= '2015-07-27 00:00:00.000' 
  and posted_date  <= '2015-07-27 23:59:59.999'

Das Ergebnis enthält jedoch einen Datensatz mit dem heutigen Datum: 2015-07-28. Mein Datenbankserver befindet sich nicht in meinem Land. Was ist das Problem ?

Antworten:


16

Da Sie datetimeDatentypen verwenden, müssen Sie verstehen, wie SQL Server Datum / Uhrzeit-Daten rundet.

╔═══════════╦═════╦═════════════════════════════╦═════════════════════════════╦══════════╦═══════════╗
   Name     sn          Minimum value                Maximum value         Accuracy   Storage  
╠═══════════╬═════╬═════════════════════════════╬═════════════════════════════╬══════════╬═══════════╣
 datetime   dt   1753-01-01 00:00:00.000      9999-12-31 23:59:59.997      3.33 ms   8 bytes   
 datetime2  dt2  0001-01-01 00:00:00.0000000  9999-12-31 23:59:59.9999999  100ns     6-8 bytes 
╚═══════════╩═════╩═════════════════════════════╩═════════════════════════════╩══════════╩═══════════╝

Bildbeschreibung hier eingeben

Wenn Sie die folgende Abfrage verwenden, können Sie leicht das Problem der Rundung erkennen, das SQL Server bei Verwendung des DATETIMEDatentyps ausführt .

select  '2015-07-27 00:00:00.000'                       as Original_startDateTime,
        convert(datetime ,'2015-07-27 00:00:00.000')    as startDateTime,
        '2015-07-27 23:59:59.999'                       as Original_endDateTime,
        convert(datetime ,'2015-07-27 23:59:59.999')    as endDateTime,
        '2015-07-27 00:00:00.000'                       as Original_startDateTime2,
        convert(datetime2 ,'2015-07-27 00:00:00.000')   as startDateTime2,  -- default precision is 7
        '2015-07-27 23:59:59.999'                       as Original_endDateTime2,
        convert(datetime2 ,'2015-07-27 23:59:59.999')   as endDateTime2     -- default precision is 7

Bildbeschreibung hier eingeben klicken um zu vergrößern

DATETIME2gibt es seit SQL Server 2008, verwenden Sie ihn also anstelle von DATETIME. Für Ihre Situation können Sie datetime2mit einer Genauigkeit von 3 Dezimalstellen verwenden, z datetime2(3).

Vorteile der Verwendung von datetime2:

  • Unterstützt bis zu 7 Dezimalstellen für Zeitkomponente vs datetimenur 3 Dezimalstellen unterstützt .. und daher sieht man die Rundung Problem , da standardmäßig datetimeRunden nächsten .003 secondsmit Schritten .000, .003oder .007Sekunden.
  • datetime2ist viel genauer als datetimeund datetime2gibt Ihnen die Kontrolle über DATEund TIMEim Gegensatz zu datetime.

Referenz :


1
gives you control of DATE and TIME as opposed to datetime.was bedeutet das?
nurettin

Re. mit DateTime2vs DateTime.: a. Für die überwiegende Mehrheit der Anwendungsfälle in der realen Welt sind die Vorteile von DateTime2Much <cost. Siehe: stackoverflow.com/questions/1334143/… b. Das ist nicht die Wurzel Problem hier. Siehe nächsten Kommentar.
Tom

Das Grundproblem hier (wie ich wette, dass die meisten älteren Entwickler zustimmen werden) ist nicht die ungenügende Genauigkeit des Enddatums eines Datum-Zeit-Bereichs-Vergleichs, sondern die Verwendung eines einschließenden (vs. ausschließenden) Zeitraums. Es ist wie bei der Überprüfung der Gleichheit mit Pi, es gibt immer die Möglichkeit, dass eines der #s eine> oder <Genauigkeit aufweist (dh was ist, wenn datetime370 (gegenüber 7) Stellen Genauigkeit hinzugefügt werden?). Es wird empfohlen, einen Wert zu verwenden, bei dem die Genauigkeit keine Rolle spielt, dh <der Beginn der nächsten Sekunde, Minute, Stunde oder Tag vs. <= das Ende der vorherigen Sekunde, Minute, Stunde oder Tag.
Tom

18

Wie mehrere andere in Kommentaren und anderen Antworten auf Ihre Frage erwähnt haben, wird das Kernproblem von SQL Server 2015-07-27 23:59:59.999abgerundet 2015-07-28 00:00:00.000. Gemäß der Dokumentation für DATETIME:

Zeitraum: 00:00:00 bis 23: 59: 59.997

Beachten Sie, dass der Zeitbereich niemals sein kann .999. Weiter unten in der Dokumentation werden die Rundungsregeln angegeben, die SQL Server für die niedrigstwertige Ziffer verwendet.

Tabelle mit Rundungsregeln

Beachten Sie, dass die niedrigstwertige Ziffer nur einen von drei möglichen Werten haben kann: "0", "3" oder "7".

Hierfür gibt es mehrere Lösungen / Problemumgehungen, die Sie verwenden können.

-- Option 1
SELECT 
    * 
FROM A 
WHERE posted_date >= '2015-07-27 00:00:00.000' 
  AND posted_date <  '2015-07-28 00:00:00.000' --Round up and remove equality

-- Option 2
SELECT 
    * 
FROM A 
WHERE posted_date >= '2015-07-27 00:00:00.000' 
  AND posted_date <=  '2015-07-27 23:59:59.997' --Round down and keep equality

-- Option 3
SELECT 
    * 
FROM A 
WHERE CAST(posted_date AS DATE) = '2015-07-27' -- Use different data type

-- Option 4
SELECT 
    * 
FROM A 
WHERE CONVERT(CHAR(8), DateColumn, 112) = '20150727' -- Cast to string stripping off time

-- Option 5
SELECT 
    * 
FROM A 
WHERE posted_date BETWEEN '2015-07-27 00:00:00.000' 
  AND '2015-07-27 23:59:59.997' --Use between

Von den fünf Optionen, die ich oben vorgestellt habe, halte ich die Optionen 1 und 3 für die einzig praktikablen Optionen. Sie vermitteln Ihre Absichten klar und brechen nicht zusammen, wenn Sie Datentypen aktualisieren. Wenn Sie SQL Server 2008 oder eine neuere Version verwenden, ist Option 3 Ihrer Meinung nach der bevorzugte Ansatz. Dies gilt insbesondere dann, wenn Sie den DATETIMEDatentyp nicht mehr in einen DATEDatentyp für Ihre posted_dateSpalte ändern können .

In Bezug auf Option 3 finden Sie hier eine sehr gute Erklärung zu einigen Problemen: Bisherige Besetzung ist kostbar, aber ist es eine gute Idee?

Ich mag die Optionen 2 und 5 nicht, weil die .997Sekundenbruchteile nur eine weitere magische Zahl sein werden , die die Leute "reparieren" wollen. Aus einigen weiteren Gründen, warum BETWEENes nicht sehr beliebt ist, möchten Sie vielleicht diesen Beitrag lesen .

Ich mag Option 4 nicht, weil das Konvertieren von Datentypen in einen String zu Vergleichszwecken für mich schmutzig ist. Ein qualitativerer Grund, dies in SQL Server zu vermeiden, ist die Beeinträchtigung der Zuverlässigkeit, da Sie keine Indexsuche durchführen können und dies häufig zu einer schlechteren Leistung führt.

Weitere Informationen zum richtigen und falschen Umgang mit Datumsbereichsabfragen finden Sie in diesem Beitrag von Aaron Bertrand .

Während des Abschieds können Sie Ihre ursprüngliche Abfrage beibehalten und sie verhält sich wie gewünscht, wenn Sie Ihre posted_dateSpalte von a DATETIMEin a ändern DATETIME2(3). Das spart Speicherplatz auf dem Server, erhöht die Genauigkeit bei gleicher Genauigkeit, ist standardkonformer / portabler und ermöglicht eine einfache Anpassung der Genauigkeit / Präzision, wenn sich Ihre Anforderungen in Zukunft ändern. Dies ist jedoch nur eine Option, wenn Sie SQL Server 2008 oder höher verwenden.

Als eine Kleinigkeit scheint die 1/300Genauigkeit einer Sekunde mit dieser StackOverflow-Antwort ein NachteilDATETIME von UNIX zu sein . Sybase, das ein gemeinsames Erbe hat, hat eine ähnliche zweite Genauigkeit in ihren und Datentypen, aber ihre am wenigsten signifikanten Ziffern unterscheiden sich bei "0", "3" und "6". Meiner Meinung nach ist die Genauigkeit von einer Sekunde und / oder 3,33 ms eine unglückliche architektonische Entscheidung, da der 4-Byte-Block für die Zeit im SQL Server- Datentyp leicht die Genauigkeit von 1 ms hätte unterstützen können.1/300DATETIMETIME1/300DATETIME


Ja, aber im Kern "Kernproblem" wird Option 1 nicht verwendet (z. B. unter Verwendung eines Endwerts für den einschließenden (im Vergleich zum ausschließlichen) Bereich, bei dem sich die Genauigkeit vergangener oder potenzieller zukünftiger Datentypen auf die Ergebnisse auswirken könnte). Es ist so, als würde man die Gleichheit mit Pi überprüfen. Es ist immer möglich, dass eine #> oder <Genauigkeit hat (es sei denn, beide sind auf die niedrigste gemeinsame Genauigkeit vorgerundet). Was ist, wenn datetime370 (gegenüber 7) Stellen Genauigkeit hinzugefügt werden? Es wird empfohlen, einen Wert zu verwenden, bei dem die Genauigkeit keine Rolle spielt, dh <der Beginn der nächsten Sekunde, Minute, Stunde oder Tag vs. <= das Ende der vorherigen Sekunde, Minute, Stunde oder Tag.
Tom

9

Implizite Konvertierung

Ich nahm an, dass posted_date Datentyp Datetime ist. Es spielt jedoch keine Rolle, ob der Typ auf der anderen Seite Datetime, Datetime2 oder nur Time ist, da der String (Varchar) implizit in Datetime konvertiert wird.

Wenn posted_date als Datetime2 (oder Time) posted_date <= '2015-07-27 23:59:59.99999'deklariert 23:59:59.99999ist , schlägt die where-Klausel fehl, da zwar ein gültiger Datetime2-Wert vorliegt, dies jedoch kein gültiger Datetime-Wert ist:

 Conversion failed when converting date and/or time from character string.

Zeitbereich für Datum / Uhrzeit

Der Zeitbereich von Datetime reicht von 00:00:00 bis 23: 59: 59.997. Daher ist 23: 59: 59.999 außerhalb des Bereichs und muss auf den nächsten Wert auf- oder abgerundet werden.

Richtigkeit

Außerdem werden die Datenzeitwerte in Schritten von .000, .003 oder .007 Sekunden gerundet. (dh 000, 003, 007, 010, 013, 017, 020, ..., 997)

Dies ist bei den Werten, 2015-07-27 23:59:59.999die in diesem Bereich liegen, nicht der Fall : 2015-07-27 23:59:59.997und 2015-07-28 0:00:00.000.

Dieser Bereich entspricht den nächsthöheren vorhergehenden und folgenden Optionen, die beide mit .000, .003 oder .007 enden.

Auf- oder Abrunden ?

Da es sich näher an 2015-07-28 0:00:00.000(+1 gegenüber -2) als 2015-07-27 23:59:59.997, wird die Zeichenfolge aufgerundet und wird diesen Wert für Datum und Uhrzeit: 2015-07-28 0:00:00.000.

Mit einer Obergrenze wie 2015-07-27 23:59:59.998(oder .995, .996, .997, .998) wäre es abgerundet worden 2015-07-27 23:59:59.997und Ihre Abfrage hätte wie erwartet funktioniert. Es wäre jedoch keine Lösung gewesen, sondern nur ein glücklicher Wert.

Datetime2 oder Zeittypen

Datetime2 und Zeitzeitbereiche sind 00:00:00.0000000durch 23:59:59.9999999mit einer Genauigkeit von 100 ns (die letzte Ziffer , wenn sie mit einem 7 - stelligen Genauigkeit verwendet wird ).

Ein Datetime-Bereich (3) ist jedoch nicht mit dem Datetime-Bereich vergleichbar:

  • Datum / Uhrzeit 0:0:00.000bis23:59:59.997
  • DatumZeit2 0:0:00.000000000bis23:59:59.999

Lösung

Am Ende ist es sicherer, nach Daten unterhalb des nächsten Tages zu suchen, als nach Daten unterhalb oder gleich dem, was Sie für das letzte Fragment der Tageszeit halten. Dies liegt hauptsächlich daran, dass Sie wissen, dass der nächste Tag immer um 0: 00: 00.000 beginnt, verschiedene Datentypen jedoch möglicherweise nicht dieselbe Uhrzeit am Ende des Tages haben:

Datetime `0:0:00.000` to `23:59:59.997`
Datetime2 `0:0:00.000000000` to `23:59:59.999-999-900`
Time2 `0:0:00.000000000` to `23:59:59.999-999-900`
  • < 2015-07-28 0:00:00.000gibt Ihnen genaue Ergebnisse und ist die beste Option
  • <= 2015-07-27 23:59:59.xxx kann unerwartete Werte zurückgeben, wenn es nicht auf das aufgerundet wird, was Sie denken, dass es sein sollte.
  • Die Umstellung auf Datum und die Verwendung von Funktionen sollten vermieden werden, da sie die Verwendung von Indizes einschränken

Wir könnten denken, dass das Ändern von [posted_date] in Datetime2 und dessen höhere Genauigkeit dieses Problem beheben könnte, aber es hilft nicht, da die Zeichenfolge immer noch in Datetime konvertiert wird. Wenn jedoch eine Besetzung hinzugefügt wird cast(2015-07-27 23:59:59.999' as datetime2), funktioniert dies einwandfrei

Besetzung und Umwandlung

Cast kann einen Wert mit bis zu 3 Stellen in Datum / Uhrzeit oder mit bis zu 9 Stellen in Datum / Uhrzeit2 oder Uhrzeit konvertieren und auf die richtige Genauigkeit runden.

Es ist zu beachten, dass Cast von Datetime2 und Time2 unterschiedliche Ergebnisse liefern können:

  • select cast('20150101 23:59:59.999999999' as datetime2(7)) ist rund 2015-05-03 00: 00: 00.0000000 (für einen Wert größer als 999999949)
  • select cast('23:59:59.999999999' as time(7)) => 23: 59: 59.9999999

Es behebt das Problem, das datetime mit den Schritten 0, 3 und 7 hat, obwohl es immer besser ist, nach Daten vor der ersten Nanosekunde des nächsten Tages zu suchen (immer 0: 00: 00.000).

Quell-MSDN: datetime (Transact-SQL)


6

Es rundet

 select cast('2015-07-27 23:59:59.999' as datetime) 
 returns 2015-07-28 00:00:00.000

.998, .997, .996, .995 alle gegossen / rund bis .997

Sollte nutzen

select * 
from A 
where posted_date >= '2015-07-27 00:00:00.000' 
  and posted_date <  '2015-07-28 00:00:00.000'

oder

where cast(posted_date as date) = '2015-07-27'

Siehe Genauigkeit in diesem Link.
Immer gemeldet als .000, .003, .007


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.