How can fill a variable of my own created data type within Oracle PL/SQL? - oracle

In Oracle I've created a data type:
TABLE of VARCHAR2(200)
I want to have a variable of this type within a Stored Procedure (defined locally, not as an actual table in the DB) and fill it with data.
Some online samples show how I'd use my type if it was filled and passed as a parameter to the stored procedure:
SELECT column_value currVal FROM table(pMyPassedParameter)
However what I want is to fill it during the PL/SQL code itself, with INSERT statements.
Anyone knows the syntax of this?
EDIT: I should have clarified: my source data is entered as a VARCHAR2 parameter passed to the stored procedure: a separator (like comma) delimited string. I'm already iterating through the delimited string to get every separate value - I would like to INSERT each one into my type so I can treat it as a TABLE for the rest of the logic.

"I want is to fill it during the PL/SQL
code itself, with INSERT statements"
It's like populating any other PL/SQL variable: we have to use INTO. Only because we're populating multiple rows we need to use the BULK COLLECT syntax.
declare
l_array your_nested_table_type;
begin
select col1
bulk collect into l_array
from t72;
end;
/
If the result set returns a lot of records it is a good idea to use the LIMIT clause inside a loop. This is because PL/SQL collections - just like every other PL/SQL variable - are held in session memory. So we don't want the array to get too big, otherwise it might blow the PGA. Find out more.
edit
"I edited the question to clarify
specifically what I want"
sigh Tokenizing a string is an altogether different issue. I have previously posted solutions in two SO threads. If you're using 9i or earlier use this approach. Otherwise use this regex solution (actually this splits the string into numeric tokens, but it is easy enough to convert to characters).
edit 2
"I only want to be able to use the
type "internally" (within the Stored
Procedure) by adding values to it. Is
this something I can do with the first
link?"
Sure. Why not?
SQL> declare
2 local_array tok_tbl;
3 begin
4 local_array := parser.my_parse('Keith Pellig,Peter Wakeman,Ted Bentley,Eleanor Stevens');
5 local_array.extend();
6 local_array(5) := 'Reese Verrick';
7 for i in local_array.first()..local_array.last()
8 loop
9 dbms_output.put_line(local_array(i));
10 end loop;
11 end;
12 /
Keith Pellig
Peter Wakeman
Ted Bentley
Eleanor Stevens
Reese Verrick
PL/SQL procedure successfully completed.
SQL>
Here I have re-used my SQL type, but it would work just as well if TOK_TBL were declared in the PL/SQL package instead.

you don't mention if the type you created is a SQL type or a PL/SQL type. They are used similarly in Pl/SQL so I will assume you created a SQL type with a command like this:
SQL> CREATE TYPE tab_varchar IS TABLE of VARCHAR2(200);
2 /
Type created
This is a nested-table. Find out how to manipulate collections in PL/SQL it in the documentation, for example:
SQL> DECLARE
2 lt tab_varchar;
3 BEGIN
4 /* initialization */
5 lt := tab_varchar('a', 'b', 'c');
6 /* adding elements */
7 lt.extend(1);
8 lt(4) := 'd';
9 FOR i IN lt.FIRST .. lt.LAST LOOP
10 dbms_output.put_line('lt(' || i || ')=' || lt(i));
11 END LOOP;
12 END;
13 /
lt(1)=a
lt(2)=b
lt(3)=c
lt(4)=d

Related

variable defined as column type of reference table but not calculated or initialized in oracle pl sql

