Stored procedure calling 2 values in parameter declaration - oracle

I have a stored procedure written like this:
CREATE OR REPLACE PROCEDURE Proc_location_example(in_data_ID IN VARCHAR2,
in_Location_name IN VARCHAR2)
IS
v_Location_ID NUMBER;
v_data_id NUMBER;
BEGIN
---------------------------------------------------------------------------
lv_prog_name := 'PRoc_location_example';
ln_step := 1;
SELECT Location_ID
INTO v_Location_ID
FROM random.client
WHERE Location_name = in_Location_name;
.....
END;
This procedure is getting 'NY ' passed in as an example for in_location_name. I want to pass in 'NY' and 'nj' to the location_name.
In other words the location name supports 2 name so which will be easiest way to do it?

You can add an expression with an IN operator such as
.. WHERE Location_name = in_Location_name AND in_Location_name IN ('NY','nj')
if case-sensitivity doesn't matter, then use
.. WHERE Location_name = in_Location_name AND REGEXP_LIKE(in_Location_name, 'Ny|nJ','i')
in order to restrict the parameters to those two values.

There are two options that I can think of that would satisfy your question.
Option #1: More Parameters
By adding more parameters you can use those parameters in your query for location IDs. If you are going to potentially more than 2 locations and don't want to keep having to add parameters, Option #2 is probably a better choice.
CREATE OR REPLACE PROCEDURE Proc_location_example (in_location_name1 IN VARCHAR2 DEFAULT NULL,
in_location_name2 IN VARCHAR2 DEFAULT NULL)
IS
TYPE location_id_t IS TABLE OF NUMBER;
v_location_ids location_id_t;
BEGIN
SELECT location_id
BULK COLLECT INTO v_location_ids
FROM (SELECT 101 AS location_id, 'NJ' AS location_name FROM DUAL
UNION ALL
SELECT 102, 'NY' FROM DUAL
UNION ALL
SELECT 103, 'MA' FROM DUAL)
WHERE location_name IN (in_location_name1, in_location_name2);
FOR i IN 1 .. v_location_ids.COUNT
LOOP
DBMS_OUTPUT.put_line ('ID #' || i || ': ' || v_location_ids (i));
END LOOP;
END;
/
BEGIN
Proc_location_example (in_location_name1 => 'NJ', in_location_name2 => 'NY');
END;
/
Option #2: Table Type Parameter
By using a table as your parameter, you can pass an unlimited number of "location names". This does require you to use a pre-defined table type, but this solution is more flexible in the number of "parameters"
CREATE OR REPLACE TYPE location_name_t IS TABLE OF VARCHAR2 (2);
CREATE OR REPLACE PROCEDURE Proc_location_example (in_locations location_name_t)
IS
TYPE location_id_t IS TABLE OF NUMBER;
v_location_ids location_id_t;
BEGIN
SELECT location_id
BULK COLLECT INTO v_location_ids
FROM (SELECT 101 AS location_id, 'NJ' AS location_name FROM DUAL
UNION ALL
SELECT 102, 'NY' FROM DUAL
UNION ALL
SELECT 103, 'MA' FROM DUAL)
WHERE location_name IN (select * from table(in_locations));
FOR i IN 1 .. v_location_ids.COUNT
LOOP
DBMS_OUTPUT.put_line ('ID #' || i || ': ' || v_location_ids (i));
END LOOP;
END;
/
BEGIN
Proc_location_example (in_locations => location_name_t('NJ','NY'));
END;
/

Related

How to pass string of comma-separated numbers to stored procedure in condition for numeric field?

