How do I display a nested table in Oracle - PL/SQL? - oracle

I have an assignment where I'm supposed to create a user-defined function that will return all employees after 1968. My code is as follows:
First I create a new object, called emp_dobs, to hold the employees' firstname, lastname, and date of birth, using the same data types as the original employee table:
CREATE OR REPLACE TYPE emp_dobs AS OBJECT (
emp_fname VARCHAR2(20),
emp_lname VARCHAR2(20),
emp_dob DATE
);
/
Then I create emp_dobs_nested as a table of emp_dobs:
CREATE OR REPLACE TYPE emp_dobs_nested AS TABLE OF emp_dobs;
/
Lastly, I create a function that's supposed to return an emp_dobs_nested table:
CREATE OR REPLACE FUNCTION get_emp_dobs RETURN emp_dobs_nested
AS
dobs emp_dobs_nested;
BEGIN
SELECT emp_dobs(firstname, lastname, birthdate) BULK COLLECT INTO dobs
FROM employee
WHERE birthdate < TO_DATE('01-JAN-1968', 'DD-MON-YYYY');
RETURN dobs;
END;
/
There is a weird quirk with compiling emp_dob_nested, however, where Oracle SQL Developer will display a new tab labeled "Output Variables - Log," and only show EMP_FNAME and EMP_LNAME. Despite that, everything compiles.
Now I want to test the function and display its results to prove that it works, but when I try this:
DECLARE
dobs emp_dobs_nested;
BEGIN
dobs := get_emp_dobs;
DBMS_OUTPUT.PUT_LINE(dobs);
END;
/
I get this error:
Error report -
ORA-06550: line 5, column 5:
PLS-00306: wrong number or types of arguments in call to 'PUT_LINE'
ORA-06550: line 5, column 5:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
What am I missing here? Thanks.

You cannot pass the whole collection to DBMS_OUTPUT, rather you must loop through it and display individual columns at each index.
DECLARE
dobs emp_dobs_nested;
BEGIN
dobs := get_emp_dobs;
FOR i IN 1..dobs.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE(dobs(i).emp_fname||','||dobs(i).emp_lname||','||dobs(i).emp_dob);
END LOOP;
END;
/

You can also use TABLE function to unnest a collection of objects into a relational resultset:
select * from table( get_emp_dobs )
Live demo: http://sqlfiddle.com/#!4/8cfb2/1

Another way of display is to use XML, try
DBMS_OUTPUT.PUT_LINE(XMLTYPE(dobs));

why not a create a view instead?
CREATE VIEW emp_view
AS
SELECT firstname, lastname, birthdate
FROM employee
WHERE birthdate < TO_DATE('01-JAN-1968', 'DD-MON-YYYY');
You can also try this,
DECLARE
dobs emp_dobs_nested;
i NUMBER := 0;
BEGIN
dobs := get_emp_dobs;
LOOP
i := dobs.NEXT(i);
DBMS_OUTPUT.PUT_LINE(dobs(i).emp_fname||','||dobs(i).emp_lname||','||dobs(i).emp_dob);
IF i = dobs.LAST THEN
EXIT;
END IF;
END LOOP;
END;
/

Related

Oracle error when selecting into temp table

