【问题标题】:postgresql list of time slots from 'Monday' | 09:00:00 | 11:00:00'星期一'的时间段的postgresql列表| 09:00:00 | 11:00:00
【发布时间】:2020-02-05 04:00:23
【问题描述】:

我正在构建一个预订系统,用户将在其中设置他们的可用性,例如:我周一上午 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,所以我只创建一个空白表。

我认为我在正确的轨道上,但目前卡在:

  • 如何从“星期一”09:00:00 创建下星期一的时间戳,因为我只关心从今天开始的时间段?
  • 如何将'Monday' | 09:00:00 | 11:00:00 转换为间隔 15 分钟的时隙列表?

【问题讨论】:

    标签: postgresql plpgsql hasura


    【解决方案1】:

    如何从“星期一”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);
    
    

    几点说明:

    • CTE avail_times 用于简化事情;它生成两列(start_timeend_time),它们是完整的时间戳(因此包括日期)。在这个例子中,第一行是“2020-02-03 09:00:00, 2020-02-03 11:00:00”(我在 2020-02-02 运行它,所以接下来是 2020-02-03星期一)。
    • 我将“星期一”等转换为一周中的某一天的方式有点小技巧(而且我没有费心去做整整一周);可能有更好的方法,但将星期几存储为整数会更简单。
    • 我从结束时间中减去 1 毫秒,因为我假设您不希望在结果集中出现这种情况。
    • 主要查询使用LATERAL Subquery。请参阅this question 了解更多信息。

    附加问题

    如何调整它,以便我可以传递开始和结束日期,这样我就可以得到 特定时期的时间段

    您可以执行以下操作(只需调整 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;
    

    【讨论】:

    • 这很好,正是我所要求的,谢谢!我将此标记为正确答案。我确实犯了一个错误,因为我只专注于从 current_date 生成 1 周的时间段,所以现在我必须弄清楚如何调整它,这样我就可以传入开始和结束日期,这样我就可以获得一个时间段特定时期,任何想法@brits?
    • 请看我修改后的答案。
    【解决方案2】:

    以下内容使用了@Brits 提到的大部分技术。他们提供了一些非常好的信息,所以我不会重复,但建议您查看它(和链接)。 然而,我确实采取了稍微不同的方法。首先是几个表更改。我使用 ISO 第 1-7 周(周一至周日)的日期而不是日期名称。日期名称很容易被提取出来供日后使用。 我也使用间隔代替开始和结束时间的时间。 (时间数据类型适用于大多数情况,但有一种情况不适用(稍后会详细介绍)。
    您的描述不清楚的一件事是结束时间是否包含在可用时间中。如果包括最后一个时间间隔将是 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; 
    

    希望这会有所帮助。

    【讨论】:

    • 感谢您的回复,我喜欢将故事中的星期几作为数字而不是说“星期一”。您还提出了一个关于使用间隔数据类型和轮班工作人员的好观点,这不是我想过的,但可以在前端涵盖。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-12
    • 1970-01-01
    • 1970-01-01
    • 2016-06-04
    • 1970-01-01
    相关资源
    最近更新 更多