Passing table parameter using select clause in Oracle - sql

I have 2 types:
CREATE OR REPLACE TYPE id_type AS OBJECT
(
id NUMBER(19, 0)
);
CREATE OR REPLACE TYPE id_table AS TABLE OF id_type;
And I have a function A(param IN id_table).
Now, if I have another function using first one, how am I supposed to pass the param like that A(SELECT 1 FROM DUAL)? Can I only do it manually creating an id_table var, filling it and then passing to A() function?

You can do something like
DECLARE
l_ids id_table;
l_return <<data type>>;
BEGIN
SELECT id_type( 1 )
BULK COLLECT INTO l_ids
FROM dual;
l_return := a( l_ids );
END;
It's not obvious, though, why you have an id_type in this case. It would seem more logical to simply declare id_table
CREATE OR REPLACE TYPE id_table
AS TABLE OF NUMBER(19,0);
It's also not obvious whether you are really intending to populate the collection by selecting from dual. If you really want to have a single-element collection, you can simply initialize it. I'm guessing, however, that you really intend to populate the collection by querying a table other than DUAL in which case you'd prefer the BULK COLLECT
DECLARE
l_ids id_table := new id_table( id_type( 1 ) );
l_return <<data type>>;
BEGIN
l_return := a( l_ids );
END;

Related

How to assign result of table function to variable in PL/pgSQL

Assume I have the following function declaration:
CREATE OR REPLACE FUNCTION build_org_branch(IN p_org_id organization.org_id%type,
IN p_padding text)
RETURNS table
(
object_id int,
parent_id int,
name text
)
Then I want to call build_org_branch with parameters and assign it to a variable inside of another function like this:
declare
l_table record[]; --??????
begin
l_table := build_org_branch(1, ' '); -- is it okay?
if l_table is not null then
-- do stuff with table rows
end if;
end;
Or should I use some another approach to pass tables of rows?
You have built a function that returns a table, so process the results that way.
do $$
declare
rec record;
begin
for rec in (select * from build_org_branch(101, ''))
loop
raise notice 'Returned Row: object_id=>%, name=>%, parent_id=>%'
, rec.object_id
, rec.name
, rec.parent_id ;
-- do stuff with table rows
end loop;
end;
$$;
I do not have your table, so I'll hard code some values but how they are populated is not the issue, but what you do afterward. See fiddle.
Postgres doesn't support table's variables. If you can pass a some relational content, then a) you can use a temporary table or b) you can pass a array of composite values:
CREATE TYPE branch_type AS
(
object_id int,
parent_id int,
name text
)
CREATE OR REPLACE FUNCTION build_org_branch(IN p_org_id organization.org_id%type,
IN p_padding text)
RETURNS branch_type[] AS ...
and then you can write
declare
l_table branch_type[];
begin
l_table := build_org_branch(1, ' ');
if l_table is not null then
-- do stuff with table rows
end if;
end;
This is array to array assignment. Table to array is possible too, but always it has to be static typed.
CREATE OR REPLACE FUNCTION build_org_branch(IN p_org_id organization.org_id%type,
IN p_padding text)
RETURNS SETOF branch_type AS ...
and processing:
declare
l_table branch_type[];
begin
l_table := ARRAY(SELECT build_org_branch(1, ' '));
if l_table is not null then
-- do stuff with table rows
end if;
end;
For smaller number of rows (to ten thousand) a arrays should be preferred. For high number of rows you should to use temp table.

How to select all rows from the oracle PL/SQL collection into SYS_REFCURSOR

