使用休息时间生成系列
generate series using break time
我有一个 table 存储营业时间和结束时间
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;
我还有一个 table 那个
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 上午,结束时间是 06:00 下午,我的休息时间是 11:00 上午到 11:30 和另一个休息时间是 03:00 PM 到 03:15 PM 然后我需要生成从 08:00 AM 到 11:00 AM 和 11:30 AM 到 03:00 PM 和 03:15 到 06:00 下午。
示例数据
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
看来你的table造型要清理干净了。例如。您不应将时间存储为文本类型,而应存储为 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):
合并两个 table。我添加了一个无意义的日期,因为这会导致 timestamp
。后面使用的函数 generate_series
仅适用于 timestamp
s 而不适用于类型 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
部分:
这部分包含三个子部分。因为我必须合并来自 tables:
的时间序列
1. 取开放时间。生成一个时间序列到第一次休息开始。
为此,我只需要上面 CTE 的第一行。这就是使用 LIMIT 1
的原因。
2. For all breaks: 生成从当前break结束到下一个break开始的时间序列。
CTE 包含一个 window function lead()
,它将下一行的 start_time
移动到当前行(查看 CTE 结果的最后一列)。所以现在我可以得到所有的休息时间,不管有多少。在我的示例中,我添加了从 9:30
到 9:45
的第三个中断来演示它。因此可以从所有这些列(当前 break_end
到 next_start_time
)生成下一个时间序列。只有最后一行不包含 next_start_time
因为有 none.
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
主要思想与示例中的相同仅一天。不同之处在于我们必须考虑不同的日期类型。我扩展了上面的示例并添加了具有不同开放时间和休息时间的第二天。
变化:
- CTE 中的 window 函数有一个
PARTITION BY
部分。这确保只有 start_time
s 被转移到同一天。
LIMIT 1
将不再有效,因为它将整个 table 限制为一行。这已更改为 DISTINCT ON (day_id)
,它将 table 限制为每天的第一行。
我有一个 table 存储营业时间和结束时间
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;
我还有一个 table 那个
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 上午,结束时间是 06:00 下午,我的休息时间是 11:00 上午到 11:30 和另一个休息时间是 03:00 PM 到 03:15 PM 然后我需要生成从 08:00 AM 到 11:00 AM 和 11:30 AM 到 03:00 PM 和 03:15 到 06:00 下午。
示例数据
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
看来你的table造型要清理干净了。例如。您不应将时间存储为文本类型,而应存储为 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):
合并两个 table。我添加了一个无意义的日期,因为这会导致 timestamp
。后面使用的函数 generate_series
仅适用于 timestamp
s 而不适用于类型 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
部分:
这部分包含三个子部分。因为我必须合并来自 tables:
的时间序列1. 取开放时间。生成一个时间序列到第一次休息开始。
为此,我只需要上面 CTE 的第一行。这就是使用 LIMIT 1
的原因。
2. For all breaks: 生成从当前break结束到下一个break开始的时间序列。
CTE 包含一个 window function lead()
,它将下一行的 start_time
移动到当前行(查看 CTE 结果的最后一列)。所以现在我可以得到所有的休息时间,不管有多少。在我的示例中,我添加了从 9:30
到 9:45
的第三个中断来演示它。因此可以从所有这些列(当前 break_end
到 next_start_time
)生成下一个时间序列。只有最后一行不包含 next_start_time
因为有 none.
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
主要思想与示例中的相同仅一天。不同之处在于我们必须考虑不同的日期类型。我扩展了上面的示例并添加了具有不同开放时间和休息时间的第二天。
变化:
- CTE 中的 window 函数有一个
PARTITION BY
部分。这确保只有start_time
s 被转移到同一天。 LIMIT 1
将不再有效,因为它将整个 table 限制为一行。这已更改为DISTINCT ON (day_id)
,它将 table 限制为每天的第一行。