我正在构建一个预订系统,用户可以在该系统中设置其可用性,例如:我可以在周一的上午9点至上午11点,周二的上午9点至下午5点等……并且需要生成一个15分钟的时隙清单
我有下表(但可以灵活更改):
availabilities(day_of_week text, start_time: time, end_time: time)
它返回记录,例如:
‘Monday’ | 09:00:00 | 11:00:00
‘Monday’ | 13:00:00 | 17:00:00
‘Tuesday’ | 08:00:00 | 17:00:00
所以我试图建立一个存储过程来生成一个时隙列表,到目前为止我已经知道了:
create or replace function timeslots ()
return setof timeslots as $$
declare
rec record;
begin
for rec in select * from availabilities loop
/*
convert 'Monday' | 09:00:00 | 11:00:00 into:
2020-02-03 09:00:00
2020-02-03 09:15:00
2020-02-03 09:30:00
2020-02-03 09:45:00
2020-02-03 10:00:00
and so on...
*/
return next
end loop
$$ language plpgsql stable;
当我使用Hasura时,我返回的是setof而不是表,它需要返回一个setof,所以我只创建了一个空白表。
我认为我走在正确的轨道上,但目前停留在:
'Monday' | 09:00:00 | 11:00:00
转换为间隔15分钟的时隙列表?答案 0 :(得分:0)
我如何从“星期一” 09:00:00创建下一个星期一的时间戳记 因为我只关心今天以后的时间段?
您可以为此使用date_trunc
(有关更多信息,请参见this question)
SELECT date_trunc('week', current_date) + interval '1 week';
从docs到week
:
ISO 8601的星期编号,是一年中的第几周。通过 定义,ISO周从星期一开始
因此,采用此值并加上一个星期可以得出下星期一(如果今天是星期一,您可能需要根据您想做的事情来修改此行为!)
如何转换“星期一” | 09:00:00 | 11:00:00进入时间清单 间隔15分钟?
这有点骗人; generate_series
将为您提供时隙,但诀窍在于将其纳入结果集中。以下应该完成这项工作(我已经包含了您的示例数据;更改values
位以引用您的表)-dbfiddle:
with avail_times as (
select
date_trunc('week', current_date) + interval '1 week' + case day_of_week when 'Monday' then interval '0 day' when 'Tuesday' then interval '1 day' end + start_time as start_time,
date_trunc('week', current_date) + interval '1 week' + case day_of_week when 'Monday' then interval '0 day' when 'Tuesday' then interval '1 day' end + end_time as end_time
from
(
values
('Monday','09:00:00'::time,'11:00:00'::time),
('Monday','13:00:00'::time,'17:00:00'::time),
('Tuesday','08:00:00'::time,'17:00:00'::time)
) as availabilities (day_of_week,
start_time,
end_time) )
select
g.ts
from
(
select
start_time,
end_time
from
avail_times) avail,
generate_series(avail.start_time, avail.end_time - interval '1ms', '15 minutes') g(ts);
一些注意事项:
avail_times
用于简化内容;它会生成两列(start_time
和end_time
),它们是完整的时间戳(因此包括日期)。在此示例中,第一行是“ 2020-02-03 09:00:00,2020-02-03 11:00:00”(我在2020-02-02上运行此操作,因此下一个是2020-02-03星期一)。如何对此进行调整,以便我可以输入开始日期和结束日期,以便获得 特定时段的时间段
您可以执行以下操作(只需调整dates
CTE以返回要包含的日期;可以转换为函数或仅将日期作为参数传递)。
请注意,正如@Belayer提到的那样,我原来的解决方案无法适应午夜的变化,因此也可以解决这一问题。
with dates as (
select
day
from
generate_series('2020-02-20'::date, '2020-03-10'::date, '1 day') as day ),
availabilities as (
select
*
from
(
values (1,'09:00:00'::time,'11:00:00'::time),
(1,'13:00:00'::time,'17:00:00'::time),
(2,'08:00:00'::time,'17:00:00'::time),
(3,'23:00:00'::time,'01:00:00'::time)
) as availabilities
(day_of_week, -- 1 = monday
start_time,
end_time) ) ,
avail_times as (
select
d.day + start_time as start_time,
case
end_time > start_time
when true then d.day
else d.day + interval '1 day' end + end_time as end_time
from
availabilities a
inner join dates d on extract(ISODOW from d.day) = a.day_of_week )
select
g.ts
from
(
select
start_time,
end_time
from
avail_times) avail,
generate_series(avail.start_time, avail.end_time - interval '1ms', '15 minutes') g(ts)
order by
g.ts;
答案 1 :(得分:0)
以下内容使用了@Brits提到的许多技术。他们提供了一些非常好的信息,因此我不再重复,但建议您对其进行检查(以及链接)。
但是,我确实采用了稍微不同的方法。首先,更改几个表。我使用的是1-7周的ISO日(周一至周日),而不是日期名称。以后可以很容易地为约会者提取日期名称。
我也使用间隔来代替开始和结束时间。 (时间数据类型适用于大多数情况,但有一种情况则不行(稍后介绍)。
您的描述不清楚的一件事是是否在可用时间中包括了结束时间。如果包括在内,则最后间隔为11:00-11:15。如果排除,则最后间隔是10:45-11:00。我假设排除了它。在最终结果中,结束时间应理解为“最多但不包括”。
-- setup
create table availabilities (weekday integer, start_time interval, end_time interval);
insert into availabilities (weekday , start_time , end_time )
select wkday
, start_time
, end_time
from (select *
from (values (1, '09:00'::interval, '11:00'::interval)
, (1, '13:00'::interval, '17:00'::interval)
, (2, '08:00'::interval, '17:00'::interval)
, (3, '08:30'::interval, '10:45'::interval)
, (4, '10:30'::interval, '12:45'::interval)
) as v(wkday,start_time,end_time)
) r ;
select * from availabilities;
查询
它以CTE(next_week)开头,它从星期一开始的一周的每一天都生成一个条目,并为其生成相应的ISO天数。主查询将这些与可用性表结合在一起,以选择匹配天数的时间。最终,该结果与生成的时间戳进行交叉连接以获得15分钟的间隔。
-- Main
with next_week (wkday,tm) as
(SELECT n+1, date_trunc('week', current_date) + interval '1 week' + n*interval '1 day'
from generate_series (0, 6) n
)
select to_char(gdtm,'Day'), gdtm start_time, gdtm+interval '15 min' end_time
from ( select wkday, tm, start_time, end_time
from next_week nw
join availabilities av
on (av.weekday = nw.wkday)
) s
cross join lateral
generate_series(start_time+tm, end_time+tm- interval '1 sec', interval '15 min') gdtm ;
离群值
如前所述,在一种情况下,时间数据类型不能令人满意,但是您可能不需要。当轮班工人说他们的可用时间为23:00-01:30时,会发生什么。相信我,当轮班工人在星期五22:00上班时,即使日历可能不同,星期五晚上01:30仍然是星期五。 (我从事这一工作已经很多年了。)以下使用间隔处理该问题。在这种情况下,将加载与以前相同的数据,并添加其他内容。
insert into availabilities (weekday, start_time, end_time )
select wkday
, start_time
, end_time + case when end_time < start_time
then interval '1 day'
else interval '0 day'
end
from (select *
from (values (1, '09:00'::interval, '11:00'::interval)
, (1, '13:00'::interval, '17:00'::interval)
, (2, '08:00'::interval, '17:00'::interval)
, (3, '08:30'::interval, '10:45'::interval)
, (5, '23:30'::interval, '02:30'::interval) -- Friday Night - Saturday Morning
) as v(wkday,start_time,end_time)
) r
;
select * from availabilities;
希望这会有所帮助。