Lekcja w formie wideo

Wstęp

Punkt w czasie w zapytaniach SQL reprezentowany jest jako specyficznie sformatowany łańcuch znaków. O tym jakimi narzędziami przekształcić zrozumiały dla człowieka napis w akceptowaną dla motoru bazy danych wartość oraz o tym jak sformatować otrzymaną z bazy danych wartość w przyjazny dla człowieka napis jest niniejsza lekcja.

Najprościej

Najprostsze wyszukiwanie wg daty to przyrównanie literału do kolumny o datoczasowym typie. Skąd wiedzieć jak formatować literał aby Oracle zaakceptował go jako datę? Domyślny format odczytasz z parametrów Twojej sesji. Parametry sesji jawnie lub nie ustawiane są przez Twojego klienta.

SELECT
  *
FROM
  nls_session_parameters
WHERE
  parameter like 'NLS_DATE%';

Parametr NLS_DATE_FORMAT wskazuje, że format daty obowiązujący dla mojej sesji to dzień określony dwoma cyframi. Miesiąc wskazany trzyliterowym skrótem składającym się z dużych liter. I rok wskazany dwoma cyframi.

Ustawienia narodowe sesji dla typu DATE

Parametr NLS_DATE_LANGUAGE wskazuje, że Oracle oczekuje nazwy miesiąca w języku angielskim. Czyli takie zapytanie wybierze z bazy danych nazwy obiektów utworzonych po 20 maja 1972 roku.

SELECT
  object_name
FROM
  all_objects
WHERE
  created>'20-MAY-72';

Jeśli Oracle nie poradzi sobie z niejawną konwersją literału do typu datoczasowego dostaniesz błąd.

ORA-01861 błąd zwracany gdy literał nie pasuje do formatu daty

Dla zrzutów ekranu używałem Oracle 19c dostępnego w przeglądarce internetowej na Oracle Live SQL.

Typy datoczasowe

Do obsługi punktu w czasie wykorzystywane są dwa typy o różnej dokładności i zakresie. Oba obsługują dane składające się zarówno z daty jak i czasu.

  • jeden to typ date o dokładności jednej sekundy
  • drugi to timestamp o dokładności nanosekundy

O zakresach będę mówił w kursie o projektowaniu bazy danych. W komentarzach daj wyraz swojej potrzebie zaliczenia takiego kursu. Typ timestamp może przechowywać strefę czasową. Wtedy nazywa się timestamp with time zone lub timestamp with local time zone.

Dokładność typów datoczasowych w Oracle

W innych motorach baz danych możesz spotkać się z osobnym typem dla daty i osobnym dla czasu.

Komponent czasowy

W Oracle punkt w czasie wskazany z pominięciem części czasowej zostanie ustawiony na północ. Czyli w moim przykładzie będzie to północ dwudziestego maja 1972 roku.

'20-MAY-72' = '20-MAY-1972 00:00:00'

Warto o tym pamiętać.

Zwróć uwagę, że ustawienie formatu daty dla mojej sesji nie zawiera części czasowej. A Ty wiesz, że dane zawierają komponent czasowy. Sprawdź co się stanie gdy do literału dopiszesz część czasową.

SELECT
  object_name
FROM
  all_objects
WHERE
  created>'20-MAY-72 12:13:14';

Pełną listę elementów formatu daty znajdziesz w dokumentacji.

Modyfikacja domyślnego formatu

Modyfikując parametr NLS_DATE_FORMAT dla swojej sesji mówisz Oracle w jaki sposób ma dokonać niejawnej konwersji literału do typu date.

ALTER SESSION
  SET NLS_DATE_FORMAT='yyyy-mm-dd hh24:mi:ss';

Teraz będzie oczekiwał czterocyfrowego roku, dwucyfrowego miesiąca i dwucyfrowego dnia poodzielanych myślnikami. Będzie także oczekiwał części czasowej w formacie dwudziestoczterogodzinnym, dwucyfrowych minut i dwucyfrowych sekund poodzielanych dwukropkami. Ustaw parametr swojej sesji i ponów zapytanie.

SELECT
  object_name
FROM
  all_objects
WHERE
  created>'1972-MAY-20 12:13:14';

Format możesz ubogacić własnymi treściami. Pod warunkiem, że użyjesz cudzysłowu.

ALTER SESSION
  SET NLS_DATE_FORMAT=' "Data Marcina:" yyyy-mm-dd hh24:mi:ss';

Format typu timestamp ustawiany jest według identycznych zasad osobnymi parametrami sesji.

SELECT
  *
FROM
  nls_session_parameters
WHERE
  parameter like 'NLS_TIMESTAMP%';

Pamiętaj, że ustawienia formatu daty jedynie instruują motor jak ma interpretować przekazywane mu literały oraz formatować zwracane przez niego dane. W bazie danych jest to zawsze ten sam ciąg bajtów.

Ustawienia narodowe sesji dla typu TIMESTAMP

ANSI konwersja literałów do typów datoczasowych

Jawnej konwersji literału do typu datoczasowego dokonasz wykorzystując słowa kluczowe DATE i TIMESTAMP ze standardu ANSI języka SQL.

DATE konwertuje literały w formacie czterocyfrowy rok, myślnik, dwucyfrowy miesiąc, myślnik, dwucyfrowy dzień do typu date.

Konwersja literału datoczasowego komendą DATE ze standardu ANSI języka SQL

Zwróć uwagę, że nie możesz podać części czasowej. Pamiętaj, że w bazie danych Oracle część czasowa zapisana jest zawsze. Dlatego gdy chcesz wyszukać rekordy z danego dnia używaj przyrównania jedynie wtedy gdy masz pewność, że w bazie danych część czasowa jest wyzerowana.

SELECT
  object_name
FROM
  all_objects
WHERE
  created=DATE'2022-05-20';

W przypadkach gdy część czasowa danych jest różna od północy wskazuj zakres.

SELECT
  object_name
FROM
  all_objects
WHERE
  created>=DATE'2022-05-20'
AND created<DATE'2022-05-21';

Zwróci nazwy obiektów utworzonych dwudziestego maja 2020 roku.

TIMESTAMP konwertuje literały w formacie czterocyfrowy rok, myślnik, dwucyfrowy miesiąc, myślnik, dwucyfrowy dzień, spacja, godzina w formacie dwudziestoczterogodzinnym, dwukropek, minuty, dwukropek, sekundy, kropka, dziewięć cyfr części ułamkowej do typu timestamp.

Konwersja literału datoczasowego komendą TIMESTAMP ze standardu ANSI języka SQL

Możesz wykorzystać do przyrównania części czasowej dla kolumn typu timestamp jak i date. W przypadku kolumn typu date Oracle dokona niejawnej konwersji. Trzeba pamiętać, że wtedy obetnie część ułamkową sekundy oraz strefę czasową. Jeśli nie interesują Cię dane z całego dnia, a tylko z określonej godziny wtedy wykorzystaj słowo kluczowe TIMESTAMP:

SELECT
  object_name
FROM
  all_objects
WHERE
  timestamp>=TIMESTAMP'2022-05-20 13:00:00'
AND timestamp<TIMESTAMP'2022-05-20 14:00:00';

Może nie zwrócić żadnych danych. Teraz samodzielnie dostosuj warunki aby zapytania zwróciły dane.

Natywna konwersja literałów do typów datoczasowych

Natywne funkcje konwertujące literały do typów date i timestamp to odpowiednio to_date i to_timestamp. Każda z tych funkcji przyjmuje trzy parametry.

  • pierwszy z nich to punkt w czasie w postaci literału, który ma być zamieniony na dane typu datoczasowego
  • drugi parametr to format mówiący motorowi bazy danych jak ma interpretować literał
  • trzeci to ustawienia narodowe według których interpretować literał

Jeśli pominiesz drugi i trzeci to Oracle użyje parametrów sesji.

Warto zwrócić uwagę, że w odróżnieniu od sztywnej konwersji narzędziami ANSI natywne funkcje konwertujące wybaczają literówki.

ALTER SESSION
  SET NLS_DATE_FORMAT='yyyy-mm-dd hh24:mi:ss';

SELECT
  to_date('20/05-2022 13:24.55', 'DD-MM-YYYY HH24:MI:SS')
  ,to_timestamp('20-05/2022 13:24,55.1234', 'DD-MM-YYYY HH24:MI:SS.FF')
FROM
  dual;

Mimo literówek – użyłem łamańca zamiast myślnika w dacie oraz kropki i przecinka zamiast dwukropka w czasie – Oracle skonwertował literał bezbłędnie.

Bezbłędna konwersja literałów datoczasowych natywnymi funkcjami to_date i to_timestamp

Może się zdarzyć, że zamiast numeru miesiąca czy dnia tygodnia dysponujesz jego nazwą. Niezależnie od języka w którym ta nazwa występuje Oracle potrafi skonwertować taki literał na dane typu datoczasowego.

