使用休息时间生成系列

时间:2018-10-26 06:38:28

标签: postgresql generate-series

我有一张表格,用于存储营业时间和营业时间

    CREATE TABLE public.open_hours
(
  id bigint NOT NULL,
  open_hour character varying(255),
  end_hour character varying(255),
  day character varying(255),
  CONSTRAINT pk_open_hour_id PRIMARY KEY (id)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE public.open_hours
  OWNER TO postgres;

我还有另一个桌子

CREATE TABLE public.break_hours
(
id bigint ,
start_time character varying(255),
end_time character varying(255),
open_hour_id bigint ,
CONSTRAINT break_hours_pkey PRIMARY KEY (id),
 CONSTRAINT fkinhl5x01pnn54nv15ol5ntxr5 FOREIGN KEY (open_hour_id )
  REFERENCES public.open_hours(id) MATCH SIMPLE
  ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
ALTER TABLE public.break_hours
OWNER TO postgres;

我需要根据休息时间生成30分钟间隔的时间序列。

例如:如果我的开放时间是08:00 AM,结束时间是06:00 PM,我的休息时间是11:00 AM至11:30,另一个休息时间是03:00 PM至03:15 PM然后我需要生成从08:00 AM到11:00 AM和11:30 AM到03:00 PM和03:15到06:00 PM的系列。

样本数据

open_hours
-----------
id              open_hours                  end_hour    day
 1              08:00 AM                    06:00 PM    Monday

break_hours


id        start_time   end_time   open_hour_id
 1        11:00 AM     11:30 AM    1
 2        03:00 PM     03:15 PM    1

Sample out put
--------------
08:00 AM
08:30 AM
09:00 AM
09:30 AM
10:00 AM
10:30 AM
11:30 AM
12:00 PM
12:30 PM
01:00 PM
01:30 PM
02:PM PM
02:30 PM
03:15 PM
03:45 PM
04:15 PM
04:45 PM
05:15 PM

Query used for generating series between open hours is
SELECT DISTINCT gs AS start_time,gs + interval '30min' as end_time 
                     FROM   generate_series( timestamp '2018-11-09 08:00 AM', timestamp '2018-11-09 06:00 PM', interval '30min' )gs 
                     ORDER BY start_time

1 个答案:

答案 0 :(得分:1)

似乎应该清理表建模。例如。您不应将时间存储为文本类型,而应存储为time without time zone


demo: db<>fiddle

WITH hours AS (
    SELECT 
        oh.open_hour + '1970-01-01'::date as open_hour, 
        oh.end_hour + '1970-01-01'::date as end_hour, 
        bh.start_time + '1970-01-01'::date as break_start,
        bh.end_time + '1970-01-01'::date as break_end,
        lead(start_time + '1970-01-01'::date) OVER (ORDER BY start_time) as next_start_time
    FROM open_hours oh
    LEFT JOIN break_hours bh
    ON oh.id = bh.start_date
)
SELECT generate_series(open_hour, break_start, interval '30 minutes')::time as time_slot 
FROM (
    SELECT 
        open_hour, break_start
    FROM hours
    ORDER BY break_start
    LIMIT 1
)s

UNION 

SELECT 
    generate_series(break_end, next_start_time, interval '30 minutes')::time
FROM ( 
    SELECT 
        break_end, next_start_time
    FROM
        hours
    WHERE next_start_time IS NOT NULL
) s

UNION

SELECT generate_series(break_end, end_hour, interval '30 minutes')::time 
FROM (
    SELECT 
        break_end, end_hour
    FROM hours
    ORDER BY break_start DESC
    LIMIT 1
) s

说明

WITH子句(CTE):

合并两个表。我要添加一个无意义的日期,因为这会导致timestamp。以后使用的功能generate_series仅适用于timestamp,不适用于类型time。在生成之后,::time演员表会将该零件切除。

CTE的结果是:

open_hour             end_hour              break_start           break_end             next_start_time
1970-01-01 08:00:00   1970-01-01 18:00:00   1970-01-01 09:30:00   1970-01-01 09:45:00   1970-01-01 11:00:00
1970-01-01 08:00:00   1970-01-01 18:00:00   1970-01-01 11:00:00   1970-01-01 11:30:00   1970-01-01 15:00:00
1970-01-01 08:00:00   1970-01-01 18:00:00   1970-01-01 15:00:00   1970-01-01 15:15:00   (NULL)

UNION部分:

这部分包含三个子部分。因为我必须合并两个表中的时间序列:

1。。参加开放时间。生成到第一个休息时间开始的时间序列。

为此,我只需要上面CTE的第一行。这就是使用LIMIT 1的原因。

2。。对于所有休息时间:生成从当前休息时间到下一个休息时间的时间序列。

CTE包含window function lead(),它将下一行的start_time移到当前行(请查看CTE结果的最后一列)。所以现在我可以得到所有的休息时间,无论有多少次。在我的示例中,我添加了一个从9:309:45的第三个中断以进行演示。因此,可以从所有这些列(当前break_endnext_start_time)中生成下一个时间序列。因为没有行,所以只有最后一行不包含next_start_time

3。。最后一步:从最后一个休息时间到结束时间生成时间序列。

与(1)类似,它很安静。迭代所有休息时间后,我必须添加从上次休息时间到关闭时间的最后一个时间序列。可以通过不使用next_start_time过滤行或对DESC进行排序并像我一样使用LIMIT 1来实现。


具有更多日期类型的更复杂案例:

demo: db<>fiddle

WITH hours AS (
    SELECT 
        oh.id as day_id,
        oh.open_hour + '1970-01-01'::date as open_hour, 
        oh.end_hour + '1970-01-01'::date as end_hour, 
        bh.start_time + '1970-01-01'::date as break_start,
        bh.end_time + '1970-01-01'::date as break_end,
        lead(start_time + '1970-01-01'::date) OVER (PARTITION BY oh.id ORDER BY start_time) as next_start_time
    FROM open_hours oh
    LEFT JOIN break_hours bh
    ON oh.id = bh.start_date
)

SELECT day_id, generate_series(open_hour, break_start, interval '30 minutes')::time as time_slot 
FROM (
    SELECT DISTINCT ON (day_id)
        day_id, open_hour, break_start
    FROM hours
    ORDER BY day_id, break_start
)s

UNION 

SELECT 
    day_id, generate_series(break_end, next_start_time, interval '30 minutes')::time
FROM ( 
    SELECT  
        day_id, break_end, next_start_time
    FROM
        hours
    WHERE next_start_time IS NOT NULL
) s

UNION

SELECT day_id, generate_series(break_end, end_hour, interval '30 minutes')::time 
FROM (
    SELECT DISTINCT ON (day_id)
        day_id, break_end, end_hour
    FROM hours
    ORDER BY day_id, break_start DESC
) s

ORDER BY day_id, time_slot

主要思想与示例相同仅一天。不同之处在于我们必须考虑不同的日期类型。我在上面的示例中进行了扩展,并增加了第二天的开放时间和休息时间。

更改:

  1. CTE中的窗口函数有PARTITION BY部分。这样可以确保仅将start_time包含的日期转移到同一天。
  2. LIMIT 1将不再起作用,因为它将整个表限制为一行。此项已更改为DISTINCT ON (day_id),将表格限制为每天的第一行。