skip to Main Content

I have a Postgres table that looks like this:

Name Type Description
business_id (PK) int4 Business ID
day (PK) int4 Day of week (0-6, monday is zero)
open time Open time
close time Close time

Every row stores open and closing times for a business on a specific day of week.
Example data for a business looks like this:

business_id day open close
1 0 18:00 23:00
1 1 18:00 23:00
1 2 18:00 23:00
1 3 18:00 23:00
1 4 18:00 01:00
1 5 18:00 02:00

You can see that the business is opened from 18:00 to 23:00 from Mo.-Fr.
Note that on the weekend the opening hours extend over to the next day.

I’m trying to write a single statement query that determines if a business is opened now or at a specific time.

I tried writing the query below but the results are wrong and I can’t think of another way to solve this problem.

select count(*)
from (
select *
from business_hours bh
where 
    bh.business_id = 1
    and bh.day =  extract(dow from now()) - 1
union all
select *
from business_hours bh
where 
    bh.business_id = 1
    and bh.day = extract(dow from now()) - 1
) a
where
    ("from" < "to" and now()::time between "from" and "to")
    or ("from" > "to" and now()::time not between "to" and "from")

Thank you for helping me out with this query

2

Answers


  1. Chosen as BEST ANSWER

    I finally came up with this query and wrapped it inside a function for easier usage:

    CREATE FUNCTION fn_business_is_open
    (
        business_id int4
        ,at timestamptz
    )
    RETURNS bool
    LANGUAGE sql
    AS
    $$
    
    SELECT COUNT(*) > 0
    FROM
    (
        SELECT *
        FROM business_hours
        WHERE
            day = EXTRACT(dow from $2) - 1
            AND
            (
                ("from" < "to" AND $2::time >= "from" AND $2::time <= "to")
                OR
                ("from" > "to" AND $2::time >= "from" AND $2::time >= "to")
            )
        UNION ALL
        SELECT *
        FROM business_hours
        WHERE
            day = EXTRACT(dow from $2) - 2
            AND "from" > "to" AND $2::time <= "to"
    ) a
    WHERE
        business_id = $1;
    
    $$;
    
    

    I'm not 100% sure if this query is correct. My tests showed that it produces the expected result when I called this function with every hour in a week.

    Please consider commenting if you think it could be done in a better/more efficient way.


  2. I would use a range type and a constraint to avoid conflicts. Something like this:

    CREATE EXTENSION btree_gist; -- for the constraints
    
    CREATE TYPE timerange AS RANGE (
        subtype = time
    );
    
    CREATE TABLE business_hours(
        business_id INT
        , day INT
        , timerange timerange
        ,     EXCLUDE USING GIST (business_id WITH =, day WITH =, timerange WITH &&) -- constraint
    );
    
    INSERT INTO business_hours VALUES
    (1,0,'[18:00,23:00)'::timerange),
    (1,1,'[18:00,23:00)'::timerange),
    (1,2,'[18:00,23:00)'::timerange),
    (1,3,'[18:00,23:00)'::timerange),
    (1,4,'[18:00,24:00)'::timerange),
    (1,5,'[00:00,01:00)'::timerange),
    (1,5,'[18:00,24:00)'::timerange),
    (1,6,'[00:00,02:00)'::timerange);
    
    SELECT  COUNT(*)
    FROM    business_hours
    WHERE   business_id = 1
    AND (
        (day = EXTRACT(DOW FROM CURRENT_DATE)::INT -1 AND   timerange @> LOCALTIME) -- this is now
        OR 
        (day = EXTRACT(DOW FROM '2022-11-12'::date) - 1 AND timerange @> '23:30'::time) -- some other day and or time
    );
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search