I have a variable 'v_bas_unused_commt' in old code, which I am trying to re-write, as following before BEGIN statement of old procedure.
CREATE OR REPLACE PROCEDURE USB."BAS2_WIRES_SEG" (v_run_date number default 0)
IS
v_bas_unused_commt WIRES_DATA.bas_unused_commt%TYPE;
--WIRES_DATA is table getting updated by this proc
Issue is: It is not doing anything inside proc with variable: 'v_bas_unused_commt' (i.e. this variable is not being used at all except being passed as an input.) and then it is being passed to a function as an parameter for calculating following field:
v_bas_eb_expected_loss := bas2_el_calc (rec.CUR_BOOK_BAL,v_BAS_UNUSED_COMMT,v_BAS_PD,v_BAS_LGD,V_BAS_EAD);
--look at input 2.
I wanted to confirm when I re-write this code, Should I use 0 in place of v_BAS_UNUSED_COMMT or sth else? I tried using original column name BAS_UNUSED_COMMT from Table WIRES_DATA, in place of v_BAS_UNUSED_COMMT , but that column has values and it is resulting in different values for v_bas_eb_expected_loss after calculation. However, when I wrote
v_bas_unused_commt WIRES_DATA.bas_unused_commt%TYPE;
and passed v_bas_unused_commt as input, same as old code, differences disappeared.
I thought it may be because v_bas_unused_commt is always 0 but wanted to confirm before I replace it in final procedure
Thanks in advance for help!
A variable declared but not initialized will have NULL value (different from zero), unless somewhere in the procedure a different value has been attributed to it. With a simple test like this you can verify it right before making your function call:
FSITJA#db01 2019-06-25 12:01:23> create table t1(col1 varchar2(8));
Table created.
FSITJA#db01 2019-06-25 12:01:23> create or replace procedure fsitja.pr_test is
2 v_empty t1.col1%type;
3 begin
4 if v_empty is null then
5 dbms_output.put_line('I AM NULL');
6 end if;
7 end;
8 /
Procedure created.
FSITJA#db01 2019-06-25 12:01:23> begin fsitja.pr_test; end;
2 /
I AM NULL
PL/SQL procedure successfully completed.
You should probably use NULL. But please do check that it is indeed passing a NULL before you make the change.

Pass Multivalue in WHERE IN ORACLE [duplicate]

This question already has answers here:
PL/SQL - comma separated list within IN CLAUSE
(4 answers)
Closed 4 years ago.
I need you help to pass a multivalue to a stored procedure in Oracle,
The values are created dynamically.
Basically I'm sending the values from .NET in a simple "string" and Oracle receives it as varchar2:
Stored Procedure
create or replace procedure sp_text_in
(text varchar2)
AS
CURSOR texts
IS
SELECT text FROM t_text where key_text in (text)
;
BEGIN
FOR reg IN texts LOOP
dbms_output.put_line(reg.text);
END LOOP;
end sp_text_in;
Example: the values can be:
select * from t_text where key_text in (197,198,196);
OR simply one value
select * from t_text where key_text in (197);
Ideal option is as per the link shared by #anonyXmous. Another sub optimal option is using dynamic SQL. But this could allow SQL injection. Dynamic SQL solution below.
create or replace procedure SP_TEXT_IN (TEXT varchar2)
as
type TEXTSTABTYP is table of varchar2(1000);
TEXTSTAB TEXTSTABTYP := TEXTSTABTYP();
begin
execute immediate 'select TEXT from T_TEXT where KEY_TEXT in ('||TEXT||')'
bulk collect into TEXTSTAB;
for IDX in TEXTSTAB.first..TEXTSTAB.last
loop
dbms_output.put_line(TEXTSTAB(IDX));
end loop;
end sp_text_in;

Using array of Records in 'IN' operator in Oracle

