Raport z zamówieniami znów pomija część rekordów z ostatniego dnia miesiąca, choć warunek wygląda poprawnie. Operator BETWEEN obejmuje obie granice zakresu, ale przy kolumnie z godziną górna data może oznaczać dopiero początek tego dnia.
BETWEEN obejmuje oba końce, lecz typ kolumny zmienia wynik
- Granice są domknięte: wartość równa dolnej lub górnej granicy pasuje do zakresu.
- Daty z godziną: granica zapisana jako data może wykluczyć późniejsze godziny tego samego dnia.
- Przedział otwarty: dla raportu obejmującego cały dzień końcowy często lepiej użyć warunku „od tej daty włącznie, przed następną”.
- NULL: rekordy z brakiem wartości nie spełniają warunku zakresu.

Granice BETWEEN są włączone do zakresu
Wynik leży w przedziale, jeśli jest większy lub równy dolnej granicy i jednocześnie mniejszy lub równy górnej. Tę logikę można zapisać krócej przez BETWEEN:
SELECT order_id, total
FROM orders
WHERE total BETWEEN 100 AND 500;
Zapytanie zwróci zamówienia o wartości od 100 do 500, łącznie z obiema kwotami granicznymi. Tak samo można zapisać warunek jako total >= 100 AND total <= 500. Uważam, że przy prostym zakresie liczbowym BETWEEN jest czytelny, o ile pamiętasz, że końce także przechodzą filtr.
Jeśli granice mają być wykluczone, operator nie pasuje. Zapisz wtedy warunek jawnie, na przykład total > 100 AND total < 500. Różnica jednego znaku może zmienić wynik dla rekordów leżących dokładnie na progu.
Data bez godziny może uciąć rekordy z końca dnia
W tabeli przechowujesz zamówienia jako znaczniki czasu, a raport ma objąć cały marzec. Warunek BETWEEN '2026-03-01' AND '2026-03-31' może wyglądać naturalnie, ale górna granica często odpowiada północy rozpoczynającej 31 marca. Zamówienie zarejestrowane tego dnia po północy wypada wtedy poza zakresem.
Bezpieczniej zdefiniować początek włącznie, a koniec wyłącznie:
WHERE created_at >= '2026-03-01'
AND created_at < '2026-04-01'
To podejście obejmuje wszystkie znaczniki czasu w marcu, niezależnie od tego, czy baza przechowuje sekundy, milisekundy czy większą precyzję. Nie trzeba zgadywać ostatniej chwili dnia ani dopisywać ułamków sekundy, które mogą nie pasować do typu kolumny.
Przy kolumnie typu `DATE`, która nie przechowuje godziny, górna granica z BETWEEN działa zgodnie z oczekiwaniem. Problem pojawia się przy `DATETIME` lub typie timestamp, dlatego najpierw sprawdź typ kolumny, a dopiero potem dobierz zapis.
NULL i odwrócone granice wymagają osobnego sprawdzenia
Filtr zakresu nie zamienia braku danych w wartość. Gdy badana kolumna albo jedna z granic ma wartość NULL, warunek nie daje wyniku prawdziwego, więc wiersz nie przechodzi przez WHERE. Jeśli brak wartości ma być uwzględniony, dopisz osobny warunek.
| Sytuacja | Skutek | Co zrobić |
|---|---|---|
Kolumna zawiera NULL |
Wiersz nie spełnia warunku zakresu. | Dodaj OR kolumna IS NULL, jeśli brak wartości ma być częścią wyniku. |
| Dolna granica jest większa od górnej | Zakres nie obejmuje zwykłych wartości. | Zweryfikuj kolejność parametrów przed wykonaniem zapytania. |
| Granice mają różne typy | Niektóre silniki odrzucą warunek, inne mogą wykonać konwersję. | Użyj zgodnych typów i sprawdź zasady swojego silnika SQL. |
| Porównywane są wartości zmiennoprzecinkowe | Zaokrąglenia reprezentacji mogą dać zaskakujące wyniki blisko granicy. | Dla kwot wybierz typ dziesiętny, a dla pomiarów określ akceptowaną tolerancję. |
Ostatni przypadek jest podstępny, bo zapytanie może wykonać się bez błędu, a mimo to zachować się inaczej, niż sugeruje zapis liczby. Dokumentacja MySQL opisuje problem przybliżonego przechowywania typów zmiennoprzecinkowych; dla wartości pieniężnych wybór typu danych bywa ważniejszy niż sam operator.
Silnik SQL wpływa na szczegóły, nie na sens operatora
Podstawowa zasada jest wspólna, lecz silniki różnią się w wymaganiach dotyczących typów i w sposobie obsługi wyrażeń. W Transact-SQL dla SQL Server wyrażenie testowane i obie granice muszą mieć zgodne typy danych. Dokumentacja PostgreSQL opisuje zakres jako domknięty, a SQLite zaznacza, że wyrażenie po lewej stronie operatora jest obliczane raz.
W praktyce nie zakładaj, że konwersja tekstu na datę lub liczbę zadziała identycznie w każdym środowisku. Parametry zapytania przekazuj jako wartości właściwego typu, szczególnie gdy aplikacja obsługuje więcej niż jeden silnik bazy.
Najpierw sprawdź typ, potem wybierz granice
Przy debugowaniu rozbieżnego raportu najkrótsza droga prowadzi przez trzy pytania: czy granice są włączone, czy kolumna zawiera czas oraz czy w danych występuje NULL. Gdy zakres dotyczy pełnych dni w kolumnie czasu, zapis z końcem wyłącznym zwykle usuwa problem bez sztucznych końcowych sekund.
Jeśli testujesz sam operator, dodaj do danych rekordy dokładnie na dolnej i górnej granicy oraz jeden tuż poza nimi. Taki mały test ujawnia, czy problem tkwi w logice zakresu, typie kolumny czy parametrach przekazanych przez aplikację.
