I have written a procedure in PL/SQL and want to return a EMP type object. Is it possible to do that? If yes, how can I do it?
Here is the code:
CREATE OR REPLACE
PROCEDURE get_emp_rs (p_deptno IN emp.deptno%TYPE,
p_recordset OUT emp_det) AS
emp_details emp_det;
BEGIN
OPEN p_recordset FOR
SELECT ename,
empno
FROM emp
WHERE deptno = p_deptno
ORDER BY ename;
fetch p_recordset into emp_details;
--exit when p_recordset%notfound;
--end loop;
--for indx in p_recordset
--loop
emp_details.ename:= 'test';
--end loop;
END get_emp_rs;
/
SET SERVEROUTPUT ON SIZE 1000000
DECLARE
l_cursor emp_det;
--l_cur emp_det;
--l_ename emp.ename%TYPE;
--l_empno emp.empno%TYPE;
l_deptno emp.deptno%TYPE;
BEGIN
l_cur:=get_emp_rs ('30',
l_cursor);
dbms_output.put_line('low');
/*LOOP
FETCH l_cursor
INTO l_ename, l_empno, l_deptno;
EXIT WHEN l_cursor%NOTFOUND;*/
DBMS_OUTPUT.PUT_LINE(l_cursor.ename || ' | ' || l_cursor.empno);
end;
/
I want to get the ename and empno after finally update in the procedure.
How can I do it? If there is any better way of doing this please suggest me.
And also please suggest me how I can do it in this way. I cannot use any functions here that's the only obligation. Also please let me know if there is a way of doing this using.
In your example (Table EMP) a single EMP Record shouldn't be selected by the department number. Use the EMPNO instead.
The following is a quick solution and hopefully self-explainable:
SET SERVEROUTPUT ON;
SET FEEDBACK OFF;
CLEAR;
--Define the dataset object
CREATE TYPE EMP_DET AS OBJECT (
EMPNO NUMBER(4),
ENAME VARCHAR2(10)
);
/
--Define a collection of dataset objects
CREATE TYPE EMP_DET_LIST IS TABLE OF EMP_DET;
/
--Get a SINGLE record into the OUT-Variable identified by EMP PK EMPNO
CREATE OR REPLACE PROCEDURE GET_EMP_RS(P_EMPNO IN EMP.EMPNO%TYPE,
P_RECORDSET IN OUT EMP_DET) AS
BEGIN
--Create the return object inside SQL
SELECT EMP_DET(EMPNO, ENAME)
INTO P_RECORDSET
FROM EMP
WHERE EMPNO = P_EMPNO;
P_RECORDSET.ENAME := 'test';
EXCEPTION
WHEN NO_DATA_FOUND THEN
--Return NULL if employee not found
P_RECORDSET := NULL;
END GET_EMP_RS;
/
--Get a LIST OF employees by department
CREATE OR REPLACE PROCEDURE GET_EMP_LIST(P_DEPTNO IN EMP.DEPTNO%TYPE,
P_RECORDLIST OUT EMP_DET_LIST) AS
TYPE C_CURSOR IS REF CURSOR; -- <-- For the explicit cursor solution only
C_EMP_RS C_CURSOR; -- <-- For the explicit cursor solution only
V_RS EMP_DET; -- <-- For the explicit cursor solution only
BEGIN
--Initialize out object
P_RECORDLIST := EMP_DET_LIST();
--Create the return object inside SQL
--via bulk collect
/*
SELECT EMP_DET(EMPNO,ENAME)
BULK COLLECT INTO INTO P_RECORDLIST
FROM EMP
WHERE DEPTNO = P_DEPTNO;
*/
--with manipulation of records
--use a FOR-LOOP with implizit cursor
/*
FOR L_RS IN (SELECT EMP_DET(EMPNO, ENAME) EMP_RS
FROM EMP
WHERE DEPTNO = P_DEPTNO) LOOP
L_RS.EMP_RS.ENAME := 'TEST';
P_RECORDLIST.EXTEND;
P_RECORDLIST(P_RECORDLIST.LAST) := L_RS.EMP_RS;
NULL;
END LOOP;
*/
--or define an explicit cursor and LOOP-FETCH
OPEN C_EMP_RS FOR
SELECT EMP_DET(EMPNO, ENAME) EMP_RS
FROM EMP
WHERE DEPTNO = P_DEPTNO;
LOOP
FETCH C_EMP_RS
INTO V_RS;
EXIT WHEN C_EMP_RS%NOTFOUND;
V_RS.ENAME := 'TEST';
P_RECORDLIST.EXTEND;
P_RECORDLIST(P_RECORDLIST.LAST) := V_RS;
END LOOP;
CLOSE C_EMP_RS;
END GET_EMP_LIST;
/
--**************************
-- Test
--**************************
DECLARE
L_CURSOR EMP_DET;
L_LIST EMP_DET_LIST;
BEGIN
DBMS_OUTPUT.PUT_LINE('---- Single EMP ----');
GET_EMP_RS(7369, L_CURSOR);
IF (L_CURSOR IS NOT NULL) THEN
DBMS_OUTPUT.PUT_LINE(L_CURSOR.ENAME || ' | ' || L_CURSOR.EMPNO);
END IF;
DBMS_OUTPUT.PUT_LINE('---- EMP List ----');
GET_EMP_LIST(30, L_LIST);
IF (L_LIST.count > 0 ) THEN
FOR L_I IN L_LIST.FIRST .. L_LIST.LAST LOOP
DBMS_OUTPUT.PUT_LINE(L_LIST(L_I).ENAME || ' | ' || L_LIST(L_I).EMPNO);
END LOOP;
END IF;
END;
/
DROP PROCEDURE GET_EMP_RS;
DROP PROCEDURE GET_EMP_LIST;
DROP TYPE EMP_DET_LIST;
DROP TYPE EMP_DET;
Output:
---- Single EMP ----
test | 7369
---- EMP List ----
TEST | 7499
TEST | 7521
TEST | 7654
TEST | 7698
TEST | 7844
TEST | 7900
SQL>
Related
Well I have a INDEX BY TABLE which looks like this:
TYPE dept_table_type IS TABLE OF departments.department_name%TYPE INDEX BY PLS_INTEGER;
my_dept_table dept_table_type;
And with this Loop I want to fill it:
FOR l IN 1..10
LOOP
deptno := deptno + 10;
SELECT department_id, department_name INTO my_dept_table FROM departments WHERE department_id=deptno;
END LOOP;
Is there a possible way to do that because with my solution it's not working...
Thanks in advice
You can either read each department into the appropriate element of the collection:
DECLARE
TYPE dept_table_type IS TABLE OF departments.department_name%TYPE
INDEX BY PLS_INTEGER;
my_dept_table dept_table_type;
DEPTNO NUMBER;
BEGIN
FOR l IN 1..10
LOOP
deptno := l + 10;
SELECT department_name
INTO my_dept_table(l)
FROM departments
WHERE department_id = deptno;
END LOOP;
FOR I IN 1..10 LOOP
DBMS_OUTPUT.PUT_LINE('MY_DEPT_TABLE(' || I || ') = ''' || MY_DEPT_TABLE(I) || '''');
END LOOP;
END;
works fine.
But better yet, you can BULK COLLECT the data into your collection, which saves a bunch of code:
DECLARE
TYPE dept_table_type IS TABLE OF departments.department_name%TYPE
INDEX BY PLS_INTEGER;
my_dept_table dept_table_type;
DEPTNO NUMBER;
BEGIN
SELECT DEPARTMENT_NAME
BULK COLLECT INTO MY_DEPT_TABLE
FROM DEPARTMENTS;
FOR I IN 1..10 LOOP
DBMS_OUTPUT.PUT_LINE('MY_DEPT_TABLE(' || I || ') = ''' || MY_DEPT_TABLE(I) || '''');
END LOOP;
END;
db<>fiddle here
EDIT
OP has indicated that he wants the department number to be used as the index to the collection. In that case I'd use a cursor and add each element separately:
DECLARE
TYPE dept_table_type IS TABLE OF departments.department_name%TYPE
INDEX BY PLS_INTEGER;
my_dept_table dept_table_type;
BEGIN
FOR aRow IN (SELECT DEPARTMENT_ID, DEPARTMENT_NAME
FROM DEPARTMENTS
ORDER BY DEPARTMENT_ID)
LOOP
MY_DEPT_TABLE(aRow.DEPARTMENT_ID) := aRow.DEPARTMENT_NAME;
END LOOP;
FOR I IN MY_DEPT_TABLE.FIRST..MY_DEPT_TABLE.LAST LOOP
DBMS_OUTPUT.PUT_LINE('MY_DEPT_TABLE(' || I || ') = ''' || MY_DEPT_TABLE(I) || '''');
END LOOP;
END;
db<>fiddle here
set serveroutput on;
DECLARE
PI_STARTDATE DATE;
PO_STATUS NUMBER;
PO_HEADER VARCHAR2(200);
PO_LABEL VARCHAR2(200);
PO_RECORD SYS_REFCURSOR;
PO_NEXTINCSTARTDATE DATE;
BEGIN
PI_STARTDATE := to_date('2020-05-01','yyyy-MM-dd');
PCK_FAB_REPORTS.PRC_MONTHLY_WRTOFF_REPORT(
PI_STARTDATE => PI_STARTDATE,
PO_STATUS => PO_STATUS,
PO_HEADER => PO_HEADER,
PO_LABEL => PO_LABEL,
PO_RECORD => PO_RECORD,
PO_NEXTINCSTARTDATE => PO_NEXTINCSTARTDATE);
DBMS_OUTPUT.PUT_LINE('PO_STATUS = ' || PO_STATUS);
DBMS_OUTPUT.PUT_LINE('PO_HEADER = ' || PO_HEADER);
DBMS_OUTPUT.PUT_LINE('PO_LABEL = ' || PO_LABEL);
DBMS_OUTPUT.PUT_LINE('PO_NEXTINCSTARTDATE = ' || PO_NEXTINCSTARTDATE);
END;
I want to unit test the procedure and want to display the ref cursor variable as well. How to display ref cursor using dbms?
Here's an example based on Scott's EMP table:
SQL> set serveroutput on
SQL>
SQL> declare
2 l_ename emp.ename%type;
3 l_job emp.job%type;
4 l_rc sys_refcursor;
5 begin
6 open l_rc for select ename, job from emp
7 where deptno = 10;
8
9 loop
10 fetch l_rc into l_ename, l_job;
11 exit when l_rc%notfound;
12
13 dbms_output.put_line(l_ename ||' '|| l_job);
14 end loop;
15 close l_rc;
16 end;
17 /
CLARK MANAGER
KING PRESIDENT
MILLER CLERK
PL/SQL procedure successfully completed.
SQL>
So: you have to fetch refcursor into variables and then display contents of those variables.
I'm using V_SQL as parameter which stores SQL query as string and takes DATE input which is returned from another function.
Function which is returning date value:
----------------------------------------
FUNCTION RETURN_DATE(V_D DATE) RETURN DATE IS
IS_BUS CHAR(1);
V_CNT NUMBER(5);
V_DT DATE;
BEGIN
V_DT :=V_D;
WHILE IS_BUSINESS_DAY(V_DT) = 'N'
LOOP
V_DT := V_DT - 1;
END LOOP;
IF IS_BUSINESS_DAY(V_DT) = 'Y' THEN
V_DT := V_DT - 1;
END IF;
RETURN V_DT;
END RETURN_DATE;
V_SQL := 'SELECT A.ACCOUNT_TYPE, B.FIN_ELEM, A.ORG_UNIT_ID, A.GL_ACCOUNT_ID, B.CMN_COA_ID, B.PROD1, B.PROD2, B.PROD3, ' ||
'SUM(CURRENT_BAL) AS CB_SUM, SUM(AVG_BAL) AS AB_SUM, B.FLAG1 FROM DAILYGL_TEST A, AL_LOOKUP B '||
'WHERE A.GL_ACCOUNT_ID = B.GL_ACCT AND A.AS_OF_DATE = '||
**RETURN_DATE(V_RUN_DATE)** ||
' AND ROWNUM <=15 GROUP BY A.ACCOUNT_TYPE, B.FIN_ELEM, A.ORG_UNIT_ID, A.GL_ACCOUNT_ID,B.CMN_COA_ID, B.PROD1, B.PROD2, B.PROD3, A.AS_OF_DATE, B.FLAG1';
I am getting date returned as '29-AUG-2019' and when it is being supplied to this V_SQL query, it is throwing 'AUG not valid identifier issue'. Also, Date in AS_OF_DATE column is in MM/DD/YYYY format such 09/02/2019 for 02-SEP-2019.
Could you please help me in editing, formatting this code properly so this error can be removed. Let me know what I should change in RETURN_DATE(V_RUN_DATE) while supplying it to V_SQL.
Thanks in advance!
The way you are calling the function is wrong; it shouldn't be concatenated to the main query, but embedded into it. Have a look at this simplified example (line #5 is what you should pay attention to).
SQL> create or replace function return_date (v_d in varchar2)
2 return date
3 is
4 begin
5 return to_date(v_d, 'dd.mm.yyyy');
6 end;
7 /
Function created.
SQL> select ename, hiredate from emp where rownum = 1;
ENAME HIREDATE
---------- -------------------
SMITH 17.12.1980 00:00:00
SQL> declare
2 v_sql varchar2(500);
3 l_row emp%rowtype;
4 begin
5 v_sql := q'[select * from emp where hiredate = return_date('&v_run_date') ]' ||
6 ' and rownum = 1';
7 dbms_output.put_Line(v_sql);
8 execute immediate v_sql into l_row;
9 dbms_output.put_line(l_row.ename);
10 end;
11 /
Enter value for v_run_date: 17.12.1980
select * from emp where hiredate = return_date('17.12.1980') and rownum = 1
SMITH
PL/SQL procedure successfully completed.
SQL>
I want to create a dynamic query to handle array list.
create or replace TYPE p_type IS table of varchar2(4000) ;
CREATE OR REPLACE PROCEDURE test_proc_sk(
p_class_array IN p_type,
p_emp_record OUT SYS_REFCURSOR)
IS
lv_stmt VARCHAR2(100);
BEGIN
lv_stmt := 'Select * from dept where deptno = 10 ';
IF(p_class_array IS NOT NULL) THEN
lv_stmt := lv_stmt || 'AND dname IN (select column_value from table(' || p_class_array ||'))';
END IF;
dbms_output.put_line(lv_stmt);
OPEN p_emp_record FOR lv_stmt;
END;
It gives a compilation error
Error(9,5): PL/SQL: Statement ignored Error(9,23): PLS-00306: wrong
number or types of arguments in call to '||'
Please help
Use the MEMBER OF operator:
CREATE OR REPLACE PROCEDURE test_proc_sk(
p_class_array IN p_type,
p_emp_record OUT SYS_REFCURSOR
)
IS
BEGIN
OPEN p_emp_record FOR
SELECT *
FROM dept
WHERE deptno = 10
AND ( p_class_array IS EMPTY OR dname MEMBER OF p_class_array );
END;
You will have to bind the parameter p_class_array .In dynamic SQL you would prefix them with a colon (:). If you use EXECUTE IMMEDIATE or OPEN ... FOR, you will bind your parameters via position to avoid sql injection.
Also note that in your Select statement you are doing *, so while execution you must declare individual variable to hold the outcome. To aviod mistake you must declare the column name in the select statement as shown below:
CREATE OR REPLACE PROCEDURE test_proc_sk(
p_class_array IN p_type,
p_emp_record OUT SYS_REFCURSOR)
IS
lv_stmt VARCHAR2(200);
BEGIN
lv_stmt := 'Select deptno,dname from dept where deptno = 10 ';
IF(p_class_array.count > 0) THEN
lv_stmt := lv_stmt || ' AND dname IN (select column_value from table(:p_class_array))';
END IF;
dbms_output.put_line(lv_stmt);
OPEN p_emp_record FOR lv_stmt using p_class_array;
END;
/
Execution:
SQL> Select * from dept ;
SQL> /
DEPTNO DNAME
---------- ----------------------------------------------------------------------------------------------------
10 CTS
20 WIPRO
30 TCS
SQL> DECLARE
vr p_type:= p_type();
x SYS_REFCURSOR;
z VARCHAR2(10);
z1 number;
BEGIN
vr.extend(2);
vr (1) := 'CTS';
vr (2) := 'TCS';
test_proc_sk (vr, x);
LOOP
FETCH x INTO z1,z;
EXIT WHEN x%NOTFOUND;
DBMS_OUTPUT.PUT_LINE (z ||' ' ||z1);
END LOOP;
END;
/
SQL> /
Select deptno,dname from dept where deptno = 10 AND dname IN (select column_value from table(:p_class_array))
CTS 10
PL/SQL procedure successfully completed.
I'm executing the following PL/SQL script on sqlplus :
declare
cursor c is select sal, empno, ename from emp where ((comm is null and sal>2000) or (comm is not null and (sal+comm)>2000));
v_sal emp.sal%type;
v_empno emp.sal%type;
v_ename emp.ename%type;
begin
open c;
loop
fetch c into v_sal,v_empno,v_ename;
insert into temp values(v_sal,v_empno,v_ename);
exit when(c%notfound);
end loop;
close c;
end;
/
I obtain all the n-uplets I want but the last one is duplicated.
Put the exit statement before insert.