sql >> Database >  >> RDS >> Sqlserver

DATEDIFF() geeft verkeerde resultaten in SQL Server? Lees dit.

Als je rare resultaten krijgt bij het gebruik van de DATEDIFF() functie in SQL Server, en je bent ervan overtuigd dat de functie een bug bevat, trek je haren er nog niet uit. Het is waarschijnlijk geen bug.

Er zijn scenario's waarin de resultaten die door deze functie worden geproduceerd behoorlijk maf kunnen zijn. En als u niet begrijpt hoe de functie eigenlijk werkt, zien de resultaten er helemaal verkeerd uit.

Hopelijk kan dit artikel helpen verduidelijken hoe de DATEDIFF() functie is ontworpen om te werken en biedt enkele voorbeeldscenario's van waar uw resultaten mogelijk niet zijn zoals u zou verwachten.

Voorbeeld 1 – 365 dagen is niet altijd een jaar

Vraag: Wanneer is 365 dagen niet een jaar?

Antwoord: Bij gebruik van DATEDIFF() natuurlijk!

Hier is een voorbeeld waarbij ik DATEDIFF() . gebruik om het aantal dagen tussen twee datums te retourneren en vervolgens het aantal jaren tussen dezelfde twee datums.

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

Resultaat:

+--------+---------+
| Days   | Years   |
|--------+---------|
| 365    | 0       |
+--------+---------+

Als u denkt dat dit resultaat verkeerd is, en dat DATEDIFF() heeft duidelijk een bug, lees verder – niet alles is wat het lijkt.

Geloof het of niet, dit is eigenlijk het verwachte resultaat. Dit resultaat is precies in overeenstemming met hoe DATEDIFF() is ontworpen om te werken.

Voorbeeld 2 – 100 nanoseconden =1 jaar?

Laten we het anders doen.

DECLARE @startdate datetime2 = '2016-12-31 23:59:59.9999999', 
  @enddate datetime2 = '2017-01-01 00:00:00.0000000';

SELECT DATEDIFF(year,     @startdate,   @enddate) Year,
  DATEDIFF(quarter,       @startdate,   @enddate) Quarter,
  DATEDIFF(month,         @startdate,   @enddate) Month,
  DATEDIFF(dayofyear,     @startdate,   @enddate) DOY,
  DATEDIFF(day,           @startdate,   @enddate) Day,
  DATEDIFF(week,          @startdate,   @enddate) Week,
  DATEDIFF(hour,          @startdate,   @enddate) Hour,
  DATEDIFF(minute,        @startdate,   @enddate) Minute,
  DATEDIFF(second,        @startdate,   @enddate) Second,
  DATEDIFF(millisecond,   @startdate,   @enddate) Millisecond,
  DATEDIFF(microsecond,   @startdate,   @enddate) Microsecond,
  DATEDIFF(nanosecond,    @startdate,   @enddate) Nanosecond;

Resultaten (weergegeven met verticale uitvoer):

Year        | 1
Quarter     | 1
Month       | 1
DOY         | 1
Day         | 1
Week        | 1
Hour        | 1
Minute      | 1
Second      | 1
Millisecond | 1
Microsecond | 1
Nanosecond  | 100

Er is slechts honderd nanoseconden (.0000001 seconde) verschil tussen de twee datums/tijden, maar we krijgen precies hetzelfde resultaat voor elk datumdeel, behalve nanoseconden.

Hoe kan dit gebeuren? Hoe kan het tegelijkertijd 1 microseconde verschil en 1 jaar verschil zijn? Om nog maar te zwijgen van alle tussenliggende dateparts?

Het lijkt misschien gek, maar dit is ook geen bug. Deze resultaten zijn precies in overeenstemming met hoe DATEDIFF() hoort te werken.

En om het nog verwarrender te maken, kunnen we verschillende resultaten krijgen, afhankelijk van het gegevenstype. Maar daar komen we snel achter. Laten we eerst eens kijken hoe de DATEDIFF() functie werkt echt.

De werkelijke definitie van DATEDIFF()

De reden dat we de resultaten krijgen die we hebben, is omdat de DATEDIFF() functie is als volgt gedefinieerd:

