plsql- Oracle collections - plsql

How to fetch all the column from the table and store into the collection variable.(as single variable)
Hi guys,
please help me to write code among these questions in oracle plsql

"How to fetch all the column from the table and store into the collection variable.(as single variable" - What if your table has millions/billions of rows? You want to flood your PGA memory!
You can use Bulk collect with a limit to fetch a limited number of rows and process them, when processing is complete you may store them somewhere and fetch the next set of rows.
By default, the optimizer puts Limit 100 after the BULK COLLECT clause. The PLSQL_OPTIMIZE_LEVEL needs to be set at level 2 or more.
Here is an example
DECLARE
TYPE yourtable_nt IS TABLE OF yourtable%rowtype;
tab yourtable_nt;
CURSOR cur IS SELECT * FROM yourtable;
BEGIN
OPEN cur;
LOOP
FETCH cur BULK COLLECT INTO tab LIMIT 100; -- FOR EVERY FETCH DATA IS STORED IN tab FROM INDEX 1
FOR i in 1..tab.COUNT LOOP
Null;
-- PROCESS YOUR COLLECTION DATA
END LOOP;
EXIT WHEN cur%NOTFOUND;
END LOOP;
CLOSE cur;
EXCEPTION
WHEN OTHERS THEN
IF cur%ISOPEN THEN
CLOSE cur;
END IF;
END;
/
You may need to find a sweet number for BULK COLLECT LIMIT for your table.
If your table has lots of rows try different numbers for LIMIT otherwise the difference in execution time is insignificant.

The question is too vague to offer a certain answer but this code is the solution to one interpretation of it: "how to populate a PL/SQL collection which matches a table's projection?"
declare
--
-- collection of records which match EMP table
type emp_nt is table of emp%rowtype;
--
-- variable instance of that type
emp_recs emp_nt;
begin
-- use BULK COLLECT clause to populate the collection with records
select e.*
bulk collect into emp_recs
from emp e
where e.deptno = 20;
end;

Related

Reusing large SQL queries in stored procedures

I have a number of long SQL select queries( 150 lines+) that I want to use in a PL/SQL package. The package has procedures to execute the SQL queries and insert the results into a separate table, compare the SQL results to another table, delete rows etc
Its fairly easy to store the SQL results with:
INSERT into TABLE1
SELECT .... (150 line ugly select query goes here)
Problem is, I want to store the select SQL in a cursor/function/view/whatever-works so I don't have to paste the 150 lines query into each procedure where the SQL is used.
I can store the SQL as a cursor then loop through the cursor within a package procedure, fetching each row and eg inserting into my table. But this seems very inefficient considering my only motivation for using a cursor is reducing the amount of lines my package.
Is there a better way to call the SQL select query in different procedures without copying & pasting all 150 lines? If this was a script, I would store the SQL in a text file then just read the text file into a variable and pass the variable to sqlplus when needed. But I'm not very familiar with PL/SQL.
Code:
CREATE OR REPLACE PACKAGE BODY MyPackage
as
Cursor my_cursor
select (150+ lines goes here)
PROCEDURE PopulateTable
is
TYPE fetch_array IS TABLE OF my_cursor%ROWTYPE;
s_array fetch_array;
BEGIN
open my_cursor;
LOOP
FETCH tran_cursor BULK COLLECT INTO s_array;
FORALL counter in 1..s_array.COUNT
INSERT INTO my_table VALUES s_array(counter);
EXIT when s_array%NOTFOUND;
END LOOP;
close my_cursor;
COMMIT;
END PopulateTable;
END MyPackage;
I am not sure if this would be the best way to do, but what came to my mind, is a variable cursor. You could do that using SYS_REFCURSOR. You can build a function that contains your query, and returns ref curosr. In all your procedures, you can just call that function. This will save you writing 150+ lines query in every procedure. More important, it will limit your program to one copy of the query, and therefore easy to maintain.
The function that returns the ref cursor, could be something like this:
CREATE OR REPLACE FUNCTION my_ugly_query()
RETURN SYS_REFCURSOR
AS
my_cursor_ref SYS_REFCURSOR;
BEGIN
OPEN my_cursor_ref FOR
SELECT -- 150+ lines of query;
RETURN my_cursor_ref;
END;
This is how to use it:
CREATE OR REPLACE PACKAGE BODY MyPackage
as
PROCEDURE PopulateTable
IS
l_cur_refcur SYS_REFCURSOR;
s_array fetch_array;
BEGIN
l_cur_refcur := my_ugly_query();
LOOP
FETCH tran_cursor BULK COLLECT INTO s_array;
EXIT when s_array%NOTFOUND;
FORALL counter in 1..s_array.COUNT
INSERT INTO my_table VALUES s_array(counter);
END LOOP;
CLOSE my_cursor;
COMMIT;
END PopulateTable;
END MyPackage;
Create your cursor in the package specification rather than the package body. You may then refer to it from any package procedure/function using package_name.cursor_name

