Execute a Select inside a loop PL/SQL and return cursor? - sql

This is probably a simple question for who knows PL/SQL.
I have a stored procedure who takes an array of varchar in input:
TYPE MULTI is table of VARCHAR(15) index by BINARY_INTEGER;
PROCEDURE MYPROC(
SINGLE IN MULTI,
P_RESULT OUT MY_PCK.MYCURSOR,
P_SOMETHING OUT VARCHAR2,
);
The cursor works because i have tested it in other cases but this is the first with an array parameter.
I have a problem with the body, how can i assign each value i get from the select to the cursor?
BEGIN
FOR i IN SINGLE.first .. SINGLE.last
LOOP
--BEGIN OPEN P_RESULT FOR this --this on left gives me error
SELECT MT.DESCR INTO P_SOMETHING
FROM MYTABLE1 MT
WHERE MT.IDS = SINGLE(i)
AND and rownum < 2;
--dbms_output.put_line(SINGLE(i)); --if i use this instead of select i get the values i send to this procedure.
END LOOP;
i tried also:
SELECT MT.DESCR INTO P_RESULT but gives error
What i'm doing wrong?
Thanks in advice.

You can't use your PL/SQL collection type in a SQL statement in 11g. You could create a SQL collection type, or find one you already have access to (which has a suitable string length) and use that to at least verify the mechanism.
For instance, this uses a local variable of type SYS.HSBLKNAMLST, which is defined as a table of varchar2(30), more than enough to match your own PL/SQL type's string length. That variable is populated from your passed-in PL/SQL type, and that is then used in a query to open the cursor:
PROCEDURE MYPROC(
SINGLE IN MULTI,
P_RESULT OUT MY_PCK.MYCURSOR
) IS
LOCAL_COLL SYS.HSBLKNAMLST := SYS.HSBLKNAMLST();
BEGIN
FOR i IN SINGLE.first .. SINGLE.last
LOOP
LOCAL_COLL.extend();
LOCAL_COLL(LOCAL_COLL.last) := SINGLE(i);
END LOOP;
OPEN P_RESULT FOR
SELECT MT.DESCR
FROM MYTABLE1 MT
LEFT JOIN TABLE(LOCAL_COLL) LC
ON LC.COLUMN_VALUE = MT.IDS
WHERE LC.COLUMN_VALUE IS NULL;
END;
I'm a bit confused about the query you showed in your loop though; you seem to be attempting to re-open the cursor for each element of the array, though maybe you were trying to append the result of the query for each element to the same cursor - thought the != would mean that all rows would be included at some point. I've guess that you're trying to get all records with IDS values that are not in the array. If you actually want all that are then that would be:
OPEN P_RESULT FOR
SELECT MT.DESCR
FROM TABLE(LOCAL_COLL) LC
JOIN MYTABLE1 MT
ON MT.IDS = LC.COLUMN_VALUE;
You can see which built-in types are available to you by querying the data dictionary, e.g:
select owner, type_name, coll_type, elem_type_name, length
from all_coll_types
where elem_type_name = 'VARCHAR2'
and coll_type = 'TABLE'
and owner = 'SYS'
order by length;
It would be preferable to create your own SQL type if you're able to.

Related

Select only one column value based on id from table and use in function in postgresql

Description
I am creating a postgresql function and encountered a problem. I am reading data from table and based on that data i want to update data or not.
but for selection i need to either create a temp table or create another function that return a single decimal value.
Here is my code
Declare command text := 'select distance from road where gid ='|| id;
Execute command;
i am stuck at this point
i dont know what to do as i am new to postgresql
What i need
i want to apply condition on distance returned by this query
for example
IF distance < 100
THEN
(Insert into another table)
END;
What i tried
select distance into varDistance from road where gid ='|| id;
i go through Select Into command and came to know that this should be same as table . which is not acceptable to me .
Is this possible to have double type variable and after query i get my varibale initialed with value? Or else solution
It's unclear to me what you are trying to do, but to read a single value from a table, you would need the select into
Something along the lines:
create function some_function(p_id integer)
returns ...
as
$$
declare
l_distance double precision;
begin
select distance
into l_distance
from road
where id = p_id; --<< this is the parameter
if l_distance < 100 then
insert into some_other_table (...)
values (...)
end if;
end;
$$
language plpgsql;
From the little information you have provided, I don't see any reason for dynamic SQL.
If you do need dynamic SQL, use the format() function to create the SQL string with a placeholder, then use execute with an into and using clause
l_sql := format('select distance from %I gid = $1', l_table_name);
execute l_sql
into l_distance
using p_id; --<< this is the parameter

PL/SQL Statement Ignored. ORA-22905: cannot access rows from a non-nested table item

