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