Praca z datami w SQL – data bieżąca, różnica dat w latach i dniach, wyciąganie roku i miesiąca, BETWEEN na datach oraz zapis daty w warunku.
Data w bazie nie jest tekstem. Wygląda jak tekst na ekranie, ale przechowywana jest jako liczba – co ma jedną bardzo praktyczną konsekwencję: daty można od siebie odejmować, a wynikiem jest liczba dni między nimi. Na tej jednej właściwości opiera się większość zadań z datami.
Z tego samego powodu daty da się porównywać zwykłymi operatorami: data późniejsza jest „większa” od wcześniejszej. Warunek „wizyty po 16 maja” to po prostu > z odpowiednią datą po prawej stronie.
Zapis daty w warunku to najczęstsze źródło kłopotów. Nie wystarczy napisać jej w apostrofach jak tekst – trzeba powiedzieć bazie, że to data. Access wymaga otoczenia jej znakami # i zapisu w formacie amerykańskim (miesiąc, dzień, rok), niezależnie od ustawień systemu. Alternatywnie można użyć funkcji, która zamienia datę zapisaną po polsku na wewnętrzny format bazy – to zapis bezpieczniejszy, bo nie wymaga pamiętania o kolejności.
Data bieżąca jest dostępna jako funkcja, którą wywołuje się bez argumentów. Warto rozróżnić funkcję zwracającą samą datę od tej, która zwraca datę razem z godziną – przy odejmowaniu różnica potrafi zmienić wynik o jeden dzień.
Różnica dat w wybranej jednostce. Odejmowanie daje wynik w dniach. Gdy potrzebujesz lat, miesięcy czy kwartałów, służy do tego osobna funkcja, której pierwszym parametrem jest jednostka. Uwaga: liczy ona przekroczone granice jednostki, a nie pełne okresy – różnica lat między 31 grudnia a 1 stycznia wyniesie 1, mimo że minął jeden dzień.
Stąd bierze się klasyczna pułapka z liczeniem wieku. Odjęcie samych roczników daje wynik zawyżony dla wszystkich, którzy w tym roku jeszcze nie mieli urodzin. Dokładniejszy sposób to policzyć różnicę w dniach i podzielić przez średnią długość roku, a wynik obciąć do liczby całkowitej.
Wyciąganie części daty. Osobne funkcje zwracają z daty sam rok, sam miesiąc albo sam dzień. To wygodniejsze niż wycinanie fragmentów tekstu i działa niezależnie od formatu wyświetlania. Po tak wyciągniętej wartości można też grupować – na przykład zliczyć zdarzenia w poszczególnych latach.
>, <.# i kolejności miesiąc/dzień/rok, standard SQL – apostrofów i kolejności rok-miesiąc-dzień.Date()
Now()
Year(data)
Month(data)
Day(data)
Weekday(data)
DateDiff("jednostka"; od; do)
DatePart("jednostka"; data)
| W Accessie | Standardowy SQL | Znaczenie |
|---|---|---|
Date() |
CURRENT_DATE |
dzisiejsza data |
#5/16/2024# |
DATE '2024-05-16' |
data wpisana wprost w warunku |
DateDiff("d"; a; b) |
(b - a) |
różnica w dniach |
Year(data) |
EXTRACT(YEAR FROM data) |
rok z daty |
-- Data wpisana wprost: w Accessie w znakach # i w formacie amerykańskim SELECT nazwisko, data_ur FROM uczniowie WHERE data_ur > #1/1/2007#
| nazwisko | data_ur |
|---|---|
| Kowalska | 2007-03-12 |
| Wiśniewski | 2007-01-28 |
| Dąbrowski | 2007-08-30 |
-- Rok wyciągnięty funkcją Year() użyty jako warunek w WHERE SELECT nazwisko, data_ur FROM uczniowie WHERE Year(data_ur) = 2007 ORDER BY nazwisko
| nazwisko | data_ur |
|---|---|
| Dąbrowski | 2007-08-30 |
| Kowalska | 2007-03-12 |
| Wiśniewski | 2007-01-28 |
-- Uczniowie urodzeni w pierwszym półroczu roku, wg miesiąca urodzenia SELECT nazwisko, Month(data_ur) AS miesiac FROM uczniowie WHERE Month(data_ur) <= 5 ORDER BY miesiac
| nazwisko | miesiac |
|---|---|
| Wiśniewski | 1 |
| Kowalska | 3 |
| Zając | 5 |
-- Grupowanie po roku wyciągniętym z daty SELECT Year(data_ur) AS rocznik, COUNT(*) AS ilu FROM uczniowie GROUP BY Year(data_ur)
| rocznik | ilu |
|---|---|
| 2006 | 2 |
| 2007 | 3 |
-- BETWEEN na datach - obie granice włącznie, obie w znakach # SELECT nazwisko, data_ur FROM uczniowie WHERE data_ur BETWEEN #1/1/2007# AND #12/31/2007# ORDER BY data_ur
| nazwisko | data_ur |
|---|---|
| Wiśniewski | 2007-01-28 |
| Kowalska | 2007-03-12 |
| Dąbrowski | 2007-08-30 |
-- Wiek liczony w dniach i dzielony przez długość roku - dokładniejszy niż -- różnica roczników. Dzień odniesienia w tym przykładzie: 7 maja 2026. SELECT nazwisko, Int(DateDiff("d"; data_ur; #5/7/2026#) / 365.25) AS wiek FROM uczniowie ORDER BY wiek DESC, data_ur ASC
| nazwisko | wiek |
|---|---|
| Zając | 19 |
| Nowak | 19 |
| Wiśniewski | 19 |
| Kowalska | 19 |
| Dąbrowski | 18 |
-- Pulapka: data w apostrofach jak tekst, nie w znakach # SELECT nazwisko FROM uczniowie WHERE data_ur = '2007-03-12'
| efekt |
|---|
| błąd niezgodności typów danych w wyrażeniu kryterium (zależnie od instalacji: po prostu pusty wynik) |
Data w apostrofach zamiast w znakach #. Zła wersja: WHERE data_ur = '2007-03-12'. Baza widzi to jako porównanie z tekstem, nie z datą, i zwykle zgłasza niezgodność typów danych albo w ogóle nie znajduje wiersza. Poprawka to znaki # i format amerykański: WHERE data_ur = #3/12/2007#.
Dzień i miesiąc zamienione miejscami. #16/5/2024# wygląda naturalnie po polsku (16 maja), ale Access czyta pierwszą liczbę jako miesiąc – miesiąca 16 nie ma, więc zgłasza błąd. Poprawny zapis to #5/16/2024#: najpierw miesiąc, potem dzień.
Wiek liczony odjęciem samych roczników. Year(Date()) − Year(data_ur) zawyża wiek każdemu, kto w tym roku jeszcze nie miał urodzin – w naszej tabeli dotyczy to trzech z pięciu uczniów. Dokładniejszy sposób liczy różnicę w dniach i dzieli przez 365,25.
DateDiff z jednostką roku brany za pełne lata. DateDiff("yyyy"; data_ur; Date()) liczy, ile razy przekroczono granicę 31 grudnia/1 stycznia, a nie ile pełnych lat minęło – to ta sama pułapka co wyżej, tylko ukryta za nazwą funkcji, która brzmi precyzyjnie.
Porównanie z Now() zamiast Date() przy kolumnie bez godziny. WHERE data_zdarzenia = Now() prawie nigdy nie znajdzie dopasowania, bo Now() zwraca też godzinę i sekundy, a data_zdarzenia ma same zera po północy. Do porównań „ten sam dzień” służy Date(), do zapisania, kiedy dokładnie coś się stało – Now().
Zapomniany drugi znak # w BETWEEN. WHERE data_ur BETWEEN #1/1/2007# AND 12/31/2007 bez znaków wokół drugiej daty to błąd składni albo, gorzej, wyrażenie policzone jak dzielenie liczb. Każda z dwóch granic w Accessie potrzebuje własnej pary #...#.
>, <.# i kolejności miesiąc/dzień/rok; standard SQL – apostrofów i kolejności rok-miesiąc-dzień.#.Fragment tabeli uczniowie – tylko kolumny potrzebne do dat:
| nazwisko | data_ur |
|---|---|
| Kowalska | 2007-03-12 |
| Nowak | 2006-11-05 |
| Wiśniewski | 2007-01-28 |
| Zając | 2006-05-19 |
| Dąbrowski | 2007-08-30 |
Uzupełnij odpowiedzi na podstawie tabeli:
| Pytanie | Odpowiedź |
|---|---|
Ile wierszy zwróci SELECT nazwisko FROM uczniowie WHERE Year(data_ur) = 2007? | |
| Jaka funkcja wyciąga z daty sam miesiąc? | |
| Jaki znak otacza literał daty w zapytaniach Accessa? | |
| Który uczeń ma najmniejszą wartość Month(data_ur)? | |
| Ile wynosi Year(data_ur) dla Zającego? | |
| Który operator sprawdza obie granice przedziału dat włącznie? | |
| Która funkcja zwraca datę razem z godziną? | |
| Ile dni pokaże DateDiff dla jednostki dni między 1 stycznia a 31 grudnia tego samego roku (nieprzestępnego)? |
Warunek na przedziale WHERE data_ur BETWEEN #1/1/2007# AND #12/31/2007# obejmuje obie granice włącznie, dlatego znalazłby też ucznia urodzonego 2007 roku. Data porównana w apostrofach zamiast w znakach # bywa odczytana jako zwykły , więc warunek może nie znaleźć żadnego wiersza.
Bo wynik jest w dniach. 6500 to nie błąd, tylko mniej więcej osiemnaście lat. Jeśli potrzebujesz innej jednostki, użyj funkcji liczącej różnicę z podaną jednostką zamiast zwykłego odejmowania.
Tak, i to poprawnie chronologicznie – bo data jest liczbą, a nie tekstem. To jedna z przewag trzymania dat w kolumnie typu data zamiast w tekście: tekst '01.12.2024' posortowałby się przed '02.01.2024'.
Najczytelniej przez warunek na przedziale dat – od pierwszego dnia miesiąca do ostatniego – albo przez porównanie wyciągniętego roku i miesiąca. Drugie podejście bywa wolniejsze na dużych tabelach, ale na maturze jest zupełnie wystarczające.
Year() zawsze zwraca rok i tylko rok. DatePart() jest uniwersalna – tym samym poleceniem, zmieniając wyłącznie napis jednostki ("m", "d", "q"), można wyciągnąć dowolną część daty, więc bywa wygodniejsza, gdy jednostka zależy od parametru zapytania. Do samego roku czytelniej jest jednak użyć Year().
Najdokładniej przez porównanie dnia i miesiąca urodzenia z dzisiejszą datą w wyrażeniu warunkowym, a nie samym dzieleniem różnicy dni przez 365,25 – to przybliżenie, które w pojedynczych przypadkach potrafi się pomylić o rok w okolicy urodzin. Na maturze wystarcza zwykle przybliżenie przez dni.
Tak – obie granice są włączone, dokładnie tak samo jak dla liczb i tekstu. Różnica jest tylko w zapisie granic: w Accessie każda z dat musi być otoczona znakami #, a nie apostrofami.
Wartość Null. Funkcje daty, podobnie jak większość funkcji w SQL, przenoszą brak wartości dalej – jeśli data urodzenia nie jest wypełniona, żadna funkcja wyciągająca jej część też nie da wyniku.
Do tego hasła nie mamy jeszcze przypisanych pojedynczych zadań. Cały spis zadań maturalnych z tego działu jest tutaj:
Zagadnienia z tego samego obszaru matury – warto je powtórzyć razem: