I'm trying to calling 2 variables inside another procedure that should give me the min and max in a column like this:
create or replace procedure ClassEnrollmentReport
(p_CLASSNAME in class.classname%TYPE)
as
begin
dbms_output.put_line('Max gpa:');
StudentWithGivenGPA(MinMaxGPA.p_maxStudentGPA(p_CLASSNAME));
dbms_output.put_line('Min gpa:');
StudentWithGivenGPA(MinMaxGPA.p_minStudentGPA(p_CLASSNAME));
end ClassEnrollmentReport;
Which gives me an error message like this:
6/5 PL/SQL: Statement ignored
6/35 PLS-00225: subprogram or cursor 'MINMAXGPA' reference is out of scope
8/5 PL/SQL: Statement ignored
8/35 PLS-00225: subprogram or cursor 'MINMAXGPA' reference is out of scope
Here's how the minmaxgpa procedure look like:
create or replace procedure MinMaxGPA
(
p_CLASSNAME in class.classname%type,
p_maxStudentGPA OUT student.gpa%type,
p_minStudentGPA OUT student.gpa%type
)
as
maxStudentGPA student.gpa%type;
minStudentGPA student.gpa%type;
begin
select max(gpa) into maxStudentGPA
from student
where classno = (select classno from class where upper(classname) = upper(p_CLASSNAME));
select min(gpa) into minStudentGPA
from student
where classno = (select classno from class where upper(classname) = upper(p_CLASSNAME));
p_maxStudentGPA := maxStudentGPA;
p_minStudentGPA := minStudentGPA;
end MinMaxGPA;
I know how to do this with a function, but having no idea how can I get this with procedure. Can you help me with this?
With really simple sample tables (just to make the procedure compile):
SQL> create table class as select 'abc' classname, 100 clasno from dual;
Table created.
SQL> create table student as select 1 gpa, 100 classno from dual;
Table created.
Procedure code (I didn't change it):
SQL> create or replace procedure MinMaxGPA
2 (
3 p_CLASSNAME in class.classname%type,
4 p_maxStudentGPA OUT student.gpa%type,
5 p_minStudentGPA OUT student.gpa%type
6
7 )
8 as
9 maxStudentGPA student.gpa%type;
10 minStudentGPA student.gpa%type;
11 begin
12 select max(gpa) into maxStudentGPA
13 from student
14 where classno = (select classno from class where upper(classname) = upper(p_CLASSNAME));
15
16
17 select min(gpa) into minStudentGPA
18 from student
19 where classno = (select classno from class where upper(classname) = upper(p_CLASSNAME));
20
21 p_maxStudentGPA := maxStudentGPA;
22 p_minStudentGPA := minStudentGPA;
23 end MinMaxGPA;
24 /
Procedure created.
Your "new" ClassEnrollmentReport should then look like this: you have to follow the MinMaxGPA procedure's description - it accepts 3 parameters (one in, two out):
SQL> create or replace procedure ClassEnrollmentReport
2 (p_CLASSNAME in class.classname%TYPE)
3 as
4 v_mingpa number;
5 v_maxgpa number;
6 begin
7 minmaxgpa(p_classname, v_mingpa, v_maxgpa);
8 dbms_output.put_line('Max gpa: ' || v_maxgpa);
9 dbms_output.put_line('Min gpa: ' || v_mingpa);
10 end ClassEnrollmentReport;
11 /
Procedure created.
If we test it:
SQL> set serveroutput on
SQL> exec classenrollmentreport('abc');
Max gpa: 1
Min gpa: 1
PL/SQL procedure successfully completed.
SQL>
Related
I am working in a games data base. I want to create a procedure which shows the games created between two dates.
I am using a cursor and a rowtype like this:
CREATE OR REPLACE procedure p_games(v_date1 games.date%type, v_date2 games.date%type)
AS
v_games games%rowtype;
CURSOR checkGames IS
SELECT * INTO v_games
FROM games
WHERE date BETWEEN v_date1 AND v_date2;
BEGIN
FOR register IN checkGames LOOP
dbms_output.put_line(register.v_games);
END LOOP;
END;
/
but when I run it the error is
PLS-00302: the component 'V_GAMES' must be declared.
Should I declare it in any other way?
Not exactly like that.
you don't have to declare cursor variable as you're using a cursor FOR loop
you don't select INTO while declaring a cursor; you would FETCH into if you used a different approach (see example below)
Sample table:
SQL> create table games
2 (id number,
3 c_date date
4 );
Table created.
SQL> insert into games (id, c_date) values (1, date '2022-04-25');
1 row created.
Your procedure, slightly modified:
SQL> CREATE OR REPLACE procedure p_games(v_date1 games.c_date%type, v_date2 games.c_date%type)
2 AS
3 CURSOR checkGames IS
4 SELECT *
5 FROM games
6 WHERE c_date BETWEEN v_date1 AND v_date2;
7
8 BEGIN
9 FOR register IN checkGames LOOP
10 dbms_output.put_line(register.id);
11 END LOOP;
12 END;
13 /
Procedure created.
Testing:
SQL> set serveroutput on
SQL> exec p_games(date '2022-01-01', date '2022-12-31');
1
PL/SQL procedure successfully completed.
SQL>
A different approach; as you can notice, a cursor FOR loop is way simpler as Oracle does most of the dirty job for you (opening the cursor, fetching from it, taking care about exiting the loop, closing the cursor):
SQL> CREATE OR REPLACE procedure p_games(v_date1 games.c_date%type, v_date2 games.c_date%type)
2 AS
3 CURSOR checkGames IS
4 SELECT *
5 FROM games
6 WHERE c_date BETWEEN v_date1 AND v_date2;
7
8 v_games checkGames%rowtype;
9 BEGIN
10 open checkGames;
11 loop
12 fetch checkGames into v_games;
13 exit when checkGames%notfound;
14
15 dbms_output.put_line(v_games.id);
16 END LOOP;
17 close checkGames;
18 END;
19 /
Procedure created.
SQL> set serveroutput on
SQL> exec p_games(date '2022-01-01', date '2022-12-31');
1
PL/SQL procedure successfully completed.
SQL>
I’ve read some on declaring and using variables in PL/SQL. and I’ve only done so in other versions (TSQL) or code languages.
Below is what I’m trying to do, which is
to declare a variable
assign a value to that variable via query
use the subsequent result from #2
in another query.
I’ve tried other methods listed on the Internet but nothing I do seems to work when calling the variable in the final query.
The declaration and into statements work but the last select query does not.
declare
Degr_term varchar2(20);
begin
select max(z.term)
INTO degr_term
from dwh.rpt_ersd_vw d
join dwh.dim_ers_term_vw z
on d.DIM_DEGR_ERS_TERM_SKEY = z.dim_ers_term_skey;
select * from ir_wip.stem_enr s where s.min_deg_term = degr_term;
end;
Just like you selected INTO from the 1st select statement, you have to do it in the 2nd as well. It means that you'll have to declare a variable which will hold its contents.
Presuming - maybe not correctly - that 2nd select returns only one row - you could
SQL> declare
2 degr_term varchar2(20);
3 l_stem stem_enr%rowtype; --> this
4 begin
5 select max(z.term)
6 into degr_term
7 from rpt_ersd_vw d
8 join dim_ers_term_vw z
9 on d.DIM_DEGR_ERS_TERM_SKEY = z.dim_ers_term_skey;
10
11 select *
12 into l_stem --> this
13 from stem_enr s
14 where s.min_deg_term = degr_term;
15 end;
16 /
PL/SQL procedure successfully completed.
SQL>
(I removed schema names as I don't have them and didn't feel like creating ones.)
If the 2nd query returns more than a single row, then you'd e.g.
SQL> declare
2 degr_term varchar2(20);
3 type l_stem_typ is table of stem_enr%rowtype;
4 l_stem_tab l_stem_typ;
5 begin
6 select max(z.term)
7 into degr_term
8 from rpt_ersd_vw d
9 join dim_ers_term_vw z
10 on d.DIM_DEGR_ERS_TERM_SKEY = z.dim_ers_term_skey;
11
12 select *
13 bulk collect
14 into l_stem_tab
15 from stem_enr s
16 where s.min_deg_term = degr_term;
17 end;
18 /
PL/SQL procedure successfully completed.
SQL>
[EDIT: How to output the result? Using a loop]
Presume that table looks like this (I don't know really, you never posted any sample data):
SQL> select * from stem_enr;
MIN_DEG_TERM NAME SURN
------------ ------ ----
1 Little Foot
1 Big Foot
Then you'd (simplified example; I didn't feel like creating other tables as well):
SQL> declare
2 type l_stem_typ is table of stem_enr%rowtype;
3 l_stem_tab l_stem_typ;
4 begin
5 select *
6 bulk collect
7 into l_stem_tab
8 from stem_enr s
9 where s.min_deg_term = 1;
10
11 for i in 1 .. l_stem_tab.count loop
12 dbms_output.put_line(l_stem_tab(i).name ||' '|| l_stem_tab(i).surname);
13 end loop;
14 end;
15 /
Little Foot
Big Foot
PL/SQL procedure successfully completed.
SQL>
TEMP table:
Node
Curr_Cnt
Prev_Cnt
Diff
First
20
40
20
Second
30
70
40
CREATE OR REPLACE FUNCTION NEW_FUNCTION
RETURNS table
IS
c_rec TEMP%ROWTYPE;
TYPE c_tab IS TABLE OF c_rec%TYPE INDEX BY PLS_INTEGER;
l_c_tab c_tab;
BEGIN
SELECT * INTO c_tab FROM
--**The below with clause starting from with returns the same table structure as above temp table**
( WITH batch_id_dtl AS
(SELECT a.*,
rownum rnum
FROM
(SELECT MIN(creation_date) min_cr,
MAX(creation_date) max_cr,
batch_id
FROM oalterr.q_audit_results
GROUP BY batch_id
ORDER BY 1 DESC
)a
WHERE BATCH_ID <= 251940
),
curr_cnt AS
......rest of the code......
);
RETURN C_TAB;
END NEW_FUNCTION;
The above function returns the following error:
expression 'C_TAB' is inappropriate as the left hand side of an assignment statement.
Can anyone please tell me what type should I add in return part and what am I doing wrong in the execution part between begin and end.
For sample table (the one you posted)
SQL> select * from temp;
NODE CURR_CNT PREV_CNT DIFF
------ ---------- ---------- ----------
First 20 40 20
Second 30 70 40
create type at SQL level so that function recognizes it:
SQL> create or replace type t_row as object
2 (node varchar2(10),
3 curr_cnt number,
4 prev_cnt number,
5 diff number);
6 /
Type created.
SQL> create or replace type t_tab as table of t_row;
2 /
Type created.
Function returns a type; for example, number, varchar2 or - in your case - t_tab:
SQL> create or replace function new_function
2 return t_tab
3 is
4 l_tab t_tab;
5 begin
6 select t_row(node, curr_cnt, prev_cnt, diff)
7 bulk collect
8 into l_tab
9 from temp;
10 return l_tab;
11 end new_function;
12 /
Function created.
So far so good. Now, call it. One way is straightforward, just like selecting sysdate (which is also a function):
SQL> select new_function from dual;
NEW_FUNCTION(NODE, CURR_CNT, PREV_CNT, DIFF)
--------------------------------------------------------------------------------
T_TAB(T_ROW('First', 20, 40, 20), T_ROW('Second', 30, 70, 40))
but that looks kind of ugly. Better way is
SQL> select * from table(new_function);
NODE CURR_CNT PREV_CNT DIFF
---------- ---------- ---------- ----------
First 20 40 20
Second 30 70 40
SQL>
The easiest way is to create the function with the PIPELINED clause and then, in body, using the native PL/SQL approach, in a loop iterate trough your select as cursor and return each row with PIPE ROW.
Aditionaly, it would be better to declare the return type and the table function in a package and to avoid generic return types.
Ofc. you then use such a function as
select * from table(package.function())
See also: https://docs.oracle.com/cd/B19306_01/appdev.102/b14289/dcitblfns.htm
I am passing arguments `EBN,BGE' into a procedure , then I am passing this argument to a cursor.
create or replace procedure TEXT_MD (AS_IDS VARCHAR2)
is
CURSOR C_A (AS_ID VARCHAR2) IS
SELECT
name
FROM S_US
WHERE US_ID IN (AS_ID);
BEGIN
FOR A IN C_A (AS_IDS) LOOP
DBMS_OUTPUT.PUT_LINE('I got here: '||AS_IDS);
end loop;
END;
But while debuging the count of the cursor is still null
So my question , why the cursor not returning values with in condition
You are passing a string parameter, so it will be used as a string, not as a list of strings; so, your cursor will be something like
SELECT name
FROM S_US
WHERE US_ID IN ('EBN,BGE')
This will, of course, not do what you need.
You may need to change your procedure and the way to pass parameters; if you want to keep a string parameter , one way could be the following:
setup:
SQL> CREATE TABLE S_US
2 (
3 US_ID,
4 NAME
5 ) AS
6 SELECT 'EBN', 'EBN name' FROM DUAL
7 UNION ALL
8 SELECT 'BGE', 'BGE name' FROM DUAL;
Table created.
procedure:
SQL> CREATE OR REPLACE PROCEDURE TEXT_MD_2(AS_IDS VARCHAR2) IS
2 vSQL varchar2(1000);
3 c sys_refcursor;
4 vName varchar2(16);
5 BEGIN
6 vSQL := 'SELECT name
7 FROM S_US
8 WHERE US_ID IN (' || AS_IDS || ')';
9 open c for vSQL;
10 loop
11 fetch c into vName;
12 if c%NOTFOUND then
13 exit;
14 end if;
15 DBMS_OUTPUT.PUT_LINE(vName);
16 END LOOP;
17 END;
18 /
Procedure created.
You need to call it with a string already formatted to be a parameter list for IN:
SQL> EXEC TEXT_MD_2('''EBN'',''BGE''');
EBN name
BGE name
PL/SQL procedure successfully completed.
This is only an example of a possible way, and not the way I would do this.
Among the reasons to avoud this kind of approach, consider what Justin Cave says:
"that would be a security risk due to SQL injection and would have a potentially significant performance penalty due to constant hard parsing".
I believe you should better check how to pass a list of values to your procedure, rather then using a string to represent a list of strings.
Here is a possible way to do the same thing with a collection:
SQL> CREATE OR REPLACE TYPE tabVarchar2 AS TABLE OF VARCHAR2(16)
2 /
Type created.
SQL>
SQL> CREATE OR REPLACE PROCEDURE TEXT_MD_3(AS_IDS tabVarchar2) IS
2 vSQL VARCHAR2(1000);
3 c SYS_REFCURSOR;
4 vName VARCHAR2(16);
5 BEGIN
6 FOR i IN (SELECT name
7 FROM S_US INNER JOIN TABLE(AS_IDS) tab ON (tab.COLUMN_VALUE = US_ID))
8 LOOP
9 DBMS_OUTPUT.PUT_LINE(i.name);
10 END LOOP;
11 END;
12 /
Procedure created.
SQL>
SQL> DECLARE
2 vList tabVarchar2 := NEW tabVarchar2();
3 BEGIN
4 vList.EXTEND(2);
5 vList(1) := 'BGE';
6 vList(2) := 'EBN';
7 TEXT_MD_3(vList);
8 END;
9 /
BGE name
EBN name
PL/SQL procedure successfully completed.
SQL>
Again, you can define collections in different ways, within a stored procedure or not, indexed or not, and so on; this is only one of the possible ways, not necessarily the best, depending on your environment, needs.
I want insert a cursor into my custom tableObject, but it is not always found.
My RECORD:
create or replace type "RECORDRANKING" as object
(
-- Attributes
COL1 NUMBER,
COL2 VARCHAR(50),
COL3 NUMBER
-- Member functions and procedures
-- member procedure <ProcedureName>(<Parameter> <Datatype>)
)
This is object table:
CREATE OR REPLACE TYPE "TBRANKING" AS TABLE OF RECORDRANKING;
Now I go into creating a function (into a package):
CREATE OR REPLACE PACKAGE BODY PKLBOTTONI as
FUNCTION testlb
(
p_gapup IN NUMBER,
p_gadown IN NUMBER
)
RETURN SYS_REFCURSOR IS
cursor_ranking SYS_REFCURSOR;
position NUMBER ;
gap_ranking TBRANKING;
gaprecord RECORDRANKING;
upgap NUMBER;
downgap NUMBER;
BEGIN
select * into cursor_ranking from(
select pkranking.getRanking( 100 ) from dual);
LOOP
FETCH cursor_ranking INTO gap_ranking;
EXIT WHEN cursor_ranking%NOTFOUND;
INSERT INTO gap_ranking (COL1,COL2,COL3)
VALUES
(cursor_ranking.C1,
cursor_ranking.C2,
cursor_ranking.C3);
END LOOP;
return gap_ranking;
END;
END PKLBOTTONI;
I always get:
Compilation errors for PACKAGE BODY PKLBOTTONI
Error: PL/SQL: ORA-00942: table or view does not exist
Line: 32
Text: INSERT INTO gap_ranking
In the loop you're both fetching into "gap_ranking" and then trying to "insert into" it again. Insert into a collection is not a valid syntax. Fetch is ok, either in a loop or using bulk collect to fetch multiple records at once.
From your excerpt it doesn't look like you have a physical database table, so the way to do it in PL/SQL would be something like below.
For more help using collections you can check Oracle docs below too:
http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/collections.htm
SQL> set serveroutput on
SQL> declare
2 gap_ranking tbranking := tbranking(null); -- initialize
3 begin
4 gap_ranking.delete; -- clear empty record
5 for cur in
6 (select level as i from dual connect by level <= 5)
7 loop
8 -- insert empty
9 gap_ranking.extend;
10 -- attribute values
11 gap_ranking(gap_ranking.last) := recordranking(1000 + cur.i, 'TEST' || cur.i, 10 + cur.i);
12 end loop;
13 -- loop to print - just to illustrate
14 for j in gap_ranking.first .. gap_ranking.last
15 loop
16 dbms_output.put_line(gap_ranking(j).col1 || ',' ||
17 gap_ranking(j).col2 || ',' ||
18 gap_ranking(j).col3);
19
20 end loop;
21 -- same as...
22 for j in 1 .. gap_ranking.count
23 loop
24 dbms_output.put_line(gap_ranking(j).col1 || ',' ||
25 gap_ranking(j).col2 || ',' ||
26 gap_ranking(j).col3);
27
28 end loop;
29 end;
30 /
1001,TEST1,11
1002,TEST2,12
1003,TEST3,13
1004,TEST4,14
1005,TEST5,15
1001,TEST1,11
1002,TEST2,12
1003,TEST3,13
1004,TEST4,14
1005,TEST5,15
PL/SQL procedure successfully completed
SQL>