oracle call stored procedure inside select - sql

I'm working on a query (a SELECT) and I need to insert the result of this one in a table.
Before doing the insert I have some checking to do, and if all columns are valid, I will do the insert.
The checking is done in a stored procedure. The same procedure is used somewhere else too.
So I'm thinking using the same procedure to do my checks.
The procedure does the checkings and insert the values is all OK.
I tryied to call the procedure inside my SELECT but it does not works.
SELECT field1, field2, myproc(field1, field2)
from MYTABLE.
This kind of code does not works.
I think it can be done using a cursor, but I would like to avoid the cursors.
I'm looking for the easiest solution.
Anybody, any idea ?

use a PL/SQL loop:
BEGIN
FOR c IN (SELECT field1, field2 FROM mytable) LOOP
my_proc(c.field1, c.field2);
END LOOP;
END;

SQL can only use functions in the projection: it needs something which returns a value. So you are going to have to write some functions. That's the bad news. The good news is, you can re-use all the investement in your stored procedures.
Here is a procedure which enforces a completely just business rule: only managers can have a high salary.
SQL> create or replace procedure salary_rule
2 ( p_sal in emp.sal%type
3 , p_job in emp.job%type)
4 is
5 x_sal exception;
6 begin
7 if p_sal > 4999 and p_job != 'MANAGER' then
8 raise x_sal;
9 end if;
10 exception
11 when x_sal then
12 raise_application_error(-20000, 'Only managers can earn that much!');
13 end salary_rule;
14 /
Procedure created.
SQL>
Because it is a procedure we cannot use it in a SELECT statement; we need to wrap it in a function. This function just calls the stored procedure. It returns the input parameter P_SAL. In other words, if the salary is valid (according to the rules) it will be returned. Otherwise the function will re-hurl the stored procedure's exception.
SQL> create or replace function validate_salary
2 ( p_sal in emp.sal%type
3 , p_job in emp.job%type)
4 return emp.sal%type
5 is
6 begin
7 salary_rule(p_sal, p_job);
8 return p_sal;
9 end validate_salary;
10 /
Function created.
SQL>
The function has to return a value which we want to insert into our table. It cannot return some meaningless phrase like "salary okay". Also, if we want to validate two columns we need a separate function for each, even if there is a relationship between them and we use the same stored procedure to validate them both. Good use for the DETERMINISTIC keyword.
Here's the test: plumbers cannot earn 5000 spondulicks ....
SQL> insert into emp
2 (empno
3 , ename
4 , job
5 , deptno
6 , sal )
7 select
8 emp_seq.nextval
9 , 'HALL'
10 , 'PLUMBER'
11 , 60
12 , validate_salary(5000, 'PLUMBER')
13 from dual
14 /
, validate_salary(5000, 'PLUMBER')
*
ERROR at line 12:
ORA-20000: Only managers can earn that much!
ORA-06512: at "APC.SALARY_RULE", line 12
ORA-06512: at "APC.VALIDATE_SALARY", line 7
SQL>
... but managers can (because they deserve it):
SQL> insert into emp
2 (empno
3 , ename
4 , job
5 , deptno
6 , sal )
7 select
8 emp_seq.nextval
9 , 'HALL'
10 , 'MANAGER'
11 , 60
12 , validate_salary(5000, 'MANAGER')
13 from dual
14 /
1 row created.
SQL>
Note that the hurled exception is crucial to this working. We cannot write some bizarre IF SALARY IS VALID THEN INSERT logic in our SQL statement. So, if the stored procedure doesn't raise an exception but instead returns some wimpy error status the wrapping function will have to interpret the output and hurl its own exception.

You can't use stored procedures in SELECT statement.
You can use functions for that.
As I understand you are calling insert in your SP, so take into consideration that you can's use INSERT/UPDATE in function body. But if you need to do some checks you can use function which will do that checks and use that function in your select statement.

Related

How to Capture Run Time in an Oracle Stored Procedure

