PL/SQL select into a nested array - oracle

I'm trying to select values from my table and stock them into an array to manipulate them. this is what i have done.
DECLARE
TYPE student IS TABLE OF VARCHAR2(20);
s student := student();
n number := 1;
BEGIN
FOR i IN (SELECT name from HR.STUDENT) loop
s(n) := i.name;
n := n + 1;
end loop;
end;
when i do this i get this error in SQL Developer
An in-limit subscript was greater than the count of a varray
or too large for a nested table.
So i'm asking is this the right way to get a my table values into the student Type? any hints ?
i'm new to PL/SQL so i maybe saying wrong things. correct me when i do so. Thank you.

Any easier way to accomplish this would be:
SELECT name
BULK COLLECT INTO s
FROM HR.STUDENT;
With no need for the loop at all.
The reason the error occurs (I think) is that you are trying to access and index of the table which is not yet available. I believe you need to manually call extend prior to the access, which would then make the next index of the array available e.g.
FOR i IN (SELECT name from HR.STUDENT)
LOOP
s.extend;
s(n) := i.name;
n := n+1;
END LOOP;
Also I believe you need to have s students := students(); - you seem to be missing a colon.

Related

Preview the order of the unsorted cursor query in oracle

I have this simple oracle plsql procedure:
declare
cursor A is
select column_A
from A_TAB; -- no order by
begin
for rec_ in A loop
procedure_A(rec_.column_A);
end loop;
end;
And this is running now for ages.
When I look into sys.v_$sql_bind_capture, value_string column, I can see the current value of bound column_A, and thankfully, that value keeps changing every few minutes.
As the cursor was not sorted by anything, is there a way to see how many more records to go (until this is finished)?
In other words I would need to see the currently fetched values of the query from that cursor. Where to look for it?
This is Oracle 12 database.
You can use dbms_application_info.set_session_longops to do this. The results are visible in V$SESSION_LONGOPS.
In your example, that could do something like:
DECLARE
rindex BINARY_INTEGER;
slno BINARY_INTEGER;
totalwork number;
sofar number;
obj BINARY_INTEGER;
cursor A is
select column_A,
COUNT(*) OVER () cnt
from A_TAB; -- no order by
begin
rindex := dbms_application_info.set_session_longops_nohint;
sofar := 0;
for rec_ in A loop
totalwork := rec_.cnt;
sofar := sofar + 1;
dbms_application_info.set_session_longops(rindex,
slno,
'Process a_tab',
'A_TAB',
0,
sofar,
totalwork,
'table',
'rows');
procedure_A(rec_.column_A);
end loop;
end;
Note that in order to get the totalwork value, I've used the analytic COUNT() function to get the total number of rows within the resultset. You could run a separate query to get the count before looping through your original cursor, if that is faster. You'd have to test both methods to work out which would be fastest for your data etc.
Of course, depending on what procedure_a does, you might be able to avoid the need to monitor the progress if you can refactor things so that all the work is being done in a single SQL statement. My answer above assumes that it's not possible to do that. If it is, I highly recommend you refactor your code instead!

PL SQL Compund Triggers on Batch Inserts

I've written a compound trigger to fire on inserts. Multiple inserts are batched together and sent to the DB where the compound trigger picks it up. My problem is that i need to perform an update query on the same table for certain inserts depending on the data provided by the query. I can't run a row level action since that would result in a mutating trigger table error (ORA-4091). Best thing i could think of was to have the update query in the before or after statement blocks. i cannot have it on the before statement block since each update is dependent on individual inserts and there's no way of knowing the values before actually reaching that query. so i created a "Type" table and updated it before each row is modified and then later at the after statement block i iterate through the Type table and perform update queries using the data on the table. No matter what i tried the After statement block will only perform update queries for the last insert only.
TYPE apple IS RECORD ( v_size apple_t.size%Type, v_color apple_t.color%Type);
TYPE t_apple IS TABLE OF apple INDEX BY VARCHAR2(20);
BEFORE ROW
t_apple(key).v_size := :New.size;
t_apple(key).v_color := :New.color;
END BEFORE ROW
AFTER STATEMENT
Iterator := t_apple.First;
LOOP EXIT WHEN ITERATOR IS NULL;
UPDATE apple_t SET SIZE = 10
WHERE color = t_apple(Iterator).color;
Iterator := t_apple.Next(Iterator);
END LOOP
END AFTER STATEMENT
This basically is how the trigger is designed. Using a second table is out of the question since trigger cost is a major factor. Any Pointers? Please and Thankyou
I dont fully understand but I think you can get your keys after each row ,then update data in after statament block as follows.
declare
idx number := 1 ;
type array_t is varray(10000) of varchar2(100) ;
colorArr array_t := array_t();
AFTER EACH ROW IS
BEGIN
if inserting then
colorArr (idx) := :new.color;
idx := idx + 1 ;
end if;
END
AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
for i in 1..sicilNoCol.count
loop
-- update here
end loop;
END AFTER STATEMENT;
or why dont you write a simple before insert trigger that you can manuplate :new.size in it? Does it give table mutable error?

