Why is this check for null associative array in PL/SQL failing? - oracle

I have an associative array created by a type of rowtype of a table column.
To give an example, this is how it is(the table names are different, but the structure is the same):
This is the DDL of the table
CREATE TABLE employees
(
id NUMBER,
name VARCHAR2(240),
salary NUMBER
);
Here's what my procedure is doing:
DECLARE
TYPE table_of_emp
IS TABLE OF employees%ROWTYPE INDEX BY BINARY_INTEGER;
emp TABLE_OF_EMP;
BEGIN
IF emp IS NULL THEN
dbms_output.Put_line('Null associative array');
ELSE
dbms_output.Put_line('Not null');
END IF;
END;
I assume this should result in "Null associative array" being printed. However, the if condition fails and the execution jumps to the else part.
Now if I put in a for loop to print the collection values
DECLARE
TYPE table_of_emp
IS TABLE OF employees%ROWTYPE INDEX BY BINARY_INTEGER;
emp TABLE_OF_EMP;
BEGIN
IF emp IS NULL THEN
dbms_output.Put_line('Null associative array');
ELSE
dbms_output.Put_line('Not null');
FOR i IN emp.first..emp.last LOOP
dbms_output.Put_line('Emp name: '
|| Emp(i).name);
END LOOP;
END IF;
END;
then the program unit raises an exception, referencing the for loop line
ORA-06502: PL/SQL: Numeric or value error
which I presume is because of the null associative array. Is the error being raised because of null associative array?
So why is the first check failing then? What am I doing wrong?
The database server is Oracle 11g EE (version 11.2.0.3.0 64 bit)

I assume this should result in "Null associative array" being printed. That assumption is wrong for associative arrays. They exist when declared, but are empty. It would be correct for other types of PL/SQL collections:
Until you initialize it, a nested table or varray is atomically null;
the collection itself is null, not its elements. To initialize a
nested table or varray, you use a constructor, a system-defined
function with the same name as the collection type. This function
constructs collections from the elements passed to it.
You must explicitly call a constructor for each varray and nested
table variable. Associative arrays, the third kind of collection, do
not use constructors. Constructor calls are allowed wherever function
calls are allowed. Initializing and Referencing Collections
Compare:
SQL> declare
2 type varchar2_100_aa is table of varchar2(100) index by binary_integer;
3 test varchar2_100_aa;
4 begin
5 test(1) := 'Hello';
6 dbms_output.put_line(test(1));
7 end;
8 /
Hello
PL/SQL procedure successfully completed.
SQL> declare
2 type varchar2_100_va is varray(100) of varchar2(100);
3 test varchar2_100_va;
4 begin
5 test(1) := 'Hello';
6 dbms_output.put_line(test(1));
7 end;
8 /
declare
*
ERROR at line 1:
ORA-06531: Reference to uninitialized collection
ORA-06512: at line 5
Variable array done correctly:
SQL> declare
2 type varchar2_100_va is varray(10) of varchar2(100);
3 test varchar2_100_va;
4 begin
5 test := varchar2_100_va(); -- not needed on associative array
6 test.extend; -- not needed on associative array
7 test(1) := 'Hello';
8 dbms_output.put_line(test(1));
9 end;
10 /
Hello
PL/SQL procedure successfully completed.
Because the associative array is empty first and last are null, which is why your second example results in ORA-06502: PL/SQL: Numeric or value error:
SQL> declare
2 type varchar2_100_aa is table of varchar2(100) index by binary_integer;
3 test varchar2_100_aa;
4 begin
5 dbms_output.put_line(test.count);
6 dbms_output.put_line(coalesce(to_char(test.first), 'NULL'));
7 dbms_output.put_line(coalesce(to_char(test.last), 'NULL'));
8 test(1) := 'Hello';
9 dbms_output.new_line;
10 dbms_output.put_line(test.count);
11 dbms_output.put_line(coalesce(to_char(test.first), 'NULL'));
12 dbms_output.put_line(coalesce(to_char(test.last), 'NULL'));
13 end;
14 /
0
NULL
NULL
1
1
1
PL/SQL procedure successfully completed.
EDIT Also note that associative arrays can be sparse. Looping over the numbers between first and last will raise an exception for any collection that is sparse. Instead use first and next like so: (Last and prev to loop the other direction.)
SQL> declare
2 type varchar2_100_aa is table of varchar2(100) index by binary_integer;
3 test varchar2_100_aa;
4 i binary_integer;
5 begin
6 test(1) := 'Hello';
7 test(100) := 'Good bye';
8 dbms_output.put_line(test.count);
9 dbms_output.put_line(coalesce(to_char(test.first), 'NULL'));
10 dbms_output.put_line(coalesce(to_char(test.last), 'NULL'));
11 dbms_output.new_line;
12 --
13 i := test.first;
14 while (i is not null) loop
15 dbms_output.put_line(to_char(i, '999') || ' - ' || test(i));
16 i := test.next(i);
17 end loop;
18 end;
19 /
2
1
100
1 - Hello
100 - Good bye
PL/SQL procedure successfully completed.

