Jeśli otrzymujesz naprawdę dziwne wyniki podczas korzystania z DATEDIFF()
funkcji w SQL Server i jesteś przekonany, że funkcja zawiera błąd, jeszcze nie wyrywaj sobie włosów. To prawdopodobnie nie jest błąd.
Istnieją scenariusze, w których wyniki uzyskane przez tę funkcję mogą być dość zwariowane. A jeśli nie rozumiesz, jak faktycznie działa ta funkcja, wyniki będą wyglądać zupełnie niepoprawnie.
Mam nadzieję, że ten artykuł pomoże wyjaśnić, w jaki sposób DATEDIFF()
funkcja została zaprojektowana do pracy i przedstawia kilka przykładowych scenariuszy, w których wyniki mogą nie być zgodne z oczekiwaniami.
Przykład 1 – 365 dni to nie zawsze rok
Pytanie: Kiedy jest 365 dni nie rok?
Odpowiedź: Używając DATEDIFF()
oczywiście!
Oto przykład, w którym używam DATEDIFF()
aby zwrócić liczbę dni między dwiema datami, a następnie liczbę lat między tymi samymi dwiema datami.
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;
Wynik:
+--------+---------+ | Days | Years | |--------+---------| | 365 | 0 | +--------+---------+
Jeśli uważasz, że ten wynik jest błędny, a DATEDIFF()
oczywiście ma błąd, czytaj dalej – nie wszystko jest takie, jak się wydaje.
Wierzcie lub nie, ale jest to właściwie oczekiwany rezultat. Ten wynik jest dokładnie zgodny z metodą DATEDIFF()
jest przeznaczony do pracy.
Przykład 2 – 100 nanosekund =1 rok?
Spójrzmy na to w inny sposób.
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;
Wyniki (pokazane z wyjściem pionowym):
Year | 1 Quarter | 1 Month | 1 DOY | 1 Day | 1 Week | 1 Hour | 1 Minute | 1 Second | 1 Millisecond | 1 Microsecond | 1 Nanosecond | 100
Jest tylko sto nanosekund (0,0000001 sekundy) różnicy między tymi dwiema datami/godzinami, ale otrzymujemy dokładnie ten sam wynik dla każdej daty, z wyjątkiem nanosekund.
Jak to się może stać? Jak to może być różnica 1 mikrosekundy i różnica 1 roku w tym samym czasie? Nie wspominając o wszystkich datach pomiędzy nimi?
To może wydawać się szalone, ale to też nie jest błąd. Te wyniki są dokładnie zgodne z metodą DATEDIFF()
ma działać.
Aby było jeszcze bardziej zagmatwać, możemy uzyskać różne wyniki w zależności od typu danych. Ale wkrótce do tego dojdziemy. Najpierw spójrzmy, jak DATEDIFF()
funkcja faktycznie działa.
Rzeczywista definicja funkcji DATEDIFF()
Powodem, dla którego otrzymujemy wyniki, które robimy, jest to, że DATEDIFF()
funkcja jest zdefiniowana w następujący sposób:
Ta funkcja zwraca liczbę (jako liczbę całkowitą ze znakiem) określonych granic części daty przekroczonych między określoną data rozpoczęcia i data końcowa .
Zwróć szczególną uwagę na słowa „przekroczono granice randki”. Dlatego otrzymujemy wyniki, które osiągnęliśmy w poprzednich przykładach. Łatwo założyć, że DATEDIFF()
wykorzystuje czas do swoich obliczeń, ale tak nie jest. Wykorzystuje liczbę przekroczonych granic daty.
W pierwszym przykładzie daty nie przekraczały żadnych granic rocznych. Rok pierwszej randki był dokładnie taki sam jak rok drugiej randki. Żadne granice nie zostały przekroczone.
W drugim przykładzie mieliśmy odwrotny scenariusz. Daty przekroczyły każdą granicę datepart co najmniej raz (100 razy przez nanosekundy).
Przykład 3 – inny wynik tygodnia
Teraz załóżmy, że minął cały rok. I tutaj jesteśmy dokładnie rok później z wartościami daty/czasu, z wyjątkiem tego, że wartości roku wzrosły o jeden.
Powinniśmy uzyskać takie same wyniki, prawda?
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;
Wyniki:
Year | 1 Quarter | 1 Month | 1 DOY | 1 Day | 1 Week | 0 Hour | 1 Minute | 1 Second | 1 Millisecond | 1 Microsecond | 1 Nanosecond | 100
Źle.
Większość z nich jest taka sama, ale tym razem tydzień zwrócił 0
.
Co?
Stało się tak, ponieważ daty wejściowe mają ten sam tydzień kalendarzowy wartości. Tak się złożyło, że wybrane daty, na przykład 2, miały różne wartości tygodnia kalendarzowego.
Mówiąc dokładniej, przykład 2 przekroczył granice części tygodnia od „31.12.2016” do „01.01.2017”. Dzieje się tak dlatego, że ostatni tydzień 2016 roku zakończył się 31.12.2016, a pierwszy tydzień 2017 rozpoczął się 01.01.2017 (niedziela).
Ale w przykładzie 3 pierwszy tydzień 2018 roku zaczął się w dniu rozpoczęcia 31.12.2017 (niedziela). Nasza data końcowa, czyli dzień następny, wypadła w tym samym tygodniu. Dlatego nie przekroczono granic tygodnia.
To oczywiście zakłada, że niedziela jest pierwszym dniem każdego tygodnia. Jak się okazuje, DATEDIFF()
funkcja robi załóżmy, że niedziela jest pierwszym dniem tygodnia. Nawet ignoruje twój SET DATEFIRST
ustawienie (to ustawienie pozwala jednoznacznie określić, który dzień jest uważany za pierwszy dzień tygodnia). Rozumowanie Microsoftu dotyczące ignorowania SET DATEFIRST
jest to, że zapewnia DATEDIFF()
funkcja jest deterministyczna. Oto obejście, jeśli jest to dla Ciebie problem.
Krótko mówiąc, Twoje wyniki mogą wyglądać „niewłaściwie” dla dowolnej daty w zależności od dat/godzin. Twoje wyniki mogą wyglądać wyjątkowo źle podczas korzystania z części tygodnia. I mogą wyglądać jeszcze bardziej źle, jeśli użyjesz SET DATEFIRST
wartość inna niż 7 (dla niedzieli) i oczekujesz DATEDIFF()
aby to uhonorować.
Ale wyniki nie są błędne i nie jest to błąd. To po prostu więcej „gotcha” dla tych, którzy nie wiedzą, jak ta funkcja faktycznie działa.
Wszystkie te błędy dotyczą również DATEDIFF_BIG()
funkcjonować. Działa tak samo jak DATEDIFF()
z wyjątkiem tego, że zwraca wynik jako podpisany bigin (w przeciwieństwie do int dla DATEDIFF()
).
Przykład 4 – Wyniki zależą od typu danych
Możesz również uzyskać nieoczekiwane wyniki ze względu na typ danych używany do dat wprowadzania danych. Wyniki często będą się różnić w zależności od typu danych wejściowych. Ale nie możesz winić DATEDIFF()
w tym celu, ponieważ wynika to wyłącznie z możliwości i ograniczeń różnych typów danych. Nie można oczekiwać uzyskania wyników o wysokiej precyzji z wartości wejściowych o niskiej precyzji.
Na przykład, gdy data rozpoczęcia lub data zakończenia ma smalldatetime wartość, sekundy i milisekundy zawsze zwracają 0. Dzieje się tak, ponieważ smalldatetime typ danych jest dokładny tylko co do minuty.
Oto, co się stanie, jeśli przełączymy przykład 2 na użycie smalldatetime zamiast 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;
Wynik:
Year | 0 Quarter | 0 Month | 0 DOY | 0 Day | 0 Week | 0 Hour | 0 Minute | 0 Second | 0 Millisecond | 0 Microsecond | 0 Nanosecond | 0
Powodem, dla którego wszystkie te wartości są zerowe, jest fakt, że obie daty wprowadzenia są w rzeczywistości identyczne:
DECLARE @startdate smalldatetime = '2016-12-31 23:59:59', @enddate smalldatetime = '2017-01-01 00:00:00'; SELECT @startdate 'Start Date', @enddate 'End Date';
Wynik:
+---------------------+---------------------+ | Start Date | End Date | |---------------------+---------------------| | 2017-01-01 00:00:00 | 2017-01-01 00:00:00 | +---------------------+---------------------+
Ograniczenia smalldatetime typ danych spowodował, że sekundy zostały zaokrąglone w górę, co z kolei spowodowało efekt przepływu i wszystko zostało zaokrąglone w górę. Nawet jeśli nie uzyskasz identycznych wartości wejściowych, nadal możesz uzyskać nieoczekiwany wynik, ponieważ typ danych nie zapewnia wymaganej precyzji.