PL/SQL Cursor and Array of different size - arrays

I want to store the content of a cursor in an associative array (Table index by binary_integer). But in the same array I also want to store an additional variable, say a boolean.
My cursor has n elements per row and the array is defined to have n+1 elements (n with the same %type as the cursorelements), the last one being the boolean.
What I whant is something like this
for cursorrow in cursor(...)
loop
array(row i) := cursorrow, boolean_variable;
end loop;
|1|2|...|n|n+1| := |1|2|...|n|, |1|
Unfortunately I can't get it to work.
Anybody knows how to do it?

How about defining a composite record type consisting of a %rowtype record and a Boolean?
Test setup:
create table demo_table
( some_id integer primary key
, some_name varchar2(30) not null unique
, some_type varchar2(10) not null );
insert all
into demo_table values (1, 'One', 'X' )
into demo_table values (2, 'Two', 'Y' )
into demo_table values (3, 'Three', 'Z' )
select * from dual;
Test:
(Edit: added dbms_output messages within loop)
declare
subtype demo_rectype is demo_table%rowtype;
type demo_rec is record
( details demo_rectype
, somecheck boolean );
type demo_tt is table of demo_rec index by pls_integer;
t_demo demo_tt;
cursor c_demo is select * from demo_table;
begin
for r in c_demo
loop
t_demo(c_demo%rowcount).details := r;
t_demo(c_demo%rowcount).somecheck := dbms_random.value < 0.5;
dbms_output.put_line
( t_demo(c_demo%rowcount).details.some_name || ': ' ||
case t_demo(c_demo%rowcount).somecheck
when true then 'TRUE'
when false then 'FALSE'
else 'NULL'
end );
end loop;
dbms_output.new_line();
dbms_output.put_line('Array t_demo contains ' || t_demo.count || ' items.');
end;
Output:
One: FALSE
Two: FALSE
Three: TRUE
Array t_demo contains 3 items.

Since the record type differs by 1 field from the table structure (i.e. the boolean), you can not assign the cursor row to the record type in 1 assignment statement. You need to do it for every table column individually:
for cursorrow in cursor(...)
loop
array(row i).col1 := cursorrow.col1;
array(row i).col2 := cursorrow.col2;
...
array(row i).coln := cursorrow.coln;
array(row i).boolean_variable := some_boolean_value;
end loop;

As mentioned in comments,you can create a record and do it. You can use the below procedure to achieve your requirement.
create or replace procedure proc_test
as
cursor cur_tab is
select a --have selected only 1 column..you can choose many
from test;
TYPE var_temp IS TABLE OF cur_tab%ROWTYPE INDEX BY PLS_INTEGER;
v_var var_temp;
/**You can add columns selected in you cursor here in your record****/
TYPE abc IS RECORD
(
id varchar2(100),
orig_name boolean
);
TYPE xx IS TABLE OF abc ;
-- initialization of record
v_xx xx := xx() ;
t boolean:=TRUE;
begin
open cur_tab;
fetch cur_tab bulk collect into v_var;
close cur_tab;
for i in 1..v_var.count
loop
v_xx.extend;
v_xx(i).id := v_var(i).a;
v_xx(i).orig_name := t;
dbms_output.put_line (v_xx(i).id ||'----'||sys.diutil.bool_to_int(v_xx(i).orig_name));
---OR
dbms_output.put_line (v_xx(i).id ||'----'||case when v_xx(i).orig_name = true then 'TRUE' ELSE 'FALSE' end );
end loop;
exception
when others then
null;
end;
Call:
execute proc_test;

Related

Get value from from a json_array in oracle