I tried to select values from the nested table and bulk collecting into an associative array collection. When I try to bulk collect oracle throwing the above exception(PL/SQL: SQL Statement ignored
PL/SQL: ORA-22905: cannot access rows from a non-nested table
item) though I fetch the data from the nested table.
It is not happening in all the cases. When the same package compiled in the different client database, Some case it is not throwing an error and in some environment, it is throwing an error. Can you please help what was the exact issue.
I have not attached the entire package. Instead provided the case where the issue occurs.
DECLARE
TYPE rc_rec_multiset IS record (
rc_id NUMBER,
doc_num VARCHAR2(100)
);
TYPE rc_type IS TABLE OF rc_rec_multiset;
TYPE tab_rec_type_multiset IS
TABLE OF rc_rec_multiset INDEX BY pls_integer;
rc_tab_type rc_type := rc_type() ;
rc_tab_type_dist_rc tab_rec_type_multiset;
BEGIN
SELECT DISTINCT rc_id,
doc_num BULK COLLECT
INTO rc_tab_type_dist_rc
FROM TABLE(rc_tab_type);
END;
You cannot do this using SQL; an associative array is a PL/SQL data type and cannot be used in the SQL scope. In a similar vein, collections defined in the PL/SQL scope cannot be used in SQL scope (in 11g and earlier) - you either need to define collections in the SQL scope (Note - you cannot do this for associative arrays as they are purely PL/SQL) or just use PL/SQL.
Assuming the rc_tab_type collection is not sparse then you can use pure PL/SQL like this:
DECLARE
TYPE rc_rec_multiset IS record (
rc_id NUMBER,
doc_num VARCHAR2(100)
);
TYPE rc_type IS TABLE OF rc_rec_multiset;
TYPE tab_rec_type_multiset IS
TABLE OF rc_rec_multiset INDEX BY pls_integer;
rc_tab_type rc_type := rc_type();
rc_tab_type_dist_rc tab_rec_type_multiset;
BEGIN
/*
* Populate rc_tab_type here.
*/
FOR i IN 1 .. rc_tab_type.COUNT LOOP
rc_tab_type_dist_rc( rc_tab_type(i).rc_id ) := rc_tab_type(i).doc_num;
END LOOP;
END;
If it is sparse then, instead of the FOR loop, you will have to use:
i := rc_tab_type.FIRST;
WHILE i IS NOT NULL LOOP
rc_tab_type_dist_rc( rc_tab_type(i).rc_id ) := rc_tab_type(i).doc_num;
i := rc_tab_type.NEXT(i);
END LOOP;

Oracle SQL - SELECT with Variable arguments stored procedure

I'm struggling with a variable argument stored procedure that has to perform a SELECT on a table using every argument passed to it in its WHERE clause.
Basically I have N account numbers as parameter and I want to return a table with the result of selecting three fields for each account number.
This is what I've done so far:
function sp_get_minutes_expiration_default(retval IN OUT char, gc IN OUT GenericCursor,
p_account_num IN CLIENT_ACCOUNTS.ACCOUNT_NUM%TYPE) return number
is
r_cod integer := 0;
begin
open gc for select account_num, concept_def, minutes_expiration_def from CLIENT_ACCOUNTS
where p_account_num = account_num; -- MAYBE A FOR LOOP HERE?
return r_cod;
exception
-- EXCEPTION HANDLING
end sp_get_minutes_expiration_default;
My brute force solution would be to maybe loop over a list of account numbers, select and maybe do a UNION or append to the result table?
If you cast your input parameter as a table, then you can join it to CLIENT_ACCOUNTS
select account_num, concept_def, minutes_expiration_def
from CLIENT_ACCOUNTS ca, table(p_account_num) a
where a.account_num = ca.account_num
But I would recommend you select the output into another collection that is the output of the function (or procedure). I would urge you to not use reference cursors.
ADDENDUM 1
A more complete example follows:
create or replace type id_type_array as table of number;
/
declare
ti id_type_array := id_type_array();
n number;
begin
ti.extend();
ti(1) := 42;
select column_value into n from table(ti) where rownum = 1;
end;
/
In your code, you would need to use the framework's API to:
create an instance of the collection (of type id_type_array)
populate the collection with the list of numbers
Execute the anonymous PL/SQL block, binding in the collection
But you should immediately see that you don't have to put the query into an anonymous PL/SQL block to execute it (even though many experienced Oracle developers advocate it). You can execute the query just like any other query so long as you bind the correct parameter:
select account_num, concept_def, minutes_expiration_def
from CLIENT_ACCOUNTS ca, table(:p_account_num) a
where a.column_value = ca.account_num

PL/SQL stored function return with row type

