Oracle pl/sql map with value is array - arrays

I need create associative array where key is VARCHAR(20) and value is array of varchars.
For example:
customer_by_locations: {
"London": {
"John Doe", "Samantha Simpson", "Nicolas Darcula"
},
"Stambul": {
"Abdula Ibn Rahim", "Another Abdula"
}
}
I have created query:
declare
type customers is varray(10) of varchar2(20);
type locations is table of customers index by varchar2(20);
list_locations locations;
list_customers varchar(20);
begin
list_locations('London') := ("John Doe", "Samantha Simpson", "Nicolas Darcula");
list_locations('Stambul') := ("Abdula Ibn Rahim", "Another Abdula");
for i in 1 .. list_locations loop
DBMS_OUTPUT.PUT_LINE('Total ' || list_locations(i));
//Something do
end loop;
end;
But I have error
PLS-00382: expression is of wrong type
Please, tell me, how i will declare array as value and assign values in it in oracle pl/sql.

declare
type customers is varray(10) of varchar2(20);
type locations is table of customers index by varchar2(20);
list_locations locations;
list_customers varchar(20);
v_location varchar2(20);
begin
list_locations('London') := customers('John Doe', 'Samantha Simpson', 'Nicolas Darcula');
list_locations('Stambul') := customers('Abdula Ibn Rahim', 'Another Abdula');
v_location := list_locations.first;
loop
exit when v_location is null;
for i in 1 .. list_locations(v_location).count loop
dbms_output.put_line(v_location||': '||list_locations(v_location)(i));
end loop;
v_location := list_locations.next(v_location);
end loop;
end;
/

Related

postgres - how update an item of jsonb list by date

I have a table.
create table tb(
userid int,
nodes jsonb)
nodes sample:
{
"weight": [{"date":"<date>", value: 50}, {"date":"<date>", value: 60}],
"height": [{"date":"<date>", value: 170}, {"date":"<date>", value: 172}],
}
I want to change the weight of a date in the list of weights.
Or enter a new value in the height list on a specific date. What should I do?
I wrote this. but it is add a new item only.
INSERT INTO tb (UserId, Nodes)
values (1, '{"weight": [{"value":"50","date":"2021-07-24 18:17:33.000"}]}')
ON CONFLICT (UserId) DO UPDATE
SET Nodes = jsonb_set(tb.Nodes, '{"weight"}',
tb.Nodes->'weight' || '{"value":"50","date":"2021-07-24 18:17:43.000"}');
I need to edit a value for a specific date.
I self wrote this.
BEGIN;
LOCK TABLE "UserFitnessData" IN EXCLUSIVE MODE;
do $$
declare today date;
declare r jsonb; -- RECORD
declare idx int :=0;
begin
INSERT INTO "UserFitnessData" (UserId, Nodes)
values (1, '{"FrontImage": [{"value":"50","date":"2021-07-28 18:17:33.000"}]}');
EXCEPTION WHEN unique_violation THEN
today := '2021-07-25 18:17:33.000'::date;
raise notice 'exep >>> %', today;
FOR r IN
SELECT jsonb_array_elements(Nodes->'FrontImage') arr FROM "UserFitnessData" WHERE userid = 1
LOOP
if (r->>'date')::date = today then
UPDATE "UserFitnessData"
SET Nodes = Nodes #- FORMAT('{"FrontImage", %s}', idx)::text[] WHERE userid = 1;
idx := idx-1;
UPDATE "UserFitnessData"
SET Nodes = jsonb_set("UserFitnessData".Nodes, '{"FrontImage"}',
"UserFitnessData".Nodes->'FrontImage' || '{"value":"1245","date":"2021-07-27 18:17:43.000"}');
end if;
idx := idx+1;
END LOOP;
RETURN;
end $$;
COMMIT;
This is what started me right now. If anyone has a shorter solution, tell me.

Use SQL Server table-valued parameters without creating a type on the SQL Server

