sqlpostgresqlsql-timestamp

Why my checked input date is not selected?


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;

Solution

  • 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 timestamps 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;