CREATE OR REPLACE FUNCTION lab( f_name IN VARCHAR2 )
RETURN test%ROWTYPE
IS
total NUMBER := 0;
CURSOR c_app IS
SELECT count(*),LISTAGG(s.sname,',') WITHIN GROUP (ORDER BY s.sname)
FROM APPLICANT a INNER JOIN SPOSSESSED s ON a.A# = s.A#
WHERE a.fname = f_name;
rec_app c_app%ROWTYPE;
BEGIN
OPEN c_app;
LOOP
FETCH c_app into rec_app;
EXIT WHEN c_app%NOTFOUND;
END LOOP;
CLOSE c_app;
RETURN rec_app;
END lab;
/
Fail to compile with errors that expression wrong type?
Isn't it possible to return with rowtype result?
for example i run this function
select lab(fname) from position where fname='PETER';
so the result will be display like
PETER : aaaa,bbbb,cccc
You're declaring the return as test%rowtype, then trying to return rec_app, which is declared as c_app%rowtype - so the types don't match. You can't do that.
c_app is only in scope within this function so it would not have any meaning for any callers, and you can't use it as the return type. You can return something that is actually test%rowtype, assuming test is a table, but not an arbitrary different type. It isn't clear that there is any relationship at all between your cursor and its row type, and the test table.
You're also looping round to potentially fetch multiple rows, but only returning the last one (or trying to, anyway), which probably isn't what you mean to do.
The simplest way to get all the cursor rows back to the caller is with a ref cursor:
CREATE OR REPLACE FUNCTION lab( f_name IN VARCHAR2 )
RETURN SYS_REFCURSOR
IS
ref_cur SYS_REFCURSOR;
BEGIN
OPEN ref_cur FOR
SELECT count(*),LISTAGG(s.sname,',') WITHIN GROUP (ORDER BY s.sname)
FROM APPLICANT a INNER JOIN SPOSSESSED s ON a.A# = s.A#
WHERE a.fname = f_name;
RETURN ref_cur;
END lab;
/
If you create an external type you could use PIPELINED but that doesn't appear necessary here. neither is quite using a %rowtype though. You can only return a %rowtype if you have a table that has the columns you want to return.

using comma separated values inside IN clause for NUMBER column

I have 2 procedures inside a package. I am calling one procedure to get a comma separated list of user ids.
I am storing the result in a VARCHAR variable. Now when I am using this comma separated list to put inside an IN clause in it is throwing "ORA-01722:INVALID NUMBER" exception.
This is how my variable looks like
l_userIds VARCHAR2(4000) := null;
This is where i am assigning the value
l_userIds := getUserIds(deptId); -- this returns a comma separated list
And my second query is like -
select * from users_Table where user_id in (l_userIds);
If I run this query I get INVALID NUMBER error.
Can someone help here.
Do you really need to return a comma-separated list? It would generally be much better to declare a collection type
CREATE TYPE num_table
AS TABLE OF NUMBER;
Declare a function that returns an instance of this collection
CREATE OR REPLACE FUNCTION get_nums
RETURN num_table
IS
l_nums num_table := num_table();
BEGIN
for i in 1 .. 10
loop
l_nums.extend;
l_nums(i) := i*2;
end loop;
END;
and then use that collection in your query
SELECT *
FROM users_table
WHERE user_id IN (SELECT * FROM TABLE( l_nums ));
It is possible to use dynamic SQL as well (which #Sebas demonstrates). The downside to that, however, is that every call to the procedure will generate a new SQL statement that needs to be parsed again before it is executed. It also puts pressure on the library cache which can cause Oracle to purge lots of other reusable SQL statements which can create lots of other performance problems.
You can search the list using like instead of in:
select *
from users_Table
where ','||l_userIds||',' like '%,'||cast(user_id as varchar2(255))||',%';
This has the virtue of simplicity (no additional functions or dynamic SQL). However, it does preclude the use of indexes on user_id. For a smallish table this shouldn't be a problem.
The problem is that oracle does not interprete the VARCHAR2 string you're passing as a sequence of numbers, it is just a string.
A solution is to make the whole query a string (VARCHAR2) and then execute it so the engine knows he has to translate the content:
DECLARE
TYPE T_UT IS TABLE OF users_Table%ROWTYPE;
aVar T_UT;
BEGIN
EXECUTE IMMEDIATE 'select * from users_Table where user_id in (' || l_userIds || ')' INTO aVar;
...
END;
A more complex but also elegant solution would be to split the string into a table TYPE and use it casted directly into the query. See what Tom thinks about it.
DO NOT USE THIS SOLUTION!
Firstly, I wanted to delete it, but I think, it might be informative for someone to see such a bad solution. Using dynamic SQL like this causes multiple execution plans creation - 1 execution plan per 1 set of data in IN clause, because there is no binding used and for the DB, every query is a different one (SGA gets filled with lots of very similar execution plans, every time the query is run with a different parameter, more memory is needlessly used in SGA).
Wanted to write another answer using Dynamic SQL more properly (with binding variables), but Justin Cave's answer is the best, anyway.
You might also wanna try REF CURSOR (haven't tried that exact code myself, might need some little tweaks):
DECLARE
deptId NUMBER := 2;
l_userIds VARCHAR2(2000) := getUserIds(deptId);
TYPE t_my_ref_cursor IS REF CURSOR;
c_cursor t_my_ref_cursor;
l_row users_Table%ROWTYPE;
l_query VARCHAR2(5000);
BEGIN
l_query := 'SELECT * FROM users_Table WHERE user_id IN ('|| l_userIds ||')';
OPEN c_cursor FOR l_query;
FETCH c_cursor INTO l_row;
WHILE c_cursor%FOUND
LOOP
-- do something with your row
FETCH c_cursor INTO l_row;
END LOOP;
END;
/