I am having a table Course which have column DepId and Course and some other value. I need to search this table for a some set of (DepId, Course). This set will be decided at runtime.
I want to write a single query in a procedure to fetch all the record pertaining to above set.
For example consider the table has data like
DepId Course ...
------------------
1 A
2 B
3 C
4 D
5 E
6 F
Now only I want to search for below records:
DepId Course ...
------------------
1 A
4 D
What would be most efficient way to write above query?
I was thinking of creating an Array of record and passing it in the 'IN' operator. But I was not able to get any example for this. Can someone guide me on this?
Thanks
Leveraging Oracle Collections to Build Array-typed Solutions
The answer to your question is YES, dimensioned variables such as ARRAYS and COLLECTIONS are viable data types in solving problems where there are multiple values in either or both the input and output values.
Additional good news is that the discussion for a simple example (such as the one in the OP) is pretty much the same as for a complex one. Solutions built with arrays are nicely scalable and dynamic if designed with a little advanced planning.
Some Up Front Design Decisions
There are actual collection types called ARRAYS and ASSOCIATIVE ARRAYS. I chose to use NESTED TABLE TYPES because of their accessibility to direct SQL queries. In some ways, they exhibit "array-like" behavior. There are other trade-offs which can be researched through Oracle references.
The query applied to search the COURSE TABLE would apply a JOIN condition instead of an IN-LIST approach.
The use of a STORED PROCEDURE typed object improves database response. Queries within the procedure call can leverage and reuse already compiled code plus their cached execution plans.
Choosing the Right Collection or Array Type
There are a lot of choices of collection types in Oracle for storing variables into memory. Each has an advantage and some sort of limitation. AskTom from Oracle has a good example and break-down of what a developer can expect by choosing one variable collection type over another.
Using NESTED TABLE Types for Managing Multiple Valued Variables
For this solution, I chose to work with NESTED TABLES because of their ability to be accessed directly through SQL commands. After trying several different approaches, I noticed that the plain-SQL accessibility leads to more clarity in the resulting code.
The down-side is that you will notice that there is a little overhead here and there with respect to declaring an instance of a nested table type, initializing each instance, and managing its size with the addition of new values.
In any case, if you anticipate a unknown number of input variables or values (our output), an array-typed data type (collection) of any sort is a more flexible structure for your code. It is likely to require less maintenance in the end.
The Example: A Stored Procedure Search Query
Custom TYPE Definitions
CREATE OR REPLACE TYPE "COURSE_REC_TYPE" IS OBJECT (DEPID NUMBER(10,0), COURSE VARCHAR2(10));
CREATE OR REPLACE TYPE "COURSE_TBL_TYPE" IS TABLE of course_rec_type;
PROCEDURE Source Code
create or replace PROCEDURE ZZ_PROC_COURSE_SEARCH IS
my_input course_tbl_type:= course_tbl_type();
my_output course_tbl_type:= course_tbl_type();
cur_loop_counter pls_integer;
c_output_template constant varchar2(100):=
'DEPID: <<DEPID>>, COURSE: <<COURSE>>';
v_output VARCHAR2(200);
CURSOR find_course_cur IS
SELECT crs.depid, crs.course
FROM zz_course crs,
(SELECT depid, course
FROM TABLE (CAST (my_input AS course_tbl_type))
) search_values
WHERE crs.depid = search_values.depid
AND crs.course = search_values.course;
BEGIN
my_input.extend(2);
my_input(1):= course_rec_type(1, 'A');
my_input(2):= course_rec_type(4, 'D');
cur_loop_counter:= 0;
for i in find_course_cur
loop
cur_loop_counter:= cur_loop_counter + 1;
my_output.extend;
my_output(cur_loop_counter):= course_rec_type(i.depid, i.course);
end loop;
for j in my_output.first .. my_output.last
loop
v_output:= replace(c_output_template, '<<DEPID>>', to_char(my_output(j).depid));
v_output:= replace(v_output, '<<COURSE>>', my_output(j).course);
dbms_output.put_line(v_output);
end loop;
end ZZ_PROC_COURSE_SEARCH;
Procedure OUTPUT:
DEPID: 1, COURSE: A
DEPID: 4, COURSE: D
Statement processed.
0.03 seconds
MY COMMENTS: I wasn't particularly satisfied with the way the input variables were stored. There was a clumsy kind of problem with "loading" values into the nested table structure... If you can consider using a single search key instead of a composite pair (i.e., depid and course), the problem condenses to a simpler form.
Revised Cursor Using a Single Search Value
This is the proposed modification to the table design of the OP. Add a single unique key id column (RecId) to represent each unique combination of DepId and Course.
Note that the RecId column represents a SURROGATE KEY which should have no internal meaning aside from its property as a uniquely assigned value.
Custom TYPE Definitions
CREATE OR REPLACE TYPE "NUM_TBL_TYPE" IS TABLE of INTEGER;
Remove Array Variable
This will be passed directly through an input parameter from the procedure call.
-- REMOVE
my_input course_tbl_type:= course_tbl_type();
Loading and Presenting INPUT Parameter Array (Nested Table)
The following can be removed from the main procedure and presented as part of the call to the procedure.
BEGIN
my_input.extend(2);
my_input(1):= course_rec_type(1, 'A');
my_input(2):= course_rec_type(4, 'D');
Becomes:
create or replace PROCEDURE ZZ_PROC_COURSE_SEARCH (p_search_ids IN num_tbl_type) IS...
and
my_external_input.extend(2);
my_external_input:= num_tbl_type(1, 4);
Changing the Internal Cursor Definition
The cursor looks about the same. You can just as easily use an IN-LIST now that there is only one search parameter.
CURSOR find_course_cur IS
SELECT crs.depid, crs.course
FROM zz_course_new crs,
(SELECT column_value as recid
FROM TABLE (CAST (p_search_ids AS num_tbl_type))
) search_values
WHERE crs.recid = search_values.recid;
The Actual SEARCH Call and Output
The searching portion of this operation is now isolated and dynamic. It does not need to be changed. All the Changes happen in the calling PL/SQL block where the search ID values are a lot easier to read and change.
DECLARE
my_input_external num_tbl_type:= num_tbl_type();
BEGIN
my_input_external.extend(3);
my_input_external:= num_tbl_type(1,3,22);
ZZ_PROC_COURSE_SEARCH (p_search_ids => my_input_external);
END;
-- The OUTPUT (Currently set to DBMS_OUT)
DEPID: 1, COURSE: A
DEPID: 4, COURSE: D
DEPID: 7, COURSE: G
Statement processed.
0.01 seconds
This is something I havee used in the past in a situation similar to yours. Hopefully it helps.
The main benefit of this method would be that if you only passed it a single paramter it would still return all records for that single parameter. This way a single stored procedure with 5 input parameters could be used to search for all combinations of inputs.
Just call the stored procedure passing in the set and should return all values mathcing the criteria
usp_custom_search '1','A'
usp_custom_search '4','D'
usp_custom_search '4',NULL
usp_custom_search NULL,'A'
etc
Stored Procedure:
CREATE OR REPLACE PROCEDURE custom_search (
dep_id IN VARCHAR2,
course_id IN VARCHAR2,
result_set OUT SYS_REFCURSOR)
BEGIN
query_str VARCHAR2(1000);
query_str := 'SELECT';
query_str := query_str || ' DepId, Course';
query_str := query_str || ' FROM Course';
query_str := query_str || ' WHERE 1=1';
IF (dep_id is not null) then query_str := query_str || ' AND DepId = ''' || dep_id || ''''; END IF;
IF (course_id is not null) then query_str := query_str || ' AND Course = ''' || course_id || ''''; END IF;
open result_set for query_str;
END custom_search;
/

PL/SQL: Selecting from a table into an assoc array

I am trying to select data into a pl/sql associative array in one query. I know I can do this with a hardcoded key, but I wanted to see if there was some way I could reference another column (the key column) instead.
DECLARE
TYPE VarAssoc IS TABLE OF varchar2(2) INDEX BY varchar2(3);
vars VarAssoc;
BEGIN
SELECT foo, bar INTO vars(foo) FROM schema.table;
END;
I get an error saying foo must be declared when I do this. Is there some way to create my associate array in a single query or do I need to fall back on a FOR loop?
Just read your comment on APC's answer, it sounds like you figured this out on your own. But I figured I'd put the answer in anyway for future searchers.
This is simpler code, but does not have the speed advantage of using BULK COLLECT. Just loop through the rows returned by the query and set the elements in the associative array individually.
DECLARE
TYPE VarAssoc IS TABLE OF varchar2(200) INDEX BY varchar2(30);
vars VarAssoc;
BEGIN
FOR r IN (SELECT table_name,tablespace_name FROM user_tables) LOOP
vars(r.table_name) := r.tablespace_name;
END LOOP;
dbms_output.put_line( vars('JAVA$OPTIONS') );
END;
It would be neat if it were possible but that isn't a straightforward way of acheiving this.
What we can do is load the data into a regular PL/SQL collection and then load that into an associative array. Whethter this is faster than just looping round the table is a matter of tatse: it probably doesn't matter unless we're dealing with loads of data.
Given this test data ...
SQL> select * from t23
2 order by c1
3 /
C1 C2
-- ---
AA ABC
BB BED
CC CAR
DD DYE
EE EYE
ZZ ZOO
6 rows selected.
SQL>
...we can populate an associative array in two steps:
SQL> set serveroutput on
SQL>
SQL> declare
2 type varassoc is table of varchar2(3) index by varchar2(2);
3 vars varassoc;
4
5 type nt is table of t23%rowtype;
6 loc_nt nt;
7
8 begin
9 select * bulk collect into loc_nt from t23;
10 dbms_output.put_line('no of recs = '||sql%rowcount);
11
12 for i in loc_nt.first()..loc_nt.last()
13 loop
14 vars(loc_nt(i).c1) := loc_nt(i).c2;
15 end loop;
16
17 dbms_output.put_line('no of vars = '||vars.count());
18
19 dbms_output.put_line('ZZ = '||vars('ZZ'));
20
21 end;
22 /
no of recs = 6
no of vars = 6
ZZ = ZOO
PL/SQL procedure successfully completed.
SQL>
The real question is probably whether populating an associative array performs better than just selecting rows in the table. Certainly if you have 11g Enterprise edition you should consider result set caching instead.
are you absolutely married to associative arrays? And I assume that you are doing this because you want to be able to do a lookup against the array using a character key.
If so, have you considered implementing this as a collection type instead?
e.g.
CREATE OR REPLACE TYPE VAR_ASSOC as OBJECT(
KEYID VARCHAR2(3),
DATAVAL VARCHAR2(2)
)
/
CREATE OR REPLACE TYPE VAR_ASSOC_TBL AS TABLE OF VAR_ASSOC
/
CREATE OR REPLACE PROCEDURE USE_VAR_ASSOC_TBL
AS
vars Var_Assoc_tbl;
-- other variables...
BEGIN
select cast ( multiset (
select foo as keyid,
bar as dataval
from schema.table
) as var_Assoc_tbl
)
into vars
from dual;
-- and later, when you want to do your lookups
select ot.newfoo
,myvars.dataval
,ot.otherval
into ....
from schema.other_Table ot
join table(vars) as myvars
on ot.newfoo = myvars.keyid;
end;
/
This gives you the lookup by character key value and lets you do everything in bulk.

Oracle optimise query avoiding cursors

I'm working on a piece of sql that I want to optimize.
I have inside a bunch of cursors.
I'm wondering if I can use something else instead of cursors.
I'm thinking using some kind of variables, filling them, and for the rest of the treatment avoiding the DB connection (I have a complex treatment).
For instance I have a piece of code like :
TYPE rec_basket IS RECORD (
FIELD1 VARCHAR2(40),
FIELD2 NUMBER(10),
FIELD3 VARCHAR2(6)
);
TYPE tab_basket IS TABLE OF rec_basket
INDEX BY BINARY_INTEGER;
........................
CURSOR cur_baskets
IS
select * from toto
............................
FOR i IN cur_baskets
LOOP
l_tab_basket (l_nbasket).field1 := i.field1;
l_tab_basket (l_nbasket).field2 := i.field2;
l_tab_basket (l_nbasket).field3 := i.field3;
l_nbasket := l_nbasket + 1;
END LOOP;
Using a cursor and filling the l_tab_basket variable is the best way to go? I'm using l_tab_basket (index) somewhere in my code.
The reason I've put this piece of code is that I would like use this mechanism for my other cursors.
Actually I have a cursor inside another one. And for each line of each of them I have some treatment. I would like to replace the cursors with something else, but I don't know how.
Thanks.
You can use BULK COLLECT to fetch all records into your nested table. This would work in 10g+:
SQL> DECLARE
2 TYPE rec_basket IS RECORD(
3 field1 VARCHAR2(40),
4 field2 NUMBER(10),
5 field3 VARCHAR2(6));
6 TYPE tab_basket IS TABLE OF rec_basket INDEX BY BINARY_INTEGER;
7 l_tab_basket tab_basket;
8 BEGIN
9 SELECT 'a', ROWNUM, 'b'
10 BULK COLLECT INTO l_tab_basket
11 FROM dual CONNECT BY LEVEL <= 1000;
12 END;
13 /
PL/SQL procedure successfully completed
Keep in mind that Oracle 10g automatically fetch records from implicit cursors in bulk (100) in pl/sql, so the gains should be marginal at best: you will probably spend more time querying the DB than building the array unless the array is really really big (and in that case is it wise to use a nested table?)
Logic of your code is not very clear. You have not written whole program. Let us examine:
CURSOR cur_baskets
IS
select * from toto
Here the value are read from the table toto and put into a cursor.
In the following lines the values are read from the cursor and put into l_tab_basket.
FOR i IN cur_baskets
LOOP
l_tab_basket (l_nbasket).field1 := i.field1;
l_tab_basket (l_nbasket).field2 := i.field2;
l_tab_basket (l_nbasket).field3 := i.field3;
l_nbasket := l_nbasket + 1;
END LOOP;
So same values are going to local variable twice. This can be avoided. You can find out ways to directly insert into or update target table.
You can try bulk collect. If toto is small, you can insert into or update the target table without using cursor.

Resources