Note: I have seen many solution and all says I can not use SQL with a PL/SQL type. I must have to use CREATE or REPLACE, but my restriction is I can not use system object for this task.
What I have tried the below example returns only last row.
create or replace PROCEDURE SP_TEST (TEST_cursor OUT SYS_REFCURSOR)IS
TYPE TEMP_RECORD IS RECORD(
entries NUMBER,
name VARCHAR2(50),
update VARCHAR2(200)
);
TYPE TEMP_TABLE IS TABLE OF TEMP_RECORD INDEX BY PLS_INTEGER;
VAR_TEMP TEMP_TABLE;
IDX PLS_INTEGER := 0;
BEGIN
VAR_TEMP(IDX).cur_entries := 1;
VAR_TEMP(IDX).cur_entries := 2;
OPEN TEST_cursor FOR
SELECT VAR_TEMP(idx).cur_entries from dual;
END SP_TEST;
Another way tried.
OPEN TEST_cursor FOR
SELECT * FROM TABLE(VAR_TEMP)
--- It gives compilation error ora-
Given that you can't create an object in the database, the only solution I can think of is to use dynamic SQL:
CREATE TYPE temp_record AS OBJECT
(
entries NUMBER,
entry_name VARCHAR2 (50),
update_value VARCHAR2 (200)
);
CREATE TYPE temp_table IS TABLE OF temp_record;
CREATE OR REPLACE PROCEDURE sp_test (test_cursor OUT SYS_REFCURSOR) IS
var_temp temp_table := temp_table ();
strSql VARCHAR2(32767);
BEGIN
-- Populate the temp table, or pass it in from elsewhere
var_temp.EXTEND();
var_temp (var_temp.LAST).entries := 1;
var_temp (var_temp.LAST).entry_name := 'test';
FOR i IN 1..var_temp.COUNT LOOP
strSql := strSql ||
CASE
WHEN LENGTH(strSql) > 0 THEN ' UNION ALL '
ELSE NULL
END ||
'SELECT ' || var_temp.ENTRIES || ' ENTRIES,' ||
'''' || var_temp.ENTRY_NAME || ''' ENTRY_NAME FROM DUAL';
END LOOP;
OPEN test_cursor FOR strSql;
END sp_test;
Now, I may have messed up the string concatenation logic there a bit, but the objective is to end up with an SQL string which looks something like
SELECT 1 ENTRIES,'test' ENTRY_NAME FROM DUAL
UNION ALL
SELECT 2 ENTRIES,'test 2' ENTRY_NAME FROM DUAL
UNION ALL
SELECT 3 ENTRIES,'test_3' ENTRY_NAME FROM DUAL
but, of course, without the nice white space and etc.
The 32K limit on dynamic SQL may bite you eventually, but if push comes to shove you can the DBMS_SQL package to handle arbitrarily large SQL text, although that presents its own challenges.
Best of luck.
In order to reference types in SQL (as opposed to PL/SQL), they must be created as objects in the database. This is effectively a scope issue: when you run SQL you are shifting to a different context. Any structures that you have created locally are not available there.
CREATE TYPE temp_record AS OBJECT
(
entries NUMBER,
entry_name VARCHAR2 (50),
update_value VARCHAR2 (200)
);
CREATE TYPE temp_table IS TABLE OF temp_record;
CREATE OR REPLACE PROCEDURE sp_test (test_cursor OUT SYS_REFCURSOR) IS
var_temp temp_table := temp_table ();
BEGIN
var_temp.EXTEND ();
var_temp (var_temp.LAST).entries := 1;
var_temp (var_temp.LAST).entry_name := 'test';
OPEN test_cursor FOR SELECT * FROM TABLE (var_temp);
END sp_test;

Returning a custom type in a PL/SQL Stored Procedure

My goal is to write either a function or a stored procedure in PL/SQL that returns a table for the user to immediately view. I have dozens of history tables and want to give myself a snapshot of a single users history across all of the tables. but I am running into an error and I am not sure if this is even the best way to do this.
My current approach is to gather all of the information from the tables into a custom "summary table" type and then return it. However I am getting errors when using my custom "userTable" as the type of my out parameter
CREATE OR REPLACE PROCEDURE GetUserSnapShot(myid in Number, numdays in Number,
myTable out userTable)
AS
BEGIN
/*maybe insert a row into the table here?*/
myTable := null;
END;
DECLARE
TYPE userChanges IS RECORD (
historyTable VARCHAR(100),
historyId NUMBER,
changeType VARCHAR(6),
changeDate DATE
);
TYPE userTable IS TABLE OF userChanges INDEX BY BINARY_INTEGER;
myTable userTable;
BEGIN
GetUserSnapShot(5, 7, myTable);
END;
here is the error I get when trying to run this: identifier 'USERTABLE' must be declared which is confusing to me since I have declared userTable
You've defined the type USERTABLE in your anonymous block, but the procedure doesn't know what's in your anonymous block and thus can't see the type.
I suggest that it would be best for you to use a package so you can put the record type, table type, and the procedure in the package. An example would be:
CREATE PACKAGE MY_PACKAGE AS -- define the package spec
TYPE userChanges IS RECORD
(
historyTable VARCHAR(100),
historyId NUMBER,
changeType VARCHAR(6),
changeDate DATE
);
TYPE userTable IS TABLE OF userChanges
INDEX BY BINARY_INTEGER;
PROCEDURE GetUserSnapShot(myid in Number,
numdays in Number,
myTable in out userTable);
END MY_PACKAGE;
CREATE PACKAGE BODY MY_PACKAGE AS -- define the package body
PROCEDURE GetUserSnapShot(myid in Number,
numdays in Number,
myTable in out userTable)
AS
rec userChanges;
BEGIN
rec.historyTable := 'SOMEWHERE';
rec.historyId := 1;
rec.changeType := 'ABCDEF';
rec.changeDate := SYSDATE;
myTable(1) := rec;
END;
END MY_PACKAGE;
Now you can reference the packaged procedure and type in your anonymous block as:
DECLARE
MY_PACKAGE.myTable userTable;
BEGIN
MY_PACKAGE.GetUserSnapShot(5, 7, myTable);
END;
Best of luck.

Store data of each iteration of a LOOP and return all of them on a ref cursor?