I have a stored procedure like below where multiple employee IDs will be passed as comma-separated value (multiple IDs). It is throwing error as "ORA-01722: invalid number". I know it's because of passing varchar2 variable for the numeric ID column. But is there any way we can achieve this simply?
create or replace PROCEDURE Fetch_Emp_Name(Emp_id in varchar2)
IS
BEGIN
select Name from EMP where id in (emp_id);
END;
You can use dynamic sql.
create or replace PROCEDURE Fetch_Emp_Name(emp_id in varchar2) IS
v_result varchar2;
begin
execute immediate
'select Name from EMP where id in (' || 'emp_id' || ')'
into
v_result;
end;
Also you can use package dbms_sql for dynamic sql.
Update
Another approach. I think may be better.
create or replace PROCEDURE Fetch_Emp_Name(emp_id in varchar2) IS
v_result varchar2;
begin
select
Name
from
EMP
where
id in
(
select
to_number(regexp_substr(emp_id, '[^,]+', 1, level))
from
dual
connect by regexp_substr(emp_id, '[^,]+', 1, level) is not null
);
exception
when no_data_found then
-- error1;
when too_many_rows then
-- error2;
end;
Sorry for before, I did not get the question in the right way. If you get a lot of IDs as different parameters, you could retrieve the list of names as an string split by comma as well. I put this code where I handled by regexp_substr the name of different emp_ids you might enter in the input parameter.
Example ( I am assuming that the IDs are split by comma )
create or replace PROCEDURE Fetch_Emp_Name(p_empid in varchar2) IS
v_result varchar2(4000);
v_append emp.name%type;
v_emp emp.emp_id%type;
counter pls_integer;
i pls_integer;
begin
-- loop over the ids
counter := REGEXP_COUNT(p_empid ,'[,]') ;
--dbms_output.put_line(p_empid);
if counter > 0
then
i := 0;
for r in ( SELECT to_number(regexp_substr(p_empid,'[^,]+',1,level)) as mycol FROM dual CONNECT BY LEVEL <= REGEXP_COUNT(p_empid ,'[,]')+1 )
loop
--dbms_output.put_line(r.mycol);
v_emp := r.mycol ;
select name into v_append from emp where emp_id = v_emp;
if i < 1
then
v_result := v_append ;
else
v_result := v_result ||','|| v_append ;
end if;
i := i + 1;
end loop;
else
v_emp := to_number(p_empid);
select name into v_result from emp where emp_id = v_emp;
end if;
dbms_output.put_line(v_result);
exception
when no_data_found
then
raise_application_error(-20001,'Not Employee found for '||v_emp||' ');
when too_many_rows
then
raise_application_error(-20002,'Too many employees for id '||v_emp||' ');
end;
Test
SQL> create table emp ( emp_id number, name varchar2(2) ) ;
Table created.
SQL> insert into emp values ( 1 , 'AA' );
1 row created.
SQL> insert into emp values ( 2 , 'BB' ) ;
1 row created.
SQL> commit;
SQL> insert into emp values ( 3 , 'CC' ) ;
1 row created.
SQL> select * from emp ;
EMP_ID NA
---------- --
1 AA
2 BB
3 CC
SQL> exec Fetch_Emp_Name('1') ;
AA
PL/SQL procedure successfully completed.
SQL> exec Fetch_Emp_Name('1,2,3') ;
AA,BB,CC
PL/SQL procedure successfully completed.
SQL>

How to assign multiple values to a variable using into clause in oracle query?

