Deleting a particular element from a nested table - oracle

I have created a type of nested table like below:
CREATE OR REPLACE TYPE STRING_ARRAY AS TABLE OF VARCHAR2(1000);
And I want to have some ways to remove a particular element from the array.
E.g.
AVC_NAMES := STRING_ARRAY('ALEX', 'BETTY', 'CARL', 'DONALD');
SP_EXCLUDE(AVC_NAMES, 'BETTY'); // this will remove BETTY from the array.
Is there any build-in methods in the nested table that can remove specific element from an array? Or should I write a s.p. to do the job?

Try Multiset operator. Multisete operators combine the results of two nested tables into a single nested table.
result := collection1 MULTISET EXCEPT collection2
declare
type mycollection is table of varchar2(10);
c1 mycollection := mycollection('A','B','C','D','E');
to_remove mycollection := mycollection('C');
begin
c1 := c1 multiset EXCEPT to_remove;
for i in c1.first..c1.last
loop
dbms_output.put_line(c1(i));
end loop;
end;

Hope this below snipet helps.
SET serveroutput ON;
DECLARE
lv number_ntt;
lv_out number_ntt;
rem_value NUMBER:='&Enter_val';
BEGIN
lv:=number_ntt(1,2,3,4,5,6);
SELECT COLUMN_VALUE BULK COLLECT
INTO lv_out
FROM TABLE(lv)
WHERE COLUMN_VALUE NOT IN (rem_value);
lv:=lv_out;
FOR i IN lv.FIRST..lv.LAST
LOOP
dbms_output.put_line(lv(i));
END LOOP;
END;

Related

wrong number or types of arguments in call to P_AA

When I try to compile the procedure with collection type as in parameter, I'm getting the error like
wrong number or types of arguments in call to 'P_AA'
-------Procedure created with in parameter as nested table------------
create or replace procedure p_aa(serv in t45)
is
aa serv_item%rowtype;
begin
for i in 1..serv.count
loop
select a.* into aa from serv_item a where a.serv_item_id = serv(i);
dbms_output.put_line('Serv item '||aa.serv_item_id||' '||'status '||aa.status);
end loop;
end;
/
----------Calling the package----------
declare
type t1 is table of number;
cursor c1 is select serv_item_id from serv_item;
n number:=0;
v t1;
begin
open c1;
loop
fetch c1 into v(n);
exit when c1%notfound;
n:=n+1;
end loop;
close c1;
p_aa(v);
end;
/
How can I fix my code?
Your procedure defines the parameter like this:
serv in t45
So t45 is the defined datatype of the parameter.
Now when you call the procedure you pass in a variable v. And how is v defined?
type t1 is table of number;
...
v t1;
t1 is a different type to t45. Even if they have identical structures they are different types. And that's why you get PLS-00306. The solution is quite simple: define v as t45.
i am getting error like 'Reference to uninitialized collection'.
You need to initialise the collection. You do this using the default constructor of the type, either at the start of the program ...
v := t45();
... or when you declare it:
v t45 := t45();
Once you get beyond that you will find your assignment logic is wrong: you're fetching into an element of the collection before you increment the counter or extend the array. So what you need is this:
declare
cursor c1 is select serv_item_id from serv_item;
n number:=0;
v t45 := t45();
x number;
begin
open c1;
loop
fetch c1 into x;
exit when c1%notfound;
n:=n+1;
v.extend();
v(n) := x;
end loop;
close c1;
p_aa(v);
end;
/
Alternatively use the less verbose bulk collect, which handles all the looping and type management implicitly :
declare
v t45;
begin
select serv_item_id
bulk collect into v
from serv_item;
p_aa(v);
end;
/
Here is a db<>fiddle demo showing both approaches working.

How to change the subscript/index value in an associative array?