how to insert into nested table collection using cursor plsql

I'm writing a function that needs to manipulate multiple rows at the same time and they need to be indexed. After several hours of reading about Oracle pl/sql I figured I could create a nested table kind of collection. Since I couldn't find a definitive answer and trial/error method takes way to long.
Here is question part:
QUESTION: What is the best practice to populate a nested table collection? Oracle PL/SQL
type partsTable is table of Parts_north_wing%rowtype;
pt PartsTable;
index number;
cursor pCursor is select * from Parts_north_wing;
begin
index := 1;
open pCursor;
loop
fetch pCursor into tempRow;
pt(index) := tempRow;
index := index + 1;
exit when pCursor%notfound;
end loop;
close pCursor;
A cursor FOR LOOP is almost always the best way to process rows in PL/SQL. It's simpler than the OPEN/FETCH/CLOSE method - no need to declare variables and manipulate cursors. It's also faster since it automatically bulk collects the results.
begin
for pt in
(
select parts_north_wing.*, rownum row_index
from parts_north_wing
) loop
--Do something here
null;
end loop;
end;
/
Try this. Hope this helps you to clear some of your concepts.
--Create a dummy object tyep
CREATE OR REPLACE TYPE av_obj
IS
OBJECT
(
ADD1 VARCHAR2(100),
ADD2 VARCHAR2(100) );
--Create a nested tale type
CREATE OR REPLACE TYPE AV_TT
IS
TABLE OF AV_OBJ;
--Bulk collect into nested table type
DECLARE
av_nested_tab AVROY.AV_TT;
BEGIN
SELECT avroy.av_obj(LEVEL
||'add1',LEVEL
||'add2') BULK COLLECT
INTO av_nested_tab
FROM DUAL
CONNECT BY LEVEL < 10;
END;

Not able to handle ORA-01476: “divisor is equal to zero”

I have a simple procedure in which I am using a cursor to fetch some valid items. Using these valid items, I am obtaining some values and doing a calculation on them. The code looks like this:
PROCEDURE p_loadanalyse
IS
qty_1 NUMBER;
qty_2 NUMBER := 0;
V_CALC Number := 0;
Item_No_Rec valid_items%Rowtype;
CURSOR c_validitem
IS
SELECT DISTINCT item_no
From valid_items A;
Begin
For Item_No_Rec In C_Validitem
Loop
SELECT ROUND(SUM(Qty1),2)
INTO qty_1 -- FCST_QTY_PCD
FROM qty1_table
WHERE item_No = item_No_rec.item_No;
SELECT SUM(Qty2)
INTO qty_2
FROM qty2_table
WHERE A.Item_No = Item_No_Rec.Item_No;
V_CALC := (QTY_1 /QTY_2)*100;
Dbms_Output.Put_Line('deviation' ||V_Deviation);
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN
qty_1 := 0;
qty_2 := 0;
WHEN OTHERS THEN
DBMS_output.put_line('There is an error');
END;
The problem is obviously in the statement V_CALC := (QTY1 /QTY2)*100; when QTY2 is 0. So I try to handle it like:
IF QTY_2 <> 0 THEN
V_CALC := (QTY_1 /QTY_2)*100;
ELSE
V_CALC := 0;
END IF;
This surprisingly hangs my SQL Developer when I try to compile the procedure. And gives a timeout occurred while trying to lock object after 10-11 mins. What I did was not rocket science and Oracle should have behaved the way I wanted, but instead I had to think and try all other ways of handling this divide by zero thingy like:
Including EXCEPTION WHEN ZERO_DIVIDE THEN QTY_2 := 0;
Using CASE instead of IF
After trying all permutations and combinations for 3 days, I am out of ideas and reasons and still with no output. Any hints or suggestions are welcome.
I doubt SQL developer is hanging because of your if statement or a divide by zero error. You may want to see if other users are using the procedure or if there are any processes in the database that need to be killed.
This can be shown if you can recreate the procedure but with another name..
Close the procedure before recreating it. SQL Developer block opened procedures.
Additionaly, try create it with different name of package/procedure.
And to be sure it's not zero division problem, create procedure empty...

