I have a condition where I want to get results between two dates:
start_time: 2025-01-02
end_time: 2025-04-27
The problem is I don't get the result 2025-04-27, I get 2025-04-26 so it ends one day earlier. I also want to get the time 2025-04-27
What I am doing wrong ?
SELECT l.id AS aslocation_id
, l.name
, lo.id
, lo.start_time
, lo.end_time
, lo.leader_id
, lo.is_storno
, lo.storno_at
, lo.storno_by
, lo.employees_need
, lo.created_at
FROM location_orders AS lo
JOIN location AS l ON l.id = lo.location_id
WHERE lo.tenant_id = 321
AND lo.deleted_by IS NULL
AND lo.start_time >= '2025-01-02'
AND lo.end_time <= '2025-04-27'
ORDER BY lo.start_time DESC
, lo.id DESC
LIMIT 50;
You can ask Postgres to explain it to you by adding explain analyze verbose
before the query. Once you do, it'll show you a plan and something like this in it:
Index Cond: ((lo.start_time >= '2025-01-02 00:00:00'::timestamp without time zone) AND (lo.end_time <= '2025-04-27 00:00:00'::timestamp without time zone))
It's making your '2025-01-02'
and '2025-04-27'
a timestamp
type, not a date
. It's happening because your start_time
and end_time
columns are timestamp
s already and incoming constants are type unknown
.
In pg_operator
you can see there are 3 different >=
operator variants for comparisons with a timestamp
on the left:
select oprname
, oprleft::regtype
, oprright::regtype
, oprcode
from pg_operator
where oprname='>='
and oprleft='timestamp'::regtype;
oprname | oprleft | oprright | oprcode |
---|---|---|---|
>= | timestamp without time zone | date | timestamp_ge_date |
>= | timestamp without time zone | timestamp without time zone | timestamp_ge |
>= | timestamp without time zone | timestamp with time zone | timestamp_ge_timestamptz |
This list is used to guess the right type for your right operand, the unknown
string constant. Although there's one that accepts a date
on the right, type conversion rule 2.a says it's preferrable to make them match:
If one argument of a binary operator invocation is of the
unknown
type, then assume it is the same type as the other argument for this check.
Even if it wasn't converted and you explicitly made it <= '2025-04-27'::date
, when compared to your timestamp
field, the date-looking string on the right counts as midnight on that date.
As already stated, it's easiest if you just exclude the upper bound and bump up the date:
demo at db<>fiddle
explain analyze verbose
SELECT l.id AS aslocation_id
, l.name
, lo.id
, lo.start_time
, lo.end_time
, lo.leader_id
, lo.is_storno
, lo.storno_at
, lo.storno_by
, lo.employees_need
, lo.created_at
FROM location_orders AS lo
JOIN location AS l ON l.id = lo.location_id
WHERE lo.tenant_id = 321
AND lo.deleted_by IS NULL
AND lo.start_time >= '2025-01-02'
AND lo.end_time < '2025-04-28' -----------here
ORDER BY lo.start_time DESC
, lo.id DESC
LIMIT 50;