I'm wanting to log the run time of certain SELECT stored procedures in Oracle. I've broken this down into the following steps.
STEP 1 - Get StartTime
STEP 2 - Insert into the LOG Table that the proc is running
STEP 3 - Get Inserted RowId
STEP 4 - Run the SELECT statement within the Proc
STEP 5 - Get End Time
STEP 5 - Update the row (in LOG Table) that was inserted, with the "Total Run Time".
IMPORTANT NOTE: The SELECT statement takes several minutes to run.
What happens is this:
The procedure runs
A row gets inserted into the LOG table
The LOG table immediately gets updated with the total run time.
The SELECT statement continues to take 5 minutes to run
After the SELECT statement completes, the result data finally returns.
The LOG table should NOT be updated until the entire procedure completes.
Basically what's happening is that the procedure immediately inserts, then updates the LOG table "BEFORE" the SELECT statement finishes.
I've tried wrapping and nesting additional BEGIN and END statements. The stored procedure still runs the "UPDATE" statement at the end of the procedure BEFORE the SELECT statement returns.
CREATE OR REPLACE EDITIONABLE PROCEDURE SP_CUSTOMERDATA_GET (
PARAM_USERID IN VARCHAR2,
PARAM_FIRSTNAME IN VARCHAR2,
PARAM_LASTNAME IN VARCHAR2
OUTPUT OUT types.cursor_type)
AS
BEGIN
DECLARE
l_Id Number;
l_StartTime TIMESTAMP;
l_EndTime TIMESTAMP;
l_TotalTime Number;
BEGIN
l_StartTime:= systimestamp;
INSERT INTO PROC_LOG (SPNAME, PARM1, PARM2, PARM3)
VALUES ('SP_CUSTOMERDATA_GET',I_USERNAME, PARAM_USERID, PARAM_FIRSTNAME, PARAM_LASTNAME)
RETURNING ID INTO l_Id;
COMMIT;
OPEN OUTPUT FOR
SELECT *
FROM CUSTOMER
WHERE USERID=PARAM_USERID
AND FIRSTNAME=PARAM_FIRSTNAME
AND LASTNAME=PARAM_LASTNAME;
l_EndTime:= systimestamp;
l_TotalTime:= extract(second from (l_EndTime-l_StartTime));
--ISSUE: This statement runs before the SELECT statement above completes
UPDATE PROC_LOG
SET RUNTIME_SECONDS=l_TotalTime
WHERE ID=l_Id;
COMMIT;
END;
END SP_CUSTOMERDATA_GET;
Is there a property I can set in the PROC, to force the procedure to not run the next command until the prior command finishes. It doesn't make sense that the procedure doesn't run in order?
I re-read the question and Justin's comments, and based on his proposal came up with a code solution for it.
First a general setup of database structures:
FSITJA#db01>create table customer (userid,
2 firstname,
3 lastname) as
4 select level, 'John', 'Doe'
5 from dual
6 connect by level <= 1000000;
Table created.
FSITJA#db01>create table PROC_LOG (id number generated as identity,
2 SPNAME varchar2(30),
3 PARM1 varchar2(100),
4 PARM2 varchar2(100),
5 PARM3 varchar2(100),
6 RUNTIME_SECONDS number);
Table created.
FSITJA#db01>create or replace type tp_customer_row as object (userid number,
2 firstname varchar2(100),
3 lastname varchar2(100));
4 /
Type created.
FSITJA#db01>create or replace type tp_customer as table of tp_customer_row;
2 /
Type created.
FSITJA#db01>create or replace package types as
2 type cursor_type is ref cursor return customer%rowtype;
3 end;
4 /
Package created.
Then we will need a stored procedure with the autonomous transaction to log the time, and the table function that allows us to query data from a collection. We can pass a cursor into the function in a Select to test that it works:
FSITJA#db01>create or replace procedure sp_log_customerdata_get(proc_log_id in proc_log.id%type, starttime in timestamp) as
2 pragma autonomous_transaction;
3 begin
4 UPDATE PROC_LOG
5 SET RUNTIME_SECONDS=extract(second from (systimestamp-starttime))
6 WHERE ID=proc_log_id;
7 COMMIT;
8 end;
9 /
Procedure created.
FSITJA#db01>create or replace function fn_customerdata_get(cust_cursor types.cursor_type,
2 proc_log_id in proc_log.id%type,
3 starttime in timestamp) return tp_customer
4 pipelined as
5 in_cust_rec customer%rowtype;
6 out_cust_rec tp_customer_row := tp_customer_row(null, null, null);
7 begin
8 loop
9 fetch cust_cursor into in_cust_rec;
10 exit when cust_cursor%notfound;
11 out_cust_rec.userid := in_cust_rec.userid;
12 out_cust_rec.firstname := in_cust_rec.firstname;
13 out_cust_rec.lastname := in_cust_rec.lastname;
14 pipe row(out_cust_rec);
15 end loop;
16 close cust_cursor;
17 sp_log_customerdata_get(proc_log_id, starttime);
18 return;
19 end;
20 /
Function created.
FSITJA#db01>select *
2 from table(fn_customerdata_get(cursor(select userid,
3 firstname,
4 lastname
5 from customer
6 where rownum < 5),
7 null,
8 systimestamp));
USERID FIRSTNAME LASTNAME
---------- --------------- ---------------
1 John Doe
2 John Doe
3 John Doe
4 John Doe
Now the original procedure, which will call the function passing a ref cursor, and then forward this cursor out in its parameter for the client application:
FSITJA#db01>CREATE OR REPLACE PROCEDURE SP_CUSTOMERDATA_GET (
2 PARAM_USERID IN VARCHAR2,
3 PARAM_FIRSTNAME IN VARCHAR2,
4 PARAM_LASTNAME IN VARCHAR2,
5 OUTPUT OUT types.cursor_type) AS
6 l_Id Number;
7 l_StartTime TIMESTAMP;
8 l_EndTime TIMESTAMP;
9 l_TotalTime Number;
10 l_CustResult tp_customer;
11 BEGIN
12 l_StartTime:= systimestamp;
13 INSERT INTO PROC_LOG (SPNAME, PARM1, PARM2, PARM3)
14 VALUES ('SP_CUSTOMERDATA_GET', PARAM_USERID, PARAM_FIRSTNAME, PARAM_LASTNAME)
15 RETURNING ID INTO l_Id;
16 COMMIT;
17 open output for
18 select *
19 from table(fn_customerdata_get(cursor(SELECT userid,
20 firstname,
21 lastname
22 FROM CUSTOMER
23 WHERE USERID=PARAM_USERID
24 AND FIRSTNAME=PARAM_FIRSTNAME
25 AND LASTNAME=PARAM_LASTNAME),
26 l_Id,
27 l_StartTime
28 )
29 );
30 END SP_CUSTOMERDATA_GET;
31 /
Procedure created.
And finally a piece of code to test that only after the client application fetches data from the table function there will be a log entry for time elapsed:
FSITJA#db01>declare
2 v_output types.cursor_type;
3 v_runtime_seconds number;
4 type tp_cust_table is table of customer%rowtype;
5 v_cust_table tp_cust_table;
6 begin
7 SP_CUSTOMERDATA_GET (1, 'John', 'Doe', v_output);
8 select runtime_seconds
9 into v_runtime_seconds
10 from proc_log
11 where id = 1;
12 dbms_output.put_line('Runtime before client fetches: ' || v_runtime_seconds);
13 fetch v_output
14 bulk collect into v_cust_table;
15 select runtime_seconds
16 into v_runtime_seconds
17 from proc_log
18 where id = 1;
19 dbms_output.put_line('Runtime AFTER client fetches: ' || v_runtime_seconds);
20 end;
21 /
Runtime before client fetches:
Runtime AFTER client fetches: .118791
PL/SQL procedure successfully completed.
The problem is that your procedure doesn't actually run the SELECT statement. Your procedure just opens the cursor which parses the statement and gets the statement handle. It doesn't cause the database to actually execute the statement. That happens when the caller goes to fetch data from the cursor that is returned. When the procedure finishes, it has no idea whether the caller is ever going to fetch data from the cursor, whether it is going to just fetch the first 10 rows, or whether it is going to eventually fetch every row. If your goal is to measure how long it takes to fetch the data from the cursor, you'd want to add logging to the caller not to this procedure.
Of course, you could also just run the SELECT statement separately. If the actual query is anything close to what you posted, I'd strongly wager that you're missing an index on customer. I'd guess that userID is unique so if there was an index on userID, that query should run in a few milliseconds.