I'm trying to pass several values on a single parameter (table-valued parameter) and it works fine following the sample
C:\Users\Public\Documents\Embarcadero\Studio\21.0\Samples\Object Pascal\Database\FireDAC\Samples\DBMS Specific\MSSQL\TVP
SQL:
create type TVPType as table(Code integer, Name varchar(100), RegDate datetime, Notes varchar(max))
go
create table TVPTab(Code integer, Name varchar(100), RegDate datetime, Notes varchar(max))
go
Delphi:
procedure TForm1.btnQryManualSetupClick(Sender: TObject);
var
oDS: TFDMemTable;
i: Integer;
begin
Start;
FDQuery2.SQL.Text := 'insert into TVPTab (Code, Name, RegDate, Notes) ' +
'select Code, Name, RegDate, Notes from :t';
oDS := TFDMemTable.Create(nil);
oDS.FieldDefs.Add('Code', ftInteger);
oDS.FieldDefs.Add('Name', ftString, 100);
oDS.FieldDefs.Add('RegDate', ftTimeStamp);
oDS.FieldDefs.Add('Notes', ftMemo);
FDQuery2.Params[0].DataTypeName := 'TVPType';
FDQuery2.Params[0].AsDataSet := oDS;
(FDQuery2.Params[0].AsDataSet as TFDMemTable).EmptyDataSet;
for i := 1 to C_Recs do
with FDQuery2.Params[0].AsDataSet do begin
Append;
Fields[0].AsInteger := i;
Fields[1].AsString := 'str' + IntToStr(i * 10);
Fields[2].AsDateTime := Now() + i;
Fields[3].AsString := StringOfChar('x', 1000);
Post;
end;
FDConnection1.StartTransaction;
FDQuery2.Execute;
FDConnection1.Commit;
Done('TVP Qry manual setup');
end;
The problem with this code is that it forces me to create a type (in this case the TVPType type) on the server, and I would like to use these table-valued parameters without having to create any object on the server.
Can it be done ?. I have tried defining the parameter as ftDataset but I get an error: The data type READONLY cannot be found.
procedure TForm1.btnQryManualSetupClick(Sender: TObject);
var
oDS: TFDMemTable;
i: Integer;
begin
Start;
FDQuery2.SQL.Text := 'insert into TVPTab (Code, Name, RegDate, Notes) ' +
'select Code, Name, RegDate, Notes from :t';
oDS := TFDMemTable.Create(nil);
oDS.FieldDefs.Add('Code', ftInteger);
oDS.FieldDefs.Add('Name', ftString, 100);
oDS.FieldDefs.Add('RegDate', ftTimeStamp);
oDS.FieldDefs.Add('Notes', ftMemo);
//FDQuery2.Params[0].DataTypeName := 'TVPType';
FDQuery2.Params[0].DataType := ftDataset;
FDQuery2.Params[0].AsDataSet := oDS;
(FDQuery2.Params[0].AsDataSet as TFDMemTable).EmptyDataSet;
for i := 1 to C_Recs do
with FDQuery2.Params[0].AsDataSet do begin
Append;
Fields[0].AsInteger := i;
Fields[1].AsString := 'str' + IntToStr(i * 10);
Fields[2].AsDateTime := Now() + i;
Fields[3].AsString := StringOfChar('x', 1000);
Post;
end;
FDConnection1.StartTransaction;
FDQuery2.Execute;
FDConnection1.Commit;
Done('TVP Qry manual setup');
end;
No. If your multiple values are just a single row you can look into passing them as a comma-separated string and then use the STRING_SPLIT function, or using temp or permanent tables to store multi-row values.

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;

How can I use %TYPE with Oracle associative arrays?

I have declared an associative array type in some Oracle package header like:
TYPE ParamArray IS TABLE OF VARCHAR2(4096) INDEX BY VARCHAR2(512);
In my package body I would like to iterate through the array without repeating the string sizes to avoid mismatches when updating the package header.
My try was:
PROCEDURE IterateArray( Params ParamArray )
AS
v_ParamName ParamArray%TYPE;
BEGIN
v_ParamName := Params.First;
WHILE v_ParamName IS NOT NULL LOOP
-- do something with the array entry
v_ParamName := Params.Next(v_ParamName);
END LOOP;
END;
But this didn't work on my Oracle 10g test server.
You can define you own type to handle the varchar2 and the use your type, with no need to repeat the size.
For example:
CREATE OR REPLACE PACKAGE testpck AS
SUBTYPE myType IS VARCHAR2(4096); /* a type for the values */
SUBTYPE myIndexType is VARCHAR2(512); /* a type for the index */
TYPE ParamArray IS TABLE OF myType
INDEX BY myIndexType;
PROCEDURE IterateArray(Params ParamArray);
END;
CREATE OR REPLACE PACKAGE BODY testpck AS
PROCEDURE IterateArray(Params ParamArray) AS
v_ParamName myType;
v_index myIndexType;
BEGIN
v_index := Params.FIRST;
WHILE v_index IS NOT NULL
LOOP
-- do something with the array entry
v_ParamName := Params(v_index);
dbms_output.put_line('value of ' || v_index || ' is ' || v_ParamName);
v_index := Params.NEXT(v_index);
END LOOP;
END;
END;
The call:
declare
myArray testpck.ParamArray;
myValue testpck.myType;
myIndex testpck.myIndexType;
begin
myIndex := 'ONE';
myValue := 'VALUE OF ONE';
myArray(myIndex) := myValue;
--
myIndex := 'TWO';
myValue := 'VALUE OF TWO';
myArray(myIndex) := myValue;
--
testpck.IterateArray(myArray);
end;

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;

Resources