I have an oracle stored procedure like this
CREATE OR REPLACE PROCEDURE DEMO (V_IN CHAR, V_OUT VARCHAR2)
IS
BEGIN
FOR ITEM IN LOOP (SELECT DISTINCT (NAME)
FROM TABLE1 INTO V_OUT
WHERE ID = V_IN
LOOP
--CODE TO PRINT V_OUT
END LOOP;
END;
Now how should I create that V_OUT variable so that it can hold all the values coming from query? I'm doing this in oracle12C.
You don't put the INTO clause in the cursor query. And even if you did, you have it in the wrong place in the SQL statement.
You deal with that when you fetch a row from the query:
CREATE OR REPLACE PROCEDURE
DEMO (V_IN CHAR, V_OUT
VARCHAR2)IS
BEGIN
FOR ITEM IN LOOP
(SELECT DISTINCT (NAME)
FROM TABLE1
WHERE ID= V_IN
)
LOOP
dbms_output.put_line(item.name);
v_out := item.name;
END LOOP;
END;
But then the problem is that we just keep overlaying the previous value, so that when your procedure actually exits, the only value of v_out is that last assigned. If you truely need a collection of values, you need to declare your output variable to be a ref cursor, and adjust code accordingly. I've never actually worked with them, so perhaps someone else will chime in.
You can work with collections, like this:
--declare the pakage type
CREATE OR REPLACE PACKAGE PKG_TYPES AS
TYPE LIST_VARCHAR IS TABLE OF VARCHAR2(2000);
END;
--create the proc that will assemble the value list
CREATE OR REPLACE PROCEDURE DEMO ( V_IN IN varchar2, V_OUT IN OUT PKG_TYPES.LIST_VARCHAR) IS
BEGIN
FOR ITEM IN (
SELECT DISTINCT (NAME) name
FROM (SELECT 'X' ID, 'A' name FROM dual
UNION
SELECT 'X' ID, 'b' name FROM dual
UNION
SELECT 'y' ID, 'c' name FROM dual
) TABLE1
WHERE ID= V_IN
)
LOOP
V_OUT.EXTEND;
V_OUT(V_OUT.LAST) := item.name;
--CODE TO PRINT V_OUT
END LOOP;
END;
--use the list. I separated this step but it can be in the demo proc as well
DECLARE
names PKG_TYPES.LIST_VARCHAR := PKG_TYPES.LIST_VARCHAR();
BEGIN
demo('X',names) ;
FOR i IN names.first..names.last LOOP
Dbms_Output.put_line(i);
END LOOP;
END;
You will have to handle exceptions for when no value is returned from the cursor (when no ID is found).
If you need a collection variable - you can use a nested table and bulk collect like below.
To be able to return the value from the procedure you will need to declare the nested table type in some package or on DB schema level.
declare
type test_type is table of varchar2(2000);
test_collection test_type;
begin
select distinct(name) bulk collect into test_collection
from (
select 1 id, 'AAA' name from dual
union all
select 1 id, 'BBB' name from dual
union all
select 1 id, 'AAA' name from dual
union all
select 2 id, 'CCC' name from dual
)
where id = 1;
for i in test_collection.first..test_collection.last loop
dbms_output.put_line(test_collection(i));
end loop;
end;
/
If you just need a string with concatenated values - you can use listagg to create it like below
declare
test_str varchar2(4000);
begin
select listagg(name, ', ') within group(order by 1)
into test_str
from (
select distinct name
from (
select 1 id, 'AAA' name from dual
union all
select 1 id, 'BBB' name from dual
union all
select 1 id, 'AAA' name from dual
union all
select 2 id, 'CCC' name from dual
)
where id = 1
);
dbms_output.put_line(test_str);
end;
/

Oracle PL/SQL: Returning specific selected columns from procedure

What is the best method for returning specific column values from a Procedure. For example, the below code doesn't work
/*DECLARATION*/
TYPE t_data IS TABLE OF Table1%ROWTYPE;
PROCEDURE get_values(data OUT t_data) AS
BEGIN
SELECT a.object_id, a.num, b.descrip
BULK COLLECT INTO data
FROM Table1 a INNER JOIN
Table2 b ON (a.id = b.id)
WHERE a.date IS NULL;
END get_values;
In the same scenario if I use a SELECT *, it works...
I assume this is an exercise on how to use a procedure to get a list of structured values; I would never recommend such an approach to get data from a table, preferring, if possible, a pure SQL method.
You seem to have tables like these:
create table table1(id ,object_id, num, "date") as (
select 1, 1, 100, sysdate from dual union all
select 2, 2, 200, null from dual
);
create table table2(id, descrip) as (
select 1, 'desc1' from dual union all
select 2, 'desc2' from dual
);
You're trying to create a procedure that return a set of rows, where each row contains elements from both tables; to do so, you need to build a type that matches the result of your select query.
You may want to define your package like this:
CREATE OR REPLACE PACKAGE yourPackage AS
TYPE tRec IS RECORD /* made to match the columns you want to extract in your query */
(
object_id NUMBER,
num NUMBER,
descrip VARCHAR2(100)
);
TYPE tTab IS TABLE OF tRec;
PROCEDURE get_values(data OUT tTab);
END yourPackage;
create or replace package body yourPackage as
PROCEDURE get_values(data OUT tTab) AS
BEGIN
SELECT a.object_id, a.num, b.descrip
BULK COLLECT INTO data
FROM Table1 a INNER JOIN
Table2 b ON (a.id = b.id)
WHERE a."date" IS NULL;
END get_values;
end yourPackage ;
You can call the procedure in the package this way:
declare
someVar yourPackage.tTab;
begin
yourPackage.get_values(someVar);
--
if someVar.first is not null then
for i in someVar.first .. someVar.last loop
dbms_output.put_line(someVar(i).object_id || ' - ' || someVar(i).num || ' - ' || someVar(i).descrip);
end loop;
end if;
end;
and this is the result you get:
2 - 200 - desc2
Firstly you cannot create a Type of any table%rowtype outside a PLSQL block. You need to create table as object and then have to create a type of that object. Then you can use it.
See below:
CREATE OR REPLACE TYPE Table11 AS OBJECT
(
id NUMBER,
num NUMBER,
description VARCHAR2 (20)
)
CREATE OR REPLACE TYPE t_data IS TABLE OF Table11;
CREATE OR REPLACE PROCEDURE get_values (v_data OUT t_data)
AS
BEGIN
SELECT Table11 (a.row_id, 222, 'hello')
BULK COLLECT INTO v_data
FROM Table1 a INNER JOIN Table2 b
ON (a.row_id = b.appid)
WHERE a.date IS NULL;
END get_values;
execution:
DECLARE
v_var t_data;
BEGIN
get_values (v_var);
FOR i IN 1 .. v_var.COUNT
LOOP
DBMS_OUTPUT.put_line (v_var (i).id ||' ' ||v_var(i).num ||' ' || v_var(i).description );
END LOOP;
END;
Output:
SQL> /
1 222 hello
2 222 hello
PL/SQL procedure successfully completed.

Can I access a cursor from within a procedure by name?

The cursor name would be passed in as a varchar2, and the cursor itself exists in the same package as the procedure.
Given only the name (and not a cursor reference) is it possible to access the cursor and loop through it?
If this requires the use of an "execute immediate" or similar, that's not out of the question... (though it's not quite clear to me how that would work, I was under the impression anything it declares is out of scope once it completes).
Apologies in advance, this seems like it should be obvious to me but I'm coming up blank.
Thinking about this a bit, I think you're going about it the wrong way. I'd UNION ALL the results from each of the "cursors" together and then use the "cursor name" to eliminate all the unwanted rows (which the optimizer should optimize away) so that you only get the rows you want. So something like
CREATE OR REPLACE PROCEDURE DO_SOMETHING(pin_Cursor_name IN VARCHAR2)
IS
CURSOR csrFruits IS
SELECT UPPER(pin_Cursor_name) AS FRUIT_TYPE,
VARIETY_NAME,
COLOR,
SIZE,
TARTNESS_RATING,
NULL AS FUZZ_LENGTH,
ROOTSTOCK,
NULL AS PEEL_THICKNESS
FROM APPLES
WHERE pin_Cursor_name = 'apples'
UNION ALL
SELECT UPPER(pin_Cursor_name) AS FRUIT_TYPE,
VARIETY_NAME,
COLOR,
SIZE,
NULL AS TARTNESS_RATING,
FUZZ_LENGTH,
NULL AS ROOTSTOCK,
NULL AS PEEL_THICKNESS
FROM PEACHES
WHERE pin_Cursor_name = 'peaches'
UNION ALL
SELECT UPPER(pin_Cursor_name) AS FRUIT_TYPE,
VARIETY_NAME,
COLOR,
SIZE,
NULL AS TARTNESS_RATING,
NULL AS FUZZ_LENGTH,
NULL AS ROOTSTOCK,
PEEL_THICKNESS
FROM KUMQUATS
WHERE pin_Cursor_name = 'kumquats'
UNION ALL
SELECT UPPER(pin_Cursor_name) AS FRUIT_TYPE,
VARIETY_NAME,
'GREEN' AS COLOR,
SIZE,
NULL AS TARTNESS_RATING,
FUZZ_LENGTH,
ROOTSTOCK,
NULL AS PEEL_THICKNESS
FROM KIWIS
WHERE pin_Cursor_name = 'kiwis';
BEGIN
FOR aRow IN csrFruits LOOP
DBMS_OUTPUT.PUT_LINE(pin_Cursor_name || ' - ' ||
aRow.VARIETY_NAME || ', ' ||
aRow.COLOR || ', ' ||
aRow.SIZE);
END LOOP;
END DO_SOMETHING;
So here we have a cursor which will read from one of four different tables (APPLES, PEACHES, KUMQUATS, and KIWIS) depending on the input parameter. The idea is to have each of the subqueries return a rowset of the same "shape", adding NULL AS XXX for each column which an individual subquery doesn't supply.
Best of luck.
Your original problem statement is quite a bit vague and it's unclear for me what constraints you have and what that "other system" expects as a return value. You also might have an XY-problem, so the answer by #bobjarvis might have a valid point too.
The key problem here is that in PL/SQL there is no way to convert an explicit cursor to a cursor variable. Thus the following "simple" solution is not possible:
-- pseudo PL/SQL code
cursor cur_a ...
cursor cur_b ...
function get_cursor(p_cur_name varchar2) return sys_refcursor is
v_cur sys_refcursor;
begin
execute immediate 'open v_cur for p_cur_name';
return v_cur;
end;
v_cur := get_cursor('cur_b');
In the example package below I assume that all the cursors will have the same result set structure. I was lazy and used weak cursor variables even I should have used strong ones. The package code should be easy to follow.
There is at least one other variation that might be useful for you - bulk collect the data to a collection and process the collection with other subroutines. Below print(varchar2) just demonstrates how to open-iterate-close a cursor "dynamically" with a dbms_output.put_line.
create or replace package so48 is
cursor cur_a is
select 'A1' as val, 1 as id from dual union all
select 'A2' as val, 2 as id from dual union all
select 'A3' as val, 3 as id from dual
;
cursor cur_b is
select 'B1' as val, 4 as id from dual union all
select 'B2' as val, 5 as id from dual union all
select 'B3' as val, 6 as id from dual
;
function get_cursor(p_cur_name in varchar2) return sys_refcursor;
procedure print(p_cur in sys_refcursor);
procedure print(p_cur_name in varchar2);
end;
/
show errors
create or replace package body so48 is
function get_cursor(p_cur_name in varchar2) return sys_refcursor is
v_cur sys_refcursor;
begin
case
when p_cur_name = 'A' then
open v_cur for
select 'A1' as val, 1 as id from dual union all
select 'A2' as val, 2 as id from dual union all
select 'A3' as val, 3 as id from dual
;
when p_cur_name = 'B' then
open v_cur for
select 'B1' as val, 4 as id from dual union all
select 'B2' as val, 5 as id from dual union all
select 'B3' as val, 6 as id from dual
;
else
null;
end case;
return v_cur;
end;
procedure print(p_cur in sys_refcursor) is
v_val varchar2(32767);
v_id number;
begin
loop
fetch p_cur into v_val, v_id;
exit when p_cur%notfound;
dbms_output.put_line('(val = ' || v_val || ')(id = ' || v_id || ')');
end loop;
end;
procedure print(p_cur_name in varchar2) is
plsql_compilation_error exception;
pragma exception_init(plsql_compilation_error, -6550);
v_cur_name constant varchar2(32767) := 'so48.' || p_cur_name;
v_plsql constant varchar2(32767) :=
q'[declare
v_val varchar2(32767);
v_id number;
begin
open ]' || v_cur_name || q'[;
loop
fetch ]' || v_cur_name || q'[ into v_val, v_id;
exit when ]' || v_cur_name || q'[%notfound;
dbms_output.put_line('(val = ' || v_val || ')(id = ' || v_id || ')');
end loop;
close ]' || v_cur_name || q'[;
end;]';
begin
execute immediate v_plsql;
exception
when plsql_compilation_error then
dbms_output.put_line('PL/SQL compilation error');
end;
end;
/
show errors
Example run
SQL> exec so48.print(so48.get_cursor('A'))
(val = A1)(id = 1)
(val = A2)(id = 2)
(val = A3)(id = 3)
PL/SQL procedure successfully completed.
SQL> exec so48.print('cur_b')
(val = B1)(id = 4)
(val = B2)(id = 5)
(val = B3)(id = 6)
PL/SQL procedure successfully completed.
SQL>