i need the values of a json_array. I tried this:
DECLARE
l_stuff json_array_t;
BEGIN
l_stuff := json_array_t ('["Stirfry", "Yogurt", "Apple"] ');
FOR indx IN 0 .. l_stuff.get_size - 1
LOOP
INSERT INTO t_taböe (name, type)
VALUES(l_stuff.get(i), 'TEXT');
END LOOP;
END;
You are passing the position as i instead of indx; but you need a string so use get_string(indx) as #Sayan said.
But if you try to use that directly in an insert you'll get "ORA-40573: Invalid use of PL/SQL JSON object type" because of a still-outstanding (as far as I know) bug.
To work around that you can assign the string to a variable first:
l_name := l_stuff.get_string(indx);
INSERT INTO t_taböe (name, type)
VALUES(l_name, 'TEXT');
db<>fiddle
You do not need PL/SQL and can do it in a single SQL statement:
INSERT INTO t_taböe (name, type)
SELECT value,
'TEXT'
FROM JSON_TABLE(
'["Stirfry","Yogurt","Apple"]',
'$[*]'
COLUMNS (
value VARCHAR2(50) PATH '$'
)
);
db<>fiddle here
First convert the JSON array into an ordinary PL/SQL array, then use a bulk insert.
Here is a reproducible example:
create table tab (name varchar2 (8), type varchar2 (8))
/
declare
type namelist is table of varchar2(8) index by pls_integer;
names namelist;
arr json_array_t := json_array_t ('["Stirfry", "Yogurt", "Apple"]');
begin
for idx in 1..arr.get_size loop
names(idx) := arr.get_string(idx-1);
end loop;
forall idx in indices of names
insert into tab (name, type) values (names(idx), 'TEXT');
end;
/
The query and outcomes:
select * from tab
/
NAME TYPE
-------- --------
Stirfry TEXT
Yogurt TEXT
Apple TEXT
Just use get_string:
DECLARE
l_stuff json_array_t;
BEGIN
l_stuff := json_array_t ('["Stirfry", "Yogurt", "Apple"] ');
FOR indx IN 0 .. l_stuff.get_size - 1
LOOP
--INSERT INTO t_taböe (name, type)
-- VALUES(l_stuff.get_string(indx), 'TEXT');
dbms_output.put_line(l_stuff.get_string(indx));
END LOOP;
END;

Postgresql: Custom Type + Arrays combination

I'm unable to find where the issue is for the below program. the values of the custom type are displaying without any errors when I use RAISE NOTICE statements at the end. When I run the final select statement, the error is Array value must start with "{" or dimension information. Please help me with the select statement on how to call the package/function.
create
or
replace TYPE t_col_foo as object
(
ID NUMBER
, CLUSTERNAME VARCHAR2(300)
, "1200AM" varchar2(10));
create
or
replace TYPE T_COL_R AS TABLE OF t_col_foo;
CREATE OR REPLACE PACKAGE foo_avail_pkg
IS
FUNCTION foo_slots
(
p_ref_data anyarray
)
RETURN t_col_r[];
END foo_avail_pkg;
CREATE OR REPLACE PACKAGE BODY foo_avail_pkg
IS
FUNCTION foo_slots
(
p_ref_data anyarray
)
RETURN t_col_r[]
IS
-- declare
r_target_data t_col_foo:=t_col_foo(null,null,null);
r_target_data_1 t_col_foo;
r_source_data text[];
t_return t_col_tab1;
BEGIN
t_return:=t_col_tab1();
select
array
(
select
unnest( p_ref_data )
)
into r_source_data
;
-- r_target_data = '{}';
for i in coalesce(array_lower(r_source_data,1),0) .. coalesce(array_upper(r_source_data,1),0)
LOOP
r_target_data.ID := substr(r_source_data[i],1,instr(r_source_data[i],',',1,1)-1);
r_target_data.CLUSTERNAME := substr(r_source_data[i],length(r_target_data.ID)+2,(instr(r_source_data[i],',',length(r_target_data.ID)+1,2) - instr(r_source_data[i],',',1,1))-1);
r_target_data."1200AM" := 3;
r_target_data_1 :=row(r_target_data.ID ,r_target_data.CLUSTERNAME,r_target_data."1200AM") :: t_col_foo;
END LOOP;
-- dbms_output.put_line(r_target_data_1);
RETURN r_target_data_1;
end;
END foo_avail_pkg;
This is how I have to call
select * from foo_avail_pkg.foo_SLOTS(array
(
select
ID
||','
||CLUSTER_NAME
||','
||LOB
from
y limit 1
));
And the error is
ERROR: malformed array literal: "(1398,Sanity20feb,3)"
DETAIL: Array value must start with "{" or dimension information.

Passing in a Java Array into a stored procedure

I am attempting to pass a java array into my stored procedure and updating records with the values in the array. Currently when I attempt to execute and test the stored procedure I am running into
Error:ORA-06531: Reference to uninitialized collection.
Can anybody push me in the right direction or help me clean up my code. Below is the package spec followed the body.
CREATE OR REPLACE package AOMS.test_array1 as
type t1 is record (
s1 varchar2(1),
i_part_no varchar2(20),
i_itc varchar2(20),
s2 varchar2(1),
l_part_no varchar2(20));
type tab1 is table of t1 ;
tab2 tab1;
Here is the body.
CREATE OR REPLACE PACKAGE BODY AOMS.TEST_ARRAY1 AS
I_ARRAY varchar2(1000);
PROCEDURE test_array2(i_array IN tab2%TYPE) AS
l_s1 VARCHAR2(50);
l_part_no1 VARCHAR2(50);
l_itc varchar2(50);
l_s2 varchar2(50);
l_part_no2 varchar2(50);
BEGIN
FOR x IN i_array.first .. i_array.last
LOOP
l_s1 := i_array(x).s1;
l_part_no1 := i_array(x).i_part_no;
l_itc := i_array(x).i_itc;
l_s2 := i_array(x).s2;
l_part_no2 := i_array(x).l_part_no;
UPDATE replacement_parts
SET frst_src = l_s1,
frst_part_no = l_part_no1,
ITC = l_itc,
last_src = l_s2,
last_part_no = l_part_no2
WHERE
frst_src = 'P'
AND frst_part_no = '96424447 ';
COMMIT;
END LOOP;
END test_array2;
END test_array1;
/
I'm using Toad so when I call the procedure I just right click and execute and enter in my params. Here is the anonymous block code that gets generated when I attempt to execute.
DECLARE
I_ARRAY AOMS.TEST_ARRAY1.tab2%type;
BEGIN
-- I_ARRAY := NULL; Modify the code to initialize this parameter
AOMS.TEST_ARRAY1.TEST_ARRAY2 ( I_ARRAY );
COMMIT;
END;
You have some issues both in the package and in the procedure call.
This should work:
CREATE OR REPLACE PACKAGE test_array1 AS
TYPE t1 IS RECORD
(
s1 VARCHAR2(1),
i_part_no VARCHAR2(20),
i_itc VARCHAR2(20),
s2 VARCHAR2(1),
l_part_no VARCHAR2(20)
);
TYPE tab1 IS TABLE OF t1;
tab2 tab1;
PROCEDURE test_array2(i_array IN tab1);
END test_array1;
CREATE OR REPLACE PACKAGE BODY TEST_ARRAY1 AS
I_ARRAY VARCHAR2(1000);
PROCEDURE test_array2(i_array IN tab1) IS
l_s1 VARCHAR2(50);
l_part_no1 VARCHAR2(50);
l_itc VARCHAR2(50);
l_s2 VARCHAR2(50);
l_part_no2 VARCHAR2(50);
BEGIN
IF i_array.COUNT > 0
THEN
FOR x IN i_array.FIRST .. i_array.LAST
LOOP
l_s1 := i_array(x).s1;
l_part_no1 := i_array(x).i_part_no;
l_itc := i_array(x).i_itc;
l_s2 := i_array(x).s2;
l_part_no2 := i_array(x).l_part_no;
UPDATE replacement_parts
SET frst_src = l_s1,
frst_part_no = l_part_no1,
ITC = l_itc,
last_src = l_s2,
last_part_no = l_part_no2
WHERE frst_src = 'P'
AND frst_part_no = '96424447 ';
COMMIT;
END LOOP;
END IF;
END test_array2;
END test_array1;
/
The call:
DECLARE
I_ARRAY TEST_ARRAY1.tab1;
BEGIN
I_ARRAY := TEST_ARRAY1.tab1();
TEST_ARRAY1.TEST_ARRAY2 ( I_ARRAY );
COMMIT;
END;
The changes I made:
you define a type in your package, then use something like variable%type to declare the procedure, while you can simply use the type.
in the package, while scanning a collection, it's better to check if the collection has values before trying to use collection.first. Trying to access the .first on an empty collection can lead to an issue.
in the caller, you need to initialize the collection the way I showed to avoid the error you are having
As an aside, you should better try to use more explanatory names for variables, types, procedures, packages to avoid confusion between different objects.
Another thing: you have a commit inside a loop; this means that, keeping aside performances, if the first, say, 3 records are updated and then you have an error, you commit 3 updates; is this really what you need? Also, this way the commit in the caller is unuseful.

Oracle PL/SQL Parameter value from parameter name in String

Hi I need to retrieve a parameter value where the parameter name is in different parameter.
Lets say the proc is as below
PROCEDURE findValue
(
p_date IN VARCHAR2,
p_name IN VARCHAR2,
p_class IN VARCHAR2,
p_paramname IN VARCHAR2,
)
IS
Now lets say i want to pass p_paramname as p_date and further use the value of p_date parameter in PL/SQL block, how do i use it ?
If you can afford PL/SQL table inputs then try the logic below:
CREATE TYPE param_tbl_type IS TABLE OF VARCHAR2(255);
CREATE OR REPLACE FUNCTION
(p_param_names param_tbl_type,
p_param_values param_tbl_type)
RETURN VARCHAR2
IS
l_dyn_func_str VARCHAR2(4000);
l_ret_val VARCHAR2(4000);
vCursor integer;
fdbk PLS_INTEGER;
BEGIN
-- make sure you have equal number of params and currespondin values.
-- Index much match too. i.e. if p_param_names(0) is 'p_currency' then p_param_value(0) must be 'USD' (value of currency)
IF p_param_names.COUNT <> p_param_values.COUNT THEN
raise_application_error(-2000,'Incorrect number of arguments');
END IF;
-- use this variable to generate anonymous block code
l_dyn_fun_str:='BEGIN :retval := function_tobe_called (';
-- loop through each parameter and add to the parameter list
FOR i in 1..p_param_names.COUNT LOOP
IF i=0 THEN
l_dyn_fun_str:=l_dyn_fun_str||':'||l_param_name(i);
ELSE
l_dyn_fun_str:=l_dyn_fun_str||','||':'||l_param_name(i);
END IF;
END LOOP;
l_dyn_fun_str:=l_dyn_fun_str||'); END;'
-- open cursor and associate with function call string (l_dyn_fun_str)
vCursor:=DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(vCursor,l_dyn_fun_str);
-- loop through parameter values and associate them with bind variables
DBMS_SQL.BIND_VARIABLE(vCursor,':retval',l_ret_val);
FOR j in 1..p_param_values.COUNT LOOP
DBMS_SQL.BIND_VARIABLE(vCursor, ':'||l_param_names(j), l_param_values(j));
END LOOP;
-- execute function
fdbk := DBMS_SQL.EXECUTE (vCursor);
-- get output of function
DBMS_SQL.VARIABLE_VALUE (vCursor, 'retval', l_ret_val);
RETURN l_ret_val;
END;
Note: The code syntax may not be perfect but the pseudo should still work for your requirement.
Just check the value of P_PARAMNAME against the possible parameter names and branch accordingly. E.g.,
CREATE OR REPLACE PROCEDURE matt_test1 (p_date IN VARCHAR2,
p_name IN VARCHAR2,
p_class IN VARCHAR2,
p_paramname IN VARCHAR2) IS
BEGIN
CASE p_paramname
WHEN 'P_NAME' THEN
DBMS_OUTPUT.put_line ('Did something with P_NAME. Value was ' || p_name);
WHEN 'P_DATE' THEN
DBMS_OUTPUT.put_line ('Did something with P_DATE. Value was ' || p_date);
WHEN 'P_CLASS' THEN
DBMS_OUTPUT.put_line ('Did something with P_CLASS. Value was ' || p_class);
ELSE
raise_application_error (-20001, 'Invalid parameter name: ' || p_paramname);
END CASE;
END matt_test1;
begin
matt_test1(SYSDATE,'Fred','English 101', 'P_DATE');
matt_test1(SYSDATE,'Fred','English 101', 'P_NAME');
matt_test1(SYSDATE,'Fred','English 101', 'P_CLASS');
end;
Output:
Did something with P_DATE. Value was 11-Aug-2015
Did something with P_NAME. Value was Fred
Did something with P_CLASS. Value was English 101

