Array Input for Stored Procedure - sql

I am a newbie to Oracle and this is my first post for Oracle queries.
Below is the existing query which inserts 1 row for each SP call.
I want to make change in the SP which would accept input as array where SAP system would would send the Array to Stored Procedure.
As you observe in SP, the value of ID is incremented each time with the each update. The SP will take this input of Phone and Text and insert the value of ID in sequence wise.The ID is not passed in the input.
CREATE OR REPLACE PROCEDURE DetailsTable
(
Phoneno IN NUMBER,
Text IN VARCHAR2
)
aS
BEGIN
INSERT INTO PERSON.DETAILS(
ID,
PHONENO,
TEXT,
COUNTRY,
LANG,
--PRIORITY,
SENDER)
VALUES (
DETAILS_seq.nextval ,
p_phoneno,
p_text ,
'RSA',
'EN',
'Nest-Payroll');
commit;
END DetailsTable;
Please guide.

SQL> CREATE OR REPLACE TYPE arraytype AS VARRAY(1000) OF VARCHAR2(100);
2 /
Type created
SQL> CREATE OR REPLACE PROCEDURE test_array (in_array arraytype) IS
2 BEGIN
3 FOR i IN 1..in_array.count LOOP
4 DBMS_OUTPUT.PUT_LINE(in_array(i));
5 END LOOP;
6 END;
7 /
Procedure created
SQL> DECLARE
2 var_array arraytype;
3 BEGIN
4 var_array := arraytype();
5 var_array.EXTEND(10);
6 var_array(1) := '1st sentence in the array';
7 var_array(2) := '2nd sentence in the array';
8 test_array(var_array);
9 END;
10 /
1st sentence in the array
2nd sentence in the array

We can use a Type in SQL but it needs to be declared as a SQL Type:
create or replace type person_t as object
(phoneno number
, text varchar2(100)
);
/
create or replace type person_nt as table of person_t
/
Use it like this:
CREATE OR REPLACE PROCEDURE DetailsTable
(
p_array in person_nt
)
aS
BEGIN
INSERT INTO PERSON.DETAILS(
ID,
PHONENO,
TEXT,
COUNTRY,
LANG,
--PRIORITY,
SENDER)
select DETAILS_seq.nextval ,
t.phoneno,
t.text ,
'RSA',
'EN',
'Nest-Payroll'
from table (p_array)t;
commit;
END DetailsTable;
/

Related

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.

Use column name as key for PL/SQL associative array when updating another column

