And*_*rus 6 sql postgresql schedule range
除星期日和公众假期外,工作时间为上午10:00至晚上21:00.
他们的工作每隔15分钟保留一次.工作时间为15分钟至4小时.整个工作必须适合单日.
如何找到Postgres 9.3中从当前日期和时间开始未在指定时间内保留的第一个最近的免费开始时间?
例如,Mary已于12:30至16:00预订,John已于12:00至13:00预订
Reservat表包含预留,yksus2表包含工作,pyha表包含公共假期.表结构如下.如果这有帮助,可以改变储存结构.
查询最长的开始时间为1.5小时,应该返回
John 2014-10-28 10:00
Mary 2014-10-28 10:00
John 2014-10-28 10:15
Mary 2014-10-28 10:15
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:15
Mary 2014-10-28 16:30
... etc and also starting from next days
Run Code Online (Sandbox Code Playgroud)
我尝试了基于如何从PostgreSql中的预约返回工作时间的答案的查询?下面但它返回错误的结果:
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
Run Code Online (Sandbox Code Playgroud)
此外,还不会返回10:00,10:30等滑动开始时间.
如何获得适当的首次预订?
返回错误结果的查询是:
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
Run Code Online (Sandbox Code Playgroud)
架构:
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);
Run Code Online (Sandbox Code Playgroud)
CREATE EXTENSION btree_gist;
CREATE TYPE timerange AS RANGE (subtype = time); -- create type once
-- Workers
CREATE TABLE worker(
worker_id serial PRIMARY KEY
, worker text NOT NULL
);
INSERT INTO worker(worker) VALUES ('JOHN'), ('MARY');
-- Holidays
CREATE TABLE pyha(pyha date PRIMARY KEY);
-- Reservations
CREATE TABLE reservat (
reservat_id serial PRIMARY KEY
, worker_id int NOT NULL REFERENCES worker ON UPDATE CASCADE
, day date NOT NULL CHECK (EXTRACT('isodow' FROM day) < 7)
, work_from time NOT NULL -- including lower bound
, work_to time NOT NULL -- excluding upper bound
, CHECK (work_from >= '10:00' AND work_to <= '21:00'
AND work_to - work_from BETWEEN interval '15 min' AND interval '4 h'
AND EXTRACT('minute' FROM work_from) IN (0, 15, 30, 45)
AND EXTRACT('minute' FROM work_from) IN (0, 15, 30, 45)
)
, EXCLUDE USING gist (worker_id WITH =, day WITH =
, timerange(work_from, work_to) WITH &&)
);
INSERT INTO reservat (worker_id, day, work_from, work_to) VALUES
(1, '2014-10-28', '10:00', '11:30') -- JOHN
, (2, '2014-10-28', '11:30', '13:00'); -- MARY
-- Trigger for volatile checks
CREATE OR REPLACE FUNCTION holiday_check()
RETURNS trigger AS
$func$
BEGIN
IF EXISTS (SELECT 1 FROM pyha WHERE pyha = NEW.day) THEN
RAISE EXCEPTION 'public holiday: %', NEW.day;
ELSIF NEW.day < now()::date OR NEW.day > now()::date + 31 THEN
RAISE EXCEPTION 'day out of range: %', NEW.day;
END IF;
RETURN NEW;
END
$func$ LANGUAGE plpgsql STABLE; -- can be "STABLE"
CREATE TRIGGER insupbef_holiday_check
BEFORE INSERT OR UPDATE ON reservat
FOR EACH ROW EXECUTE PROCEDURE holiday_check();
Run Code Online (Sandbox Code Playgroud)
不要使用. 相反char(n)
varchar(n)
,或者更好,varchar
或者只是text
。
不要使用工人的姓名作为主键。它不一定是唯一的并且可以改变。请改用代理主键,最好是serial
. 还可以使条目reservat
更小、索引更小、查询更快……
更新:为了更便宜的存储(8 个字节而不是 22 个字节)和更简单的处理,我像time
现在一样保存开始和结束,并为排除约束动态构建一个范围:
EXCLUDE USING gist (worker_id WITH =, day WITH =
, timerange(work_from, work_to) WITH &&)
Run Code Online (Sandbox Code Playgroud)由于根据定义,您的范围永远不会跨越日期边界date
,因此拥有单独的列(day
在我的实现中)和时间范围会更有效。该类型timerange
不在默认安装中提供,但可以轻松创建。这样您就可以很大程度上简化您的检查约束。
用于EXTRACT('isodow', ...)
简化排除星期日
一周中的某一天为星期一 (1) 到星期日 (7)
我假设您希望允许“21:00”的上边框。
假设边界包括下限,不包括上限。
检查新的/更新的日期是否在“现在”之后的一个月内不是IMMUTABLE
。将其从CHECK
约束移至触发器 - 否则您可能会遇到转储/恢复问题!细节:
除了
简化输入和检查约束之外,我预计与仅占用 4 个字节timerange
相比可以节省 8 个字节的存储空间。但事实证明它在磁盘上占用了 22 个字节(RAM 中为 25 个字节),就像(或) 一样。所以你也可以一起去。查询和排除约束的原理是一样的。tsrange
time
timerange
tsrange
tstzrange
tsrange
包装到 SQL 函数中以方便参数处理:
CREATE OR REPLACE FUNCTION f_next_free(_start timestamp, _duration interval)
RETURNS TABLE (worker_id int, worker text, day date
, start_time time, end_time time) AS
$func$
SELECT w.worker_id, w.worker
, d.d AS day
, t.t AS start_time
,(t.t + _duration) AS end_time
FROM (
SELECT _start::date + i AS d
FROM generate_series(0, 31) i
LEFT JOIN pyha p ON p.pyha = _start::date + i
WHERE p.pyha IS NULL -- eliminate holidays
) d
CROSS JOIN (
SELECT t::time
FROM generate_series (timestamp '2000-1-1 10:00'
, timestamp '2000-1-1 21:00' - _duration
, interval '15 min') t
) t -- times
CROSS JOIN worker w
WHERE d.d + t.t > _start -- rule out past timestamps
AND NOT EXISTS (
SELECT 1
FROM reservat r
WHERE r.worker_id = w.worker_id
AND r.day = d.d
AND timerange(r.work_from, r.work_to) && timerange(t.t, t.t + _duration)
)
ORDER BY d.d, t.t, w.worker, w.worker_id
LIMIT 30 -- could also be parameterized
$func$ LANGUAGE sql STABLE;
Run Code Online (Sandbox Code Playgroud)
称呼:
SELECT * FROM f_next_free('2014-10-28 12:00'::timestamp, '1.5 h'::interval);
Run Code Online (Sandbox Code Playgroud)
现在在 Postgres 9.3 上使用SQL Fiddle 。
该函数采用 a_start
timestamp
作为最小启动时间,并且_duration interval
。请注意,仅排除开始当天的较早时间,而不是随后的几天。最简单的方法是添加日期和时间:t + d > _start
。
要从“现在”开始预订,只需通过now()::timestamp
:
SELECT * FROM f_next_free(`now()::timestamp`, '1.5 h'::interval);
Run Code Online (Sandbox Code Playgroud)子查询d
生成从输入值开始的天数_day
。节假日除外。
t
。w
。NOT EXISTS
最后使用反半连接,特别是重叠运算符,消除与现有保留冲突的所有候选者&&
。有关的:
归档时间: |
|
查看次数: |
492 次 |
最近记录: |