PL/SQL Comparing collections

I'm trying to create a function that compares 2 collections with each other. Let me explain what I'm trying to do first: I have a table for accounts and a table with teachers. I want to use this function to see if there are any accounts that aren't linked to a teacher and delete them. I have most of the function done, but I can't seem to figure out how to delete the account that isn't linked from the database. Does anyone have any ideas? Here's my code:
declare
type type_coll_accnr is table of account.account_id%type;
type type_coll_teachernr is table of teacher.teacher_id%type;
t_teachernr type_coll_teachernr;
t_accnr type_coll_accnr;
begin
select account_account_id
bulk collect into t_teachernr
from teacher;
select account_id
bulk collect into t_accnr
from account
where acces = 'Teacher';
for i_counter IN 1 .. t_teachernr.count
loop
if t_teachernr(i_counter) member of t_accnr then
dbms_output.put_line(t_accnr(i_counter));
else
delete from account where account_id = t_accnr(i_counter);
-- It should delete the account here, but I have no clue how.
end if;
end loop;
end;
I had to translate all of this, so please let me know if I missed something. I'm also pretty new to PL/SQL, so I know this might look like a stupid question!
Ok, I was right - loop should be on t_accnr list. And it should checks if current value from t_accnr is member of t_teachernr.
I create tables and checked it.
t_accnr is large, but it is not a problem
declare
type type_coll_accnr is table of account.account_id%type;
type type_coll_teachernr is table of teacher.teacher_id%type;
t_teachernr type_coll_teachernr;
t_accnr type_coll_accnr;
begin
select account_account_id
bulk collect into t_teachernr
from teacher;
select account_id
bulk collect into t_accnr
from account where acces = 'Teacher';
for i_counter IN 1 .. t_accnr.count
loop
if t_accnr(i_counter) member of t_teachernr then
dbms_output.put_line(t_accnr(i_counter));
else
dbms_output.put_line('delete from account where account_id ='|| t_accnr(i_counter));
delete from account where account_id = t_accnr(i_counter);
-- It should delete the account here, but I have no clue how.
end if;
end loop;
end;
Hope it helps

plsql record type and collection of record type

I have a plsql record type mrec and mreclist is collection of that record. I would like to know if it is possible to add each record into mreclist through one statement. Or is there another efficient way to do the same.
declare
type mrec is record ( a varchar2(10),b varchar2(20));
type mreclist is table of mrec;
r mrec;
rlist mreclist;
begin
rlist:=mreclist();
--insert value
select 'dummy1','dummy2' into r.a,r.b from dual;
--how to copy the above value into the mreclist with one single statement instead of the following statements.
rlist.Extend(1);
rlist(1).a:=r.a;
rlist(1).b:=r.b;
select 'dummy3','dummy4' into r.a,r.b from dual;
rlist.Extend(1);
rlist(2).a:=r.a;
rlist(2).b:=r.b;
end;
Maybe are you looking for a combination of BULK COLLECT and MULTISET operators?
Something like that:
declare
type mrec is record ( a varchar2(10),b varchar2(20));
type mreclist is table of mrec;
r mrec;
rlist mreclist;
tlist mreclist; -- <-- we need two collections here
begin
select 'dummy1','dummy2' bulk collect into rlist from dual;
-- collect initial data into the collection `rlist`
select 'dummy3','dummy4' bulk collect into tlist from dual;
-- collect next data into the other collection `tlist`
rlist := rlist multiset union all tlist;
-- merge `rlist` and `tlist`. Replace the collection `rlist` by the result.
I wouldn't say much about the efficiency of such code. It is probably very dependent of your concrete use case. Please note however that at some point, you hold both rlist and the result of the union in memory. For large collections this could be prohibitive.
For very basic use cases, you probably only need BULK COLLECT and a simple UNION ALL:
select * bulk collect into rlist from (
select 'dummy1','dummy2' from dual
union all select 'dummy3','dummy4'from dual
);
Finally, I would suggest you to take a look at "Taking Up Collections" by Steven Feuerstein for considerations about the result order of multiset operations. Just to quote few words:
Oracle documentation states that the difference between nested tables and varying arrays is that the data stored in a nested table column does not preserve its order, while that order is preserved in a varying array. Prior to Oracle Database 10g, this distinction did not mean much in the PL/SQL world. Now, with the set operators, the special characteristics of a multiset, or nested table, show themselves quite clearly.

