From: | Thom Brown <thom(at)linux(dot)com> |
---|---|
To: | Andrus <kobruleht2(at)hot(dot)ee> |
Cc: | PGSQL Mailing List <pgsql-general(at)postgresql(dot)org> |
Subject: | Re: How to find earlest possible start times for given duration excluding reservations |
Date: | 2014-10-28 16:42:24 |
Message-ID: | CAA-aLv6NDTUwV=P3z_aO88cwqLUfTdb8ttS9rjgrQYby7pyJaQ@mail.gmail.com |
Views: | Raw Message | Whole Thread | Download mbox | Resend email |
Thread: | |
Lists: | pgsql-general |
On 28 October 2014 15:10, Andrus <kobruleht2(at)hot(dot)ee> wrote:
> Hi!
>
> I'm looking for finding ealiest possible start times from reservations
> table.
>
> People work from 10:00AM to 21:00PM in every week day except Sunday and
> public holidays.
>
> Jobs for them are reserved at 15 minute intervals and whole job must fit
> to single day.
> Job duration is from 15 minutes to 4 hours.
>
> Reservat table contains reservations, yksus2 table contains workes and
> pyha table contains public holidays. Table structures are below. Reservat
> structure can changed if this helps.
>
> How to first earliest 30 possible start times considering existing
> reservations ?
>
> For example, Mary has already reservation at 12:30 .. 16:00 and
> John has already reservation at 12:00 to 13:00
>
> In this case query for job with duration of 1.5 hours should return
>
> John 2014-10-28 10:00
> Mary 2014-10-28 10:00
> John 2014-10-28 10:30
> Mary 2014-10-28 10:30
> Mary 2014-10-28 11:00
> John 2014-10-28 13:00
> Mary 2014-10-28 16:00
> Mary 2014-10-28 16:30
> ... etc and also starting from next days
>
> I tried query based on answer in http://stackoverflow.com/
> questions/13433863/how-to-return-only-work-time-from-
> reservations-in-postgresql below but it returns wrong result:
>
> MARY 2014-10-28 13:00:00
> MARY 2014-10-29 22:34:40.850255
> JOHN 2014-10-30 22:34:40.850255
> MARY 2014-10-31 22:34:40.850255
> MARY 2014-11-03 22:34:40.850255
>
> Also sliding start times 10:00, 10:30 etc are not returned.
>
> How to get proper first reservations ?
>
> Query which I tried is
>
> insert into reservat (objekt2, during) values
> ('MARY', '[2014-10-28 11:30:00,2014-10-28 13:00:00)'),
> ('JOHN', '[2014-10-28 10:00:00,2014-10-28 11:30:00)');
>
> with gaps as (
> select
> yksus,
> upper(during) as start,
> lead(lower(during),1,upper(during)) over (ORDER BY during) -
> upper(during) as gap
> from (
> select
> yksus2.yksus,
> during
> from reservat join yksus2 on reservat.objekt2=yksus2.yksus
> where upper(during)>= current_date
> union all
> select
> yksus2.yksus,
> unnest(case
> when pyha is not null then array[tsrange1(d, d +
> interval '1 day')]
> when date_part('dow', d) in (0, 6) then
> array[tsrange1(d, d + interval '1 day')]
> when d::date = current_Date then array[
> tsrange1(d, current_timestamp ),
> tsrange1(d + interval '20 hours', d +
> interval '1 day')]
> else array[tsrange1(d, d + interval '8 hours'),
> tsrange1(d + interval '20 hours', d +
> interval '1 day')]
> end)
> from yksus2, generate_series(
> current_timestamp,
> current_timestamp + interval '1 month',
> interval '1 day'
> ) as s(d)
> left join pyha on pyha = d::date
> ) as x
> )
>
> select yksus, start
> from gaps
> where gap >= interval'1hour 30 minutes'
> order by start
> limit 30
>
>
> Schema:
>
> CREATE EXTENSION btree_gist;
> CREATE TABLE Reservat (
> id serial primary key,
> objekt2 char(10) not null references yksus2 on update cascade
> deferrable,
> during tsrange not null check(
> lower(during)::date = upper(during)::date
> and lower(during) between current_date and current_date+
> interval'1 month'
>
> and (lower(during)::time >= '10:00'::time and
> upper(during)::time < '21:00'::time)
> AND EXTRACT(MINUTE FROM lower(during)) IN (0, 15, 30,45)
> AND EXTRACT(MINUTE FROM upper(during)) IN (0, 15, 30, 45)
> and (date_part('dow', lower(during)) in (1,2,3,4,5,6)
> and date_part('dow', upper(during)) in (1,2,3,4,5,6))
> ),
>
> EXCLUDE USING gist (objekt2 WITH =, during WITH &&)
> );
>
> create or replace function holiday_check() returns trigger language
> plpgsql stable as $$
> begin
> if exists (select * from pyha where pyha in
> (lower(NEW.during)::date, upper(NEW.during)::date)) then
> raise exception 'public holiday %', lower(NEW.during) ;
> else
> return NEW;
> end if;
> end;
> $$;
>
> create trigger holiday_check_i before insert or update on Reservat for
> each row execute procedure holiday_check();
>
> CREATE OR REPLACE FUNCTION public.tsrange1(start timestamp with time
> zone,
> finish timestamp with time zone ) RETURNS tsrange AS
> $BODY$
> SELECT tsrange(start::timestamp without time zone, finish::timestamp
> without time zone );
> $BODY$ language sql immutable;
>
>
> -- Workers
> create table yksus2( yksus char(10) primary key);
> insert into yksus2 values ('JOHN'), ('MARY');
>
> -- public holidays
> create table pyha( pyha date primary key);
>
>
> I posted it also in http://stackoverflow.com/questions/26608683/how-to-
> find-first-free-start-times-from-reservations-in-postgres
>
> Andrus.
Would you be able to adapt this to your needs?:
CREATE TABLE yksus2 (yksus char(10) PRIMARY KEY);
INSERT INTO yksus2 VALUES ('JOHN'),('MARY');
CREATE TABLE reservat
(
id serial primary key,
objekt2 char(10) REFERENCES yksus2 (yksus),
during tstzrange
);
ALTER TABLE reservat ADD CONSTRAINT time_between_1000_and_2100
CHECK (lower(during) >= (lower(during)::date + '10:00'::time)::timestamptz
AND upper(during) < (upper(during)::date + '21:00+1'::time)::timestamptz);
ALTER TABLE reservat ADD CONSTRAINT time_at_15_min_offset
CHECK (extract(epoch from lower(during)::time)::int % (60*15) = 0);
ALTER TABLE reservat ADD CONSTRAINT duration_between_15min_and_4hours
CHECK (upper(during) - lower(during) between '15 mins'::interval and '4
hours'::interval);
INSERT INTO reservat (objekt2, during)
VALUES ('MARY','[2014-10-28 12:30+0,2014-10-28 16:00+0)'::tstzrange);
INSERT INTO reservat (objekt2, during)
VALUES ('JOHN','[2014-10-28 12:00+0,2014-10-28 13:00+0)'::tstzrange);
SELECT yksus2.yksus, times.period
FROM generate_series('2014-10-28 10:00+0'::timestamptz, '2014-10-28
21:00+0', '15 mins'::interval) times(period)
CROSS JOIN yksus2
LEFT JOIN reservat ON tstzrange(times.period,times.period + '1 hour 30
mins'::interval, '[)') && reservat.during AND yksus2.yksus =
reservat.objekt2
WHERE reservat.during IS NULL
ORDER BY 2, 1
LIMIT 30;
yksus | period
------------+------------------------
JOHN | 2014-10-28 10:00:00+00
MARY | 2014-10-28 10:00:00+00
JOHN | 2014-10-28 10:15:00+00
MARY | 2014-10-28 10:15:00+00
JOHN | 2014-10-28 10:30:00+00
MARY | 2014-10-28 10:30:00+00
MARY | 2014-10-28 10:45:00+00
MARY | 2014-10-28 11:00:00+00
JOHN | 2014-10-28 13:00:00+00
JOHN | 2014-10-28 13:15:00+00
JOHN | 2014-10-28 13:30:00+00
JOHN | 2014-10-28 13:45:00+00
JOHN | 2014-10-28 14:00:00+00
JOHN | 2014-10-28 14:15:00+00
JOHN | 2014-10-28 14:30:00+00
JOHN | 2014-10-28 14:45:00+00
JOHN | 2014-10-28 15:00:00+00
JOHN | 2014-10-28 15:15:00+00
JOHN | 2014-10-28 15:30:00+00
JOHN | 2014-10-28 15:45:00+00
JOHN | 2014-10-28 16:00:00+00
MARY | 2014-10-28 16:00:00+00
JOHN | 2014-10-28 16:15:00+00
MARY | 2014-10-28 16:15:00+00
JOHN | 2014-10-28 16:30:00+00
MARY | 2014-10-28 16:30:00+00
JOHN | 2014-10-28 16:45:00+00
MARY | 2014-10-28 16:45:00+00
JOHN | 2014-10-28 17:00:00+00
MARY | 2014-10-28 17:00:00+00
(30 rows)
--
Thom
From | Date | Subject | |
---|---|---|---|
Next Message | Michael P. Soulier | 2014-10-28 17:25:02 | cannot drop user |
Previous Message | Andrus | 2014-10-28 15:10:30 | How to find earlest possible start times for given duration excluding reservations |