Using array of Records in 'IN' operator in Oracle

I am having a table Course which have column DepId and Course and some other value. I need to search this table for a some set of (DepId, Course). This set will be decided at runtime.
I want to write a single query in a procedure to fetch all the record pertaining to above set.
For example consider the table has data like
DepId Course ...
------------------
1 A
2 B
3 C
4 D
5 E
6 F
Now only I want to search for below records:
DepId Course ...
------------------
1 A
4 D
What would be most efficient way to write above query?
I was thinking of creating an Array of record and passing it in the 'IN' operator. But I was not able to get any example for this. Can someone guide me on this?
Thanks
Leveraging Oracle Collections to Build Array-typed Solutions
The answer to your question is YES, dimensioned variables such as ARRAYS and COLLECTIONS are viable data types in solving problems where there are multiple values in either or both the input and output values.
Additional good news is that the discussion for a simple example (such as the one in the OP) is pretty much the same as for a complex one. Solutions built with arrays are nicely scalable and dynamic if designed with a little advanced planning.
Some Up Front Design Decisions
There are actual collection types called ARRAYS and ASSOCIATIVE ARRAYS. I chose to use NESTED TABLE TYPES because of their accessibility to direct SQL queries. In some ways, they exhibit "array-like" behavior. There are other trade-offs which can be researched through Oracle references.
The query applied to search the COURSE TABLE would apply a JOIN condition instead of an IN-LIST approach.
The use of a STORED PROCEDURE typed object improves database response. Queries within the procedure call can leverage and reuse already compiled code plus their cached execution plans.
Choosing the Right Collection or Array Type
There are a lot of choices of collection types in Oracle for storing variables into memory. Each has an advantage and some sort of limitation. AskTom from Oracle has a good example and break-down of what a developer can expect by choosing one variable collection type over another.
Using NESTED TABLE Types for Managing Multiple Valued Variables
For this solution, I chose to work with NESTED TABLES because of their ability to be accessed directly through SQL commands. After trying several different approaches, I noticed that the plain-SQL accessibility leads to more clarity in the resulting code.
The down-side is that you will notice that there is a little overhead here and there with respect to declaring an instance of a nested table type, initializing each instance, and managing its size with the addition of new values.
In any case, if you anticipate a unknown number of input variables or values (our output), an array-typed data type (collection) of any sort is a more flexible structure for your code. It is likely to require less maintenance in the end.
The Example: A Stored Procedure Search Query
Custom TYPE Definitions
CREATE OR REPLACE TYPE "COURSE_REC_TYPE" IS OBJECT (DEPID NUMBER(10,0), COURSE VARCHAR2(10));
CREATE OR REPLACE TYPE "COURSE_TBL_TYPE" IS TABLE of course_rec_type;
PROCEDURE Source Code
create or replace PROCEDURE ZZ_PROC_COURSE_SEARCH IS
my_input course_tbl_type:= course_tbl_type();
my_output course_tbl_type:= course_tbl_type();
cur_loop_counter pls_integer;
c_output_template constant varchar2(100):=
'DEPID: <<DEPID>>, COURSE: <<COURSE>>';
v_output VARCHAR2(200);
CURSOR find_course_cur IS
SELECT crs.depid, crs.course
FROM zz_course crs,
(SELECT depid, course
FROM TABLE (CAST (my_input AS course_tbl_type))
) search_values
WHERE crs.depid = search_values.depid
AND crs.course = search_values.course;
BEGIN
my_input.extend(2);
my_input(1):= course_rec_type(1, 'A');
my_input(2):= course_rec_type(4, 'D');
cur_loop_counter:= 0;
for i in find_course_cur
loop
cur_loop_counter:= cur_loop_counter + 1;
my_output.extend;
my_output(cur_loop_counter):= course_rec_type(i.depid, i.course);
end loop;
for j in my_output.first .. my_output.last
loop
v_output:= replace(c_output_template, '<<DEPID>>', to_char(my_output(j).depid));
v_output:= replace(v_output, '<<COURSE>>', my_output(j).course);
dbms_output.put_line(v_output);
end loop;
end ZZ_PROC_COURSE_SEARCH;
Procedure OUTPUT:
DEPID: 1, COURSE: A
DEPID: 4, COURSE: D
Statement processed.
0.03 seconds
MY COMMENTS: I wasn't particularly satisfied with the way the input variables were stored. There was a clumsy kind of problem with "loading" values into the nested table structure... If you can consider using a single search key instead of a composite pair (i.e., depid and course), the problem condenses to a simpler form.
Revised Cursor Using a Single Search Value
This is the proposed modification to the table design of the OP. Add a single unique key id column (RecId) to represent each unique combination of DepId and Course.
Note that the RecId column represents a SURROGATE KEY which should have no internal meaning aside from its property as a uniquely assigned value.
Custom TYPE Definitions
CREATE OR REPLACE TYPE "NUM_TBL_TYPE" IS TABLE of INTEGER;
Remove Array Variable
This will be passed directly through an input parameter from the procedure call.
-- REMOVE
my_input course_tbl_type:= course_tbl_type();
Loading and Presenting INPUT Parameter Array (Nested Table)
The following can be removed from the main procedure and presented as part of the call to the procedure.
BEGIN
my_input.extend(2);
my_input(1):= course_rec_type(1, 'A');
my_input(2):= course_rec_type(4, 'D');
Becomes:
create or replace PROCEDURE ZZ_PROC_COURSE_SEARCH (p_search_ids IN num_tbl_type) IS...
and
my_external_input.extend(2);
my_external_input:= num_tbl_type(1, 4);
Changing the Internal Cursor Definition
The cursor looks about the same. You can just as easily use an IN-LIST now that there is only one search parameter.
CURSOR find_course_cur IS
SELECT crs.depid, crs.course
FROM zz_course_new crs,
(SELECT column_value as recid
FROM TABLE (CAST (p_search_ids AS num_tbl_type))
) search_values
WHERE crs.recid = search_values.recid;
The Actual SEARCH Call and Output
The searching portion of this operation is now isolated and dynamic. It does not need to be changed. All the Changes happen in the calling PL/SQL block where the search ID values are a lot easier to read and change.
DECLARE
my_input_external num_tbl_type:= num_tbl_type();
BEGIN
my_input_external.extend(3);
my_input_external:= num_tbl_type(1,3,22);
ZZ_PROC_COURSE_SEARCH (p_search_ids => my_input_external);
END;
-- The OUTPUT (Currently set to DBMS_OUT)
DEPID: 1, COURSE: A
DEPID: 4, COURSE: D
DEPID: 7, COURSE: G
Statement processed.
0.01 seconds
This is something I havee used in the past in a situation similar to yours. Hopefully it helps.
The main benefit of this method would be that if you only passed it a single paramter it would still return all records for that single parameter. This way a single stored procedure with 5 input parameters could be used to search for all combinations of inputs.
Just call the stored procedure passing in the set and should return all values mathcing the criteria
usp_custom_search '1','A'
usp_custom_search '4','D'
usp_custom_search '4',NULL
usp_custom_search NULL,'A'
etc
Stored Procedure:
CREATE OR REPLACE PROCEDURE custom_search (
dep_id IN VARCHAR2,
course_id IN VARCHAR2,
result_set OUT SYS_REFCURSOR)
BEGIN
query_str VARCHAR2(1000);
query_str := 'SELECT';
query_str := query_str || ' DepId, Course';
query_str := query_str || ' FROM Course';
query_str := query_str || ' WHERE 1=1';
IF (dep_id is not null) then query_str := query_str || ' AND DepId = ''' || dep_id || ''''; END IF;
IF (course_id is not null) then query_str := query_str || ' AND Course = ''' || course_id || ''''; END IF;
open result_set for query_str;
END custom_search;
/

Resources