Bulk Collect Twice over same nested table

Is there any way that after the second bulk collect, data does not get override of the first bulk collect. I don't want to iterate in loop.
DECLARE
TYPE abc IS RECORD (p_id part.p_id%TYPE);
TYPE abc_nt
IS
TABLE OF abc
INDEX BY BINARY_INTEGER;
v_abc_nt abc_nt;
BEGIN
SELECT p_id
BULK COLLECT
INTO v_abc_nt
FROM part
WHERE p_id IN ('E1', 'E2');
SELECT p_id
BULK COLLECT
INTO v_abc_nt
FROM part
WHERE p_id IN ('E3', 'E4');
FOR i IN v_abc_nt.FIRST .. v_abc_nt.LAST
LOOP
DBMS_OUTPUT.put_line (
'p_id is ' || v_abc_nt (i).p_id
);
END LOOP;
END;
OUTPUT:
p_id is E3
p_id is E4
Note: E1 and E2 is present in part table.
You can't simply add the data to the collection, no.
You can, however, do a BULK COLLECT into a separate collection and then combine the collections assuming that you really just need/ want a nested table rather than an associative array...
DECLARE
TYPE abc IS RECORD (p_id part.p_id%TYPE);
TYPE abc_nt
IS
TABLE OF abc;
v_abc_nt abc_nt;
v_abc_nt2 abc_nt;
BEGIN
SELECT p_id
BULK COLLECT
INTO v_abc_nt
FROM part
WHERE p_id IN ('E1', 'E2');
SELECT p_id
BULK COLLECT
INTO v_abc_nt2
FROM part
WHERE p_id IN ('E3', 'E4');
v_abc_nt := v_abc_nt MULTISET UNION v_abc_nt2;
FOR i IN v_abc_nt.FIRST .. v_abc_nt.LAST
LOOP
DBMS_OUTPUT.put_line (
'p_id is ' || v_abc_nt (i).p_id
);
END LOOP;
END;
If you really want to use an associative array, you would need to write some code because there is no way for Oracle to know automatically how to remap the associations of one array when you combine it with another associative array that has some of the same keys.
You can write it like this
bad example:
declare
type t_numb is record(
numb number);
type t_numb_list is table of t_numb;
v_numb_list t_numb_list;
begin
with q as
(select 1 a from dual union select 2 from dual union select 3 from dual)
select q.a bulk collect into v_numb_list from q;
with w as
(select 4 a from dual union select 5 from dual union select 6 from dual)
select w.a bulk collect into v_numb_list from w;
for r in 1 .. v_numb_list.count loop
dbms_output.put_line(v_numb_list(r).numb);
end loop;
end;
and this works good:
declare
type t_numb is record(
numb number);
type t_numb_list is table of t_numb;
v_numb_list t_numb_list := t_numb_list();
v_numb t_numb;
begin
for q in (select 1 a
from dual
union
select 2
from dual
union
select 3
from dual) loop
v_numb.numb := q.a;
v_numb_list.extend;
v_numb_list(v_numb_list.count) := v_numb;
end loop;
for w in (select 4 a
from dual
union
select 5
from dual
union
select 6
from dual) loop
v_numb.numb := w.a;
v_numb_list.extend;
v_numb_list(v_numb_list.count) := v_numb;
end loop;
for r in 1 .. v_numb_list.count loop
dbms_output.put_line(v_numb_list(r).numb);
end loop;
end;

Resources