Trzecim parametrem mówisz mu w jakim języku otrzyma nazwę. Gdy tego nie zrobisz weźmie tę informację z ustawień sesji.

ALTER SESSION
  SET NLS_DATE_FORMAT='yyyy-mm-dd hh24:mi:ss';

SELECT
   to_date('maj','MONTH','NLS_DATE_LANGUAGE=Polish')
  ,to_date('cze','MON','NLS_DATE_LANGUAGE=Polish')
  ,to_timestamp('wtorek','day','NLS_DATE_LANGUAGE=Polish') -- użyj bieżącego dnia tygodnia
  ,to_timestamp('wt','dy','NLS_DATE_LANGUAGE=Polish') -- użyj bieżącego dnia tygodnia
FROM
  dual;

Możesz używać skrótów nazw.

Funkcje zwracające aktualny czas

Udostępniane przez Oracle funkcje zwracające aktualny czas różnią się zwracanym typem, wrażliwością na strefę czasową i źródłem czasu.

  • current_date zwraca typ date w strefie czasowej sesji. Czyli to co ma ustawione klient.
  • current_timestamp zwraca timestamp with time zone w strefie czasowej sesji. Czyli według ustawień klienta.
  • localtimestamp zwraca timestamp bez strefy czasowej, ale z jej uwzględnieniem. Czyli to co jest ustawione na kliencie.
  • sysdate zwraca typ date według ustawień hosta na którym pracuje instancja bazy danych
  • systimestamp zwraca timestamp with time zone według ustawień hosta na którym pracuje instancja bazy danych.
ALTER SESSION
  SET NLS_DATE_FORMAT='yyyy-mm-dd hh24:mi:ss';

SELECT
  current_date "czas sesji"
  ,current_timestamp "stempel sesji ze strefą"
  ,localtimestamp "stempel sesji bez strefy"
  ,sysdate "czas hosta"
  ,systimestamp "stempel hosta ze strefą"
FROM
  dual;

Formatowanie danych datoczasowych

Do jawnego przekształcenia danych datoczasowych w przyjazny człowiekowi napis służy przyjmująca trzy argumenty funkcja to_char.

  • pierwszym jest wartość datoczasowa
  • drugim docelowy format napisu
  • trzeci ustawia język w jakim mają być prezentowane nazwy dni tygodnia lub miesięcy
SELECT
  to_char(DATE'1972-05-20','day','NLS_DATE_LANGUAGE=Polish') "dzień tygodnia"
  ,to_char(DATE'1972-05-20','DL','NLS_DATE_LANGUAGE=Polish') "długi format daty"
FROM
  dual;

Ja urodziłem się w majową sobotę. Sprawdź w jaki dzień tygodnia przypadły Twoje narodziny.

Dzień urodzin Marcina

Zauważ, że funkcja to_char może zwrócić jedynie potrzebny fragment daty. Sprawdź co się stanie gdy argument 'day’ napiszesz dużymi literami. Albo jedynie litera 'd’ będzie duża.

Na wzór mojego napisz zapytanie, które zwróci miesiąc Twoich urodzin w języku francuskim.

Do wycinania fragmentów daty służą dwie dedykowane funkcje. Obie zwracają typ datoczasowy.

  • trunc domyślnie zwraca datę z pierwszego argumentu obciętą z dokładnością dnia. Czyli część czasowa ustawiona jest na północ. Drugim argumentem możesz wskazać inną część daty.
  • round zwraca datę z pierwszego argumentu zaokrągloną do najbliższego dnia. Chyba, że drugim argumentem zaznaczysz inne zaokrąglenie.
ALTER SESSION
  SET NLS_DATE_FORMAT='yyyy-mm-dd hh24:mi:ss';

SELECT
  trunc(DATE'2021-11-12', 'YEAR') "data obcięta do roku"
  ,round(DATE'2021-11-12', 'YEAR') "data zaokrąglona do roku"
  ,trunc(TIMESTAMP'2021-11-12 13:44:15', 'HH') "data obcięta do godziny"
  ,round(TIMESTAMP'2021-11-12 13:44:15', 'HH') "data zaokrąglona do godziny"
FROM
  dual;

Wszystkie możliwe modele formatu znajdziesz w dokumentacji.

Arytmetyka datoczasowa

Oracle przechowuje dane typu date jako liczbę dni. Dlatego przesuniesz datę w czasie dodając lub odejmując odpowiednią liczbę.

