【问题标题】:Oracle generating schedule rows with an intervalOracle 生成具有间隔的计划行
【发布时间】:2021-10-18 19:43:48
【问题描述】:

我有一些每 5 分钟生成行的 SQL。如何修改它以消除重叠时间(见下文)

注意:每一行都应该与一个 location_id 相关联,并且在 location_id 上没有重复。在这种情况下,应该生成 25 行,因此 CONNECT by 应该类似于 SELECT count(*) from locations。

我的目标是创建一个函数,该函数接受 schedule_id 和 start_date 的格式 'MMDDYYYY HH24:MI';如果下一个条目将超过午夜,则停止创建行;这意味着某些 location_id 可能不会被使用。

最终结果是将行放置在下面的计划表中。由于我还没有函数,所以可以将 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|
…

【问题讨论】:

  • 所以要“消费”location_id,应该有一些您希望将生成的行分配给位置的顺序。 schedule_id 是否与位置有任何关联,或者它们应该以相同的方式用于任何 schedule_id

标签: sql oracle date intervals


【解决方案1】:

您可以避免递归查询或循环,因为您基本上需要locations 表中每一行的行号。因此,您需要为分析函数提供适当的排序顺序。以下是查询:

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: 或者,如果您想要一个程序,那么它甚至更简单。因为从 12c 开始 Oracle 增加了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 |结束日期 ----------: | ----------: | :----------------- | :----------------- 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小提琴here

【讨论】:

  • @Beefstu 请检查更新版本,不要递归查询。
【解决方案2】:

只需每 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小提琴here

【讨论】:

  • @MTO 感谢您的专业知识。是否可以将循环修改为从位置停止在 SELECT count(*) 处。我需要为每个位置设置 1 行,但在穿过午夜之前停止。还包括一个非重复的 location_id。如果有 25 个位置,并且我从 23:30 开始,我只会得到几行,而从 01:00 开始,我将得到每个位置的一行。我不是 SQL 开发人员,但这似乎需要一个带有停止条件的 LOOP,而不是一个 CONNECT。我是否做出了正确的假设。感谢您的专业知识
  • @Beefstu 只需将条件添加到CONNECT BY 子句即可。
  • @MTO 几乎完美。如何获取与每一行关联的 location_id。只要没有重复,它们就可以按 1-N 的顺序排列。在您下面的 dbfiddle 中,当我们在午夜之前停止或我们用完 location_id 时,我会有 location_id 1-3。请注意 location_id 可能不像我的示例中那样连续。现在回到我的常规 DBA 工作。
【解决方案3】:

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 开发人员来说似乎有点复杂。是否可以将其修改为类似的内容,但在过午夜之前退出 select timestamp '2016-01-10 14:00:00' + numtodsinterval(rownum*10,'MINUTE') from dual connect by level
  • start_timestop_time 应该很明显。 10 分钟的间隔将是 'FREQ=MINUTELY;INTERVAL=10;'
猜你喜欢
  • 2019-04-13
  • 1970-01-01
  • 2020-03-10
  • 1970-01-01
  • 2021-02-17
  • 1970-01-01
  • 1970-01-01
  • 2013-04-15
  • 1970-01-01
相关资源
最近更新 更多