Oracle 生成具有间隔的计划行
Oracle generating schedule rows with an interval
我有一些 SQL 每 5 分钟生成一次行。如何修改以消除重叠时间(见下文)
注意:每行应与 location_id 相关联,location_id 不重复。在这种情况下,应该生成 25 行,因此 CONNECT by 应该类似于 SELECT count(*) from locations.
我的目标是创建一个接受 schedule_id 和 start_date 格式的函数
'MMDDYYYY HH24:MI';如果下一个条目将跨越午夜,则停止创建行;这意味着某些 location_id 可能无法使用。
最终结果是将这些行放在下面的时间表 table 中。因为我还没有函数,所以 schedule_id 可以硬编码为 1。我听说过递归 CTE,这种方法的质量如何?
提前感谢所有回答者和您的专业知识。
ALTER SESSION SET NLS_DATE_FORMAT = 'MMDDYYYY HH24:MI:SS';
create table schedule(
schedule_id NUMBER(4),
location_id number(4),
start_date DATE,
end_date DATE,
CONSTRAINT start_min check (start_date=trunc(start_date,'MI')),
CONSTRAINT end_min check (end_date=trunc(end_date,'MI')),
CONSTRAINT end_gt_start CHECK (end_date >= start_date),
CONSTRAINT same_day CHECK (TRUNC(end_date) = TRUNC(start_date))
);
CREATE TABLE locations AS
SELECT level AS location_id,
'Door ' || level AS location_name,
CASE. round(dbms_random.value(1,3))
WHEN 1 THEN 'A'
WHEN 2 THEN 'T'
WHEN 3 THEN 'G'
END AS location_type
FROM dual
CONNECT BY level <= 25;
with
row_every_5_mins as
( select trunc(sysdate) + (rownum-1)*5/1440 t_from,
trunc(sysdate) + rownum*5/1440 t_to
from dual
connect by level <= 1440/5
) SELECT * from row_every_5_mins;
Current output:
|T_FROM|T_TO|
|-----------------|-----------------|
|08162021 00:00:00|08162021 00:05:00|
|08162021 00:05:00|08162021 00:10:00|
|08162021 00:10:00|08162021 00:15:00|
|08162021 00:15:00|08162021 00:20:00|
…
期望的输出
|T_FROM|T_TO|
|-----------------|-----------------|
|08162021 00:00:00|08162021 00:05:00|
|08162021 00:10:00|08162021 00:15:00|
|08162021 00:20:00|08162021 00:25:00|
…
只需每 10 分钟而不是每 5 分钟循环一次:
WITH input (start_time) AS (
SELECT TRUNC(SYSDATE) + INTERVAL '23:30' HOUR TO MINUTE FROM DUAL
)
SELECT start_time + (LEVEL-1) * INTERVAL '10' MINUTE
AS t_from,
start_time + (LEVEL-1) * INTERVAL '10' MINUTE + INTERVAL '5' MINUTE
AS t_to
FROM input
CONNECT BY (LEVEL-1) * INTERVAL '10' MINUTE < INTERVAL '1' DAY
AND LEVEL <= (SELECT COUNT(*) FROM locations)
AND start_time + (LEVEL-1) * INTERVAL '10' MINUTE < TRUNC(start_time) + INTERVAL '1' DAY;
db<>fiddle here
您可以避免递归查询或循环,因为您本质上需要 locations
table 中每一行的行号。因此,您需要为分析函数提供适当的排序顺序。下面是查询:
with a as (
select
date '2021-01-01'
+ to_dsinterval('0 23:30:00')
as start_dt_param
from dual
)
, date_gen as (
select
location_id
, start_dt_param
, start_dt_param + (row_number() over(order by location_id) - 1)
* interval '10' minute as start_dt
, start_dt_param + (row_number() over(order by location_id) - 1)
* interval '10' minute + interval '5' minute as end_dt
from a
cross join locations
)
select
location_id
, start_dt
, end_dt
from date_gen
where end_dt < trunc(start_dt_param + 1)
LOCATION_ID | START_DT | END_DT
----------: | :------------------ | :------------------
1 | 2021-01-01 23:30:00 | 2021-01-01 23:35:00
2 | 2021-01-01 23:40:00 | 2021-01-01 23:45:00
3 | 2021-01-01 23:50:00 | 2021-01-01 23:55:00
UPD:
或者,如果您想要一个程序,那么它甚至更简单。因为Oracle从12c开始有fetch first
加法,解析函数可能会简化为rownum
伪列:
create or replace procedure populate_schedule (
p_schedule_id in number
, p_start_date in date
) as
begin
insert into schedule (schedule_id, location_id, start_date, end_date)
select
p_schedule_id
, location_id
, p_start_date + (rownum - 1) * interval '10' minute
, p_start_date + (rownum - 1) * interval '10' minute + interval '5' minute
from locations
/*Put your order of location assignment here*/
order by location_id
/*The number of 10-minute intervals before midnight from the first end_date*/
fetch first ((trunc(p_start_date + 1) - p_start_date + 1/24/60*5)*24*60/10) rows only
;
commit;
end;
/
begin
populate_schedule(1, timestamp '2020-01-01 23:37:00');
populate_schedule(2, timestamp '2020-01-01 23:35:00');
populate_schedule(3, timestamp '2020-01-01 23:33:00');
end;/
select *
from schedule
order by schedule_id, start_date
SCHEDULE_ID | LOCATION_ID | START_DATE | END_DATE
----------: | ----------: | :------------------ | :------------------
1 | 1 | 2020-01-01 23:37:00 | 2020-01-01 23:42:00
1 | 2 | 2020-01-01 23:47:00 | 2020-01-01 23:52:00
2 | 1 | 2020-01-01 23:35:00 | 2020-01-01 23:40:00
2 | 2 | 2020-01-01 23:45:00 | 2020-01-01 23:50:00
2 | 3 | 2020-01-01 23:55:00 | 2020-01-02 00:00:00
3 | 1 | 2020-01-01 23:33:00 | 2020-01-01 23:38:00
3 | 2 | 2020-01-01 23:43:00 | 2020-01-01 23:48:00
3 | 3 | 2020-01-01 23:53:00 | 2020-01-01 23:58:00
db<>fiddle here
CTE 无疑是最快的解决方案。如果你想获得更多的时间间隔灵活性,那么你可以使用 SCHEDULER SCHEDULE
。作为缺点,性能可能较弱。
CREATE OR REPLACE TYPE TimestampRecType AS OBJECT (
T_FROM TIMESTAMP(0),
T_TO TIMESTAMP(0)
);
CREATE OR REPLACE TYPE TimestampTableType IS TABLE OF TimestampRecType;
CREATE OR REPLACE FUNCTION GetGchedule(
start_time IN TIMESTAMP,
stop_time in TIMESTAMP DEFAULT TRUNC(SYSDATE)+1)
RETURN TimestampTableType AS
ret TimestampTableType := TimestampTableType();
return_date_after TIMESTAMP := start_time;
next_run_date TIMESTAMP ;
BEGIN
LOOP
DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING('FREQ=MINUTELY;INTERVAL=5;', NULL, return_date_after, next_run_date);
ret.EXTEND;
ret(ret.LAST) := TimestampRecType(return_date_after, next_run_date);
return_date_after := next_run_date;
EXIT WHEN next_run_date >= stop_time;
END LOOP;
RETURN ret;
END;
SELECT *
FROM TABLE(GetGchedule(trunc(sysdate)));
在此处查看日历语法:Calendaring Syntax
我有一些 SQL 每 5 分钟生成一次行。如何修改以消除重叠时间(见下文)
注意:每行应与 location_id 相关联,location_id 不重复。在这种情况下,应该生成 25 行,因此 CONNECT by 应该类似于 SELECT count(*) from locations.
我的目标是创建一个接受 schedule_id 和 start_date 格式的函数 'MMDDYYYY HH24:MI';如果下一个条目将跨越午夜,则停止创建行;这意味着某些 location_id 可能无法使用。
最终结果是将这些行放在下面的时间表 table 中。因为我还没有函数,所以 schedule_id 可以硬编码为 1。我听说过递归 CTE,这种方法的质量如何?
提前感谢所有回答者和您的专业知识。
ALTER SESSION SET NLS_DATE_FORMAT = 'MMDDYYYY HH24:MI:SS';
create table schedule(
schedule_id NUMBER(4),
location_id number(4),
start_date DATE,
end_date DATE,
CONSTRAINT start_min check (start_date=trunc(start_date,'MI')),
CONSTRAINT end_min check (end_date=trunc(end_date,'MI')),
CONSTRAINT end_gt_start CHECK (end_date >= start_date),
CONSTRAINT same_day CHECK (TRUNC(end_date) = TRUNC(start_date))
);
CREATE TABLE locations AS
SELECT level AS location_id,
'Door ' || level AS location_name,
CASE. round(dbms_random.value(1,3))
WHEN 1 THEN 'A'
WHEN 2 THEN 'T'
WHEN 3 THEN 'G'
END AS location_type
FROM dual
CONNECT BY level <= 25;
with
row_every_5_mins as
( select trunc(sysdate) + (rownum-1)*5/1440 t_from,
trunc(sysdate) + rownum*5/1440 t_to
from dual
connect by level <= 1440/5
) SELECT * from row_every_5_mins;
Current output:
|T_FROM|T_TO|
|-----------------|-----------------|
|08162021 00:00:00|08162021 00:05:00|
|08162021 00:05:00|08162021 00:10:00|
|08162021 00:10:00|08162021 00:15:00|
|08162021 00:15:00|08162021 00:20:00|
…
期望的输出
|T_FROM|T_TO|
|-----------------|-----------------|
|08162021 00:00:00|08162021 00:05:00|
|08162021 00:10:00|08162021 00:15:00|
|08162021 00:20:00|08162021 00:25:00|
…
只需每 10 分钟而不是每 5 分钟循环一次:
WITH input (start_time) AS (
SELECT TRUNC(SYSDATE) + INTERVAL '23:30' HOUR TO MINUTE FROM DUAL
)
SELECT start_time + (LEVEL-1) * INTERVAL '10' MINUTE
AS t_from,
start_time + (LEVEL-1) * INTERVAL '10' MINUTE + INTERVAL '5' MINUTE
AS t_to
FROM input
CONNECT BY (LEVEL-1) * INTERVAL '10' MINUTE < INTERVAL '1' DAY
AND LEVEL <= (SELECT COUNT(*) FROM locations)
AND start_time + (LEVEL-1) * INTERVAL '10' MINUTE < TRUNC(start_time) + INTERVAL '1' DAY;
db<>fiddle here
您可以避免递归查询或循环,因为您本质上需要 locations
table 中每一行的行号。因此,您需要为分析函数提供适当的排序顺序。下面是查询:
with a as ( select date '2021-01-01' + to_dsinterval('0 23:30:00') as start_dt_param from dual ) , date_gen as ( select location_id , start_dt_param , start_dt_param + (row_number() over(order by location_id) - 1) * interval '10' minute as start_dt , start_dt_param + (row_number() over(order by location_id) - 1) * interval '10' minute + interval '5' minute as end_dt from a cross join locations ) select location_id , start_dt , end_dt from date_gen where end_dt < trunc(start_dt_param + 1)
LOCATION_ID | START_DT | END_DT ----------: | :------------------ | :------------------ 1 | 2021-01-01 23:30:00 | 2021-01-01 23:35:00 2 | 2021-01-01 23:40:00 | 2021-01-01 23:45:00 3 | 2021-01-01 23:50:00 | 2021-01-01 23:55:00
UPD:
或者,如果您想要一个程序,那么它甚至更简单。因为Oracle从12c开始有fetch first
加法,解析函数可能会简化为rownum
伪列:
create or replace procedure populate_schedule ( p_schedule_id in number , p_start_date in date ) as begin insert into schedule (schedule_id, location_id, start_date, end_date) select p_schedule_id , location_id , p_start_date + (rownum - 1) * interval '10' minute , p_start_date + (rownum - 1) * interval '10' minute + interval '5' minute from locations /*Put your order of location assignment here*/ order by location_id /*The number of 10-minute intervals before midnight from the first end_date*/ fetch first ((trunc(p_start_date + 1) - p_start_date + 1/24/60*5)*24*60/10) rows only ; commit; end; /
begin populate_schedule(1, timestamp '2020-01-01 23:37:00'); populate_schedule(2, timestamp '2020-01-01 23:35:00'); populate_schedule(3, timestamp '2020-01-01 23:33:00'); end;/
select * from schedule order by schedule_id, start_date
SCHEDULE_ID | LOCATION_ID | START_DATE | END_DATE ----------: | ----------: | :------------------ | :------------------ 1 | 1 | 2020-01-01 23:37:00 | 2020-01-01 23:42:00 1 | 2 | 2020-01-01 23:47:00 | 2020-01-01 23:52:00 2 | 1 | 2020-01-01 23:35:00 | 2020-01-01 23:40:00 2 | 2 | 2020-01-01 23:45:00 | 2020-01-01 23:50:00 2 | 3 | 2020-01-01 23:55:00 | 2020-01-02 00:00:00 3 | 1 | 2020-01-01 23:33:00 | 2020-01-01 23:38:00 3 | 2 | 2020-01-01 23:43:00 | 2020-01-01 23:48:00 3 | 3 | 2020-01-01 23:53:00 | 2020-01-01 23:58:00
db<>fiddle here
CTE 无疑是最快的解决方案。如果你想获得更多的时间间隔灵活性,那么你可以使用 SCHEDULER SCHEDULE
。作为缺点,性能可能较弱。
CREATE OR REPLACE TYPE TimestampRecType AS OBJECT (
T_FROM TIMESTAMP(0),
T_TO TIMESTAMP(0)
);
CREATE OR REPLACE TYPE TimestampTableType IS TABLE OF TimestampRecType;
CREATE OR REPLACE FUNCTION GetGchedule(
start_time IN TIMESTAMP,
stop_time in TIMESTAMP DEFAULT TRUNC(SYSDATE)+1)
RETURN TimestampTableType AS
ret TimestampTableType := TimestampTableType();
return_date_after TIMESTAMP := start_time;
next_run_date TIMESTAMP ;
BEGIN
LOOP
DBMS_SCHEDULER.EVALUATE_CALENDAR_STRING('FREQ=MINUTELY;INTERVAL=5;', NULL, return_date_after, next_run_date);
ret.EXTEND;
ret(ret.LAST) := TimestampRecType(return_date_after, next_run_date);
return_date_after := next_run_date;
EXIT WHEN next_run_date >= stop_time;
END LOOP;
RETURN ret;
END;
SELECT *
FROM TABLE(GetGchedule(trunc(sysdate)));
在此处查看日历语法:Calendaring Syntax