Home > Software engineering >  Query if business is open
Query if business is open

Time:11-12

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

CodePudding user response:

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
);

CodePudding user response:

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.

  • Related