Stored procedure variable error in PLSQL when declaring variables

Using Oracle 11g when creating the following stored procedure
create or replace PROCEDURE sp_EqualVote(AREA IN NVARCHAR2, DATEOFVOTE IN DATE)
IS
DECLARE test nvarchar(255);
BEGIN
SELECT
AREA,
DATEOFVOTE,
CASE
WHEN (REMAINVOTES = LEAVEVOTES) THEN REMAINVOTES
END AS EqualVote
INTO test
FROM VOTING
WHERE REMAINVOTES = LEAVEVOTES;
END;
END;
I encounter the following error, I'm not quite sure where to go
PLS-00103: Encountered the symbol "DECLARE" when expecting one of the following: begin function pragma procedure subtype type <an identifier> <a double-quoted delimited-identifier> current cursor delete exists prior external language The symbol "begin" was substituted for "DECLARE" to continue.
I'm a university student and not really that familiar with PLSQL. The idea is the stored procedure should display if an an area has equal votes, given the area and date in the procedure then display an equalvotes labeled column with a value of 50
Quite a few mistakes.
you don't need DECLARE within the named PL/SQL procedure
parameters names should differ from column names, so you'd rather use - for example - p_area in nvarchar2, p_dateofvote in date
if you select 3 columns, you have to put them INTO 3 variables - you've declared only one, so either declare two more, or remove AREA and DATEOFOTE from SELECT
what are those parameters used for? Usually, as a part of the WHERE clause - which is not the case in your code
pay attention to number of rows returned by the SELECT statement. If you're selecting into a scalar variable, make sure that it returns only one row
what will you do with TEST variable, once you get its value? Currently, nothing
you've got an END that is a surplus.
Therefore, consider something like this which should at least compile (depending on table description):
SQL> create table voting (area nvarchar2(10),
2 dateofvote date,
3 remainvotes nvarchar2(10),
4 leavevotes nvarchar2(10));
Table created.
SQL> create or replace procedure
2 sp_equalvote(p_area in nvarchar2, p_dateofvote in date)
3 is
4 test nvarchar2(255);
5 begin
6 select
7 case when remainvotes = leavevotes then remainvotes end
8 into test
9 from voting
10 where remainvotes = leavevotes
11 and area = p_area
12 and dateofvote = p_dateofvote;
13 end;
14 /
Procedure created.
SQL>
[EDIT]
After reading the comment, perhaps you'd rather use a function.
Some sample values:
SQL> insert into voting values (1, date '2019-02-20', 100, 15);
1 row created.
SQL> insert into voting values (1, date '2019-03-10', 300, 300);
1 row created.
Function:
SQL> create or replace function
2 sp_equalvote(p_area in nvarchar2, p_dateofvote in date)
3 return nvarchar2
4 is
5 test nvarchar2(255);
6 begin
7 select
8 case when remainvotes = leavevotes then 'draw'
9 else 'not equal'
10 end
11 into test
12 from voting
13 where area = p_area
14 and dateofvote = p_dateofvote;
15
16 return test;
17 end;
18 /
Function created.
SQL>
Testing:
SQL> select * From voting;
AREA DATEOFVOTE REMAINVOTE LEAVEVOTES
---------- ---------- ---------- ----------
1 20.02.2019 100 15
1 10.03.2019 300 300
SQL> select sp_equalvote(1, date '2019-02-20') res from dual;
RES
--------------------
not equal
SQL> select sp_equalvote(1, date '2019-03-10') res from dual;
RES
--------------------
draw
SQL>
DECLARE is not allowed in the body of a PL/SQL procedure. The IS or AS serves the purpose of delimiting where the variable declaration section starts - so your procedure should be
create or replace PROCEDURE sp_EqualVote(AREA IN NVARCHAR2, DATEOFVOTE IN DATE)
IS
test nvarchar(255);
BEGIN
SELECT
AREA,
DATEOFVOTE,
CASE
WHEN (REMAINVOTES = LEAVEVOTES) THEN REMAINVOTES
END AS EqualVote
INTO test
FROM VOTING
WHERE REMAINVOTES = LEAVEVOTES;
END;
You also had an extra END, which I removed.
Best of luck.

