简体   繁体   中英

Oracle converting working SQL with CTE to procedure

I have some SQL code, which works as designed. I'm trying to convert the code to a procedure to make it flexible so that different values maybe passed in.

I am running into an error while trying to create the procedure.

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

The problem seems to be occurring in a CTE, which contains a SELECT so I'm a bit confused and can use some assistance.

Below is a test CASE that contains the working SQL along with the procedure I'm trying to create.

Thanks in advance for your help, patience and expertise and to all who answer.


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

The issue is this statement.

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

I'm not sure what you're trying to accomplish. Syntactically, if you want to have a CTE as part of an insert , you'd want to do an 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;

From there, though, you've still got several syntax issues that it's not obvious how you'd want to solve all of them.

  • p.schedule_id isn't valid because there is no schedule_id column in the params CTE. My guess is that you want to alias i_schedule_id to schedule_id in the CTE.
  • l.location_id doesn't make sense because you're not selecting from a table that could plausibly be given an alias of l .
  • There is no base_date , start_date , or end_date column in your params CTE. And it's not obvious how you'd plausibly fix that.

Maybe you actually intended the subsequent select statement to actually be part of this insert statement despite the fact that you terminated the insert with a semicolon? If so, maybe you want

    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;

That would produce code that compiles at least. There doesn't, however, appear to be any reason to bother with a CTE here at all. Just reference the local variables and input parameters directly. I've also eliminated the order by since it doesn't make sense in the context of an insert statement. I assume there is a reason that you need to take input parameters as integers and convert them to local variables with the same name and a different data type rather than just passing in interval parameters to the procedure. If you can do that, you can further simplify things by eliminating the local variables.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM