Get value from from a json_array in oracle - arrays

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;

Related

Oracle: Insert only unique records into custom record in compound trigger

I am having a compound trigger in the following format. I need to ensure that my custom declaration ints_rows takes only "unique rows". Meaning if the ints_rows already has a record similar to what is being inserted, it should ignore it. More like a SET data structure. How do I do it in oracle? (I am pretty new to oracle, hence I am not great at syntax) I think I have to either change the BULK COLLECT statement or the declaration of int_records but I might be wrong. Any help/hints are much appreciated.
This is my compound trigger code.
CREATE OR REPLACE
TRIGGER MY_COMPOUND_TRIGGER
FOR UPDATE OF some_random_column ON some_random_table
COMPOUND TRIGGER
TYPE int_records IS RECORD (
column_one another_table.column_one%TYPE,
column_two another_table.column_two%TYPE
);
TYPE row_list IS TABLE OF int_records INDEX BY simple_integer;
ints_rows row_list;
BEFORE STATEMENT IS
BEGIN
ints_rows.delete;
END BEFORE STATEMENT;
AFTER EACH ROW IS
BEGIN
SELECT column_one, column_two BULK COLLECT INTO ints_rows
FROM some_table_x WHERE some_col_id=:OLD.some_col_id;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
FOR i IN 1 .. ints_rows.COUNT LOOP
-- DO SOMETHING
END LOOP;
auctions_rows.delete;
END AFTER STATEMENT;
END;
This statement should not be getting duplicates at all.
SELECT column_one, column_two BULK COLLECT INTO ints_rows
FROM some_table_x WHERE some_col_id=:OLD.some_col_id;
In the AFTER EACH ROW part you overwrite entire ints_rows. In principle it could be this one:
CREATE TABLE SOME_RANDOM_TABLE (some_random_column NUMBER, some_col_id NUMBER);
CREATE TABLE ANOTHER_TABLE (column_one NUMBER, column_two NUMBER);
CREATE TABLE SOME_TABLE_X (some_col_id NUMBER, column_one NUMBER, column_two NUMBER);
CREATE OR REPLACE TYPE int_records AS OBJECT (
column_one NUMBER,
column_two NUMBER,
MAP MEMBER FUNCTION getId RETURN VARCHAR2
);
CREATE OR REPLACE TYPE BODY int_records AS
MAP MEMBER FUNCTION getId RETURN VARCHAR2 IS
BEGIN
RETURN column_one ||','|| column_two;
END getId;
END;
/
CREATE OR REPLACE TYPE row_list IS TABLE OF int_records;
CREATE OR REPLACE TRIGGER MY_COMPOUND_TRIGGER
FOR UPDATE OF some_random_column ON SOME_RANDOM_TABLE
COMPOUND TRIGGER
ints_rows row_list;
BEFORE STATEMENT IS
BEGIN
ints_rows := row_list();
END BEFORE STATEMENT;
AFTER EACH ROW IS
int_row row_list;
BEGIN
SELECT int_records(column_one, column_two)
BULK COLLECT INTO int_row
FROM SOME_TABLE_X x
WHERE x.some_col_id = :OLD.some_col_id;
ints_rows := ints_rows MULTISET UNION DISTINCT int_row;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
FOR i IN 1 .. ints_rows.COUNT LOOP
NULL;
END LOOP;
END AFTER STATEMENT;
END;
Please test and let us know if it does not work. You may make the records distinct manually with a loop or you need to implement a MAP MEMBER FUNCTION for the RECORD.

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.

PL/SQL Cursor and Array of different size

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;

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