Liczba jaką należy dodać lub odjąć od wartości typu DATE lub TIMESPAMP aby przesunąć datę o żądaną ilość czasu.
SELECT
  sysdate-1 "ta sama godzina wczoraj"
  ,sysdate+1 "ta sama godzina jutro"
FROM
  dual;

Gdy od daty odejmiesz datę otrzymasz liczbę oznaczającą liczbę dzielących je dni.

SELECT
  sysdate-DATE'1972-05-20' "liczba dni od narodzin Marcina"
FROM
  dual;

Miesiące mają różną liczbę dni, więc arytmetyka jest bardziej skomplikowana. W sukurs przychodzą dedykowane miesiącom funkcje:

  • add_months dodaje określoną w argumencie drugim liczbę miesięcy do daty z pierwszego argumentu. Zwraca typ date.
  • months_between odejmuje datę z argumentu drugiego od daty z argumentu pierwszego. Zwraca liczbę miesięcy.

Część ułamkowa obliczana jest przy założeniu, że miesiąc ma 31 dni.

SELECT
  to_char(add_months(date'1972-05-20',-9), 'yyyy-mm-dd') "poczęcie Marcina"
  ,to_char(add_months(date'1972-05-31',1), 'yyyy-mm-dd') "ostatni dzień miesiąca"
  ,months_between(date'2022-05-20', date'2022-04-20') "dokładnie miesiąc"
  ,months_between(date'2022-05-20', date'2022-04-19') "miesiąc+1 dzień"
FROM
  dual;

Czyli pomiędzy datami 20 maja 2022, a 20 kwietnia 2022 jest dokładnie 1 miesiąc różnicy. Oracle uwzględni różną liczbę dni w miesiącach.

Natomiast pomiędzy 20 maja 2022, a 19 kwietnia 2022 jest 1 miesiąc i 1 dzień. Ten jeden dzień zostanie pokazany jako 1/31 miesiąca.

Więcej na temat arytmetyki datoczasowej w nadchodzącej lekcji o odcinkach czasu.

Wydajność

Przed wysłaniem danych do klienta Oracle formatuje dane datoczasowe w napis. Warto zadbać aby format był taki jak potrzebujesz aby nie było potrzeby przeformatowania w aplikacji.

Gdy używasz funkcji przekształcających w klauzuli WHERE zadbaj aby parametrem był literał, a nie kolumna. Użycie przekształcenia na kolumnie zaowocuje wykonaniem go na wszystkich rekordach tabeli oraz uniemożliwi użycie indeksu. Przekształcenie literału zostanie wykonane jedynie raz.

Aby uzyskać optymalną wydajność funkcji w klauzuli WHERE używaj na literale zamiast kolumnie

Zniechęcam do polegania na konwersji niejawnej.

Podsumowanie

Gratuluję Ci ukończenia osiemnastej lekcji darmowej edycji mojego kursu 'Podstawy SQL dla użytkowników Oracle’. Wiesz z niej, jak formatować i przekształcać dane datoczasowe aby były zrozumiałe dla Oracle i przyjazne dla człowieka. Wiesz także na co zwrócić uwagę aby wyszukiwanie danych datoczasowych było efektywne.

Zadanie dla Ciebie: napisz i uruchom zapytanie zwracające nazwę dnia tygodnia Twoich przyszłorocznych imienin w języku hiszpańskim. Jeśli potrzebujesz podpowiedzi wbijaj na discord. Tam też chwal się swoim rozwiązaniem prezentując zrzut ekranu swojego klienta. Dedykowany serwer: Marcin Badtke – bazy danych i SQL.

Wiesz już jak używać słów kluczowych date i timestamp do przekształcania literału zawierającego datę w wartość typu datoczasowego. Wiesz także jak wykorzystać funkcje przekształcające do pracy z danymi datoczasowymi. W następnej lekcji zaprezentuję obsługę odcinków czasu.

Tymczasem dziękuję za ukończenie niniejszej.

Wiesz co trzeba zrobić aby nie przegapić kolejnych.

Gratuluję Ci ukończenia osiemnastej lekcji darmowej edycji mojego kursu 'Podstawy SQL dla użytkowników Oracle’. Skomentuj poniżej jak Ci się podobała.

Jak obsługiwać w bazie danych Oracle odcinki czasu? Pokażę Ci w następnej lekcji. Chodź.

Idź do następnej lekcji

Wróć do poprzedniej lekcji

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *