暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Oracle 递归CTE从另一表获取值

askTom 2021-08-20
559

问题描述

我有一个递归的CTE,它可以为多个日期生成开始和结束日期,它是按预期工作的。

它将在超过位置表的计数(*)之前停止创建行。

我正在寻找一种方法,在递归CTE中为每一行指定一个location_id ,而不具有重复的location_id。


开始/结束日期和地点之间没有共同性,所以我觉得加入是不可能的。

在下面的示例代码中,我尝试了一个交叉应用,它为递归代码生成的每个组提供了相同的location_id ,因此它也不起作用,这就是我如何离开测试用例的。

我正在使用管道功能来模拟多个日期,但我不一定会和这个想法联系在一起。如果解决方案要求一次处理一个日期。

我的目标是通过传入一个计划ID、日期范围和一个表示计划开始时间的数字来填充下面的计划表。
在我的测试用例中,它都是硬编码的,因为我希望测试用例尽可能简单。您可以假定Schedule_id =1或任意数字。


一旦我得到这个逻辑工作,我想使用一个. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

主键为:调度ID、位置ID、基日期

我的测试用例在下面,我似乎不能得到工作。提前感谢您的专业技能和耐心。


Current Result 

LOCATION_ID    BASE_DATE    START_DATE    END_DATE
1    08212021 00:00:00    08212021 23:16:00    08212021 23:21:00
1    08212021 00:00:00    08212021 23:26:00    08212021 23:31:00
1    08212021 00:00:00    08212021 23:36:00    08212021 23:41:00
…
…

Expected Result

LOCATION_ID    BASE_DATE    START_DATE    END_DATE
1    08212021 00:00:00    08212021 23:16:00    08212021 23:21:00
2    08212021 00:00:00    08212021 23:26:00    08212021 23:31:00 
3    08212021 00:00:00    08212021 23:36:00    08212021 23:41:00
…
...


ALTER SESSION SET NLS_DATE_FORMAT = 'MMDDYYYY HH24:MI:SS';

CREATE OR REPLACE TYPE nt_date IS TABLE OF DATE;
/

    CREATE OR REPLACE FUNCTION generate_dates_pipelined(
      p_from IN DATE,
      p_to   IN DATE
    )
    RETURN nt_date  PIPELINED   DETERMINISTIC
    IS
      v_start DATE := TRUNC(LEAST(p_from, p_to));
      v_end   DATE := TRUNC(GREATEST(p_from, p_to));
    BEGIN
     LOOP
        PIPE ROW (v_start);
        EXIT WHEN v_start >= v_end;
        v_start := v_start + INTERVAL '1' DAY;
      END LOOP;
      RETURN;
    END       generate_dates_pipelined;
/

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 'T' 
         END AS location_type

    FROM   dual
    CONNECT BY level <= 20;

create table schedule(
       schedule_id NUMBER(4),
       location_id number(4),
       base_date DATE,
       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))
      );


WITH input  (base_date,start_time) AS (
SELECT 
COLUMN_VALUE,
COLUMN_VALUE+
NUMTODSINTERVAL(83760, 'SECOND')
  FROM   TABLE(generate_dates_pipelined(DATE '2021-08-21',DATE '2021-08-30')) 
)
SELECT
     location_id,
     base_date, start_time + (LEVEL-1) * INTERVAL '10' MINUTE
         AS start_date,
       start_time + (LEVEL-1) * INTERVAL '10' MINUTE + INTERVAL '5' MINUTE
         AS end_date 
FROM   input i
CROSS APPLY  (SELECT location_id from locations)
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;


专家解答

I'm looking for a way to assign a location_id to each row in the recursive CTE without having a duplicate location_id


你在这里到底是什么意思?

每个位置都可以显示only结果中的一次?或者在重新使用同一位置之前,必须显示所有其他位置?

无论哪种方式,您都已使用1..N中的值创建了位置表,并且没有间隙。所以在我看来,你可以给你的日期生成器指定行号,然后把它们连接到各个位置。

如果要循环查看位置,请先取日期行号的模数。使用位置表中的行数作为模数。

当您在19c上时,下面是一个使用SQL宏来生成日期并循环访问位置的示例:

create or replace function generate_days (   
  start_date date, end_date date,  
  day_increment integer default 1  
)   
  return clob sql_macro as  
  stmt clob;  
begin  
    
  stmt := 'select start_date  
           + ( level - 1 ) * day_increment as dt  
  from   dual  
  connect by level <= (  
    ( ( end_date - start_date ) + day_increment 
    ) / day_increment  
  )';  
  
  return stmt;  
    
end generate_days;  
/

select * from (
  select dt, 
         mod ( row_number () over ( order by dt ), 20 ) + 1 rn
  from   generate_days ( date '2021-08-21',date '2021-08-30', 10/1440 )
) join locations
on     rn = location_id
order  by dt
fetch first 23 rows only;

DT                         RN    LOCATION_ID LOCATION_NAME    LOCATION_TYPE   
21-AUG-2021 00:00:00        2              2 Door 2           T                
21-AUG-2021 00:10:00        3              3 Door 3           T                
...  ...
21-AUG-2021 02:50:00       19             19 Door 19          T                
21-AUG-2021 03:00:00       20             20 Door 20          T                
21-AUG-2021 03:10:00        1              1 Door 1           T                
21-AUG-2021 03:20:00        2              2 Door 2           T                
21-AUG-2021 03:30:00        3              3 Door 3           T                
21-AUG-2021 03:40:00        4              4 Door 4           T


有关日期生成和宏的更多信息,请访问https://blogs.oracle.com/sql/how-to-generate-days-weeks-or-months-between-two-dates-in-oracle-database

示例查询只是一个普通的CTE ,它不是递归的。要实现递归,必须有一个联合所有分支,将CTE连接到源表。
文章转载自askTom,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论