How to select a record defined in a package in sql query - oracle

to test the code:
https://dbfiddle.uk/?rdbms=oracle_21&fiddle=b02671a25f9d7949e0b55ca59084ecd1
If I define a record as an object, I can call it in a sql statement this way objectname(field1, field2)
But If I define a record inside package as a record. I can't do that anymore.
create TYPE arguments_r IS object
(
q integer,
b INTEGER
);
CREATE FUNCTION f (p IN arguments_r) RETURN INTEGER
IS
BEGIN
RETURN 1;
END;
/
select arguments_r(1,1) from dual -- not printed but exists nevertheless. THe following statement prove it.
select f(arguments_r(1,1)) from dual --print the expected result
CREATE PACKAGE pck
as
TYPE arguments_r IS record
(
q integer,
b INTEGER
);
FUNCTION f (p IN pck.arguments_r) RETURN INTEGER;
end;
CREATE PACKAGE body pck
as
FUNCTION f (p IN pck.arguments_r) RETURN INTEGER
is
begin
return 1;
end;
END;
select pck.arguments_r(1,1) from dual -- ORA-06553: PLS-306: wrong number or types of arguments in call to 'ARGUMENTS_R'

[TL;DR] In general, you cannot.
A record is a PL/SQL ONLY data type and it CANNOT be used in SQL. If you want to use the data in SQL then you will need to put it into an SQL data type such as an Object.
There are ways of achieving what you want via PIPELINED functions (which implicitly convert a RECORD to an OBJECT so, technically you never use a PL/SQL data type in the SQL scope but it certainly looks like you do) but the implementation is convoluted and if I ever did a code review on code that tried to use records the way I show below then I would fail it in the review and tell the author to just use an SQL OBJECT.
Given the setup:
CREATE PACKAGE pck as
TYPE args_r IS record (
q integer,
b INTEGER
);
TYPE args_t IS TABLE OF args_r;
FUNCTION f RETURN args_t PIPELINED;
FUNCTION f2 RETURN args_t;
FUNCTION create_arg(q INTEGER, b INTEGER) RETURN args_t PIPELINED;
FUNCTION create_arg2(q INTEGER, b INTEGER) RETURN args_t;
FUNCTION g(args IN args_r) RETURN INTEGER;
END;
/
CREATE PACKAGE BODY pck as
FUNCTION f RETURN args_t PIPELINED
IS
BEGIN
PIPE ROW (args_r(1,1));
PIPE ROW (args_r(2,2));
END;
FUNCTION f2 RETURN args_t
IS
BEGIN
RETURN args_t(args_r(1,1), args_r(2,2));
END;
FUNCTION create_arg(q INTEGER, b INTEGER) RETURN args_t PIPELINED
IS
BEGIN
PIPE ROW (args_r(q, b));
END;
FUNCTION create_arg2(q INTEGER, b INTEGER) RETURN args_t
IS
BEGIN
RETURN args_t(args_r(q, b));
END;
FUNCTION g(args IN args_r) RETURN INTEGER
IS
BEGIN
RETURN args.q;
END;
END;
/
From PL/SQL to SQL via a PIPELINED function:
If you want to pass PL/SQL records from a PL/SQL function into an SQL query then it will "work" (see the comment below for clarification of why) if you use a PIPELINED function then:
SELECT *
FROM TABLE(pck.f());
Outputs:
Q
B
1
1
2
2
BUT that is not because you can use PL/SQL records in SQL it is because a PIPELINED function is intended to be used in the SQL scope so Oracle has implicitly created a duplicate OBJECT data type (and TABLE OF ... data type) and mapped the PL/SQL record to the SQL object and despite what the signature of the function says, it is not returning records and returning Objects instead.
From PL/SQL to SQL via a function returning a collection:
If you try to do exactly the same thing with a non-PIPELINED function:
SELECT *
FROM TABLE(pck.f2());
Then you get the error:
ORA-00902: invalid datatype
Because no such implicit conversion has been applied and you CANNOT use PL/SQL record types in SQL.
From SQL to PL/SQL:
Going the other way, as per the question, and trying to create records in the SQL scope and pass them to a PL/SQL function then it CANNOT work because it is impossible to create PL/SQL records in the SQL scope.
The query:
SELECT pck.g(pck.args_r(1,1))
FROM DUAL;
Fails with the error:
ORA-06553: PLS-306: wrong number or types of arguments in call to 'ARGS_R'
From PL/SQL to SQL back to PL/SQL via a PIPELINED function:
If you create the record in PL/SQL and return it via a PIPELINED function then it "works":
SELECT pck.g((SELECT VALUE(t) FROM TABLE(pck.create_arg(1,1)) t))
FROM DUAL;
and outputs:
PCK.G((SELECTVALUE(T)FROMTABLE(PCK.CREATE_ARG(1,1))T))
1
From PL/SQL to SQL back to PL/SQL via a function returning a collection:
However, if you use a non-PIPELINED function:
SELECT pck.g((SELECT VALUE(t) FROM TABLE(pck.create_arg2(1,1)) t))
FROM DUAL;
Then it raises the exception:
ORA-00902: invalid datatype
db<>fiddle here
Conclusion:
Just define an OBJECT data type; do not try to use convoluted methods to ram PL/SQL only data types into an SQL scope where they are not meant to be used.

