FORALL loop - select statement from associative array - plsql

I have a function, which accepts associative array as parameters. Function - before insert/update - needs to check if records exists in table, so I need to loop over array.
Since FORALL doesn't allow SELECT statement, what is the right way to do this ?
Function (just the part I need to fix - It's an example!!):
type t_name is table of MySchema.Orders.NAME%type index by pls_integer;
type t_year is table of MySchema.Orders.YEAR%type index by pls_integer;
type t_month is table of MySchema.Orders.MONTH%type index by pls_integer;
FUNCTION Check_records (name_in IN t_name, year_in IN t_year, month_in IN t_month) RETURN INTEGER
IS
record_exists INTEGER;
BEGIN
FORALL i in name_in.FIRST..name_in.LAST
SELECT COUNT(*) INTO record_exists
FROM MySchema.Orders#link_to_table
WHERE name= name_in(i)
AND year= year_in(i)
AND month= month_in(i);
return record_exist
END Check_records;

The simplest approach would simply be to use a for loop
DECLARE
l_total_records pls_integer;
l_count pls_integer;
BEGIN
l_total_records := 0;
FOR i in name_in.FIRST..name_in.LAST
LOOP
SELECT COUNT(*)
INTO l_count
FROM MySchema.Orders#link_to_table
WHERE name= name_in(i)
AND year= year_in(i)
AND month= month_in(i);
l_total_count := l_total_count + l_count;
END LOOP;
END;
If you were querying a local table and you could use a single collection (i.e. a collection of an object type with name, year, and month fields), you could do something more elegant with a MEMBER OF function but I'm not sure that works over a database link and don't have one available to test at the moment.

Related

PLS-00382: expression is of wrong type in populating an associative array table

I have this structure of table in my database
Now i want all the values inside of it in a associative array that is indexed by the job_id%type which is a varchar2, so i created this anonymous block first before creating a procedure to test it and i want to populate my associative array with the results:
DECLARE
TYPE jobs_tab_type IS TABLE OF jobs%rowtype INDEX BY jobs.job_id%type;
jobstab jobs_tab_type;
BEGIN
FOR rec IN (SELECT * FROM jobs)
LOOP
jobstab(rec.job_id) := rec.job_id;
END LOOP;
END;
I know for sure that this is a wrong way to do it since I've encountered this error: PLS-00382: expression is of wrong type , but at the same time i don't know if there is any proper way to do this. I have seen the documentation but all the examples were to use a user defined string that will hold the job_id, but the problem is that i need the job_id to be indexed and not a user defined string. Is there any way to fix this problem in PL/SQL?
You don't need the index by INDEX BY jobs.job_id%type. It is making your code more complicated. The INDEX BY determines the index of the collection - that is the character used to indicate the position in the collection. That doesn't need to be a column from the table you are taking a %rowtype from.
Typically an INDEX BY VARCHAR2 is used if you want to reference the collection based on a meaningful string, like capitals('United States') = 'Washingston'
Now, about your question. Here is the answer to the original question:
create table jobs
(job_id VARCHAR2(10)
,job_title VARCHAR2(30)
,min_salary NUMBER
,max_salary NUMBER
);
INSERT INTO jobs (job_id, job_title, min_salary, max_salary) VALUES ('a','CLERK',100,500);
INSERT INTO jobs (job_id, job_title, min_salary, max_salary) VALUES ('b','PRESIDENT',1000,5000);
INSERT INTO jobs (job_id, job_title, min_salary, max_salary) VALUES ('c','SALESMAN',500,800);
DECLARE
TYPE jobs_tab_type IS TABLE OF jobs%rowtype INDEX BY jobs.job_id%type;
jobstab jobs_tab_type;
l_idx VARCHAR2(100);
BEGIN
FOR rec IN (SELECT * FROM jobs)
LOOP
jobstab(rec.job_id) := rec;
END LOOP;
l_idx := jobstab.FIRST;
WHILE (l_idx IS NOT NULL) LOOP
dbms_output.put_line(l_idx ||': '||jobstab(l_idx).job_title);
l_idx := jobstab.NEXT(l_idx);
END LOOP;
END;
/
a: CLERK
b: PRESIDENT
c: SALESMAN
Notice that in this block, it's a bit tedious to loop through the elements, just because the index is not an integer. Now lets look at the same functionality, but using an INDEX BY BINARY_INTEGER:
DECLARE
TYPE jobs_tab_type IS TABLE OF jobs%rowtype INDEX BY BINARY_INTEGER;
jobstab jobs_tab_type;
l_idx NUMBER := 1;
BEGIN
-- example 1: using a cursor for loop
-- FOR rec IN (SELECT * FROM jobs)
-- LOOP
-- jobstab(l_idx) := rec;
-- l_idx := l_idx + 1;
-- END LOOP;
-- example 2: using bulk collect
SELECT * BULK COLLECT INTO jobstab FROM jobs;
FOR r IN 1 .. jobstab.COUNT LOOP
dbms_output.put_line(r ||': '||jobstab(r).job_title);
END LOOP;
END;
/
1: CLERK
2: PRESIDENT
3: SALESMAN
It's simpler to loop through the values of a collection because of the numeric index and it also can be implicitly assigned as well, using the BULK COLLECT statement.
The value of your associative array is the entire record. Hence just remove .job_id from this line of your code.
jobstab(rec.job_id) := rec.job_id;
In other words, the line should be
jobstab(rec.job_id) := rec;
Refer to this db<>fiddle

multiset union distinct gives "wrong number of types or arguments passed" error

I am using a for loop to pass different values to a cursor, bulk collect the data and append it to the same nested table using MULTISET UNION operator. However, to avoid duplicate data, i tried to use MULTISET UNION DISTINCT and it throws the error PLS-00306: wrong number or types of arguments in call to 'MULTISET_UNION_DISTINCT' The codes works well without DISTINCT. Please let me know if i'm missing anything here.
I'm using Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
My code is as follows:
DECLARE
TYPE t_search_rec
IS
RECORD
(
search_id VARCHAR2(200),
search_name VARCHAR2(240),
description VARCHAR2(240) );
TYPE t_search_data
IS
TABLE OF t_search_rec;
in_user_id NUMBER;
in_user_role VARCHAR2(20);
in_period VARCHAR2(20) := 'Sep-15';
in_search_string VARCHAR2(20) := 'v';
l_search_tt t_search_data;
x_search_tt t_search_data;
v_entity_gl VA_LOGIN_API.t_entity_list ;
X_RETURN_CODE VARCHAR2(20);
X_RETURN_MSG VARCHAR2(2000);
CURSOR c_vendors_gl(v_entity VARCHAR2)
IS
SELECT UNIQUE vendor_id,
vendor,
NULL description
FROM XXPOADASH.XX_VA_PO_LINES
WHERE period = in_period
AND entity = v_entity
AND upper(vendor) LIKE upper(in_search_string)
||'%';
BEGIN
in_user_role := 'ROLE';
in_user_id := 4359;
VA_LOGIN_API.DATA_ACCESS_PROC( X_RETURN_CODE => X_RETURN_CODE, X_RETURN_MSG => X_RETURN_MSG, IN_PERSON_ID => in_user_id, IN_PERSON_ROLE => in_user_role, X_ACCESSED_ENTITY_LIST => v_entity_gl );
IF( v_entity_gl.COUNT >0) THEN
x_search_tt := t_search_data();
FOR I IN v_entity_gl.FIRST..v_entity_gl.COUNT
LOOP
OPEN c_vendors_gl(v_entity_gl(i));
FETCH c_vendors_gl BULK COLLECT INTO l_search_tt;
CLOSE c_vendors_gl;
x_search_tt := x_search_tt MULTISET
UNION DISTINCT l_search_tt;
l_search_tt.delete;
END LOOP;
IF x_search_tt.count = 0 THEN
x_return_msg := 'No lines found';
END IF;
END IF;
DBMS_OUTPUT.PUT_LINE(x_return_msg);
DBMS_OUTPUT.PUT_LINE(x_search_tt.count);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(x_return_msg);
DBMS_OUTPUT.PUT_LINE(x_search_tt.count);
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
multiset union distinct requires the elements of the collection to be comparable. In your case the elements are PL/SQL records that are unfortunately not comparable data structures (i.e. PL/SQL provides no build-in mechanism to compare PL/SQL records).
multiset union works because it doesn't need to compare the elements.
One possible workaround is to use Oracle object type instead of PL/SQL record. Object type allows you to implement a comparison method required by multiset union distinct.

Assigning object type in plsql

I need your help to know how to assign the object type through a string in PLSQL
Below is the problem description:
I first created the object types as below:
create or replace type picu_obj is object(Customer_ID varchar2(32767),Customer_Name varchar2(32767),Server_Name varchar2(32767),Time_stamp varchar2(32767));
create or replace type picu_obj_tab is table of picu_obj;
and I have a PLSQL block as below:
declare
l_str1 varchar2(1000);
l_str2 varchar2(10000);
l_newstr1_1 varchar2(10000);
picu_var picu_obj_tab;
cursor c1cudetails
is
select item,current_value
from
(select rownum,
last_value(category ignore nulls) over (order by rownum) category ,
last_value(item ignore nulls) over (order by rownum) item,
current_value
from pi_perfdata_new
order by rownum
)
where upper(category) like '%CUSTOMER%DETAILS%' ;
type cudet is table of c1cudetails%rowtype index by pls_integer;
l_cudet cudet;
begin
/* create dynamic string for items */
open c1cudetails;
fetch c1cudetails bulk collect into l_cudet limit 50;
for i in l_cudet.first..l_cudet.last loop
l_str1:=l_str1||','||''''||l_cudet(i).current_value||'''';
l_str2:=trim(leading ',' from l_str1);
l_newstr1_1:='picu_obj_tab(picu_obj('||l_str2||'))';
end loop;
-- dbms_output.put_line(''||l_newstr1_1||'');
-- picu_var := l_newstr1_1;
close c1cudetails;
end;
For the string "l_newstr1_1" following value is retruned from above PLSQL block
picu_obj_tab(picu_obj('CSCO5','DXRTYE','PI22-pro-333','2015-07-22-22:48:56'))
Now I want to assign the above result to variable "picu_var" which I have declared.
Basically I need to convert to the following during runtime.
picu_var := picu_obj_tab(picu_obj('CSCO5','DXRTYE','PI22-pro-333','2015-07-22-22:48:56'))
How to achieve the same?
Please suggest how to initialize the object type variable to the string values.
Use dynamic PL/SQL like this:
execute immediate 'begin :x := ' || l_newstr1_1|| '; end;'
using out picu_var;

Convert stored proc to table to be able to use in select statement

I have to use existing stored procedure which returns REF CURSOR. I need to insert that resultset into a temporary table.
Spec of procedure is:
TYPE cur IS REF CURSOR;
PROCEDURE get(p_one NUMBER ,p_two OUT cur);
How can I insert the resultset of this procedure into a table.
I just re-read the title of your question. Do you actually need to be able to select from a procedure?
If so, this can be achieved by using pipelined functions.
The process for this is:
Create an object type to represent the record-type you require.
Create a nested table type of the object.
Create a pipelined function which returns the nest table.
You can then select from this function.
This example should get you on your way:
create or replace type to_test as object (
val1 varchar2(32),
val2 varchar2(32)
);
create or replace type tt_test as table of to_test;
create or replace function demo_pipe return tt_test pipelined
is
v_test to_test;
begin
for rec in (select * from user_tables) loop
v_test := to_test(rec.table_name, rec.tablespace_name);
pipe row (v_test);
end loop;
end;
/
select * from table(demo_pipe);

Oracle 11g : When declaring new TYPE as TABLE, must I add "INDEX BY PLS_INTEGER"?

What is the diffecence between adding INDEX BY PLS_INTEGER and not at end of declaration of new table type. Look at this example:
DECLARE
GC_BULK_LIMIT CONSTANT INTEGER := 500;
CURSOR CUR_CLIENTS IS SELECT C.ID, C.NAME FROM CLIENTS C;
TYPE RT_CLIENTS IS TABLE OF CUR_CLIENTS%ROWTYPE;
-- TYPE RT_CLIENTS IS TABLE OF CUR_CLIENTS%ROWTYPE INDEX BY PLS_INTEGER;
LT_CLIENTS RT_CLIENTS;
BEGIN
OPEN CUR_CLIENTS;
LOOP
FETCH CUR_CLIENTS BULK COLLECT INTO LT_CLIENTS LIMIT GC_BULK_LIMIT;
EXIT WHEN LT_CLIENTS.COUNT = 0;
FOR I IN 1..LT_CLIENTS.COUNT LOOP
-- ... SOME LOGIC
END LOOP;
END LOOP;
CLOSE CUR_CLIENTS;
END;
in response to "must i add". The short answer is NO.
the difference is that
TYPE RT_CLIENTS IS TABLE OF CUR_CLIENTS%ROWTYPE;
Is a nested table. This means that for a given variable of this type, we know that the subscripts are sequential. i.e. the subscript starts from 1 and goes up to the array length.
The following loop therefore, is the right way to access a nested table array:
FOR I IN 1..LT_CLIENTS.COUNT LOOP
This however, is called a associative array:
TYPE RT_CLIENTS IS TABLE OF CUR_CLIENTS%ROWTYPE INDEX BY PLS_INTEGER;
(you could also index by a varchar2 if you wanted). The difference is that the subscripts in this case do not have to be sequential, depending on how the array was populated. In your code, they would be (as bulk collect would do that), but its not always the case.
The safe way to access and loop through an index by array is :
v_subscript := t_arr.first;
while v_subscript is not null loop
dbms_output.put_line(v_subscript || ': ' || t_arr(v_subscript));
v_subscript := t_arr.next(v_subscript);
end loop;
where v_subscript is a variable of the same datatype of the index by part.
also with a nested table, you can populate the array quickly with:
declare
type myarr is table of number;
t_arr myarr;
v_subscript number;
begin
t_arr := myarr(1, 12, 44);
whereas with an index by array you'd have to have three lines there to populate it:
t_arr(1):= 1;
t_arr(2):= 12;
t_arr(3):= 44;
for your particular case, without the index by is perfectly fine.
further reading: http://docs.oracle.com/cd/E18283_01/appdev.112/e17126/composites.htm

Resources