Sorry for the bad question name, i'll give you an example to be more specific.
var1 VARCHAR(20);
var2 VARCHAR(20);
--MYRECORD CONTAINS COLUMNS ELEMENT0, VAL
FOR MYRECORD IN EXPLICITCURSOR LOOP
SELECT COL1, COL2 INTO var1, var2 FROM table1 WHERE table1.COLUMNT=MYRECORD.VAL;
END LOOP;
As you can see i have a LOOP and inside it i have a SELECT. By now, for testing, i'm saving the results into two variables overwritten everytime.
I need to save on each iteration (ELEMENT0, COL1, COL2) and i'd give them on output with a REF CURSOR.
EDIT1: I'm looking in this moment on the possibility to define a RECORD and a TABLE of my record type. Can anyone give me an example for my case? I'm having problems on setting a table as output parameter.
This is what i have prepared at the beginning of the package.
TYPE my_record is RECORD(
ELEMENT0 varchar2(20),
COL1 varchar2(20),
COL2 varchar2(20));
TYPE my_table IS TABLE OF my_record;
and for now i'm using an OUT parameter for my procedure like this:
TABLERESULT OUT my_table
I'm trying to insert my three varchar values inside my OUT param on each iteration of LOOP in this way (values are setted correctly):
INSERT INTO TABLERESULT(ELEMENT0,COL1,COL2) VALUES(ELEMENT0,COL1,COL2);
and it gives me error:
PL/SQL: ORA-00942: table or view does not exist
I'm doing something wrong using this type of OUT param?
Any suggestions? Thank you. (I'm using Oracle 11g)
EDIT2: By the help of #APC i found that naming error and now the compiler doesn't give problems. I'll continue and i'll let you know.
Here this can be achieved simply without any iterations required.
Hope below snippet helps. Please pardon any syntax error as i dont have any workspace to execute this command.
--Wwhat i would suggest is rather going row-by-row this can be achieved by single iteration and can be returnedas ref cursor as output.
DECLARE
lv sys.odcivarchar2list;
lv_ref sys_refcursor;
BEGIN
SELECT val BULK COLLECT
INTO lv
FROM TABLE2;
OPEN lv_ref FOR SELECT * FROM TABLE1 WHERE TABLE1.COL IN
(SELECT COLUMN_VALUE FROM TABLE(lv)
);
END;
/
Create an object type that matches your record structure.
Create a nested table type of those object types.
For each tuplet of strings, add to the array.
Then return a cursor variable as follows:
OPEN cv FOR SELECT FROM TABLE (my_array);
RETURN cv;
The table function converts the array into a result set that can be assigned to the cursor variable.
Here's a link to a LiveSQL script that will run the code you see below:
https://livesql.oracle.com/apex/livesql/file/content_FFTOKNC4AHGPOQE79FF76S7EQ.html
CREATE OR REPLACE TYPE three_ot
AUTHID DEFINER IS OBJECT
(
element0 VARCHAR2 (200),
col1 VARCHAR2 (200),
col2 VARCHAR2 (200)
)
/
CREATE OR REPLACE TYPE three_nt IS TABLE OF three_ot
/
CREATE OR REPLACE FUNCTION data_for_you
RETURN SYS_REFCURSOR
AUTHID DEFINER
IS
l_cursor SYS_REFCURSOR;
l_nt three_nt;
BEGIN
SELECT three_ot (TO_CHAR (employee_id), last_name, first_name)
BULK COLLECT INTO l_nt
FROM hr.employees;
OPEN l_cursor FOR SELECT * FROM TABLE (l_nt);
RETURN l_cursor;
END;
/
DECLARE
l_cursor SYS_REFCURSOR := data_for_you ();
l_three three_ot;
element0 VARCHAR2 (200);
col1 VARCHAR2 (200);
col2 VARCHAR2 (200);
BEGIN
LOOP
FETCH l_cursor INTO element0, col1, col2;
EXIT WHEN l_cursor%NOTFOUND;
DBMS_OUTPUT.put_line (col1);
END LOOP;
CLOSE l_cursor;
END;
/

how to use array as bind variable

I have following array, which will be populated based on some external criteria.
TYPE t_column IS TABLE OF TABLE_1.COLUMN_1%TYPE INDEX BY PLS_INTEGER;
ar_column t_column;
Now, I want to use this ar_column into another cursor, how can i bind it ?
I am looking at something like select * from table1 where column in (ar_colum[0],ar_colum[1] ...); (its a pseudo code)
Using PL/SQL collections in SQL statements requires a few extra steps. The data type must be created, not simply declared as part of a PL/SQL block. Associative arrays do no exist in SQL and must be converted into a nested table or varray at some point. And the TABLE operator must be used to convert the data.
create table table1(column_1 number);
insert into table1 values (1);
commit;
create or replace type number_nt is table of number;
declare
ar_column number_nt := number_nt();
v_count number;
begin
ar_column.extend; ar_column(ar_column.last) := 1;
ar_column.extend; ar_column(ar_column.last) := 2;
select count(*)
into v_count
from table1
where column_1 in (select * from table(ar_column));
dbms_output.put_line('Count: '||v_count);
end;
/
Count: 1