Is this a table of tables in PL/SQL (If not what is going on with this code?) - oracle

Can someone clarify what the PL/SQL code below is doing? It looks as if assets_type is a table of base_Asset. Does that make it a Table of Tables?
I'm having a difficult time visualizing this when it comet to populating the data:
assets(v_ref_key)(dbfields(i).field) := rtrim(replace(strbuf_long2,'''',''''''));
Is this similar to a two dimensional array? Does this mean to populate the field column in the assets (temporary) table with an index of v_ref_key?
PROCEDURE LOAD
IS
TYPE dbfields_rec IS RECORD (field dbfields.field%TYPE,
article_title dbfields.title%TYPE,
image_title dbfields.title%TYPE);
TYPE dbfields_type IS TABLE OF dbfields_rec INDEX BY BINARY_INTEGER;
TYPE base_Asset IS TABLE OF VARCHAR2(4000) INDEX BY VARCHAR2(32);
TYPE assets_type IS TABLE OF asset_type INDEX BY VARCHAR2(4000);
dbfields dbfields_type;
assets assets_type;
v_ref_key assets.ref_key%TYPE;
-- CLIPPED Populate dbfields array code
-- It correctly populates
FOR i IN 1..dbfields.COUNT LOOP
BEGIN
sqlbuf := '(select rtrim(ufld3), ' || dbfields(i).field ||
' as col_label from assetstable ' ||
' where rtrim(ufld3) = ' || '''' || in_id || '''' || ' )';
OPEN assets_cur FOR
sqlbuf;
LOOP
FETCH assets_cur INTO v_ref_key, strbuf_long2;
EXIT WHEN assets_cur%NOTFOUND;
IF (trim(strbuf_long2) is not null and dbfields(i).field is not null) THEN
assets(v_ref_key) (dbfields(i).field)
:= rtrim(replace(strbuf_long2,'''',''''''));
END IF;
END LOOP;
close assets_cur;
END;
END LOOP;
END LOAD;

PL/SQL really only provides one-dimensional arrays - but each element of the array is allowed to be another array if you want to make arrays that act like multi-dimensional ones.
Here is an awfully contrived example to illustrate:
DECLARE
TYPE rows_type IS TABLE OF VARCHAR2(4000) INDEX BY VARCHAR2(4000);
TYPE spreadsheet_type IS TABLE OF row_type INDEX BY VARCHAR2(4000);
spreadsheet spreadsheet_type;
BEGIN
spreadsheet ('row 1') ('column A') := 'XYZ';
END;
The first ('row 1') is the index into the spreadsheet_type array, which would hold all the columns for a particular "row"; and the second ('column A') is the index into the rows_type array.
The "multi-dimensional" aspect of this implementation isn't perfect, however: while you can work with a whole row if you want, e.g.:
my_row rows_type;
...
my_row := spreadsheet ('row 1');
you can't pick out a particular column - there's no way to refer to the set of all elements in the rows for a particular index into rows_type. You'd have to do something like make another type and loop through the first array.

Related

Sorting Data at the runtime in Oracle PL/SQL using Associative array

I am trying to sort a data from runtime query and no way I can do that without use of a collection. Essentially, I have 2 columns in Service table - SERVICE_ID, SERVICE_NAME. I have created an Associative array in a package so that I can use it in my procedure.
TYPE g_vc_arr IS TABLE OF VARCHAR2(4000) INDEX BY PLS_INTEGER;
In my pl/sql block I am creating a variable like below:
service_list my_pkg.g_vc_arr;
I am assigning the SERVICE_ID and SERVICE_NAME to this variable like below:
LOOP
service_list(services.SERVICE_ID) := services.SERVICE_NAME;
END LOOP;
Now, I am using this to sort the name column like below snippet. I am able to print the name but in this process, I am losing the ID.
for query_result_row in (SELECT * from table(service_list) order by 1) loop
dbms_output.put_line(query_result_row.COLUMN_VALUE);
end loop;
I need both ID and NAME to process further. How can I get both?
As others have mentioned, you don't need to sort it as it is already held in the order you defined it:
create or replace package demo
as
type currency_tt is table of varchar2(3) index by pls_integer;
currencies demo.currency_tt :=
demo.currency_tt
( 1 => 'USD'
, 2 => 'GBP'
, 3 => 'EUR' );
end demo;
begin
for r in (
select * from table(demo.currencies)
)
loop
dbms_output.put_line(r.column_value);
end loop;
end;
Output:
USD
GBP
EUR
But you don't need SQL, as you can iterate over an associative array procedurally:
declare
i pls_integer := demo.currencies.first;
begin
while i is not null loop
dbms_output.put_line(i||' '||demo.currencies(i));
i := demo.currencies.next(i);
end loop;
end;
Or from 21c you can loop more conveniently like this:
begin
for id, ccy in pairs of demo.currencies loop
dbms_output.put_line(id || ' '|| ccy);
end loop;
end;
1 USD
2 GBP
3 EUR
I changed my approach a bit to solve this issue. I created a package with 2 column TYPE as record and an associative array of the record type:
create or replace package my_pkg is
TYPE g_rec_type IS RECORD (ID NUMBER, name VARCHAR2(4000));
TYPE g_rec_arr IS TABLE OF g_rec_type INDEX BY PLS_INTEGER;
end my_pkg;
In my actual PL/sqL block I created couple of variables:
i NUMBER := 0;
service_list MY_PKG.g_rec_arr;
Then I created a loop to assign the values to array:
LOOP
service_list(i).ID := services.SERVICE_ID;
service_list(i).NAME := services.SUMMARY;
i := i+1;
END LOOP;
Further to sort the value I used following:
FOR query_result_row in (SELECT ID, NAME from table(service_list) order by NAME) LOOP
dbms_output.put_line('ID is =>' || query_result_row.ID|| ' Name is =>' || query_result_row.NAME);
END LOOP;
And it worked as expected.

How to access and query objects passed as parameter to a procedure while converting from Oracle to postgresql

I have a procedure in Oracle that I need to convert to Postgresql and need help on it. It paases a collection of objects in a procedure.The procedure then checks if each object is present in a database table or not and if present it gives a message that , that specific element is found/present. if some element that is paassed to the procedure is not present in the table, the procedure just doesnt do anything. I have to write equivalent of that in postgresql. I think the heart of the issue is this statement:
SELECT COUNT (*)
INTO v_cnt
FROM **TABLE (p_cust_tab_type_i)** pt
WHERE pt.ssn = cc.ssn;
In Oracle a collection can be treated as a table and one can query it but I dont know how to do that in postgresql. The code to create the table, add data, create the procedure, call the procedure by passing the collection (3 objects) and output of that is posted below. Can someone suggest how this can be done in postgresql?
Following the oracle related code and details:
--create table
create table temp_n_tab1
(ssn number,
fname varchar2(20),
lname varchar2(20),
items varchar2(100));
/
--add data
insert into temp_n_tab1 values (1,'f1','l1','i1');
--SKIP no. ssn no. 2 intentionally..
insert into temp_n_tab1 values (3,'f3','l3','i3');
insert into temp_n_tab1 values (4,'f4','l4','i4');
insert into temp_n_tab1 values (5,'f5','l5','i5');
insert into temp_n_tab1 values (6,'f6','l6','i6');
commit;
--create procedure
SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE temp_n_proc (
p_cust_tab_type_i IN temp_n_customer_tab_type)
IS
t_cust_tab_type_i temp_n_customer_tab_type;
v_cnt NUMBER;
v_ssn temp_n_tab1.ssn%TYPE;
CURSOR c
IS
SELECT ssn
FROM temp_n_tab1
ORDER BY 1;
BEGIN
--t_cust_tab_type_i := p_cust_tab_type_i();
FOR cc IN c
LOOP
SELECT COUNT (*)
INTO v_cnt
FROM TABLE (p_cust_tab_type_i) pt
WHERE pt.ssn = cc.ssn;
IF (v_cnt > 0)
THEN
DBMS_OUTPUT.put_line (
'The array element '
|| TO_CHAR (cc.ssn)
|| ' exists in the table.');
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE (SQLERRM);
END;
/
--caller proc
SET SERVEROUTPUT ON
declare
array temp_n_customer_tab_type := temp_n_customer_tab_type();
begin
for i in 1 .. 3
loop
array.extend;
array(i) := temp_n_cust_header_type( i, 'name ' || i, 'lname ' || i,i*i*i*i );
end loop;
temp_n_proc( array );
end;
/
caller proc output:
The array element 1 exists in the table.
The array element 3 exists in the table.
When you create a table in Postgres, a type with the same name is also created. So you can simply pass an array of the table's type as a parameter to the function.
Inside the function you can then use unnest() to treat the array like a table.
The following is the closest match to your original Oracle code:
create function temp_n_proc(p_cust_tab_type_i temp_n_tab1[])
returns void
as
$$
declare
l_rec record;
l_msg text;
l_count integer;
BEGIN
for l_rec in select t1.ssn
from temp_n_tab1 t1
loop
select count(*)
into l_count
from unnest(p_cust_tab_type_i) as t
where t.ssn = l_rec.ssn;
if l_count > 0 then
raise notice 'The array element % exist in the table', l_rec.ssn;
end if;
end loop;
END;
$$
language plpgsql;
The row-by-row processing is not a good idea to begin with (neither in Postgres, nor in Oracle). It would be a lot more efficient to get the existing elements in a single query:
create function temp_n_proc(p_cust_tab_type_i temp_n_tab1[])
returns void
as
$$
declare
l_rec record;
l_msg text;
BEGIN
for l_rec in select t1.ssn
from temp_n_tab1 t1
where t1.ssn in (select t.ssn
from unnest(p_cust_tab_type_i) as t)
loop
raise notice 'The array element % exist in the table', l_rec.ssn;
end loop;
return;
END;
$$
language plpgsql;
You can call the function like this:
select temp_n_proc(array[row(1,'f1','l1','i1'),
row(2,'f2','l2','i2'),
row(3,'f3','l3','i3')
]::temp_n_tab1[]);
However a more "Postgres" like and much more efficient way would be to not use PL/pgSQL for this, but create a simple SQL function that returns the messages as a result:
create or replace function temp_n_proc(p_cust_tab_type_i temp_n_tab1[])
returns table(message text)
as
$$
select format('The array element %s exist in the table', t1.ssn)
from temp_n_tab1 t1
where t1.ssn in (select t.ssn
from unnest(p_cust_tab_type_i) as t)
$$
language sql;
This returns the output of the function as a result rather than using the clumsy raise notice.
You can use it like this:
select *
from temp_n_proc(array[row(1,'f1','l1','i1'),
row(2,'f2','l2','i2'),
row(3,'f3','l3','i3')
]::temp_n_tab1[]);

oracle bulk collect and reading the data

I have created below proc to read all the data from one table and populate it in a grid in .net form.
CREATE OR REPLACE PROCEDURE EVMPDADM.GETALLBATCHES_ARTICLE_57(p_batchstatus OUT XEVMPD_SUBMITTEDBATCH%ROWTYPE )
IS
TYPE batch_status IS TABLE OF XEVMPD_SUBMITTEDBATCH%ROWTYPE INDEX BY PLS_INTEGER;
l_batchstatus batch_status;
BEGIN
SELECT * BULK COLLECT INTO l_batchstatus FROM XEVMPD_SUBMITTEDBATCH ;
FOR i IN 1..l_batchstatus.count LOOP
p_batchstatus:= l_batchstatus(i);
END LOOP;
END GETALLBATCHES_ARTICLE_57;
To test if the proc is running fine I tried to print the data by using below Pl-sql block:
DECLARE
v_batchstatus XEVMPD_SUBMITTEDBATCH%ROWTYPE;
BEGIN
EVMPDADM.GETALLBATCHES_ARTICLE_57(v_batchstatus);
DBMS_OUTPUT.PUT_LINE( v_batchstatus.Batch_id || ' ' || v_batchstatus.BATCH_DESCRIPTION || ' ' || v_batchstatus.STATUS || ' ' ||v_batchstatus.RECORD_STATUS || ' ' ||v_batchstatus.NUMBER_OF_RECORDS);
END;
/
But from this process I am getting the last row only.
I want to print all the records present in the table.
can any one please help me to figure out what is wrong in the above code.
The error messages are very obvious. You are calling your procedures with:
Wrong number of arguments for EVMPDADM.GETALLBATCHES_ARTICLE_57: It has one OUT parameter. So you need to pass that parameter.
Wrong type of argument for DBMS_OUTPUT.PUT_LINE: It has one IN VARCHAR2 parameter, and not XEVMPD_SUBMITTEDBATCH%ROWTYPE. Read here
So, it should be this way:
DECLARE
v_batchstatus XEVMPD_SUBMITTEDBATCH%ROWTYPE;
BEGIN
v_batchstatus:= EVMPDADM.GETALLBATCHES_ARTICLE_57(v_batchstatus);
--use DBMS_OUTPUT.PUT_LINE for every column of XEVMPD_SUBMITTEDBATCH separately after you convert them to varchar2 if they are not.
END;
/
Besides, the procedure this way will return only the last row. So you might want to change that.
If you want to print all the records from the table, you need to add DBMS_OUTPUT.PUT_LINE inside the loop, it will become like this:
FOR i IN 1..l_batchstatus.count LOOP
p_batchstatus:= l_batchstatus(i);
dbms_output.put_line( p_batchstatus.col1 || ' ' || p_batchstatus.col2 || ... );
END LOOP;
Where col1, col2, ... are the columns names of XEVMPD_SUBMITTEDBATCH given they are of the type VARCHAR2. Or you will need extra processing

ROWID in casted pl-sql collection

Is there any rowid like facility in a pl-sql collection? In my case, while I am using this collection in an sql query, I also need the sequence number as they are put in. I know modification is data struecture is a way, but I want to use the index of the collection. so what I am looking for is something like this:
TYPE t_List IS TABLE OF VARCHAR2(200);
and
declare
v_Data t_List := t_List('data 1'
,'data_2'
,'data3');
......
FOR Rec IN (SELECT Column_Value v
,ROWID r
FROM TABLE(CAST(v_data t_list)))
LOOP
Dbms_Output.Put_Line('at ' || Rec.r || ':' || Rec.v);
-- .... and other codes here
END LOOP;
The loop is not expected to be executed in sequence, but I want something built-in like ROWID that is like the index of the collection.
Only schema-level types can be used in SQl statement, even within a PL/SQL block. As you seem to suggest you already know, you can create your own object type that includes the 'sequence' ID:
CREATE TYPE t_object AS OBJECT (
id NUMBER,
data VARCHAR2(200)
)
/
And a collection of that type:
CREATE TYPE t_List IS TABLE OF t_object;
/
And then populate the ID as you build the list:
DECLARE
l_List t_List := t_List(t_object(1, 'data 1')
,t_object(2, 'data_2')
,t_object(3, 'data3'));
BEGIN
FOR Rec IN (SELECT id, data
FROM TABLE(l_list))
LOOP
Dbms_Output.Put_Line('at ' || Rec.id || ':' || Rec.data);
-- .... and other codes here
END LOOP;
END;
/
Without an object type you can use the ROWNUM pseudocolumn:
CREATE TYPE t_List IS TABLE OF VARCHAR2(200);
/
DECLARE
v_Data t_List := t_List('data 1'
,'data_2'
,'data3');
BEGIN
FOR Rec IN (SELECT Column_Value v
,ROWNUM r
FROM TABLE(v_data))
LOOP
Dbms_Output.Put_Line('at ' || Rec.r || ':' || Rec.v);
-- .... and other codes here
END LOOP;
END;
/
anonymous block completed
at 1:data 1
at 2:data_2
at 3:data3
As far as I'm aware that isn't guaranteed to preserve the original creation sequence. I think it almost certainly will at the moment, but perhaps isn't something you should rely on as always being true. (There is no order without an order by, but here you don't have anything you can order by without destroying your initial order...).
If you query a subset of the table - I'm not sure what you mean by "the loop is not expected to be executed in sequence" - you'd need to generate the ROWNUM in a subquery before filtering or it won't be consistent. You'd also need to generate the ROWNUM in a subquery if you're joining this to other, real, tables - I imagine you are, otherwise you could use a PL/SQL collection.
If indexing is important, use Associative Arrays(Also known as Index-by tables)
Refer his.

wrong updation of data using collection but insertion is possible

create or replace function get_ware_house_branch(p_WAREHOUSE_IDS in varchar2,
p_PLACE_ID in varchar2)
return id_warehouse_list
is
l_warehouse_list id_warehouse_list := id_warehouse_list();
str varchar2(300);
begin
str := 'SELECT BRANCH_WAREHOUSE(w.wh_id, w.wh_name)
FROM POD_WAREHOUSE_MASTER W
where ( W.wh_id IN (' ||p_WAREHOUSE_IDS || '))';
execute immediate str bulk collect into l_warehouse_list;
for i in l_warehouse_list.first..l_warehouse_list.last loop
dbms_output.put_line(l_warehouse_list(i).wh_id || ', ' || l_warehouse_list(i).wh_name);
/*update pod_place_warehouse_mapping_tb
set wh_id = l_warehouse_list(i).wh_id
where place_id = p_PLACE_ID ;*/
insert into pod_place_warehouse_mapping_tb (id,place_id ,wh_id )
values
(POD_UNIQUE_VAL_SEQ.NEXTVAL ,p_PLACE_ID,l_warehouse_list(i).wh_id);
end loop;
commit;
return l_warehouse_list;
end;
The result you're seeing is exactly what the commented-out UPDATE statement says you want to do. In the update case you could replace the loop with a single update statement:
UPDATE POD_PLACE_WAREHOUSE_MAPPING_TB
SET WH_ID = l_warehouse_list(l_warehouse_list.LAST).WH_ID
WHERE PLACE_ID = p_PLACE_ID
The loop and the UPDATE statement above will produce the exact same results. In the loop case, you're updating all rows in POD_PLACE_WAREHOUSE_MAPPING_TB with each value from l_warehouse_list, one after the other. When the loop is complete all rows in POD_PLACE_WAREHOUSE_MAPPING_TB which have PLACE_ID = p_PLACE_ID will have their WH_ID column set to the WH_ID value from the l_warehouse_list element with the highest index.
I have no idea what other result you're expecting. I think you need to re-think what it is you're trying to do.
Best of luck.

Resources