Related

How to define a pipelined table function within a with statement?

It's possible to defined a function within a with statement (How to define a function and a query in the same with block?). But I fail to define a pipelined function inside a with statement.
WITH
FUNCTION f (a IN INTEGER)
RETURN SYS.odcinumberlist
PIPELINED
IS
ret INTEGER;
BEGIN
FOR z IN 1 .. a
LOOP
PIPE ROW (z);
END LOOP;
RETURN;
END;
SELECT * FROM f(3); --I tried with table(f(3)) too
[Error] Execution (2: 1): ORA-06553: PLS-653: aggregate/table functions are not allowed in PL/SQL scope
Is it possible to define a pipelined table function within a with statement and how?
If it is possible, I would like to do that with a table of record. Therefore I need to know how defined type within a with statement too.
Within a subquery-factoring clause, you can make a non-pipelined function that returns a collection:
WITH FUNCTION f (a IN INTEGER)
RETURN SYS.ODCINUMBERLIST
IS
v_list SYS.ODCINUMBERLIST := SYS.ODCINUMBERLIST();
BEGIN
FOR z IN 1 .. a LOOP
v_list.EXTEND;
v_list(v_list.COUNT) := z;
END LOOP;
RETURN v_list;
END f;
SELECT *
FROM TABLE(f(3));
Which outputs:
COLUMN_VALUE
1
2
3
db<>fiddle here
If it is possible, I would like to do that with a table of record.
A record is a PL/SQL data type; you cannot use it in an SQL statement. Instead use an OBJECT data type and declare it in the global SQL scope before you run your query (no, you cannot declare it locally inside your query).
Therefore I need to know how defined type within a with statement too.
Again, you cannot as the SQL syntax does not allow it. See the answer to your previous question.
You can declare it globally:
CREATE TYPE test_obj IS OBJECT (
w1 INTEGER
);
CREATE TYPE test_obj_table IS TABLE OF test_obj;
WITH FUNCTION f (a IN INTEGER)
RETURN test_obj_table
IS
v_list test_obj_table := test_obj_table();
BEGIN
FOR z IN 1 .. a LOOP
v_list.EXTEND;
v_list(v_list.COUNT) := test_obj(z);
END LOOP;
RETURN v_list;
END f;
SELECT *
FROM TABLE(f(3));
Outputs:
W1
1
2
3
db<>fiddle here

Oracle pipelined function with generic record type?