How to fetch the cursor from the last fetch in nested cursor?

I am stuck in a situation from which I am finding no solution. Let me give the PL/SQL code:
begin
outer_loop_counter := 0;
inner_loop_counter := 0;
-- Open first cursor
open get_vacancy;
--<<Outer_loop>>
loop
fetch get_vacancy
into v_category, v_gender, v_vacancy_count;
exit when get_vacancy%NOTFOUND;
open get_candidate;
--<<Inner_loop>>
for inner_loop_counter in 1 .. v_vacancy_count
loop
fetch get_candidate
into c_rollno, c_category,c_gender,c_total_marks;
update merit_list m set m.merit_position = inner_loop_counter;
end loop; --Inner_loop;
close get_candidate;
end loop; --Outer_loop;
close get_vacancy;
end;
The above code is for preparing a merit list for an Admission Application for a College which I am Developing using Java and Oracle. Now the policy is depending upon the number of vacancy from each row fetch, the candidates will be allocated a merit rank. Now the problem with the above code is that for every fetch from the get_vacancy cursor, the inner loop cursor is fetching the records from beginning of the resultset. I want that the candidates who were allocated merit rank in the last fetch do not appear in the next fetch when the cursor moves w.r.t get_vacancy cursor. What should I do?
I'll assume the GET_CANDIDATE cursor reads (or can be made to read from) the MERIT_LIST table. Given this, you should be able to add the following to the WHERE clause of your cursor
AND MERIT_LIST.MERIT_POSITION IS NULL
This assumes that MERIT_POSITION is in fact NULL for candidates which have not been assigned a position yet.
Share and enjoy.

Stored procedure to return a list of sequence IDs

I need a little stored procedure to do the following logic?
procedure_name(seq_name IN varchar2(50), block_count IN int, return_ids OUT)
loop from 1 to block_count
return_ids := select 'seq_name'||.nextVal from dual;
end loop
return return_ids
Basically what I want to do is have a stored procedure that lets me pass in a sequence name, how many IDs I need and return to me the generated listed of IDs that I can use in JAVA. The reason to do this for me is to return a list of IDs that I can use in JAVA and no one else is using those sequence IDs. Where they will be used in some other bulk inserts later down the line. In essence, reserve a block of sequence IDs.
Here is one way to return an array from PL/SQL procedure.
Create a collection type of numbers, initialize it in your procedure and populate it with numbers to return. For example:
create or replace type narray as table of number;
create or replace procedure get_seq_ids(seq_name in varchar2,
block_count in number, return_ids out narray)
as
begin
return_ids := narray();
return_ids.extend(block_count);
for i in 1 .. block_count
loop
execute immediate 'select ' || seq_name || '.nextval from dual'
into return_ids(i);
end loop;
end;
/
I'd be concerned over the logic that requires IDs to be generated before records are inserted into the database.
Alternatively you may want to consider inserting rows first, selecting the ids from the rows, and then using an update statement to do your bulk operation. This however is still not as preferable as having the Java code not depend on ids until after the actual information is ready to be inserted.
You can push your information into XML (or any other data format your database can understand) and then call a stored procedure to do the bulk inserts.
Another option may be to use the RETURNING clause to return the sequence values automatically after the insert.
IMHO, the best thing you can do is just referencing sequence_name.nextval right in your INSERT INTO, in the VALUES clause.
You said you want to avoid others using the same IDs. Referencing this site:
The sequence (or Oracle, for that matter) ensures that no other session or other call to nextval within the same session gets the same number from the sequence.
So, the uniqueness of a sequence' numbers are guaranteed in Oracle.
Here's what I do for the Java application I support (which also uses bulk inserts into deeply hierarchical tables)
PROCEDURE get_nextvals
(
p_values OUT SYS_REFCURSOR,
p_count IN PLS_INTEGER
)
IS
-- return the next p_count values from the PK sequence
BEGIN
OPEN p_values FOR
SELECT
<schema>.<sequence>.nextval
FROM
dual
CONNECT BY
LEVEL <= p_count
;
END;
It was easier to just pass a cursor out to java than having the app use a table type defined in the DB.

Resources