Is it possible to change the subscript/index value of an existing element in an associative array?
declare
type a_arr is table of varchar2(20) index by pls_integer;
tb1 a_arr;
begin
tb1(1) := 'aaaa';
tb1(2) := 'bbbb';
tb1(3) := 'cccc';
end;
/
In the above associative array tb1, is it possible to change the subscript value from 1 to 10 (ie from tb1(1) to tb1(10)) without deleting or creating a new element in the table?
It is unclear what are you trying to do. Here's basic general example. The output is 'aaaa' 10 times. You can add some logic in between. For example, if i = 3 then tbl_val:= 'bbbb' or smth lk this. You can also parameterise the start and/or end loop boundaries if you create a procedure for example.
DECLARE
type a_arr is table of varchar2(20) index by pls_integer;
tb1 a_arr;
tbl_val VARCHAR2(20):= 'aaaa';
BEGIN
FOR i IN 1..10 LOOP
tb1(i):= tbl_val;
dbms_output.put_line(tb1(i));
END LOOP;
END;
/

Assign Value to a Nested Table without Looping

say i have the table XX_TEST_DATA and its being used to populate a nested table inside a PL/SQL block below (please note the comments):
DECLARE
CURSOR XX_DATA_CUR
IS
SELECT *
FROM XX_TEST_DATA;
TYPE TYP_XX_TEST IS TABLE OF XX_TEST_DATA%ROWTYPE INDEX BY PLS_INTEGER;
XX_REC TYP_XX_TEST;
BEGIN
OPEN XX_DATA_CUR;
FETCH XX_DATA_CUR
BULK COLLECT
INTO XX_REC;
CLOSE XX_DATA_CUR;
for i in 1..XX_REC.count loop
XX_REC(i).BATCH_NAME := 'Batch 1'; -- This is the Line
end loop;
END;
I would like to assign the value "Batch 1" to ALL records inside the nested table. Would this be possible without looping through all the records?
Something like below:
BEGIN
OPEN XX_DATA_CUR;
FETCH XX_DATA_CUR
BULK COLLECT
INTO XX_REC;
CLOSE XX_DATA_CUR;
XX_REC.BATCH_NAME := 'Batch 1'; --
END;
I know the above block would not work, but I was hoping to achive something like that.
DDL of the Test Table
Create table XX_TEST_DATA
(
XX_ID NUMBER
, XX_DATA1 VARCHAR2(100)
, XX_DATA2 VARCHAR2(100)
, BATCH_NAME VARCHAR2(100)
);
The value could be set as part of the SELECT and then there would be no need to update the collection.
DECLARE
TYPE TYP_XX_TEST IS TABLE OF XX_TEST_DATA%ROWTYPE INDEX BY PLS_INTEGER;
XX_REC TYP_XX_TEST;
BEGIN
SELECT XX_ID, XX_DATA1, XX_DATA2, 'Batch 1'
BULK COLLECT INTO XX_REC
FROM XX_TEST_DATA;
END;
/

How to populate nested object table in pl/sql block?

I struggle a problem, which, i think, is rather simple.
I have a type T_OPERATION_TAG in a database which is created as:
CREATE OR REPLACE TYPE t_operation_tag AS OBJECT(
tag_name VARCHAR2(30),
tag_value VARCHAR2(30),
CONSTRUCTOR FUNCTION t_operation_tag RETURN SELF AS RESULT
)
I also have another type T_OPERATION_TAGS, which is defined as follows
CREATE OR REPLACE TYPE t_operation_tags AS TABLE OF t_operation_tag;
Then in my pl/sql block i have the following code
DECLARE
p_op_tags t_operation_tags;
BEGIN
p_op_tags := t_operation_tags();
FOR i IN (SELECT tag_name, tag_value
FROM op_tags_table
WHERE some_condition)
LOOP
--How to append new lines to p_op_tags ?
END LOOP;
END;
So, if the SELECT-query in the FOR LOOP returns,e.g., five lines then how can I populate my P_OP_TAGS object table with these five lines?
Like this:
DECLARE
p_op_tags t_operation_tags;
p_cursor sys_refcursor;
p_limit number := 5;
BEGIN
open p_cursor for
SELECT t_operation_tag(tag_name, tag_value)
FROM op_tags_table
;
fetch p_cursor bulk collect into p_op_tags limit p_limit;
DBMS_OUTPUT.put_line(p_op_tags(4).tag_name);
close p_cursor;
END;
Or if you prefer the loop clause:
DECLARE
p_op_tag t_operation_tag;
p_op_tags t_operation_tags;
p_limit number := 5;
BEGIN
p_op_tags := t_operation_tags();
for i in (SELECT tag_name, tag_value
FROM op_tags_table
WHERE some_condition
and rownum < p_limit + 1)
loop
p_op_tag := t_operation_tag(i.tag_name, i.tag_value);
p_op_tags.extend();
p_op_tags(p_op_tags.COUNT) := p_op_tag;
end loop;
DBMS_OUTPUT.put_line(p_op_tags(4).tag_name);
END;
/
You don't really need a cursor or loop at all, if you're populating the collection entirely from your query; you can bulk collect straight into it:
DECLARE
p_op_tags t_operation_tags;
BEGIN
SELECT t_operation_tag(tag_name, tag_value)
BULK COLLECT INTO p_op_tags
FROM op_tags_table
WHERE some_condition;
...
END;
/

