Oracle 将使用 CTE 的工作 SQL 转换为过程

Oracle converting working SQL with CTE to procedure

我有一些 SQL 代码,可以按设计工作。我正在尝试将代码转换为过程以使其灵活,以便可以传入不同的值。

我在尝试创建程序时 运行 遇到了错误。

Errors: PROCEDURE CREATE_XXX
Line/Col: 28/1 PL/SQL: SQL Statement ignored
Line/Col: 37/3 PL/SQL: ORA-00928: missing SELECT keyword

问题似乎出现在 CTE 中,其中包含 SELECT 所以我有点困惑,需要一些帮助。

下面是一个测试用例,其中包含工作 SQL 以及我尝试创建的过程。

在此先感谢您的帮助、耐心和专业知识以及所有回答者。


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

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))
      );
/

    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 <= 15;


     ALTER TABLE locations 
         ADD ( CONSTRAINT locations_pk
       PRIMARY KEY (location_id));


-- works fine

WITH  params  AS
(
    SELECT  1 AS schedule_id,

TO_DATE ( '2021-08-21 00:00:00'
            , 'YYYY-MM-DD HH24:MI:SS'
            )             AS base_date
    ,    INTERVAL '83760' SECOND          AS offset
    ,    INTERVAL '10' MINUTE           AS incr
    ,    INTERVAL '5' MINUTE         AS duration
    FROM    dual
)
SELECT   p.schedule_id
,               l.location_id
,      p.base_date
,      p.base_date + offset
              + (incr * (ROWNUM - 1)) AS start_date
,      p.base_date + offset
              + (incr * (ROWNUM - 1))
            + p.duration         AS end_date
FROM      locations l
CROSS JOIN params   p
ORDER BY  start_date
;

-- having problem 

CREATE OR REPLACE PROCEDURE CREATE_XXX
 (
  i_schedule_id IN PLS_INTEGER,
  i_base_date IN DATE,
  i_offset IN PLS_INTEGER DEFAULT 0, 
i_incr IN PLS_INTEGER DEFAULT 10,
  i_duration         IN PLS_INTEGER DEFAULT 5
)
 AS 
 
l_offset  interval day to second;
   l_incr interval day to second;
  l_duration interval day to second;


BEGIN
 
l_offset :=
NUMTODSINTERVAL(i_offset, 'SECOND') ;

l_incr :=
NUMTODSINTERVAL(i_incr, 'MINUTE') ;

l_duration :=
NUMTODSINTERVAL(i_duration, 'MINUTE') ;


WITH params AS(
SELECT 
   i_schedule_id 
  ,i_base_date
  ,l_offset
  ,l_incr
  ,l_duration
FROM DUAL
)

INSERT INTO schedule(
        schedule_id
      ,location_id
      ,base_date
      ,start_date
      ,end_date
  )
  VALUES 
  (p.schedule_id 
   ,l.location_id 
   ,p.base_date
   ,start_date 
   ,end_date 
);

SELECT   p.schedule_id
,               l.location_id
,      p.base_date
,      p.base_date + p.offset
              + (p.incr * (ROWNUM - 1)) AS start_date
,      p.base_date + p.offset
              + (p.incr * (ROWNUM - 1))
            + p.duration         AS end_date
FROM      locations l
CROSS JOIN params   p
ORDER BY  start_date;
END;
/

问题是这条语句。

WITH params AS(
SELECT 
   i_schedule_id 
  ,i_base_date
  ,l_offset
  ,l_incr
  ,l_duration
FROM DUAL
)

INSERT INTO schedule(
        schedule_id
      ,location_id
      ,base_date
      ,start_date
      ,end_date
  )
  VALUES 
  (p.schedule_id 
   ,l.location_id 
   ,p.base_date
   ,start_date 
   ,end_date 
);

我不确定你想要完成什么。从句法上讲,如果您想将 CTE 作为 insert 的一部分,您需要执行 insert ... select

INSERT INTO schedule(
        schedule_id
      ,location_id
      ,base_date
      ,start_date
      ,end_date
  )
WITH params AS(
SELECT 
   i_schedule_id 
  ,i_base_date
  ,l_offset
  ,l_incr
  ,l_duration
FROM DUAL
)
SELECT 
    p.schedule_id 
   ,l.location_id 
   ,p.base_date
   ,start_date 
   ,end_date 
  FROM params p;

尽管如此,您仍然遇到一些语法问题,但您并不清楚您希望如何解决所有这些问题。

  • p.schedule_id 无效,因为 params CTE 中没有 schedule_id 列。我的猜测是您想在 CTE 中将 i_schedule_id 别名为 schedule_id
  • l.location_id 没有意义,因为您没有从 table 中进行选择,而该 table 可能被赋予别名 l.
  • 您的 params CTE 中没有 base_datestart_dateend_date 列。你如何合理地解决这个问题并不明显。

也许您实际上希望后续的 select 语句成为此 insert 语句的一部分,尽管您用分号终止了 insert?如果是这样,也许你想要

    INSERT INTO schedule(
            schedule_id
          ,location_id
          ,base_date
          ,start_date
          ,end_date
      )
    WITH params AS(
    SELECT 
       i_schedule_id schedule_id
      ,i_base_date base_date
      ,l_offset offset
      ,l_incr incr
      ,l_duration duration
    FROM DUAL
    )
SELECT   p.schedule_id
,               l.location_id
,      p.base_date
,      p.base_date + p.offset
              + (p.incr * (ROWNUM - 1)) AS start_date
,      p.base_date + p.offset
              + (p.incr * (ROWNUM - 1))
            + p.duration         AS end_date
FROM      locations l
CROSS JOIN params   p
ORDER BY  start_date;

这将生成至少可以编译的代码。但是,似乎根本没有任何理由为 CTE 而烦恼。直接引用局部变量和输入参数即可。我还删除了 order by,因为它在 insert 语句的上下文中没有意义。我假设您需要将输入参数作为整数并将它们转换为具有相同名称和不同数据类型的局部变量,而不是仅仅将 interval 参数传递给过程是有原因的。如果你能做到这一点,你可以通过消除局部变量来进一步简化事情。

CREATE OR REPLACE PROCEDURE CREATE_XXX
 (
  i_schedule_id IN PLS_INTEGER,
  i_base_date IN DATE,
  i_offset IN PLS_INTEGER DEFAULT 0, 
i_incr IN PLS_INTEGER DEFAULT 10,
  i_duration         IN PLS_INTEGER DEFAULT 5
)
 AS 
 
l_offset  interval day to second;
   l_incr interval day to second;
  l_duration interval day to second;


BEGIN
 
l_offset :=
NUMTODSINTERVAL(i_offset, 'SECOND') ;

l_incr :=
NUMTODSINTERVAL(i_incr, 'MINUTE') ;

l_duration :=
NUMTODSINTERVAL(i_duration, 'MINUTE') ;


        INSERT INTO schedule(
                schedule_id
              ,location_id
              ,base_date
              ,start_date
              ,end_date
          )
    SELECT   i_schedule_id
    ,        l.location_id
    ,        i_base_date
    ,      i_base_date + l_offset
                  + (l_incr * (ROWNUM - 1)) AS start_date
    ,      i_base_date + l_offset
                  + (l_incr * (ROWNUM - 1))
                + l_duration         AS end_date
    FROM      locations l;
END;
/