Odczytanie, na podstawie daty, który to tydzień roku

Wczoraj wybrałem się na Politechnikę Gdańską na konferencję Windows 8 Community Launch organizowaną przez Trójmiejska Zawodowa Grupa .NET oraz Grupa .NET PG. Poszedłem na prezentację Cezarego Ołtuszyka (blog) „Co nowego w SQL Server 2012 dla programistów”. Wystąpienie naprawdę fajne, poruszające wiele z nowości nowego SQLa. Co warte wzmianki – Cezary prowokował do szerokiej dyskusji. Były więc dyskusje o plusach i minusach indeksów typu COLUMNSTRORE, było pytanie o nowe feature`y w bazach Sybase`a :), były też pytania na które nie znaleźliśmy od razu odpowiedzi…

Przykładowo – w jaki sposób z daty wydobyć informację który to tydzień roku? Zaraz, zaraz… jak to było? yyyy… no była taka funkcja i to wcale nie nowy feature SQL Servera 2012.. ale jak się ona nazywała? Może więc warto opisać ją na blogu:

Odczytanie na podstawie daty który to tydzień roku

Funkcja która to umożliwia to: DATEPART(symbol,data) z symbolem „wk” lub „ww” lub „week” opis MSDN

Popatrzmy na przykład dla dnia konferencji:
DECLARE @sobota date = ’2012-05-19′;
SELECT @sobota , DATEPART(wk,@sobota);

Dostajemy:

Proste? Wydawałoby się że tak, ba – banalne. Ale nie tak prędko.

Rzecz 1 – zrozumienie jak działa funkcja DATEPART

Weźmy sobie tydzień w którym kończy się rok 2012:

31 wypada w poniedziałek. Wtorek należy już do kolejnego roku. W zależności od daty (konkretnie roku) otrzymamy więc różne wyniki.

Czas na przykład:
DECLARE @poniedzialek date = ’2012-12-31′;
DECLARE @wtorek date = ’2013-01-01′;
SELECT DATEPART(ww,@poniedzialek),DATEPART(ww,@wtorek);

Dostajemy:

Pytając o rok 2012 dostajemy informację, że to 53 tydzień, pytając o datę z roku 2013 dowiadujemy się, że ten sam tydzień, to pierwszy tydzień nowego roku.

Rzecz 2 – gdyby różne pojęcia miały tą samą definicję na całym świecie to byłoby nudno…

Dawno dawno temu Anglicy musieli mieć wszystko inaczej niż Francuzi i reszta Europy. A że w pewnym okresie Anglicy byli największymi kolonizatorami świata to sporo nacji, tacy przykładowi Amerykanie, wzięło miary od Anglików. Z definicją tygodnia jest nie inaczej…

Weźmy na tapetę nasz pierwszy przykład gdzie pytaliśmy do jakiego tygodnia należy sobota. Po naszemu tydzień kończy się na niedzieli, więc jeśli sobota należy do 20 tygodnia, to niedziela też powinna. Proste? Nie w produkcie, który skryptują Amerykanie:

Uruchamiając nasz przykład:
DECLARE @sobota date = ’2012-05-19′;
DECLARE @niedziela date = ’2012-05-20′;
SELECT DATEPART(week,@sobota),DATEPART(week,@niedziela);

Otrzymujemy:

Otóż Amerykanie uważają, że tydzień zaczyna się w niedzielę a kończy w sobotę. Czy słusznie czy niesłusznie nad tym nie dywaguję. Fakt, że zapytanie działa inaczej niż wskazuje nasza logika, a co gorsza również i logika większości naszych CEO i analityków…

Na szczęście „it’s not a bug it’s a feature”:

Dopiszmy jedną linijkę kodu:
SET DATEFIRST 1;
DECLARE @sobota date = ’2012-05-19′;
DECLARE @niedziela date = ’2012-05-20′;
SELECT DATEPART(week,@sobota),DATEPART(week,@niedziela);

Otrzymujemy:

Datafirst wskazuje jaki dzień (począwszy od poniedziałku) jest pierwszym w tygodniu. Domyślna wartość to 7 (niedziela) a ustawienie jej na 1 skutkuje rozumieniem tygodnia od poniedziałku do niedzieli.

Niestety często pracujemy na gotowych systemach, gdzie zmienienie parametru bazy może zepsuć funkcjonalność w X miejscach. W takim przypadku zamiast datefirst możemy posłużyć się funkcją dateadd i jako parametr do datepartu wstawić interesującą nas datę pomniejszoną o jeden:

DECLARE @sobota date = ’2012-05-19′;
DECLARE @niedziela date = ’2012-05-20′;
SELECT DATEPART(week,DATEADD(d,-1,@sobota)),DATEPART(week,DATEADD(d,-1,@niedziela));

Otrzymujemy:

Ok, to jeszcze jedno pytanie. Manipulując parametrem datefirst dostaliśmy wskazanie że sobota należy do 21 tygodnia. Zmieniając argument w dateadd mamy, że jest to 20 tydzień(!). O co więc w tym wszystkim chodzi?

SET DATEFIRST 7;
DECLARE @poniedzialek date = ’2012-01-02′;
SELECT DATEPART(week,DATEADD(d,-1,@poniedzialek));
SET DATEFIRST 1;
SELECT DATEPART(week,@poniedzialek);

Otrzymujemy:

Rok rozpoczyna się w niedzielę.

W 1 przypadku poniedziałek należy do 1 tygodnia. W drugim przypadku pierwszy tydzień zakończył się w niedzielę, a poniedziałek jest pierwszym dniem kolejnego tygodnia.

Ok, który wynik jest właściwy?

Wyczytałem (np. tu) następującą definicję:

(…)zgodnie z normą ISO 8601. Zgodnie z tym standardem pierwszy tydzień roku to tydzień, w którym występuje dzień 4 stycznia.

Podsumowując:

Odczytanie na podstawie daty, który to tydzień roku umożliwia funkcja datepart. Jednak jej wykorzystanie wcale nie jest tematem tak banalnym jakby się na pierwszy rzut oka wydawało :)

pozdrawiam,

EDIT: Cezary udostępnił swoją prezentację

Podziel się na:
  • Google Bookmarks
  • RSS
  • Dodaj do ulubionych
  • email
  • Facebook
  • Twitter
  • Blogger.com
  • LinkedIn
  • Gadu-Gadu Live