I have an Excel table with two columns of data. Column A are codes, column B are the corresponding country names. I turned it into an associative array, ueln_country.
Now the task is to update HORSE table's column COUNTRY_OF_RESIDENCE. Horses have a column UELN where first three letters correspond to the codes in the Excel table.
I have to check if the code exists in the Excel table. If it does, I have to update HORSE.country_of_residence with a CLASSIFICATOR.code where CLASSIFICATOR.name = **the corresponding country in column B** andCLASSIFICATOR.dom_code = 'ISOCODE'`.
First try gets the error
PLS-00201: identifier 'UELN' must be declared
As I understood, it's because I can only use declared variables in PL/SQL statement.
declare
type TBL_UELN_COUNTRY is table of varchar2(50) index by varchar2 (3);
test TBL_UELN_COUNTRY;
ueln_country TBL_UELN_COUNTRY;
begin
ueln_country('008') := 'ALBAANIA';
ueln_country('010') := 'ANTARKTIS';
ueln_country('011') := 'ANTARKTIS';
....
update HORSE
set COUNTRY_OF_RESIDENCE=
when (...dummy_case...) then
(select code from meta.classifcator
where dom_code = 'ISOCODE'
and name = ueln_country(substr(UELN, 1, 3)))
where UELN is not null;
end;
/
Second try.
So because of the first error I tried to somehow declare the variables.
I knew it wouldn't work (ORA-01422: exact fetch returns more than requested number of rows) but made it to show where my idea is going:
declare
type TBL_UELN_COUNTRY is table of varchar2(50) index by varchar2 (3);
test TBL_UELN_COUNTRY;
ueln_country TBL_UELN_COUNTRY;
v_ueln horse.UELN%TYPE;
begin
select UELN into v_ueln from HORSE;
ueln_country('008') := 'ALBAANIA';
ueln_country('010') := 'ANTARKTIS';
ueln_country('011') := 'ANTARKTIS';
....
update HORSE
set COUNTRY_OF_RESIDENCE=
when (...dummy_case...) then
(select code from meta.classifcator
where dom_code = 'ISOCODE'
and name = ueln_country(substr(v_ueln, 1, 3)))
where UELN is not null;
end;
/
So I want pick a value from associative array where the key = substr(specific_horse.UELN, 1, 3).
Searched through Google and Stack for hours and didn't find an answer.
The ugly and very slow working solution was just where I didn't make the associate array and made 400+ cases for every Excel table row in the form like when -key- then select code from meta.classificator where dom_code = 'ISOKOOD' and name = -value-
associative array can not be used in SQL.
If you use expression like array(index) in SQL then, in fact, PL/SQL engine gets value by index and then result is bound into SQL engine before execution of the SQL statement.
More specifically
declare
type TBL_UELN_COUNTRY is table of varchar2(50) index by varchar2 (3);
test TBL_UELN_COUNTRY;
dummy varchar2(30);
begin
test('GBP') := 'UK';
test('USD') := 'USA';
select /*+ qwerty */ test('GBP')
into dummy
from dual;
end;
/
If we check binds for a cursor we see that actual bind value has a type VARCHAR(128) - :B1. test('GBP') in PL/SQL code is passed as bind variable B1.
SQL> column sql_text format a50
SQL> select sbc.datatype_string, sql_text
2 from v$sql s join v$sql_bind_capture sbc
3 on s.sql_id = sbc.sql_id
4 where lower(sql_text) not like '%v$sql%'
5 and lower(sql_fulltext) like 'select %qwerty%';
DATATYPE_STRING SQL_TEXT
--------------- --------------------------------------------------
VARCHAR2(128) SELECT /*+ qwerty */ :B1 FROM DUAL
SQL engine knows nothing about associative array and apparently it cannot pass and index value to array and get an element of the array back.
If you still want to use associative array to look-up some values you can declare package variable and a getter function (you may also want to implement the logic to handle a case when there is no element in array for a given index - otherwise you'll get run-time exception in such case).
create or replace package pkg as
function GetCountry(idx in varchar2) return varchar2;
end pkg;
/
sho err
create or replace package body pkg as
type TBL_UELN_COUNTRY is table of varchar2(50) index by varchar2 (3);
test pkg.TBL_UELN_COUNTRY;
function GetCountry(idx in varchar2) return varchar2 as
begin return test(idx); end;
-- initializing
begin
test('GBP') := 'UK';
test('USD') := 'USA';
end pkg;
/
sho err
And finally
SQL> set serveroutput on
SQL> declare
2 dummy varchar2(30);
3 begin
4 with t(idx) as (select 'GBP' from dual)
5 select pkg.GetCountry(t.idx)
6 into dummy
7 from t;
8 dbms_output.put_line(dummy);
9 end;
10 /
UK
PL/SQL procedure successfully completed.

SQL Oracle, returning a table from a Function

I was trying create a function (maybe procedure will be better?) which return a table. Presently I have this:
CREATE OR REPLACE TYPE rowx AS OBJECT
(
nam1 VARCHAR2 (100),
nam2 VARCHAR2 (100)
);
/
CREATE OR REPLACE TYPE tablex
IS TABLE OF rowx;
/
CREATE OR REPLACE FUNCTION example(FS varchar2)
RETURN tablex
IS
tab tablex;
BEGIN
select y.ident as PARENT, x.ident as CHILD into tab
from relation2 rt
inner join plate x on rt.child = x.id
inner join plate y on rt.parent =y.id
where x.ident like 'string1' or y.ident like 'string2';
RETURN tab;
END;
After compilation above function I recive ORA-00947. Any tips?
Your query is selecting two scalar values, and trying to put them into a table of an object type. That type has two fields, but there is no automatic comversion. So you need to build the object explicitly, which you can do as part of the query.
You should also use a bulk query to populate your collection:
select rowx(y.ident, x.ident)
bulk collect into tab
from relation2 rt
...
Have a look at this example; does it help?
My TEST table represents your tables. This function returns a collection, which is then used in SELECT statement along with the TABLE operator.
SQL> create table test (nam1 varchar2(10), nam2 varchar2(10));
Table created.
SQL> insert into test values ('Little', 'Foot');
1 row created.
SQL> insert into test values ('Stack', 'Overflow');
1 row created.
SQL> create or replace type t_tf_row as object (nam1 varchar2(10), nam2 varchar2(10));
2 /
Type created.
SQL> create or replace type t_tf_tab is table of t_tf_row;
2 /
Type created.
SQL>
SQL> create or replace function get_tab_tf return t_tf_tab as
2 l_tab t_tf_tab := t_tf_tab();
3 begin
4 for cur_r in (select nam1, nam2 from test) loop
5 l_tab.extend;
6 l_tab(l_tab.last) := t_tf_row(cur_r.nam1, cur_r.nam2);
7 end loop;
8 return l_tab;
9 end;
10 /
Function created.
SQL>
SQL> select * From table(get_Tab_tf);
NAM1 NAM2
--------------------
Little Foot
Stack Overflow
SQL>

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

Executing an sql statement stored in a procedure parameter

I have a question about stored procedures in Oracle.
Below is the stored procedure and tables as it stands:
create table STORES
(
ID number,
NAME varchar2(100),
CITY varchar2(100),
EXPIRES DATE
)
insert into stores values(1, 'Store 1', 'City 1', sysdate);
insert into stores values(2, 'Store 2', 'City 1', sysdate);
insert into stores values(3, 'Store 3', 'City 2', sysdate);
create table CLOSED
(
ID number,
NAME varchar2(100),
CITY varchar2(100)
)
create or replace PROCEDURE
pr_TestProc(subQuery IN VARCHAR2)
IS
begin
insert into CLOSED (ID, NAME, CITY)
select ID, NAME, CITY
from STORES
where ID in (1, 2, 3);
end;
What I'd like to do is replace the "in" values with the subQuery passed in as a parameter.
So if I run the procedure like:
execute pr_TestProc('select ID from STORES where EXPIRES <= sysdate');
The query passed in should be executed as a subquery being run inside the procedure.
Something like:
insert into CLOSED (ID, NAME, CITY) select ID, NAME, CITY
from STORES
where ID in (execute(subQuery));
Obviously this doesn't work, but what would be the best way to achieve this, or is it even possible?
Thanks,
Brian
You can use dynamic SQL
create or replace PROCEDURE pr_TestProc(subQuery IN VARCHAR2)
IS
l_sql_stmt varchar2(1000);
begin
l_sql_stmt := 'insert into CLOSED (ID, NAME, CITY) ' ||
' select ID, NAME, CITY ' ||
' from STORES ' ||
' where id in (' || subquery || ')';
dbms_output.put_line( l_sql_stmt );
EXECUTE IMMEDIATE l_sql_stmt;
end;
SQL> execute pr_TestProc('select ID from STORES where EXPIRES <= sysdate');
PL/SQL procedure successfully completed.
SQL> column name format a20
SQL> column city format a20
SQL> select * from closed;
ID NAME CITY
---------- -------------------- --------------------
1 Store 1 City 1
2 Store 2 City 1
3 Store 3 City 2
If you are calling this procedure rarely in a system that is relatively idle, that will probably work acceptably well. If you are calling it frequently with different subqueries, however, you are going to generate a ton of non-sharable SQL statements. That will force Oracle to do a lot of hard parsing. It will also flood your shared pool with non-sharable SQL statements, likely forcing out plans that you want to be cached, forcing more hard parsing when those SQL statements are then executed again. And if you do it fast enough, you're likely to end up getting errors (or causing other processes to get errors) that Oracle couldn't allocate enough memory in the shared pool for a particular query. Plus, dynamic SQL is harder to write, harder to debug, vulnerable to SQL injection attacks, etc. so it generally makes the system harder to deal with.
A more elegant solution would be to pass in a collection rather than a subquery would be to pass in a collection
SQL> create type id_coll
2 as table of number;
3 /
Type created.
SQL> ed
Wrote file afiedt.buf
1 create or replace PROCEDURE pr_TestProc( p_ids IN id_coll)
2 is
3 begin
4 insert into CLOSED (ID, NAME, CITY)
5 select ID, NAME, CITY
6 from STORES
7 where ID in (select column_value
8 from table( p_ids ) );
9* end;
SQL> /
Procedure created.
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_ids id_coll;
3 begin
4 select id
5 bulk collect into l_ids
6 from stores
7 where expires <= sysdate;
8 pr_TestProc( l_ids );
9* end;
SQL> /
PL/SQL procedure successfully completed.
SQL> select * from closed;
ID NAME CITY
---------- -------------------- --------------------
1 Store 1 City 1
2 Store 2 City 1
3 Store 3 City 2
Maybe you dont need to pass the query into the stored procedure. Just call the stored procedure with the query as a parameter.
create or replace PROCEDURE
pr_TestProc(listOfIds_IN IN integer)
IS
begin
ids integer := listOfIds_IN;
insert into CLOSED (ID, NAME, CITY)
select ID, NAME, CITY from STORES where ID in (ids );
end;
Call the stored procedure like this:
pr_TestProc(SELECT id FROM Table WHERE condition)