I have a pipelined function which returns results of a SQL query:
function my_pipelined_function(...)
return rec_t
PIPELINED is
begin
for l in (select * from ... join ... where ...) loop
PIPE ROW(l);
end loop;
return;
end;
This works, but I have to define the record and it's type in the package header:
TYPE rec IS RECORD(
some_date_param date,
some_number_param number,
...
);
TYPE rec_t IS TABLE OF rec;
Ugly. Isn't there a way to avoid having to declare the "hard-coded" record with all types defined, one by one?
I saw the ANYDATA type, but I can't make it work. Should I use this approach, if yes, how to do it? If not, should I use some kind of ref cursor, or is this simply not possible at all in Oracle?
Thanks

Pipelined function with a parameter declare with %ROWTYPE

I'm trying to declare a pipelined table function (t) inside a package that takes an argument declared as <tablename>%ROWTYPE. Declaring that function works and the package compiles without any error.
But I would like to use this function inside a procedure (p1) like shown below.
CREATE OR REPLACE PACKAGE BODY t1
AS
-- private
PROCEDURE p1
IS
l_person persons%ROWTYPE;
BEGIN
FOR l_row IN (SELECT *
FROM TABLE (t (l_person)))
LOOP
NULL;
END LOOP;
END;
-- public
FUNCTION t (p_persons_record persons%ROWTYPE)
RETURN t_a_list
PIPELINED
IS
l_a t_a;
BEGIN
l_a.dummy := 'A';
PIPE ROW (l_a);
END;
END;
This sample code does not makes sense but it demonstrates my problem.
It just doesn't compile but gives the following errors:
[Error] PLS-00382 (10: 38): PLS-00382: expression is of wrong type
[Error] PLS-00306 (10: 35): PLS-00306: wrong number or types of arguments in call to 'T'
[Error] ORA-00904 (10: 35): PL/SQL: ORA-00904: "T1"."T": invalid identifier
Can anyone explain what's wrong and how to fix those errors?
Edit:
The package spec is:
CREATE OR REPLACE PACKAGE t1
AS
TYPE t_a IS RECORD (dummy VARCHAR2 (1));
TYPE t_a_list IS TABLE OF t_a;
FUNCTION t (p_persons_record persons%ROWTYPE)
RETURN t_a_list
PIPELINED;
END;
You cannot use a record type in SQL Scope. So a PL/SQL function with a record parameter derived by rowtype attribute cannot be used as table function in SQL.
The only thing you have to rewrite is to use a SQL object on schema level instead of a PL/SQL record.
-- adapt your columns to your table as necessary
CREATE OR REPLACE TYPE g_persons AS OBJECT (
ID int,
C1 int
);
CREATE OR REPLACE PACKAGE t1
AS
TYPE t_a IS RECORD (dummy VARCHAR2 (1));
TYPE t_a_list IS TABLE OF t_a;
FUNCTION t (p_persons_record g_persons_t)
RETURN t_a_list
PIPELINED;
END;
/
CREATE OR REPLACE PACKAGE BODY t1
AS
-- private
PROCEDURE p1
IS
l_person g_persons_t;
BEGIN
l_person.ID := 1; -- init your record some how
l_person.C1 := 1; -- init your record some how
FOR l_row IN (SELECT *
FROM TABLE (t (l_person)))
LOOP
NULL;
END LOOP;
END;
-- public
FUNCTION t (p_persons_record g_persons_t)
RETURN t_a_list
PIPELINED
IS
l_a t_a;
BEGIN
l_a.dummy := 'A';
PIPE ROW (l_a);
END;
END;
/
I think your PACKAGE specification lacks t's declaration. Have you checked that your PACKAGE specification is something similar to this?:
CREATE OR REPLACE PACKAGE t1 AS
PROCEDURE p1;
FUNCTION t (p_persons_record persons%ROWTYPE) RETURN t_a_list PIPELINED;
END;

How to debug a pipelined function in PL/SQL Developer?