Deze functie retourneert de telling (als een geheel getal met teken) van de opgegeven datumdeelgrenzen die zijn overschreden tussen de opgegeven startdatum en einddatum .

Besteed bijzondere aandacht aan de woorden "datumdeelgrenzen overschreden". Dit is de reden waarom we de resultaten krijgen die we in de vorige voorbeelden doen. Het is gemakkelijk om aan te nemen dat DATEDIFF() gebruikt de verstreken tijd voor zijn berekeningen, maar doet dat niet. Het gebruikt het aantal gekruiste datumpartgrenzen.

In het eerste voorbeeld overschreden de datums geen jaardeelgrenzen. Het jaar van de eerste date was precies hetzelfde als het jaar van de tweede date. Er werden geen grenzen overschreden.

In het tweede voorbeeld hadden we het tegenovergestelde scenario. De datums overschreden elke datumdeelgrens minstens één keer (100 keer voor nanoseconden).

Voorbeeld 3 – Een ander resultaat voor de week

Laten we nu doen alsof er een heel jaar voorbij is. En hier zijn we precies een jaar later met de datum/tijd-waarden, behalve dat de jaarwaarden met één zijn opgehoogd.

We zouden dezelfde resultaten moeten krijgen, toch?

DECLARE @startdate datetime2 = '2017-12-31 23:59:59.9999999', 
  @enddate datetime2 = '2018-01-01 00:00:00.0000000';

SELECT DATEDIFF(year,     @startdate,   @enddate) Year,
  DATEDIFF(quarter,       @startdate,   @enddate) Quarter,
  DATEDIFF(month,         @startdate,   @enddate) Month,
  DATEDIFF(dayofyear,     @startdate,   @enddate) DOY,
  DATEDIFF(day,           @startdate,   @enddate) Day,
  DATEDIFF(week,          @startdate,   @enddate) Week,
  DATEDIFF(hour,          @startdate,   @enddate) Hour,
  DATEDIFF(minute,        @startdate,   @enddate) Minute,
  DATEDIFF(second,        @startdate,   @enddate) Second,
  DATEDIFF(millisecond,   @startdate,   @enddate) Millisecond,
  DATEDIFF(microsecond,   @startdate,   @enddate) Microsecond,
  DATEDIFF(nanosecond,    @startdate,   @enddate) Nanosecond;

Resultaten:

Year        | 1
Quarter     | 1
Month       | 1
DOY         | 1
Day         | 1
Week        | 0
Hour        | 1
Minute      | 1
Second      | 1
Millisecond | 1
Microsecond | 1
Nanosecond  | 100

Verkeerd.

De meeste zijn hetzelfde, maar deze keer keerde de week terug 0 .

Huh?

Dit gebeurde omdat de invoerdatums dezelfde kalender hebben week waarden. Het toeval wilde dat de datums die voor bijvoorbeeld 2 werden gekozen, verschillende kalenderweekwaarden hadden.

Om specifieker te zijn, voorbeeld 2 overschreed de grenzen van de weekdelen gaande van '2016-12-31' tot '2017-01-01'. Dit komt omdat de laatste week van 2016 eindigde op 31-12-2016 en de eerste week van 2017 begon op 01-01-2017 (zondag).

Maar in voorbeeld 3 begon de eerste week van 2018 eigenlijk op onze startdatum van 2017-12-31 (zondag). Onze einddatum, de volgende dag, viel in dezelfde week. Daarom werden er geen grenzen van weekdelen overschreden.

Dit veronderstelt uiteraard dat zondag de eerste dag van elke week is. Het blijkt dat de DATEDIFF() functie doet neem aan dat zondag de eerste dag van de week is. Het negeert zelfs je SET DATEFIRST instelling (met deze instelling kunt u expliciet aangeven welke dag als de eerste dag van de week wordt beschouwd). De redenering van Microsoft voor het negeren van SET DATEFIRST is dat het ervoor zorgt dat de DATEDIFF() functie is deterministisch. Hier is een oplossing als dit een probleem voor u is.