My ultimate goal is far more complex than this, but this is the cut down version of what is causing my error. I want to put some rows into a temporary table (actually several temp tables, but I can't get by this first hurdle). Here is my PL/SQL;
DECLARE
type L1_store is table of MyTable%rowtype;
L1 L1_store;
BEGIN
select
* bulk collect
into L1
from MyTable
WHERE 1=1
and length(MyColumn1) = 2;
select
L1.MyColumn1
,L1.MyColumn2
from L1;
END;
And here is the error I get;
ORA-06550: line 19, column 6:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 16, column 1:
PL/SQL: SQL Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
The line numbers may be incorrect as I have edited the actual PL/SQL for clarity
****EDIT****
OK, so I originally accepted the answer offered below as it looks like it answers my question, and I can see how I could use it. However, for clarity, here is my ultimate goal, in case there is a better answer than the one I have in my head.
If I was just doing this in SQL I would do something like;
with L1 as
(select * from table),
L2 as
(select * from anothertable)
select L1.Column
from L1
left join L2 on L1.somecolumn = L2.somecolumn
I don't know if this helps or hinders, but thanks all in anticipation of your continued patience.
type L1_store is table of MyTable%rowtype; is not a temporary table; it is a collection data type declared in the PL/SQL scope and cannot be used in the SQL scope. Similarly, %ROWTYPE is a PL/SQL construct.
If you want to use a collection in SQL then declare it in the SQL scope:
CREATE TYPE mytable_data is OBJECT (
mycolumn1 VARCHAR2(50),
mycolumn2 NUMBER,
mycolumn3 DATE
);
CREATE TYPE mytable_data_table IS TABLE OF mytable_data;
so for some test data:
CREATE TABLE MyTable(
mycolumn1 VARCHAR2(50),
mycolumn2 NUMBER,
mycolumn3 DATE
);
INSERT INTO MyTable VALUES ( 'AA', 42, SYSDATE );
Then you can do:
DECLARE
L1 mytable_data_table;
BEGIN
select mytable_data( mycolumn1, mycolumn2, mycolumn3 )
bulk collect into L1
from MyTable
WHERE length(MyColumn1) = 2;
FOR i IN 1 .. L1.COUNT LOOP
DBMS_OUTPUT.PUT_LINE( L1(i).mycolumn1 || ' ' || l1(i).mycolumn2 );
END LOOP;
END;
/
Which outputs:
AA 42
db<>fiddle here
The problem here seems to be the second select in the block. If you're trying to select the data from the record, I would be inclined to put it into a FOR loop. I.e, something like this:
for i in 1 .. L1.count() LOOP
dbms_output.put_line (L1(i).MyColumn1);
end loop;
You could assign the value of MYColumn1 to a variable or use it in some other way.

How to run Oracle function which returns more than one value

My test function is this
CREATE OR REPLACE FUNCTION MULTI_VAL
(MYNAME OUT EMP2017.ENAME%TYPE)
RETURN NUMBER AS
MYSAL EMP2017.SAL%TYPE;
BEGIN
SELECT SAL, ENAME INTO MYSAL, MYNAME FROM EMP2017 ;
RETURN MYSAL;
END;
/
When I run it like
variable mynm varchar2(20)
SELECT MULTI_VAL(:mynm) FROM dual;
it gives this error
ERROR at line 1:
ORA-06553: PLS-561: character set mismatch on value for parameter 'MYNAME'
The error you get now indicates a datatype mismatch.
However there is a fundamental problem with your code. We cannot use functions which have OUT parameters in SQL. So once you have fixed the datatype issue you will get this error: ORA-06572: Function MULTI_VAL has out arguments.
You can run it like this:
declare
n varchar2(20);
x number;
begin
x := multi_val(n);
end;
/
Generally, functions with OUT parameters are considered bad practice. The syntax allows them, but the usage is hard to understand. It's better to use a procedure with two OUT parameters (because we can only call the program in PL/SQL anyway) or else have the function return a user-defined type.
CREATE TABLE EMP2017(ENAME VARCHAR2(10),SAL NUMBER);
INSERT INTO EMP2017 VALUES ('SMITH',5000);
INSERT INTO EMP2017 VALUES ('JOHNS',1000);
COMMIT;
CREATE TYPE RET_MULT AS OBJECT
(ENAME VARCHAR2(10),SAL NUMBER);
CREATE TYPE T_RET_MULT AS TABLE OF RET_MULT;
CREATE OR REPLACE FUNCTION MULTI_VAL RETURN T_RET_MULT PIPELINED IS
MYSAL RET_MULT;
BEGIN
FOR I IN(SELECT SAL, ENAME FROM EMP2017) LOOP
MYSAL := RET_MULT(I.ENAME,I.SAL);
PIPE ROW(MYSAL);
END LOOP ;
RETURN ;
END;
SELECT * FROM TABLE(MULTI_VAL());
I think this question can be solved without using pipeline functions. Just like this. All pre required data as described #Sedat.Turan except function. Sorry for copy/past.
CREATE TABLE EMP2017(ENAME VARCHAR2(10),SAL NUMBER);
INSERT INTO EMP2017 VALUES ('SMITH',5000);
INSERT INTO EMP2017 VALUES ('JOHNS',1000);
COMMIT;
CREATE TYPE RET_MULT AS OBJECT
(ENAME VARCHAR2(10),SAL NUMBER);
CREATE TYPE T_RET_MULT AS TABLE OF RET_MULT;
create or replace function MULTI_VAL return T_RET_MULT is
RET_SET T_RET_MULT;
begin
select RET_MULT(ENAME, SAL) bulk collect into RET_SET from EMP2017;
return RET_SET;
end;

'ORA-00913: too many values' while reading data from xml to user-defined record (PL/SQL)

I get this error while executing the follwing PL/SQL code and I can't figure out why.
PLS-00382: expression is of wrong type
PL/SQL: ORA-00913: too many values
PL/SQL: SQL Statement ignored
I understand that the cause of ORA-00913 is a mismatch of values in the two sets of data being referred to in the SELECT statement.
The code was working fine before I added the checkVar variable in the record. Just wanted to know if there is any way to fix this without taking out the checkVar from testRec record?
DECLARE
X XMLType:=XMLType( '<Testmain>
<TeamSel>
<code>abc123</code>
<name>awrqer</name>
</TeamSel>
<TeamSel>
<code>abc678</code>
<name>gokhg</name>
</TeamSel>
</Testmain>
');
TYPE testRec IS RECORD(
name VARCHAR2(512),
code VARCHAR2(512),
checkVar BOOLEAN:=FALSE
);
TYPE testRecTab IS TABLE OF testRec INDEX BY BINARY_INTEGER;
testList testRecTab;
BEGIN
SELECT EXTRACTVALUE(VALUE(xml_list),'//Name') AS lname,
EXTRACTVALUE(VALUE(xml_list),'//Code') AS lcode
BULK COLLECT
INTO testList
FROM TABLE(XMLSEQUENCE(EXTRACT(X,'Testmain/TeamSel'))) xml_list;
--rest of the code
END;
You can't select into a Boolean variable so I think there are two options:
a) Remove the boolean from your record type
b) Populate your table in a loop like the example below
DECLARE
X XMLType:=XMLType( '<Testmain>
<TeamSel>
<code>abc123</code>
<name>awrqer</name>
</TeamSel>
<TeamSel>
<code>abc678</code>
<name>gokhg</name>
</TeamSel>
</Testmain>
');
TYPE testRec IS RECORD(
name VARCHAR2(512),
code VARCHAR2(512),
checkVar BOOLEAN:=FALSE
);
TYPE testRecTab IS TABLE OF testRec INDEX BY BINARY_INTEGER;
lTestRec testRec;
testList testRecTab;
BEGIN
FOR cRecord IN (
SELECT EXTRACTVALUE(VALUE(xml_list),'//name') lname,
EXTRACTVALUE(VALUE(xml_list),'//code') lcode
FROM TABLE(XMLSEQUENCE(EXTRACT(X,'Testmain/TeamSel'))) xml_list
)
LOOP
lTestRec.name := cRecord.lname;
lTestRec.code := cRecord.lcode;
testList(testList.count + 1) := lTestRec;
END LOOP;
--rest of the code
END;

PL/SQL: ORA-00932: inconsistent datatypes: expected UDT got NUMBER

I'm executing PL/SQL code to display the Currency Code from the Failed Reservation table. Object type and Nested Table collections are used.
When the PL/SQL code is run, the following error is generated. The corresponding line is highlighted in the PL/SQL code section.
Error report:
ORA-06550: line 27, column 11:
PL/SQL: ORA-00932: inconsistent datatypes: expected UDT got NUMBER
ORA-06550: line 27, column 4:
PL/SQL: SQL Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
The code is pasted below:
DDL - Table creation:
CREATE TABLE FAILEDRESERVATION
(
FAILEDRESERVATIONID NUMBER(18,0),
FK_TRANSACTIONID NUMBER(18,0),
DEBITRESERVATIONID NUMBER(18,0),
RESERVATIONTIME DATE,
RESERVATIONAMOUNT NUMBER(18,5),
CURRENCYCODE CHAR(3 BYTE),
AVAILABLEAMOUNT NUMBER(18,5)
);
ALTER TABLE FAILEDRESERVATION
ADD CONSTRAINT "PK_FAILEDRESERVATION" PRIMARY KEY ("FAILEDRESERVATIONID");
Object Type:
CREATE OR REPLACE TYPE TYPE type_failedreservation AS OBJECT
(
FK_TRANSACTIONID NUMBER(18),
DEBITRESERVATIONID NUMBER(18),
RESERVATIONTIME DATE,
RESERVATIONAMOUNT NUMBER(18,5),
CURRENCYCODE CHAR(3),
AVAILABLEAMOUNT NUMBER(18,5)
);
DML:
INSERT INTO FAILEDRESERVATION (FAILEDRESERVATIONID,FK_TRANSACTIONID,DEBITRESERVATIONID,RESERVATIONTIME,RESERVATIONAMOUNT,CURRENCYCODE,AVAILABLEAMOUNT)
VALUES (289,2,1,to_date('07-MAR-16','DD-MON-RR'),20000,'USD',10000);
INSERT INTO FAILEDRESERVATION (FAILEDRESERVATIONID,FK_TRANSACTIONID,DEBITRESERVATIONID,RESERVATIONTIME,RESERVATIONAMOUNT,CURRENCYCODE,AVAILABLEAMOUNT)
VALUES (288,1,1,to_date('01-MAR-16','DD-MON-RR'),10000,'NOK',10000);
Nested Tables:
CREATE OR REPLACE TYPE type_failedreservation_coll as TABLE OF type_failedreservation;
CREATE OR REPLACE TYPE type_dbtrsid_coll AS TABLE OF NUMBER;
PL/SQL Code:
DECLARE
P_FAILEDRESERVATION APPDATA.TYPE_FAILEDRESERVATION_COLL;
vdbtid_coll type_dbtrsid_coll := type_dbtrsid_coll();
BEGIN
SELECT TYPE_FAILEDRESERVATION(fk_transactionid,debitreservationid,reservationtime,reservationamount,currencycode,availableamount)
BULK COLLECT
INTO p_failedreservation
FROM failedreservation;
-- This is line 27
SELECT frs.debitreservationid
INTO vdbtid_coll
FROM TABLE(p_failedreservation) frs;
FOR v_iterate IN vdbtid_coll.FIRST..vdbtid_coll.LAST
LOOP
dbms_output.put_line('The currency code is: '||v_iterate);
END LOOP;
END;
Why is the code generating this error ?
You've declared vdbtid_coll as a collection type, so you need to bulk collect into that too:
SELECT frs.debitreservationid
BULK COLLECT INTO vdbtid_coll
FROM TABLE(p_failedreservation) frs;
With that change:
PL/SQL procedure successfully completed.
The currency code is: 1
The currency code is: 2
That's just giving you the index number in the collection though, so I don't think it's what you really want. You may want:
FOR v_iterate IN vdbtid_coll.FIRST..vdbtid_coll.LAST
LOOP
dbms_output.put_line('The currency code is: '
|| p_failedreservation(v_iterate).currencycode);
END LOOP;
which gets:
PL/SQL procedure successfully completed.
The currency code is: USD
The currency code is: NOK
You don't really need the second select/collection at all though, you can do:
FOR v_iterate IN 1..p_failedreservation.COUNT
LOOP
dbms_output.put_line('The currency code is: '
|| p_failedreservation(v_iterate).currencycode);
END LOOP;
... for the same result. Although I'm not sure what the relevance of the debitreservationid is in that second query, as it is the same value (1) in both rows.
You are trying to select multiple rows into a collection. You need to use BULK COLLECT INTO rather than just INTO.
Change
SELECT frs.debitreservationid
INTO vdbtid_coll
FROM TABLE(p_failedreservation) frs;
To
SELECT frs.debitreservationid
BULK COLLECT INTO vdbtid_coll
FROM TABLE(p_failedreservation) frs;
and you probably want the output to be:
FOR v_iterate IN vdbtid_coll.FIRST..vdbtid_coll.LAST LOOP
dbms_output.put_line('The currency code is: '|| vdbtid_coll(v_iterate) );
END LOOP;
However, you could simplify it all to:
DECLARE
P_FAILEDRESERVATION APPDATA.TYPE_FAILEDRESERVATION_COLL;
BEGIN
SELECT TYPE_FAILEDRESERVATION(
fk_transactionid,
debitreservationid,
reservationtime,
reservationamount,
currencycode,
availableamount
)
BULK COLLECT INTO p_failedreservation
FROM failedreservation;
FOR v_iterate IN p_failedreservation.FIRST .. p_failedreservation.LAST LOOP
dbms_output.put_line(
'The currency code is: '|| p_failedreservation(v_iterate).currencycode
);
END LOOP;
END;
Try casting the type array to its type as follows
SELECT frs.debitreservationid
BULK COLLECT INTO vdbtid_coll
FROM TABLE(CAST(p_failedreservation as APPDATA.TYPE_FAILEDRESERVATION_COLL)) frs;
PL/SQL: ORA-00932: inconsistent datatypes: expected UDT got CHAR
You may get this error in case you write type table in place of type object when
using BULK COLLECT INTO, i faced this error coz i did so.
FOR EXAMPLE:
CREATE OR REPLACE TYPE OBJ_TYPE AS OBJECT (NAME VARCHAR2(20));
/
CREATE OR REPLACE TYPE TBL_TYPE IS TABLE OF OBJ_TYPE;
/
CREATE OR REPLACE FUNCTION FUNC1 RETURN TBL_TYPE AS
TBL_TEXT TBL_TYPE := TBL_TYPE();
BEGIN
SELECT ***OBJ_TYPE*** (NAME) BULK COLLECT INTO ***TBL_TEXT*** FROM <table-name> ;
RETURN ***TBL_TEXT***;
END;
/
Care should be taken when using object type and table type in the function.

How to return a Cursor for pl/sql table

I select data from several tables. Then i need to edit the data returned from the cursor before returning. The cursor will then be passed to a perl script to display the rows.
To that i build a pl/sql table as in the following code. What i need to know is how to return the to that table ?
At present i get the error "table or view doesn't exist". Test code i use for a simple table is attached here.
CREATE OR REPLACE FUNCTION test_rep
RETURN SYS_REFCURSOR
AS
CURSOR rec_Cur IS
SELECT table1.NAME,
table1.ID
FROM TESTREPORT table1;
TYPE rec_Table IS TABLE OF rec_Cur%ROWTYPE INDEX BY PLS_INTEGER;
working_Rec_Table rec_Table;
TYPE n_trade_rec IS RECORD
(
NAME VARCHAR2(15),
ID NUMBER
);
TYPE ga_novated_trades IS TABLE OF n_trade_rec index by VARCHAR2(15);
va_novated_trades ga_novated_trades;
v_unique_key VARCHAR2(15);
TYPE db_cursor IS REF CURSOR;
db_cursor2 db_cursor;
BEGIN
OPEN rec_Cur;
FETCH rec_Cur BULK COLLECT INTO working_Rec_Table;
FOR I IN 1..working_Rec_Table.COUNT LOOP
v_unique_key := working_Rec_Table(I).NAME;
va_novated_trades(v_unique_key).NAME := working_Rec_Table(I).NAME;
va_novated_trades(v_unique_key).ID := working_Rec_Table(I).ID;
END LOOP; --FOR LOOP
OPEN db_cursor2 FOR SELECT * FROM va_novated_trades; --ERROR LINE
CLOSE rec_Cur;
RETURN db_cursor2;
END test_rep;
/
Basically there is a way to select from a table type in oracle using the TABLE() function
SELECT * FROM table(va_novated_trades);
But this works only for schema table types and on plsql tables (table types defined in the SCHEMA and not in a plsql package):
CREATE TYPE n_trade_rec AS OBJECT
(
NAME VARCHAR2(15),
ID NUMBER
);
CREATE TYPE ga_novated_trades AS TABLE OF n_trade_rec;
But I still think you should try to do it all in a query (and/or in the perl script),
For example, there is one field where i have to analyse the 4th
character and then edit other fields accordingly
This can be achieved in the query, could be something like:
select case when substr(one_field, 4, 1) = 'A' then 'A.' || sec_field
when substr(one_field, 4, 1) = 'B' then 'B.' || sec_field
else sec_field
end as new_sec_field,
case when substr(one_field, 4, 1) = 'A' then 100 * trd_field
when substr(one_field, 4, 1) = 'B' then 1000 * trd_field
else trd_field
end as new_trd_field,
-- and so on
from TESTREPORT

Resources