Hvis du får nogle virkelig mærkelige resultater, når du bruger DATEDIFF()
funktion i SQL Server, og du er overbevist om, at funktionen indeholder en fejl, skal du ikke rive dit hår af endnu. Det er nok ikke en fejl.
Der er scenarier, hvor resultaterne produceret af denne funktion kan være ret skøre. Og hvis du ikke forstår, hvordan funktionen rent faktisk fungerer, vil resultaterne se helt forkerte ud.
Forhåbentlig kan denne artikel hjælpe med at afklare, hvordan DATEDIFF()
funktion er designet til at fungere og giver nogle eksempler på scenarier, hvor dine resultater måske ikke er, som du ville forvente.
Eksempel 1 – 365 dage er ikke altid et år
Spørgsmål: Hvornår er 365 dage ikke et år?
Svar: Når du bruger DATEDIFF()
selvfølgelig!
Her er et eksempel, hvor jeg bruger DATEDIFF()
for at returnere antallet af dage mellem to datoer og derefter antallet af år mellem de samme to datoer.
DECLARE @startdate datetime2 ='2016-01-01 00:00:00.0000000', @enddate datetime2 ='2016-12-31 23:59:59.9999999';VÆLG DATODIFF(dag, @startdato) Dage, @slutdato , DATEDIFF(år, @startdato, @slutdato) År;
Resultat:
+--------+---------+| dage | År ||--------+---------|| 365 | 0 |+--------+---------+
Hvis du mener, at dette resultat er forkert, og at DATEDIFF()
har åbenbart en fejl, læs videre – ikke alt er, som det ser ud.
Tro det eller ej, men dette er faktisk det forventede resultat. Dette resultat er nøjagtigt i overensstemmelse med hvordan DATEDIFF()
er designet til at fungere.
Eksempel 2 – 100 nanosekunder =1 år?
Lad os tage det den anden vej.
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(kvartal, @startdato, @slutdato) Kvartal, DATEDIFF(måned, @startdato, @slutdato) Måned, DATEDIFF(årsdag, @startdato, @slutdato) DOY, DATEDIFF(dag, @startdato, @slutdato) Dag, DATEDIFF (uge, @startdato, @slutdato) Uge, DATEDIFF(time, @startdato, @slutdato) Time, DATEDIFF(minut, @startdato, @slutdato) Minut, DATEDIFF(sekund, @startdato, @slutdato) Anden, DATEDIFF(millisekund) , @startdato, @slutdato) Millisekund, DATEDIFF(mikrosekund, @startdato, @slutdato) Mikrosekund, DATEDIFF(nanosekund, @startdato, @slutdato) Nanosekund;
Resultater (vist med lodret output):
Der er kun hundrede nanosekunder (0,0000001 sekund) forskel mellem de to datoer/tidspunkter, men alligevel får vi nøjagtig det samme resultat for hver datodel, undtagen nanosekunder.
Hvordan kan dette ske? Hvordan kan det være 1 mikrosekunds forskel og 1 års forskel begge på samme tid? For ikke at nævne alle datoen imellem?
Det virker måske skørt, men dette er heller ikke en fejl. Disse resultater er nøjagtigt i overensstemmelse med DATEDIFF()
formodes at virke.
Og for at gøre tingene endnu mere forvirrende, kunne vi få forskellige resultater afhængigt af datatypen. Men det kommer vi snart til. Lad os først se på, hvordan DATEDIFF()
funktion fungerer faktisk.
Den faktiske definition af DATEDIFF()
Grunden til at vi får de resultater, vi laver, er fordi DATEDIFF()
funktion er defineret som følger:
Denne funktion returnerer antallet (som en signeret heltalværdi) af de angivne datepart-grænser krydset mellem den angivne startdato og slutdato .
Vær særlig opmærksom på ordene "datepart grænser krydset". Det er derfor, vi får de resultater, vi gør i de foregående eksempler. Det er let at antage, at DATEDIFF()
bruger forløbet tid til sine beregninger, men det gør den ikke. Den bruger antallet af grænser for datodele, der er krydset.
I det første eksempel krydsede datoerne ikke nogen årdelgrænser. Året for den første dato var nøjagtigt det samme som året for den anden dato. Ingen grænser blev overskredet.
I det andet eksempel havde vi det modsatte scenarie. Datoerne krydsede hver datepart-grænse mindst én gang (100 gange i nanosekunder).
Eksempel 3 – Et andet resultat for ugen
Lad os nu lade som om, der er gået et helt år. Og her er vi præcis et år senere med dato/tidsværdierne, bortset fra at årsværdierne er steget med én.
Vi burde få de samme resultater, ikke?
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(kvartal, @startdato, @slutdato) Kvartal, DATEDIFF(måned, @startdato, @slutdato) Måned, DATEDIFF(årsdag, @startdato, @slutdato) DOY, DATEDIFF(dag, @startdato, @slutdato) Dag, DATEDIFF (uge, @startdato, @slutdato) Uge, DATEDIFF(time, @startdato, @slutdato) Time, DATEDIFF(minut, @startdato, @slutdato) Minut, DATEDIFF(sekund, @startdato, @slutdato) Anden, DATEDIFF(millisekund) , @startdato, @slutdato) Millisekund, DATEDIFF(mikrosekund, @startdato, @slutdato) Mikrosekund, DATEDIFF(nanosekund, @startdato, @slutdato) Nanosekund;
Resultater:
Forkert.
De fleste af dem er de samme, men denne gang returnerede ugen 0
.
Hvad?
Dette skete, fordi inputdatoer har den samme kalender uge værdier. Det skete bare sådan, at de valgte datoer for eksempel 2 havde forskellige kalenderugeværdier.
For at være mere specifik, krydsede eksempel 2 grænser for ugedel fra '2016-12-31' til '2017-01-01'. Dette skyldes, at den sidste uge af 2016 sluttede den 31-12-2016, og den første uge af 2017 startede den 2017-01-01 (søndag).
Men i eksempel 3 begyndte den første uge af 2018 faktisk på vores startdato 2017-12-31 (søndag). Vores slutdato, som er den næste dag, faldt inden for samme uge. Derfor blev ingen uge-del grænser overskredet.
Dette forudsætter naturligvis, at søndag er den første dag i hver uge. Som det viser sig, er DATEDIFF()
funktion gør antag, at søndag er den første dag i ugen. Den ignorerer endda din SET DATEFIRST
indstilling (denne indstilling giver dig mulighed for eksplicit at angive, hvilken dag der anses for at være den første dag i ugen). Microsofts begrundelse for at ignorere SET DATEFIRST
er, at det sikrer DATEDIFF()
funktion er deterministisk. Her er en løsning, hvis dette er et problem for dig.
Så i en nøddeskal kan dine resultater se "forkerte" ud for enhver datodel afhængigt af datoerne/tidspunkterne. Dine resultater kan se ekstra forkerte ud, når du bruger uge-delen. Og de kunne se endnu mere forkerte ud, hvis du bruger en SET DATEFIRST
anden værdi end 7 (for søndag), og du forventer DATEDIFF()
at ære det.
Men resultaterne er ikke forkerte, og det er ikke en fejl. Det er bare mere en "gotcha" for dem, der ikke er klar over, hvordan funktionen faktisk fungerer.
Alle disse gotchas gælder også for DATEDIFF_BIG()
fungere. Det fungerer på samme måde som DATEDIFF()
med den undtagelse, at det returnerer resultatet som en signeret bigint (i modsætning til en int for DATEDIFF()
).
Eksempel 4 – Resultater afhænger af datatype
Du kan også få uventede resultater på grund af den datatype, du bruger til dine inputdatoer. Resultaterne vil ofte afvige afhængigt af inputdatoernes datatype. Men du kan ikke bebrejde DATEDIFF()
til dette, da det udelukkende skyldes de forskellige datatypers muligheder og begrænsninger. Du kan ikke forvente at få resultater med høj præcision fra en inputværdi med lav præcision.
For eksempel, når startdatoen eller slutdatoen har et smalldatetime værdi, vil sekunderne og millisekunderne altid returnere 0. Dette skyldes, at smalldatetime datatypen er kun nøjagtig til minuttet.
Her er, hvad der sker, hvis vi skifter eksempel 2 til at bruge smalldatetime i stedet for datetime2 :
DECLARE @startdate smalldatetime ='2016-12-31 23:59:59', @enddate smalldatetime ='2017-01-01 00:00:00';VÆLG DATODIFF(år, @startdato, @slutdato) År , DATEDIFF(kvartal, @startdato, @slutdato) Kvartal, DATEDIFF(måned, @startdato, @slutdato) Måned, DATEDIFF(årsdag, @startdato, @slutdato) DOY, DATEDIFF(dag, @startdato, @slutdato) Dag, DATEDIFF (uge, @startdato, @slutdato) Uge, DATEDIFF(time, @startdato, @slutdato) Time, DATEDIFF(minut, @startdato, @slutdato) Minut, DATEDIFF(sekund, @startdato, @slutdato) Anden, DATEDIFF(millisekund) , @startdato, @slutdato) Millisekund, DATEDIFF(mikrosekund, @startdato, @slutdato) Mikrosekund, DATEDIFF(nanosekund, @startdato, @slutdato) Nanosekund;
Resultat:
Grunden til at disse alle er nul, er fordi begge inputdatoer faktisk er identiske:
DECLARE @startdate smalldatetime ='2016-12-31 23:59:59', @enddate smalldatetime ='2017-01-01 00:00:00';VÆLG @startdate 'Start Date', @enddate 'End' Dato';
Resultat:
+---------------------+---------------------+| Startdato | Slutdato ||----------------------+--------------------|| 01-01-2017 00:00:00 | 01-01-2017 00:00:00 |+---------------------+---------------- -----+
Begrænsningerne for smalldatetime datatype fik sekunderne til at blive rundet op, hvilket så forårsagede et flow på effekt og alt blev rundet op. Selvom du ikke ender med identiske inputværdier, kan du stadig få et uventet resultat, fordi datatypen ikke giver den præcision, du har brug for.