我有一张表格,用于存储营业时间和营业时间
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
答案 0 :(得分:1)
似乎应该清理表建模。例如。您不应将时间存储为文本类型,而应存储为time without time zone
。
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:30
到9:45
的第三个中断以进行演示。因此,可以从所有这些列(当前break_end
至next_start_time
)中生成下一个时间序列。因为没有行,所以只有最后一行不包含next_start_time
。
3。。最后一步:从最后一个休息时间到结束时间生成时间序列。
与(1)类似,它很安静。迭代所有休息时间后,我必须添加从上次休息时间到关闭时间的最后一个时间序列。可以通过不使用next_start_time
过滤行或对DESC
进行排序并像我一样使用LIMIT 1
来实现。
具有更多日期类型的更复杂案例:
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
主要思想与示例相同仅一天。不同之处在于我们必须考虑不同的日期类型。我在上面的示例中进行了扩展,并增加了第二天的开放时间和休息时间。
更改:
PARTITION BY
部分。这样可以确保仅将start_time
包含的日期转移到同一天。 LIMIT 1
将不再起作用,因为它将整个表限制为一行。此项已更改为DISTINCT ON (day_id)
,将表格限制为每天的第一行。