Need help understanding how to use a cursor variable in a procedure. And use the anonymous block to call the procedure 6 times to run a set of queries. Trying to figure this out have been giving me a headache, thank you in advance for any help.
CREATE OR REPLACE PROCEDURE City_Jail_SP
(lv_query IN NUMBER,
lv_out out VARCHAR2)
AS
cursor qry_lvl IS
BEGIN
OPEN qry_lvl;
LOOP
FETCH INTO
IF chioce = 1 THEN SELECT AVG(COUNT(*))
FROM crime_officers
GROUP BY officer_id;
ELSIF chioce = 2 THEN SELECT MIN(Fine_amount)
FROM Crime_charges;
ELSIF chioce = 3 THEN COLUMN (hearing_date-date_charged) HEADING DAYS
SELECT crime_id, Classification, date_charged, hearing_date,
( hearing_date-date_charged)
FROM crimes
WHERE hearing_date-date_charged >14;
ELSIF choice = 4 THEN select cl.criminal_id, cl.last, cl.first, cc.Crime_code, cc.Fine_amount
FROM criminals cl
JOIN crimes cr
ON cr.criminal_id = cl.criminal_id
JOIN crime_charges cc
ON cc.crime_id = cr.crime_id;
ELSIF chioce = 5 THEN SELECT LAST, FIRST
FROM officers JOIN crime_officers USING (officer_id)
JOIN crimes USING (crime_id)
GROUP BY (LAST, FIRST)
HAVING COUNT(crime_id)>(SELECT AVG(COUNT(crime_id))
FROM crimes JOIN crime_officers using (crime_id)
GROUP BY officer_id);
ELSIF choice = 6 THEN SELECT DISTINCT FIRST, LAST
FROM criminals JOIN crimes USING (criminal_id)
JOIN crime_charges USING (crime_id)
GROUP BY (FIRST, LAST)
HAVING COUNT(Criminal_ID)<(SELECT AVG(COUNT(Criminal_ID))
FROM crimes JOIN criminals USING (Criminal_ID)
GROUP BY Criminal_ID)
ORDER BY FIRST, LAST;
END IF;
close qry_lvl;
END;
/
It looks as if code you wrote should be shift-deleted so that you could start over.
you started to declare a cursor, but never finished it
you didn't declare any cursor variable to store cursor's values into it
anyway, you'd rather switch to a cursor FOR loop as it is simpler to use
you're fetching into ... what?
there's no end loop
you declared IN and OUT parameters, but never used any of them
there's bunch of select statements in IF, almost all of them returning different data set. Also, there's no into clause in any of them. PL/SQL requires it, so you'll have to declare quite a few local variables
typos, typos, typos ... is it chioce or choice?
what is that column ... heading days thing supposed to do? Sounds like something related to SQL*Plus
Maybe you'd actually want to use refcursors. Here's a simple example based on Scott's schema. Depending on department number, I'm returning different data set.
Sample data:
SQL> SELECT * FROM dept;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
Procedure (kind of simulates what you tried to do):
SQL> CREATE OR REPLACE PROCEDURE p_test (par_deptno IN NUMBER,
2 par_rc OUT SYS_REFCURSOR)
3 IS
4 l_dname dept.dname%TYPE;
5 BEGIN
6 SELECT dname
7 INTO l_dname
8 FROM dept
9 WHERE deptno = par_deptno;
10
11 IF l_dname = 'ACCOUNTING'
12 THEN
13 OPEN par_rc FOR SELECT d.dname, e.ename, e.job
14 FROM dept d
15 JOIN emp e
16 ON e.deptno = d.deptno
17 AND d.deptno = par_deptno;
18 ELSIF l_dname = 'SALES'
19 THEN
20 OPEN par_rc FOR SELECT e.ename, e.job, e.sal
21 FROM emp e
22 WHERE e.job = 'SALESMAN';
23 END IF;
24 END;
25 /
Procedure created.
Testing:
SQL> var l_rc refcursor
SQL> exec p_test(10, :l_rc);
PL/SQL procedure successfully completed.
SQL> print :l_rc
DNAME ENAME JOB
-------------- ---------- ---------
ACCOUNTING CLARK MANAGER
ACCOUNTING KING PRESIDENT
ACCOUNTING MILLER CLERK
SQL> exec p_test(30, :l_rc);
PL/SQL procedure successfully completed.
SQL> print :l_rc
ENAME JOB SAL
---------- --------- ----------
ALLEN SALESMAN 1600
WARD SALESMAN 1250
MARTIN SALESMAN 1250
TURNER SALESMAN 1500
SQL>
Please, read some basic PL/SQL documentation, you can't start coding and making up your own syntax. This is Oracle 12c PL/SQL Language Reference. It will take you some time to read it, but - at least - you'll know what you're doing (I hope so).
Related
I have a function:
CREATE OR REPLACE FUNCTION numOfOrders(name VARCHAR2) RETURN sys_refcursor AS
test_cur sys_refcursor;
BEGIN
OPEN test_cur for
SELECT C_CustKey, C_Name, COUNT(*) AS Num_Of_Orders
FROM Customer c
INNER JOIN Orders o
ON o.O_CustKey = c.C_CustKey
WHERE C_Name = name
GROUP BY C_CustKey, C_Name;
RETURN test_cur;
END;
/
the screenshot of the code :
and i called it by typing
SELECT numOfOrders('john') FROM customer;
I get my intended output:
C_CustKey C_Name Num_Of_Orders
0001 john 10
but it gets repeated for the number of rows in my customer table, i can fix this by adding fetch first 1 rows only; but how do I prevent it from happening.
Link to output (I can assure you all 1214 rows are the same) :
Tables
Customer :
Orders :
To cut a long story short: this is wrong:
select numOfOrders('john') from customer;
--------
this
because you're selecting the same dataset for all rows in customer table. What would be better? This:
select numOfOrders('john') from dual;
----
this
Why dual? Because it contains only one row.
A simple example based on Scott's schema (I don't have your data): there are 14 employees in the whole emp table, some of them working in department 10.
Function, similar to yours:
SQL> create or replace function num_of_employees (p_deptno in dept.deptno%type)
2 return sys_refcursor
3 is
4 l_rc sys_refcursor;
5 begin
6 open l_rc for
7 select d.dname,
8 count(*) as num_of_employees
9 from dept d join emp e on e.deptno = d.deptno
10 where d.deptno = p_deptno
11 group by d.dname;
12 return l_rc;
13 end;
14 /
Function created.
This is what you should do - select from dual; the result is just a single line:
SQL> select num_of_employees(10) from dual;
NUM_OF_EMPLOYEES(10)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME NUM_OF_EMPLOYEES
-------------- ----------------
ACCOUNTING 3 --> this is the result
SQL>
But, if you select from emp (which has 14 rows), you'll get 14 duplicate rows as the result:
SQL> select num_of_employees(10) from emp;
NUM_OF_EMPLOYEES(10)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME NUM_OF_EMPLOYEES
-------------- ----------------
ACCOUNTING 3
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME NUM_OF_EMPLOYEES
-------------- ----------------
ACCOUNTING 3
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME NUM_OF_EMPLOYEES
-------------- ----------------
ACCOUNTING 3
<snip>
14 rows selected.
SQL>
You asked whether it'll work (the dual table) if function returns more than a single row: it will.
I modified the function so that - if no department is passed to the function - it returns number of employees in all departments (i.e. multiple rows):
SQL> create or replace function num_of_employees (p_deptno in dept.deptno%type)
2 return sys_refcursor
3 is
4 l_rc sys_refcursor;
5 begin
6 open l_rc for
7 select d.dname,
8 count(*) as num_of_employees
9 from dept d join emp e on e.deptno = d.deptno
10 where d.deptno = p_deptno or p_deptno is null --> modified WHERE clause
11 group by d.dname;
12 return l_rc;
13 end;
14 /
Function created.
Calling the function:
SQL> select num_of_employees(null) from dual;
NUM_OF_EMPLOYEES(NUL
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME NUM_OF_EMPLOYEES
-------------- ----------------
ACCOUNTING 3
RESEARCH 5
SALES 6
SQL>
I have a table T1 which 3 columns with 100 records. All columns and rows were filled but the first column named ID values are empty. So, I wanted to fill the ID column with numbering order(1,2..100) for 100 rows by using PL/SQL Program. I have tried with rownum and with a sequence which is working fine. I want to try with pl/SQL block. I have also tried to write pl/SQL block, however, not getting the desired result.
declare
count1 number;
begin
SELECT COUNT(1) INTO COUNT1 FROM T1;
FOR I IN 1..COUNT1
loop
UPDATE T1 SET ID =I;
end loop;
end;
SQL should be the way to do it; but OK, if you're learning PL/SQL, then this might be one option:
Sample table (ID column should be populated):
SQL> create table test (id number, name varchar2(10));
Table created.
SQL> insert into test (name) select ename from emp;
14 rows created.
SQL> select * From test;
ID NAME
---------- ----------
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER
14 rows selected.
Anonymous PL/SQL block:
SQL> declare
2 i number := 1;
3 begin
4 for cur_r in (select rowid rid from test) loop
5 update test set id = i where rowid = cur_r.rid;
6 i := i + 1;
7 end loop;
8 end;
9 /
PL/SQL procedure successfully completed.
Result:
SQL> select * From test;
ID NAME
---------- ----------
1 SMITH
2 ALLEN
3 WARD
4 JONES
5 MARTIN
6 BLAKE
7 CLARK
8 SCOTT
9 KING
10 TURNER
11 ADAMS
12 JAMES
13 FORD
14 MILLER
14 rows selected.
SQL>
Loops are slow, they process the table row-by-row. Yet another option (you didn't mention and - perhaps - didn't try - is merge.
SQL> update test set id = null;
14 rows updated.
SQL> begin
2 merge into test a
3 using (select b.rowid,
4 row_number() over (order by null) rn
5 from test b
6 ) x
7 on (a.rowid = x.rowid)
8 when matched then update set
9 a.id = x.rn;
10 end;
11 /
PL/SQL procedure successfully completed.
SQL> select * from test;
ID NAME
---------- ----------
1 SMITH
2 ALLEN
3 WARD
4 JONES
5 MARTIN
6 BLAKE
7 CLARK
8 SCOTT
9 KING
10 TURNER
11 ADAMS
12 JAMES
13 FORD
14 MILLER
14 rows selected.
SQL>
I am trying to use a with clause within one of the execute immediate statement. It runs fine, but doesn't provide an output. It says anonymous block completed. I have tried SET SERVEROUTPUT ON command and still doesn't work. Could someone please help me out on this one.
begin
execute immediate 'WITH GG AS (
SELECT G46.PROV_NUM ,G46.SEQ_NUM, FM46G_ALTID_TYPE_1 AS ALTID_TYPE ,ALTID_1 AS ALTID ,FM46G_ALTID_SRC_1 AS ALTID_SRC FROM SPSMDMRW.SCW_CFF_46G G46
UNION
SELECT G46.PROV_NUM ,G46.SEQ_NUM, ALTID_TYPE_2 AS ALTID_TYPE,ALTID_2 AS ALTID,FM46G_ALTID_SRC_1 AS ALTID_SRC FROM SPSMDMRW.SCW_CFF_46G G46
UNION
SELECT G46.PROV_NUM ,G46.SEQ_NUM, ALTID_TYPE_3 AS ALTID_TYPE,ALTID_3 AS ALTID,ALTID_SRC_2 AS ALTID_SRC FROM SPSMDMRW.SCW_CFF_46G G46
UNION
SELECT G46.PROV_NUM ,G46.SEQ_NUM, ALTID_TYPE_4 AS ALTID_TYPE ,ALTID_4 AS ALTID ,ALTID_SRC_3 AS ALTID_SRC FROM SPSMDMRW.SCW_CFF_46G G46
)
select * from GG';
end;
It is not the WITH factoring clause, but the fact that you aren't fetching the result into anything.
Should be something like this:
SQL> set serveroutput on
SQL>
SQL> declare
2 l_dname sys.odcivarchar2list;
3 begin
4 execute immediate 'with gg as
5 (select ename from emp where deptno = 10
6 union all
7 select ename from emp where deptno = 30
8 )
9 select ename from gg'
10 bulk collect into l_dname;
11
12 for i in l_dname.first .. l_dname.last loop
13 dbms_output.put_line(l_dname(i));
14 end loop;
15 end;
16 /
ALLEN
WARD
MARTIN
BLAKE
TURNER
JAMES
PL/SQL procedure successfully completed.
SQL>
I'm stuck on getting my stored procedure to work because I made a VARRAY data type to store all information about allergies, my goal is to input an integer primary key and the result will return the information respected to that primary key.
CREATE OR REPLACE PROCEDURE showAllergies
(
P_PID IN NUMBER,
P_f_name OUT VARCHAR2,
P_l_name OUT VARCHAR2,
P_allergies OUT allergy_ty
)
AS
BEGIN
SELECT p.name.f_name,
p.name.l_name,
al.allergies
INTO P_f_name,
P_l_name,
P_allergies
FROM PATIENT_obj_table p
INNER JOIN PATIENT_allergies al ON p.PID = al.PID
WHERE
al.PID = P_PID;
END showAllergies;
--Must do this in order to execute multiple output parameters
set serveroutput ON;
DECLARE
fname VARCHAR2(25);
lname VARCHAR2(25);
allergies allergy_ty;
total NUMBER(2,0);
begin
--select value(a) INTO allergies FROM PATIENT_allergies WHERE a.PID = 100;
total := allergies.limit;
showAllergies(101, fname, lname, allergies);
DBMS_OUTPUT.PUT_LINE('firstName: '||fname||', lastName: '||lname||' Allergies ');
FOR i in 1 .. total
LOOP DBMS_OUTPUT.PUT_LINE(allergies(i));
end loop;
end;
END;
CREATE OR REPLACE TYPE allergy_ty AS VARRAY(10)
OF VARCHAR2(30);
I don't know how get VARRAY type to print using stored procedure do ya'll lend me some help? -Thanks
You didn't post how tables you use look like, so I'm going to guess. Moreover, I'll use Scott's EMP and DEPT tables to simulate what you have; I'll display department (which is your "patient") and employees who work in that department (which is "allergies" patients suffer from).
SQL> -- this table simulates your PATIENT table
SQL> select * From dept;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL> -- this table simulates your ALLERGIES table
SQL> select deptno, ename from emp;
DEPTNO ENAME
---------- ----------
20 SMITH
30 ALLEN
30 WARD
20 JONES
30 MARTIN
30 BLAKE
10 CLARK
20 SCOTT
10 KING
30 TURNER
20 ADAMS
30 JAMES
20 FORD
10 MILLER
14 rows selected.
SQL>
This is more or less code you wrote; note the way I selected values into p_allergies parameter - not with a join, but a separate query:
SQL> CREATE OR REPLACE TYPE allergy_ty AS VARRAY(10) OF VARCHAR2(30);
2 /
Type created.
SQL> create or replace procedure showallergies
2 (p_pid in number,
3 p_f_name out varchar2,
4 p_l_name out varchar2,
5 p_allergies out allergy_ty
6 )
7 is
8 begin
9 select d.dname, d.loc
10 into p_f_name, p_l_name
11 from dept d
12 where d.deptno = p_pid;
13
14 select e.ename
15 bulk collect into p_allergies
16 from emp e
17 where e.deptno = p_pid;
18 end;
19 /
Procedure created.
SQL>
Let's see how it works:
SQL> set serveroutput on;
SQL> declare
2 fname varchar2(20);
3 lname varchar2(20);
4 allergies allergy_ty;
5 begin
6 showallergies(10, fname, lname, allergies);
7 dbms_output.put_line(fname ||', '|| lname);
8
9 for i in 1 .. allergies.count loop
10 dbms_output.put_line(allergies(i));
11 end loop;
12 end;
13 /
ACCOUNTING, NEW YORK
CLARK
KING
MILLER
PL/SQL procedure successfully completed.
SQL>
Works OK. Now, try to do the same with your tables.
I don't want to use dynamic SQL so I'm trying different ways to alter my where statement.
Inside my WHERE statement, there is a row:
AND c.nregion = pnregion
pnregion is a number of Russian region. This statement helps my cursor to work a lot faster since Oracle doesn't have to look through the full table.
Problem is that if pnregion is equal to 47 or 78 then this row should look like this
AND c.nregion in (47, 78)
How to do it? Should I use CASE somehow or is there something I could do outside my cursor?
You can use:
WHERE ( c.nregion = pnregion
OR ( pnregion IN ( 47, 78 )
AND c.nregion IN ( 47, 78 )
)
)
This is easily extensible if you want more than 2 values as the values can be added to both IN filters.
You can use decode function to add another option per codes 47/78:
AND (c.nregion = pnregion OR c.nregion = decode(pnregion, 47, 78, 78, 47))
If I understood the question, it is a varying elements in IN list that bothers you. In other words, you can't put comma-separated values into an IN list, unless you
switch to dynamic SQL (which is what you don't want)
separate those values into rows
Here's an example, based on Scott's schema; I hope you'll understand it.
This is what you currently have:
SQL> create or replace procedure p_test (pnregion in varchar2)
2 is
3 cursor c1 is select empno, ename from emp
4 where deptno in pnregion;
5 begin
6 for cr in c1 loop
7 dbms_output.put_line(cr.ename);
8 end loop;
9 end;
10 /
Procedure created.
SQL> -- This is OK
SQL> exec p_test('10');
CLARK
KING
MILLER
PL/SQL procedure successfully completed.
SQL> -- This is not OK
SQL> exec p_test('10, 20');
BEGIN p_test('10, 20'); END;
*
ERROR at line 1:
ORA-01722: invalid number
ORA-06512: at "SCOTT.P_TEST", line 6
ORA-06512: at line 1
SQL>
Now, split comma-separated values to rows and watch it work:
SQL> create or replace procedure p_test (pnregion in varchar2)
2 is
3 cursor c1 is select empno, ename from emp
4 where deptno in (select regexp_substr(pnregion, '[^,]+', 1, level)
5 from dual
6 connect by level <= regexp_count(pnregion, ',') + 1
7 );
8 begin
9 for cr in c1 loop
10 dbms_output.put_line(cr.ename);
11 end loop;
12 end;
13 /
Procedure created.
SQL> -- This is OK
SQL> exec p_test('10');
CLARK
KING
MILLER
PL/SQL procedure successfully completed.
SQL> -- This is also OK now
SQL> exec p_test('10, 20');
SMITH
JONES
CLARK
SCOTT
KING
ADAMS
FORD
MILLER
PL/SQL procedure successfully completed.
SQL> -- Or even some more values:
SQL> exec p_test('10, 20,30');
SMITH
ALLEN
WARD
JONES
MARTIN
BLAKE
CLARK
SCOTT
KING
TURNER
ADAMS
JAMES
FORD
MILLER
PL/SQL procedure successfully completed.
SQL>