Field manual · SQL traps
BETWEEN 'a' AND 'b' on a TIMESTAMP turns 'b' into 'b 00:00:00'. Everything that happened after midnight on that day is left out, even though the whole day was supposed to count.
Any date filter using BETWEEN on a TIMESTAMP column (not DATE). It's why 'from the 1st to the 30th' almost never includes all of the 30th.
Wrong: BETWEEN cuts the 30th off at midnight
SELECT card, entry
FROM access_log
WHERE entry BETWEEN '2026-09-01' AND '2026-09-30';
-- '2026-09-30' is read as '2026-09-30 00:00:00':
-- the 10:47 pm entry on that same day is left out
Right: a half-open range, with no guessing at the cutoff hour
SELECT card, entry
FROM access_log
WHERE entry >= '2026-09-01'
AND entry < '2026-10-01';
BETWEEN a AND b on a TIMESTAMP isn't 'from day a to day b': it's 'from instant a to instant b', and a date-only literal like '2026-09-30' gets read as '2026-09-30 00:00:00'. That leaves out the 23 hours and 59 minutes that follow, right where this clue's break-in happened. The half-open range >= start AND < exclusive_end doesn't have that problem because it never depends on guessing what hour the next day starts: it just needs the first instant that no longer counts. The same rule applies to months and years: < '2026-10-01' includes all of September, no matter the hour.
This trap is hiding inside a real case file, with real data.
Play the first case free →