I'm not going to answer why the first check is failing. I've never thought of doing anything like that and am quite surprised that it doesn't raise an error.
The reason why you're getting an exception raised on the loop is, as you've noted, that the index emp.first does not exist.
Rather than checking for nulls, you should really be checking for the existence of this index. Which you can do be using the .exists(i) syntax:
if not emp.exists(emp.first) then
dbms_output.put_line('Nothing in here.');
end if;

Related

Using cursor with where in condition

I am passing arguments `EBN,BGE' into a procedure , then I am passing this argument to a cursor.
create or replace procedure TEXT_MD (AS_IDS VARCHAR2)
is
CURSOR C_A (AS_ID VARCHAR2) IS
SELECT
name
FROM S_US
WHERE US_ID IN (AS_ID);
BEGIN
FOR A IN C_A (AS_IDS) LOOP
DBMS_OUTPUT.PUT_LINE('I got here: '||AS_IDS);
end loop;
END;
But while debuging the count of the cursor is still null
So my question , why the cursor not returning values with in condition
You are passing a string parameter, so it will be used as a string, not as a list of strings; so, your cursor will be something like
SELECT name
FROM S_US
WHERE US_ID IN ('EBN,BGE')
This will, of course, not do what you need.
You may need to change your procedure and the way to pass parameters; if you want to keep a string parameter , one way could be the following:
setup:
SQL> CREATE TABLE S_US
2 (
3 US_ID,
4 NAME
5 ) AS
6 SELECT 'EBN', 'EBN name' FROM DUAL
7 UNION ALL
8 SELECT 'BGE', 'BGE name' FROM DUAL;
Table created.
procedure:
SQL> CREATE OR REPLACE PROCEDURE TEXT_MD_2(AS_IDS VARCHAR2) IS
2 vSQL varchar2(1000);
3 c sys_refcursor;
4 vName varchar2(16);
5 BEGIN
6 vSQL := 'SELECT name
7 FROM S_US
8 WHERE US_ID IN (' || AS_IDS || ')';
9 open c for vSQL;
10 loop
11 fetch c into vName;
12 if c%NOTFOUND then
13 exit;
14 end if;
15 DBMS_OUTPUT.PUT_LINE(vName);
16 END LOOP;
17 END;
18 /
Procedure created.
You need to call it with a string already formatted to be a parameter list for IN:
SQL> EXEC TEXT_MD_2('''EBN'',''BGE''');
EBN name
BGE name
PL/SQL procedure successfully completed.
This is only an example of a possible way, and not the way I would do this.
Among the reasons to avoud this kind of approach, consider what Justin Cave says:
"that would be a security risk due to SQL injection and would have a potentially significant performance penalty due to constant hard parsing".
I believe you should better check how to pass a list of values to your procedure, rather then using a string to represent a list of strings.
Here is a possible way to do the same thing with a collection:
SQL> CREATE OR REPLACE TYPE tabVarchar2 AS TABLE OF VARCHAR2(16)
2 /
Type created.
SQL>
SQL> CREATE OR REPLACE PROCEDURE TEXT_MD_3(AS_IDS tabVarchar2) IS
2 vSQL VARCHAR2(1000);
3 c SYS_REFCURSOR;
4 vName VARCHAR2(16);
5 BEGIN
6 FOR i IN (SELECT name
7 FROM S_US INNER JOIN TABLE(AS_IDS) tab ON (tab.COLUMN_VALUE = US_ID))
8 LOOP
9 DBMS_OUTPUT.PUT_LINE(i.name);
10 END LOOP;
11 END;
12 /
Procedure created.
SQL>
SQL> DECLARE
2 vList tabVarchar2 := NEW tabVarchar2();
3 BEGIN
4 vList.EXTEND(2);
5 vList(1) := 'BGE';
6 vList(2) := 'EBN';
7 TEXT_MD_3(vList);
8 END;
9 /
BGE name
EBN name
PL/SQL procedure successfully completed.
SQL>
Again, you can define collections in different ways, within a stored procedure or not, indexed or not, and so on; this is only one of the possible ways, not necessarily the best, depending on your environment, needs.

Oracle Stored Procedure to select from multiple procedures, Array as IN parameter and 1 dynamic condition based on the length of the array element

I have 3 procedures which will return different columns values to insert in same table.
All procedures take Array as parameter and values of array can be of two type. if type a then where name = "tin" and if type 2 then name = "tan".
EX:
Array a = {a,b,b,a};
if(a){
name="tin"
}else{
name="tan"
}
how I will write a procedure to get all the values passing the array and 1 more condition based the length of array element.
set serveroutput on
CREATE OR REPLACE TYPE arrtype AS VARRAY(200) OF VARCHAR2(50);
/
created
CREATE OR REPLACE PROCEDURE arrtest (arr_in arrtype) IS
BEGIN
FOR i IN 1..arr_in.count LOOP
if(arr_in(i)='a') then
dbms_output.put_line('TIN');
ELSIF(arr_in(i)='b') then
dbms_output.put_line('TAN');
else
dbms_output.put_line('neither TIN nor TAN');
end if;
END LOOP;
END;
/
Procedure created
SQL> DECLARE
2 array1 arrtype;
3 BEGIN
4 array1 := arrtype();
5 array1.EXTEND(3);
6 array1(1) := 'a';
7 array1(2) := 'b';
8 array1(3) := 'c';
9 arrtest(array1);
10 END;
11 /
TIN
TAN
neither TIN nor TAN

Convert Varchar2 to Char array in Oracle

I have a varchar2 field and want to split it to array of chars
Like 'ABCDEF' --> 'A' 'B' 'C' 'D' 'E'
How can i convert my Field Values to chars array?
If you actually mean a PL/SQL collection of characters, you could do something like
SQL> ed
Wrote file afiedt.buf
1 declare
2 type char_arr is table of char(1) index by pls_integer;
3 l_str varchar2(100) := 'ABCDEF';
4 l_arr char_arr;
5 begin
6 for i in 1 .. length(l_str)
7 loop
8 l_arr(i) := substr( l_str, i, 1 );
9 end loop;
10 dbms_output.put_line( l_arr.count );
11* end;
SQL> /
6
PL/SQL procedure successfully completed.
Without understanding the business requirements, though, I would tend to be very suspicious. When you find yourself breaking apart strings in PL/SQL, that almost always implies that you have stored data in a non-atomic form and need to address the data model issue.

Struggling with Oracle Function with parameter (Reference to uninitialized collection)

I can't find what is wrong here, finally I decided to ask here for help.
This is how I am invoking:
DECLARE
RESSULT VARCHAR2(1000 CHAR);
V_TEST1 CARS_TABLE:=CARS_TABLE();
idx pls_integer :=0;
BEGIN
V_TEST1.EXTEND(2);
IDX := V_TEST1.COUNT; -- here there is NO problem
RESSULT:=MYFUNCTION(1, V_TEST1);
END;
This is what I am invoking..
CREATE OR REPLACE
FUNCTION MYFUNCTION(
CAR_ID IN INTEGER,
MYOUTTABLE OUT CARS_TABLE)
RETURN VARCHAR2
IS
RESSULT VARCHAR2(1000 CHAR);
idx pls_integer :=0;
BEGIN
IDX := MYOUTTABLE.COUNT; - HERE IS PROBLEM.. but why - I pass initialized collection
RETURN 'return param';
END MYFUNCTION;
CREATE OR REPLACE TYPE carElement
IS
OBJECT
(
kind VARCHAR2(60),
created DATE );
CREATE OR REPLACE TYPE CARS_TABLE
AS
TABLE OF carElement;
00000 - "Reference to uninitialized collection"
*Cause: An element or member function of a nested table or varray
was referenced (where an initialized collection is needed)
without the collection having been initialized.
*Action: Initialize the collection with an appropriate constructor
or whole-object assignment.
"IDX := MYOUTTABLE.COUNT; - HERE IS PROBLEM.. but why - I pass initialized collection"
you're resetting the collection to NULL on the input to the function call as you've defined the parameter as OUT which automatically nulls out the parameter as it calls the function (documentation link).
MYOUTTABLE OUT CARS_TABLE
if you want to preserve the input, then you need to pass it as IN or if you want to preserve teh input and also manipulate the object in the function then use IN OUT.
eg note with the first call it shows its null if i use out:
SQL> declare
2 v_test1 cars_table:=cars_table();
3
4 function myfunction(
5 car_id in integer,
6 myouttable &mode cars_table)
7 return varchar2
8 is
9 ressult varchar2(1000 char);
10 idx pls_integer :=0;
11 begin
12 if (myouttable is null)
13 then
14 dbms_output.put_line('ARRAY IS NULL');
15 else
16 idx := myouttable.count;
17 end if;
18 return 'return param';
19 end myfunction;
20 begin
21
22 v_test1.extend(2);
23 dbms_output.put_line(myfunction(1, v_test1));
24
25 end;
26 /
Enter value for mode: OUT
old 6: myouttable &mode cars_table)
new 6: myouttable OUT cars_table)
ARRAY IS NULL
return param
PL/SQL procedure successfully completed.
SQL> /
Enter value for mode: IN OUT
old 6: myouttable &mode cars_table)
new 6: myouttable IN OUT cars_table)
return param
PL/SQL procedure successfully completed.

Passing an array of data as an input parameter to an Oracle procedure

I'm trying to pass an array of (varchar) data into an Oracle procedure. The Oracle procedure would be either called from SQL*Plus or from another PL/SQL procedure like so:
BEGIN
pr_perform_task('1','2','3','4');
END;
pr_perform_task will read each of the input parameters and perform the tasks.
I'm not sure as to how I can achieve this. My first thought was to use an input parameter of type varray but I'm getting Error: PLS-00201: identifier 'VARRAY' must be declared error, when the procedure definiton looks like this:
CREATE OR REPLACE PROCEDURE PR_DELETE_RECORD_VARRAY(P_ID VARRAY) IS
To summarize, how can I pass the data as an array, let the SP loop through each of the parameters and perform the task ?
I'm using Oracle 10gR2 as my database.
This is one way to do it:
SQL> set serveroutput on
SQL> CREATE OR REPLACE TYPE MyType AS VARRAY(200) OF VARCHAR2(50);
2 /
Type created
SQL> CREATE OR REPLACE PROCEDURE testing (t_in MyType) IS
2 BEGIN
3 FOR i IN 1..t_in.count LOOP
4 dbms_output.put_line(t_in(i));
5 END LOOP;
6 END;
7 /
Procedure created
SQL> DECLARE
2 v_t MyType;
3 BEGIN
4 v_t := MyType();
5 v_t.EXTEND(10);
6 v_t(1) := 'this is a test';
7 v_t(2) := 'A second test line';
8 testing(v_t);
9 END;
10 /
this is a test
A second test line
To expand on my comment to #dcp's answer, here's how you could implement the solution proposed there if you wanted to use an associative array:
SQL> CREATE OR REPLACE PACKAGE p IS
2 TYPE p_type IS TABLE OF VARCHAR2(50) INDEX BY BINARY_INTEGER;
3
4 PROCEDURE pp (inp p_type);
5 END p;
6 /
Package created
SQL> CREATE OR REPLACE PACKAGE BODY p IS
2 PROCEDURE pp (inp p_type) IS
3 BEGIN
4 FOR i IN 1..inp.count LOOP
5 dbms_output.put_line(inp(i));
6 END LOOP;
7 END pp;
8 END p;
9 /
Package body created
SQL> DECLARE
2 v_t p.p_type;
3 BEGIN
4 v_t(1) := 'this is a test of p';
5 v_t(2) := 'A second test line for p';
6 p.pp(v_t);
7 END;
8 /
this is a test of p
A second test line for p
PL/SQL procedure successfully completed
SQL>
This trades creating a standalone Oracle TYPE (which cannot be an associative array) with requiring the definition of a package that can be seen by all in order that the TYPE it defines there can be used by all.
If the types of the parameters are all the same (varchar2 for example), you can have a package like this which will do the following:
CREATE OR REPLACE PACKAGE testuser.test_pkg IS
TYPE assoc_array_varchar2_t IS TABLE OF VARCHAR2(4000) INDEX BY BINARY_INTEGER;
PROCEDURE your_proc(p_parm IN assoc_array_varchar2_t);
END test_pkg;
CREATE OR REPLACE PACKAGE BODY testuser.test_pkg IS
PROCEDURE your_proc(p_parm IN assoc_array_varchar2_t) AS
BEGIN
FOR i IN p_parm.first .. p_parm.last
LOOP
dbms_output.put_line(p_parm(i));
END LOOP;
END;
END test_pkg;
Then, to call it you'd need to set up the array and pass it:
DECLARE
l_array testuser.test_pkg.assoc_array_varchar2_t;
BEGIN
l_array(0) := 'hello';
l_array(1) := 'there';
testuser.test_pkg.your_proc(l_array);
END;
/

Resources