I have a PL/SQL package in oracle database which contains a pipelined function named FN_GET_USERINFO_ROWS as bellow:
CREATE OR REPLACE PACKAGE PKG_USERINFO AS
TYPE TY_USERINFO_RECORD IS RECORD( U_ID VARCHAR2(50),
U_NAME VARCHAR2(50),
DOB DATE);
TYPE TY_USERINFO_TABLE IS TABLE OF TY_USERINFO_RECORD;
FUNCTION FN_GET_USERINFO_ROWS(P_USER_ID IN NUMBER)
RETURN TY_USERINFO_TABLE PIPELINED;
END PKG_USERINFO;
And I'm running following test script to test pipelined FN_GET_USERINFO_ROWS at PL/SQL Developer (File->New->Test Window)
declare
result PKG_USERINFO.TY_USERINFO_TABLE;
begin
-- calling pipelined function
result := PKG_USERINFO.FN_GET_USERINFO_ROWS(P_USER_ID => :P_USER_ID);
end;
But it is showing following error:
ORA-06550: line 28, column 12: PLS-00653: aggregate/table functions
are not allowed in PL/SQL scope
How can I debug a pipelined function using PL/SQL Developer ?
One of the ways is to create a block with FOR-SELECT-LOOP and put a breakpoint into the function or just log contents for each fetched row (depends on what you mean by debugging). So you can separate each PIPE ROW execution and see its results. Then in PL/SQL Dev choose File->Open->TestScript and run the block from opened window.
DECLARE
result pkg_userinfo.ty_userinfo_table;
BEGIN
-- we call pipelined functions like this
FOR rec IN (SELECT *
FROM TABLE (pkg_userinfo.fn_get_userinfo_rows(:P_USER_ID))
-- WHERE rownum < 2
-- uncomment this line and vary amount of fetched rows
)
LOOP
dbms_output.put_line('another step : ' || rec.u_id);
END LOOP;
END;
Also I advise you to debug variants when NO_DATA_NEEDED is being thrown. To do it add WHERE clause limiting number of rows.

Creating Oracle stored procedure that returns data

In Firebird you can create a stored procedure that returns data an invoke it like a table passing the arguments:
create or alter procedure SEL_MAS_IVA (
PCANTIDAD double precision)
returns (
CANTIDAD_CONIVA double precision)
as
begin
CANTIDAD_CONIVA = pCANTIDAD*(1.16);
suspend;
end
select * from SEL_MAS_IVA(100)
will return a single row single column (named CANTIDAD_CONIVA) relation with the value 116
This is a very simple example. The stored procedure can of course have any number of input and output parameters and do whatever it needs to return data (including multiple rows), which is accomplished by the "suspend" statement (which as it name implies, suspends the SP execution, returns data to the caller, and resumes with the next statement)
How can I create such kind of stored procedures in Oracle?
In Oracle it is possible by using pipelined functions. Your example:
-- define column names:
CREATE OR REPLACE TYPE SEL_MAS_IVA_obj AS OBJECT (
CANTIDAD_CONIVA NUMBER)
/
CREATE OR REPLACE TYPE SEL_MAS_IVA_type AS TABLE OF SEL_MAS_IVA_obj
/
CREATE OR REPLACE
FUNCTION SEL_MAS_IVA(PCANTIDAD IN NUMBER)
RETURN SEL_MAS_IVA_type
PIPELINED
IS
BEGIN
pipe row(SEL_MAS_IVA_obj(PCANTIDAD * 1.16));
RETURN;
END;
/
and get values:
SELECT * FROM TABLE(SEL_MAS_IVA(100));
Also see another sample where function returns more columns and rows: https://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:4447489221109
You need to use a function:
create or replace function SEL_MAS_IVA (
pCANTIDAD IN NUMBER
)
return NUMBER
as
begin
return pCANTIDAD*(1.16);
end SEL_MAS_IVA;
select SEL_MAS_IVA(100) CANTIDAD_CONIVA from dual;

Resources