How do I push items into arrays and iterate through them in PL/SQL?

I'm trying to do something very basic in PL/SQL, but I keep getting owned... how do I push items into an array and iterate through them?
Googling it seems to suggest using owa_text.multi_line;
owa_text.multi_line is a record of this type:
/* A multi_line is just an abstract datatype which can hold */
/* large amounts of text data as one piece. */
type multi_line is record
(
rows vc_arr,
num_rows integer,
partial_row boolean
);
To iterate through vc_arr, we have to use l_array.first.. l_array.last. But that gives an error while trying to access it.
Here's a simple sample to find load distinct values into an array:
declare
l_persons owa_text.multi_line := owa_text.new_multi();
/* Documentation of owa_text.new_multi(): Standard "make element" routines. */
--function new_multi return multi_line;
l_value_exists boolean := false;
cursor c_get_orders is
select person,
choice
from my_orders;
begin
for i in c_get_orders loop
l_value_exists := false;
for j in l_persons.rows.first.. l_persons.rows.last loop --Fails here,
--PL/SQL: numeric or value error
if l_persons.rows(j) = i.person then
l_value_exists := true;
exit;
end if;
end loop;
if not l_value_exists then
owa_text.add2multi(i.person, l_persons);
end if;
end loop;
for i in l_persons.rows.first.. l_persons.rows.last loop
write_to_log(l_persons.rows(i));
end loop;
end;
What am I missing? How do I do this?
EDIT: Here's a script to get set up, if it helps follow the example:
create table my_orders
(
person varchar2(4000 byte),
choice varchar2(4000 byte)
);
insert into my_orders
(person, choice)
values
('Tom', 'Juice');
insert into my_orders
(person, choice)
values
('Jane', 'Apple');
insert into my_orders
(person, choice)
values
('Tom', 'Cake');
insert into my_orders
(person, choice)
values
('Jane', 'Chocolate');
insert into my_orders
(person, choice)
values
('Tom', 'Coffee');
commit;
Presumable, the new_multi() method initializes an empty collection.
One of the more esoteric features of Oracle collections is that using FIRST / LAST to iterate over empty collections doesn't work - you have to either check whether the collection is empty, or use 1 .. <collection>.COUNT instead:
declare
type t_number_nt is table of number;
l_numbers t_number_nt := t_number_nt();
begin
-- raises ORA-006502
/* for i in l_numbers.first .. l_numbers.last
loop
dbms_output.put_line(l_numbers(i));
end loop;
*/
-- doesn't raise an error
for i in 1 .. l_numbers.count loop
dbms_output.put_line(l_numbers(i));
end loop;
end;
UPDATE
For a more thorough explanation of techniques for iterating over PL/SQL collections, see this OTN article by Steven Feuerstein
It has to be like this:
declare
l_persons owa_text.multi_line;
begin
OWA_TEXT.new_multi (l_persons);
FOR i IN 1 .. l_persons.num_rows
loop
null;
end loop;
end;

Resources