Alternate of using NVL with IN clause in Oracle 11g

I have a workplace problem to which I am looking for an easy solution.
I am trying to replicate it in a smaller scenario.
Problem in short
I want to use nvl inside an in clause. Currently I have an input string which consists of a name. It is used in a where clause like below
and column_n = nvl(in_parameter,column_n)
Now I want to pass multiple comma separated values in same input parameter. So if I replace = with in, and transpose the input comma separated string as rows, I cannot use the nvl clause with it.
Problem in Detail
Lets consider an Employee table emp1.
Emp1
+-------+-------+
| empno | ename |
+-------+-------+
| 7839 | KING |
| 7698 | BLAKE |
| 7782 | CLARK |
+-------+-------+
Now this is a simple version of an existing stored procedure
create or replace procedure emp_poc(in_names IN varchar2)
as
cnt integer;
begin
select count(*)
into cnt
from emp1
where
ename = nvl(in_names,ename); --This is one of the condition where we will make the change.
dbms_output.put_line(cnt);
end;
So this will give the count of number of employees passed as Input Parameter. But if we pass null, it will return the whole count of employee becuase of the nvl.
So these procedure calls will render the given outputs.
Procedure Call Output
exec emp_poc('KING') 1
exec emp_poc('JOHN') 0
exec emp_poc(null) 3
Now what I want to achieve is to add another functionality. So exec emp_poc('KING,BLAKE') should give me 2. So I figured a way to split the comma separated string to rows and used that in the procedure.
So if I change the where clause as below to in
create or replace procedure emp_poc2(in_names IN varchar2)
as
cnt integer;
begin
select count(*)
into cnt
from emp1
where
ename in (select trim(regexp_substr(in_names, '[^,]+', 1, level))
from dual
connect by instr(in_names, ',', 1, level - 1) > 0
);
dbms_output.put_line(cnt);
end;
So exec emp_poc2('KING','BLAKE') gives me 2. But passing null will give result as 0. However I want to get 3 like the case with emp_proc
And I cannot use nvl with in as it expect the subquery to return a single value.
1 way I can think of is rebuilding the whole query in a variable, based in input paramteter, and then use execute immediate. But I am using some variables to collect the value and it would be difficult to achieve it with execute immediate.
I am again emphasizing that this is a simple version of a complex procedure where we are capturing many variables and it joins many tables and has multiple AND conditions in where clause.
Any ideas on how to make this work.
This may help you
CREATE OR REPLACE PROCEDURE emp_poc2(in_names IN varchar2)
AS
cnt integer;
BEGIN
SELECT COUNT(*) INTO cnt
FROM emp1
WHERE
in_names IS NULL
OR ename IN (
SELECT TRIM(REGEXP_SUBSTR(in_names, '[^,]+', 1, level))
FROM dual
CONNECT BY INSTR(in_names, ',', 1, level - 1) > 0
);
dbms_output.put_line(cnt);
END;
Other way could be use IF ELSE or UNION ALL
If your real code is much more complex then your code readability might be greatly enhanced by using a proper collection type instead.
In the example below I have created an user defined type str_list_t that is a real collection of strings.
I also use common table expression (CTE) in the sql query to enhance the readability. In this simple example the CTE benefits for readability are not obvious but for all non-trivial queries it's a valuable tool.
Test data
create table emp1(empno number, empname varchar2(10));
insert into emp1 values(5437, 'GATES');
insert into emp1 values(5438, 'JOBS');
insert into emp1 values(5439, 'BEZOS');
insert into emp1 values(5440, 'MUSK');
insert into emp1 values(5441, 'CUBAN');
insert into emp1 values(5442, 'HERJAVEC');
commit;
Supporting data type
create or replace type str_list_t is table of varchar2(4000 byte);
/
Subprogram
create or replace function emp_count(p_emps in str_list_t) return number is
v_count number;
v_is_null_container constant number :=
case
when p_emps is null then 1
else 0
end;
begin
-- you can also test for empty collection (that's different thing than a null collection)
with
-- common table expression (CTE) gives you no benefit in this simple example
emps(empname) as (
select * from table(p_emps)
)
select count(*)
into v_count
from emp1
where v_is_null_container = 1
or empname in (select empname from emps)
;
return v_count;
end;
/
show errors
Example run
SQL> select 2 as expected, emp_count(str_list_t('BALLMER', 'CUBAN', 'JOBS')) as emp_count from dual
union all
select 0, emp_count(str_list_t()) from dual
union all
select 6, emp_count(null) from dual
;
EXPECTED EMP_COUNT
---------- ----------
2 2
0 0
6 6

force subquery resolution first

I'm creating a query which uses 2 embedded server functions multiple times.
Problem: the functions search through a decently large table, and they take a long time to execute.
Goal: Use a subquery as if it were a table so that I can reference columns without running the function to generate the column more than once.
Example Pseudocode:
Select general.column1, general.column2, general.column1-general.column2
from (select package.function1('I take a long time') column1,
package.function2('I take even longer') column2,
normal_column
from bigtable) general;
When I run my code general.column1 will reference the function in the statement of column1, not the data returned by it (which is ultimately what I'm after).
I'm fairly new to SQL, so any help is appreciated and if you need more info, I'll do my best to provide it.
Thanks!
I suggest you tu use the subquery factoring. The first subquery will be executed only once and then used through the rest of he query.
WITH function_result AS
(SELECT package.function1('I take a long time') column1
, package.function2('I take even longer') column2
FROM dual)
SELECT function_result.column1
, function_result.column2
, function_result.column1 - function_result.column2
, bigtable.normal_column
FROM bigtable
In general what you want to do is in this case is take advatage of scalar subquery caching.
i.e. put:
Select general.column1, general.column2, general.column1-general.column2
from (select (select package.function1('I take a long time') from dual) column1,
(select package.function2('I take even longer') from dual) column2,
normal_column
from bigtable) general;
delcaring the function as deterministic too helps if it is deterministic.
a small example:
SQL> create or replace function testfunc(i varchar2)
2 return varchar2
3 is
4 begin
5 dbms_application_info.set_client_info(userenv('client_info')+1 );
6 return 'hi';
7 end;
8 /
Function created.
now lets test a call to the function like you have:
SQL> exec dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.
SQL> set autotrace traceonly
SQL> select *
2 from (select testfunc(owner) a
3 from all_objects);
57954 rows selected.
SQL> select userenv('client_info') from dual;
USERENV('CLIENT_INFO')
----------------------------------------------------------------
57954
the function was called 57954 times (once per row). now lets use scalar caching:
SQL> exec dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.
SQL> select *
2 from (select (select testfunc(owner) from dual) a
3 from all_objects);
57954 rows selected.
SQL> select userenv('client_info') from dual;
USERENV('CLIENT_INFO')
----------------------------------------------------------------
178
178 calls instead of 57k!
in your case you've only shown that you have a literal and no input that is varying per row (if this is the case, the number of calls after using scalar caching should be 1).
if we add deterministic:
SQL> create or replace function testfunc(i varchar2)
2 return varchar2 deterministic
3 is
4 begin
5 dbms_application_info.set_client_info(userenv('client_info')+1 );
6 return 'hi';
7 end;
8 /
Function created.
SQL> exec dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.
SQL> select *
2 from (select (select testfunc(owner) from dual) a
3 from all_objects);
57954 rows selected.
SQL> select userenv('client_info') from dual;
USERENV('CLIENT_INFO')
----------------------------------------------------------------
55
now down to 55. in 11g we have result_cache which we can put in place of deterministic, which would reduce the calls on subsequant runs to 0 calls.

Using Table type in IN-clause in PLSQL procedure

I have a procedure which takes table type input parameter. Now I have to use this parameter in IN-clause of SELECT query.
CREATE TYPE ids IS TABLE OF NUMBER;
CREATE PROCEDURE (emp_ids IN ids) IS
CURSOR IS (SELECT * FROM EMPLOYEES WHERE EMP_ID IN (SELECT * FROM TABLE(emp_ids)); .....
But I found that this code is not going to work because local collection types cannot be used in an SQL statement.
Is there any alternate way to achieve using table type parameter in a SELECT statement?
According to what you have posted, you are declaring collection as schema object, not the local type. This means that you shouldn't have any problems of using it. Here is an example:
-- collection type as schema object
SQL> create or replace type ids is table of number;
2 /
Type created
SQL> create or replace procedure proc1 (emp_ids IN ids)
2 IS
3 cursor c is (
4 select first_name
5 from employees
6 where employee_id in (select column_value
7 from table(emp_ids)
8 )
9 );
10 begin
11 for i in c
12 loop
13 dbms_output.put_line(i.first_name);
14 end loop;
15 end;
16 /
Procedure created
SQL> exec proc1(ids(101, 103, 200));
Neena
Alexander
Jennifer
PL/SQL procedure successfully completed