Find specific varchar in Oracle Nested Table

I'm new to PL-SQL, and struggling to find clear documentation of operations are nested tables. Please correct any misused terminology etc.
I have a nested table type that I use as a parameters for a stored procedure.
CREATE OR REPLACE TYPE "STRARRAY" AS TABLE OF VARCHAR2 (255)
In my stored procedure, the table is initialized and populated. Say I have a VARCHAR2 variable, and I want to know true or false if that varchar exists in the nested table.
I tried
strarray.exists('somevarchar')
but I get an ORA-6502
Is there an easier way to do that other than iterating?
FOR i IN strarray.FIRST..strarray.LAST
LOOP
IF strarray(i) = value THEN
return 1;--found
END IF;
END LOOP;
For single value check I prefer the "member" operator.
zep#dev> declare
2 enames strarray;
3 wordToFind varchar2(255) := 'King';
4 begin
5 select emp.last_name bulk collect
6 into enames
7 from employees emp;
8 if wordToFind member of enames then
9 dbms_output.put_line('Found King');
10 end if;
11 end;
12 /
Found King
PL/SQL procedure successfully completed
zep#dev>
You can use the MULTISET INTERSECT operator to determine whether the string you're interested in exists in the collection. For example
declare
l_enames strarray;
l_interesting_enames strarray := new strarray( 'KING' );
begin
select ename
bulk collect into l_enames
from emp;
if( l_interesting_enames = l_interesting_enames MULTISET INTERSECT l_enames )
then
dbms_output.put_line( 'Found King' );
end if;
end;
will print out "Found King" if the string "KING" is an element of the l_enames collection.
You should pass an array index, not an array value to an exists in case you'd like to determine whether this element exists in collection. Nested tables are indexed by integers, so there's no way to reference them by strings.
However, you might want to look at associative arrays instead of collections in case you wish to reference your array element by string index. This will look like this:
DECLARE
TYPE assocArray IS TABLE OF VARCHAR2(100) INDEX BY VARCHAR2(100);
myArray assocArray;
BEGIN
myArray('foo') := 'bar';
IF myArray.exists('baz') THEN
dbms_output.put_line(myArray('baz'));
ELSIF myArray.exists('foo') THEN
dbms_output.put_line(myArray('foo'));
END IF;
END;
Basically, if your array values are distinct, you can create paired arrays referencing each other, like,
arr('b') := 'a'; arr('a') := 'b';
This technique might help you to easily look up any element and its index.
When a nested table is declared as a schema-level type, as you have done, it can be used in any SQL query as a table. So you can write a simple function like so:
CREATE OR REPLACE FUNCTION exists_in( str VARCHAR2, tab stararray)
RETURN BOOLEAN
AS
c INTEGER;
BEGIN
SELECT COUNT(*)
INTO c
FROM TABLE(CAST(tab AS strarray))
WHERE column_value = str;
RETURN (c > 0);
END exists_in;

Resources