Dus in een notendop, uw resultaten kunnen er "verkeerd" uitzien voor elk datumdeel, afhankelijk van de datums/tijden. Je resultaten kunnen er extra verkeerd uitzien als je het weekgedeelte gebruikt. En ze kunnen er nog meer verkeerd uitzien als je een SET DATEFIRST . gebruikt waarde anders dan 7 (voor zondag) en je verwacht DATEDIFF() om dat te eren.

Maar de resultaten zijn niet verkeerd, en het is geen bug. Het is gewoon meer een "gotcha" voor degenen die niet weten hoe de functie eigenlijk werkt.

Al deze valkuilen zijn ook van toepassing op de DATEDIFF_BIG() functie. Het werkt hetzelfde als DATEDIFF() met de uitzondering dat het resultaat wordt geretourneerd als een ondertekende bigint (in tegenstelling tot een int voor DATEDIFF() ).

Voorbeeld 4 – Resultaten zijn afhankelijk van gegevenstype

U kunt ook onverwachte resultaten krijgen vanwege het gegevenstype dat u gebruikt voor uw invoerdatums. Resultaten zullen vaak verschillen, afhankelijk van het gegevenstype van de invoerdatums. Maar je kunt DATEDIFF() niet de schuld geven hiervoor, omdat het puur te wijten is aan de mogelijkheden en beperkingen van de verschillende gegevenstypen. Je kunt niet verwachten dat je hoge precisie resultaten krijgt met een lage precisie invoerwaarde.

Bijvoorbeeld, wanneer de startdatum of einddatum een ​​smalldatetime . heeft waarde, zullen de seconden en milliseconden altijd 0 retourneren. Dit komt omdat de smalldatetime gegevenstype is tot op de minuut nauwkeurig.

Dit is wat er gebeurt als we voorbeeld 2 omschakelen naar smalldatetime in plaats van datetime2 :

DECLARE @startdate smalldatetime = '2016-12-31 23:59:59', 
  @enddate smalldatetime = '2017-01-01 00:00:00';

SELECT DATEDIFF(year,     @startdate,   @enddate) Year,
  DATEDIFF(quarter,       @startdate,   @enddate) Quarter,
  DATEDIFF(month,         @startdate,   @enddate) Month,
  DATEDIFF(dayofyear,     @startdate,   @enddate) DOY,
  DATEDIFF(day,           @startdate,   @enddate) Day,
  DATEDIFF(week,          @startdate,   @enddate) Week,
  DATEDIFF(hour,          @startdate,   @enddate) Hour,
  DATEDIFF(minute,        @startdate,   @enddate) Minute,
  DATEDIFF(second,        @startdate,   @enddate) Second,
  DATEDIFF(millisecond,   @startdate,   @enddate) Millisecond,
  DATEDIFF(microsecond,   @startdate,   @enddate) Microsecond,
  DATEDIFF(nanosecond,    @startdate,   @enddate) Nanosecond;

Resultaat:

Year        | 0
Quarter     | 0
Month       | 0
DOY         | 0
Day         | 0
Week        | 0
Hour        | 0
Minute      | 0
Second      | 0
Millisecond | 0
Microsecond | 0
Nanosecond  | 0

De reden dat deze allemaal nul zijn, is omdat beide invoerdatums eigenlijk identiek zijn:

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

Resultaat:

+---------------------+---------------------+
| Start Date          | End Date            |
|---------------------+---------------------|
| 2017-01-01 00:00:00 | 2017-01-01 00:00:00 |
+---------------------+---------------------+

De beperkingen van de smalldatetime gegevenstype zorgde ervoor dat de seconden naar boven werden afgerond, wat vervolgens een stroom veroorzaakte en alles naar boven werd afgerond. Zelfs als u geen identieke invoerwaarden krijgt, kunt u toch een onverwacht resultaat krijgen omdat het gegevenstype niet de gewenste precisie biedt.


  1. Is er een manier om meerdere triggers in één script te maken?

  2. Hoe ORA-29280 op te lossen:ongeldig mappad

  3. Fixing Lock wacht time-out overschreden; probeer de transactie opnieuw te starten voor een 'vastgelopen Mysql-tabel?

  4. Verkrijg de naam van de DB-eigenaar in PostgreSql