Professional Documents
Culture Documents
DECLARE
PDFmyURL.com
Advanced_PL_SQL
DECLARE
-- Create a new object with a constructor
my_favorite_vegetable_rec type_food := type_food('Brussel Sprouts', 'VEGETABLE', 'Farm,Greenhouse,Backyard');
BEGIN
--Read an attribute value
DBMS_OUTPUT.put_line (my_favorite_vegetable_rec.name);
--Modify an attribute value
my_favorite_vegetable_rec.food_group := 'SATISFACTION';
END;
/
A PL/SQL RECORD is a composite datatype, is composed of multiple pieces of information called fields. Records can be declared using
relational tables or explicit cursors as "templates" with the %ROWTYPE declaration attribute. You can also declare records based on TYPES
that you define yourself. The easiest way to define a record is by using the %ROWTYPE syntax in your declaration. For example, the
statement: bestseller books%ROWTYPE; creates a record that has a structure corresponding to the books table; for every column in the
table, there is a field in the record with the same name and datatype as the column. The %ROWTYPE keyword is especially valuable because
the declaration is guaranteed to match the corresponding schema-level template and is immune to schema-level changes in definition of the
shape of the table. If we change the structure of the books table, all we have to do is recompile the above code and bestseller will take on
the new structure of that table.
A second way to declare a record is to define your own RECORD TYPE:
DECLARE
TYPE EmpTabTyp IS TABLE OF emp%ROWTYPE INDEX BY BINARY_INTEGER;
emp_tab EmpTabTyp;
TYPE extra_book_info_t IS RECORD (
title books.title%TYPE,
is_bestseller BOOLEAN,
reviewed_by names_ list );
first_book extra_book_info_t;
Notice that the user-defined record datatype above includes a field (title) that is based on the column definition of a database table, a field ( is_bestseller)
based on a scalar data type (PL/SQL Boolean flag), and a collection (list of names of people who reviewed the book). Next, we can declare a record based on
this type (you do not use %ROWTYPE in this case, because you are already referencing a type to perform the declaration). Once you have declared a record, you
can then manipulate the data in these fields (or the record as a whole) as you can see below:
DECLARE
bestseller books%ROWTYPE; --Based on a DB Table
required_reading books%ROWTYPE;
BEGIN
-- Modify a field value
bestseller.title := 'ORACLE PL/SQL PROGRAMMING';
-- Copy one record to another
required_ reading := bestseller;
END;
Note that in the above code we have used the structure of the books table to define our PL/SQL records, but the assignment to the title field did not in any way
affect data inside that table.
You can also pass records as arguments to procedures and functions . This technique allows you to shrink down the size of a parameter list (pass a single record
instead of a lengthy and cumbersome list of individual values). Here is an example of a function with a record in the parameter list:
CREATE OR REPLACE PROCEDURE calculate_royalties ( book_in IN books%ROWTYPE, quarter_end_in IN DATE )
IS ...
PDFmyURL.com
Advanced_PL_SQL
DECLARE
-- Declare a basic Table Type Array
TYPE a_char_data IS TABLE OF VARCHAR2(10) INDEX BY BINARY_INTEGER;
-- Declare a complex record type with an embedded index by table. So now we have a ragged record type. A single record with a dimensional name column.
TYPE r_data IS RECORD (
ssn VARCHAR2(9) NOT NULL := -1,
name a_char_data, -- Notice the table_type used here
dob DATE );
-- Declare an "Associative Array (or index-by table)" using the complex record type. This creates an array of ragged records.
TYPE a_multi IS TABLE OF r_data INDEX BY BINARY_INTEGER;
BEGIN
-- Populate the ssn and dob columns of the first record of the v_data variable.
v_data(1).ssn := '123456789';
v_data(1).dob := '01-JAN-1900';
-- Populate the first and second rows of the name table in the first row of the v_data variable.
v_data(1).name(1) := 'Lewis';
v_data(1).name(2) := 'Joe';
--Finally, display the ssn of the first row of the v_data variable and then looped through the name table of the first row of the v_data variable.
dbms_output.put_line(v_data(1).ssn);
COLLECTIONS
A collection is an ordered group of elements, all of the same type. It is a general concept that encompasses lists, arrays, and other familiar
datatypes.
Each element has a unique subscript that determines its position in the collection.
PL/SQL offers these collection types:
Associat ive Arrays (Index-by Tables), let you look up elements using arbitrary numbers and strings for subscript values. (They are
similar to hash tables in other programming languages.)
Nest ed Tables hold an arbitrary number of elements. They use sequential numbers as subscripts. You can define equivalent SQL
types, allowing nested tables to be stored in database tables and manipulated through SQL.
Varrays (short for variable-size arrays) hold a fixed number of elements (although you can change the number of elements at runtime).
They use sequential numbers as subscripts. You can define equivalent SQL types, allowing varrays to be stored in database tables.
They can be stored and retrieved through SQL, but with less flexibility than nested tables.
Nested tables and Varrays must have been initialized before you can use them.
PDFmyURL.com
Advanced_PL_SQL
Repeated access to the same, static database information. If, during execution of your program (or during a session, since your
collection can be declared as package data and thereby persist with all its rows for the entire session), you need to read the same data
more than once, load it into a collection. Multiple scannings of the collection will be much more efficient than multiple executions of a
SQL query.
Management of program-only lists. You may build and manipulate lists of data that exist only within your program, never touching a
database table. In this case, collections-and, specifically, associative arrays-will be the way to go.
IMPORTANT NOTES:
Memory for collections comes out of the PGA or Process Global Area, One per session, so a program using collections can consume a large
amount of memory.
Use the NOCOPY hint to reduce overhead of passing collections in and out of program units.
Encapsulate or hide details of collection management.
Don't always fill collections sequentially. Think about how you need to manipulate the contents.
Try to read a row that doesn't exist, and Oracle raises NO_DATA_FOUND.
Associative arrays are sets of key-value pairs, where each key is unique and is used to locate a corresponding value in the array. The key can
be an integer or a string.
Associative arrays help you represent data sets of arbitrary size, with fast lookup for an individual element without knowing its position
within the array and without having to loop through all the array elements.
It is like a simple version of a SQL table where you can retrieve values based on the primary key.
Because associative arrays are intended for temporary data rather than storing persistent data, you cannot use them with SQL statements
such as INSERT and SELECT INTO. You can make them persistent for the life of a database session by declaring the type in a package and
assigning the values in a package body. You don't need to initialize the Associative Array.
Declaration:
TYPE type_name IS TABLE OF element_type [NOT NULL] INDEX BY [BINARY_INTEGER | PLS_INTEGER | VARCHAR2(size_limit)];
Probably the most familiar collection type is the associative arrays. The code block below is a typical use of an associative array:
DECLARE
TYPE num_array IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
my_num_array num_array;
TYPE emp_array IS TABLE OF emp%ROWTYPE INDEX BY PLS_INTEGER;
my_emp_array emp_array;
my_emp_array2 emp_array;
TYPE char_array IS TABLE OF VARCHAR2(50) INDEX BY BINARY_INTEGER;
my_chars_array char_array;
BEGIN
FOR i IN 1..100 LOOP
my_num_array(i) := power(2, i);
END LOOP;
PDFmyURL.com
Advanced_PL_SQL
my_chars_array(1) := 'Diego';
my_chars_array(2) := 'Diego2';
--In the example below, you store a single record in the index-by table, and its subscript is 7468 rather than 1
SELECT * INTO my_emp_array2(7468) FROM emp WHERE empno = 7468;
--I can also use BULK to Grab all the data without a Cursor
SELECT * BULK COLLECT INTO my_emp_array
FROM emp;
DBMS_OUTPUT.PUT_LINE ('Fetched ' || TO_CHAR ( my_emp_array.COUNT ) ||' records from EMP TABLE.' );
END;
/
This first loop creates an array of unlimited size (up to your OS and DB version limitations) of NUMBER which is indexed by a
BINARY_INTEGER datatype. The index is just the subscript and BINARY_INTEGER is just a numeric data type. An associative arrays does
NOT have to be initialized and it can be sparse (non-consecutive numbers).
Lets now look at a specific scenario in which a VARCHAR2 indexed array would be ideal. The requirement to look up a value via a unique
non-numeric key is a generic computational problem. Suppose we have a set of English-French vocabulary pairs stored persistently in the
most obvious way in a schema level table:
SELECT * FROM translations;
ENGLISH FRENCH
------------- ----------
computer ordinateur
tree arbre
book livre
cabbage chou
country pays
Our task is to allow lookup from French to English. Whats the most efficient way to implement the lookup procedure? We certainly have a
wide set of choices, including:
Pure SQL approach: Simply query the English word for the French each time its needed. This will be performed with a simple select using on
the where clause the english word.
Full collection scan, a.k.a. linear search: Use the traditional INDEX BY BINARY_INTEGER collection to cache all the French-English pairs.
Search the entire collection for a match each time a lookup is needed.
Hash-based indexing: Build our own VARCHAR2- based index using Oracles hashing algorithm.
VARCHAR2-indexed associative array: Cache all French-English pairs using the French word as the key, allowing direct lookup of the
English word, all within PL/SQL.
But by far the most optimized way would be to use Associative Array with the INDEX BY VARCHAR2 option.
/*
FOR l_row IN happyfamily.FIRST .. happyfamily.LAST
LOOP
DBMS_OUTPUT.put_line (happyfamily (l_row));
END LOOP;
*/
END;
/
DECLARE
SUBTYPE location_name_t IS VARCHAR2 (2000);
TYPE population_type IS TABLE OF PLS_INTEGER INDEX BY location_name_t;
country_population population_type;
continent_population population_type;
--
howmany PLS_INTEGER;
l_limit location_name_t;
BEGIN
country_population ('Greenland') := 100000;
country_population ('Iceland') := 750000;
continent_population ('Australia') := 30000000;
continent_population ('Antarctica') := 1000;
continent_population ('antarctica') := 1001;
--
PDFmyURL.com
Advanced_PL_SQL
--
howmany := country_population.COUNT;
DBMS_OUTPUT.put_line ('COUNT in country_population = ' || howmany);
l_limit := continent_population.FIRST;
DBMS_OUTPUT.put_line ('FIRST row in continent_population = ' || l_limit);
DBMS_OUTPUT.put_line ('FIRST value continent_population = ' || continent_population (l_limit));
l_limit := continent_population.LAST;
DBMS_OUTPUT.put_line ('LAST row in continent_population = ' || l_limit);
DBMS_OUTPUT.put_line ('LAST value in continent_population = ' || continent_population (l_limit));
2-NESTED TABLES
A nested table is similar to an associative array in that there is no maximum size to the array; however prior to assign a new element to a
nested table a PL/SQL program needs to explicitly extend the size before adding new elements. A nested table is an object type and
therefore needs to first be initialized with a constructor before being used.
Nested tables hold an arbitrary number of elements. They use sequential numbers as subscripts.
The size of a nested table can increase dynamically, i.e., nested tables are unbounded. Elements in a nested table initially have consecutive
subscripts, but as elements are deleted, they can have non-consecutive subscripts. The range of values for nested table subscripts is
1..2147483647. To extend a nested table, the built-in procedure EXTEND must be used. To delete elements, the built-in procedure DELETE
must be used.
An uninitialized nested table is atomically null, so the IS NULL comparison operator can be used to see if a nested table is null.
Declaration:
TYPE type_name IS TABLE OF element_type [NOT NULL];
In PL/SQL
Declare
TYPE TYP_NT_NUM IS TABLE OF NUMBER ;
In SQL
CREATE [OR REPLACE] TYPE TYP_NT_NUM IS TABLE OF NUMBER ;
With nested tables declared within PL/SQL, element_type can be any PL/SQL datatype except : REF CURSOR
DECLARE
TYPE nest_tab_t IS TABLE OF NUMBER;
my_nt nest_tab_t := nest_tab_t(); --We need to initialize this type with a constructor
TYPE emp_ntt IS TABLE OF emp%ROWTYPE;
my_emp emp_ntt := emp_ntt(); --We need to initialize this type with a constructor
PDFmyURL.com
Advanced_PL_SQL
TYPE CourseList IS TABLE OF VARCHAR2(16);
my_courses CourseList;
TYPE CourseList2 IS TABLE OF VARCHAR2(16);
my_courses CourseList := CourseList('Art 1111', 'Hist 3100', 'Engl 2005'); --Here we initialize the Nested Table Defining
its elements
BEGIN
Nt_tab := TYP_NT_NUM( 5, 10, 15, 20 ) ; --Here we initialize the Nested Table Defining its elements inside the BEGIN
my_courses := CourseList('Econ 2010', 'Acct 3401', 'Mgmt 3100'); --Here we initialize the Nested Table Defining its
elements inside the BEGIN
Note that the variable was initialized to an empty nested table using the constructor for its type. Also, the example shows how the nested
table EXTEND method is used to allocate a new element to the array so that it can be assigned to in the next statement.
c) Finally, create a database table STORAGE having type ELEMENTS_TAB as one of its columns:
CREATE TABLE STORAGE (
SALESMAN NUMBER(4),
ELEM_ID NUMBER(6),
ORDERED DATE,
ITEMS ELEMENTS_TAB)
NESTED TABLE ITEMS STORE AS ITEMS_TAB;
This example demonstrates how to populate the STORAGE table with a single row:
INSERT INTO STORAGE VALUES (100, 123456, SYSDATE,
ELEMENTS_TAB(ELEMENTS(175692,120.12),
ELEMENTS(167295,130.45),
ELEMENTS(127569,99.99)));
The following example demonstrates how to use the operator THE which is used in a SELECT statement to identify a nested table:
PDFmyURL.com
Advanced_PL_SQL
INSERT INTO
THE
(SELECT ITEMS FROM STORAGE WHERE ELEM_ID = 123456)
VALUES (125762, 101.99);
The following example shows how to update the STORAGE table row where salesman column has value 100:
UPDATE STORAGE SET ITEMS = ELEMENTS_TAB(ELEMENTS(192512, 199.99)) WHERE SALESMAN = 100;
The following example shows how to retrieve data from a table to a nested Table:
DECLARE
my_elements ELEMENTS;
BEGIN
SELECT ITEMS INTO my_elements FROM STORAGE
WHERE SALESMAN = 100;
END;
/
Within PL/SQL, you can manipulate the nested table by looping through its elements, using methods such as TRIM or EXTEND, and updating
some or all of the elements. Afterwards, you can store the updated table in the database again.
3-VARRAYS
Varrays are ordered groups of it ems of t ype VARRAY.
Wit h Varrays t he number of element s in t he array is variable up t o t he declared size. Arguably t hen, variable-sized arrays aren't t hat variable in
size. Varrays (short f or variable-size arrays) hold a f ixed number of element s (alt hough you can change t he number of element s at runt ime).
T hey use sequent ial numbers as subscript s. You can def ine equivalent SQL t ypes, allowing varrays t o be st ored in dat abase t ables. T hey can be
st ored and ret rieved t hrough SQL, but wit h less f lexibilit y t han nest ed t ables.
T he maximum size of a varray needs t o be specif ied in it s t ype def init ion. T he range of values f or t he index of a varray is f rom 1 t o t he maximum
specif ied in it s t ype def init ion. If no element s are in t he array, t hen t he array is at omically null, so t he IS NULL comparison operat or can be used t o
see if a varray is null. Varrays cannot be compared f or equalit y or inequalit y.
T he main use of a varray is t o group small or unif orm-sized collect ions of object s.
A varray can be assigned t o anot her varray, provided t he dat at ypes are t he exact same t ype. For example, suppose you declared t wo PL/SQL
t ypes:
T YPE My_Varray1 IS VARRAY(10) OF My_Type;
T YPE My_Varray2 IS VARRAY(10) OF My_Type;
An object of t ype My_Varray1 can be assigned t o anot her object of t ype My_Varray1 because t hey are t he exact same t ype.
However, an object of t ype My_Varray2 cannot be assigned t o an object of t ype My_Varray1 because t hey are not t he exact same t ype, even
t hough t hey have t he same element t ype.
VARRAYs f ind t heir opt imum applicat ion when dat a set , which has t o be st ored in order and is relat ively small.
Declaration:
TYPE type_name IS VARRAY (size_limit) OF element_type [NOT NULL];
size_limit is a positive integer literal representing the maximum number of elements in the array.
Like nested tables, varrays can be both PL/SQL types and SQL types and therefore can take advantage of the many of the features listed above. The main
differences with varrays in PL/SQL is that their maximum size must be specified when the type is declared. PL/SQL nested table or varray variables can be used to
PDFmyURL.com
Advanced_PL_SQL
atomically insert values into tables that use them. Apart from this capability, varrays are of less interest than nested tables to the PL/SQL developer because they
have the restriction of an upper bound and most anything one can do in code with a varray, one can do with a nested table.
Examples:
PL/SQL
declare
type v is varray(50) of number;
type Calendar is varray(366) OF DATE;
SQL
CREATE [OR REPLACE] TYPE TYP_V_CHAR IS VARRAY(10) OF VARCHAR2(20)
Initialization
Declare
TYPE TYP_V_DAY IS VARRAY(7) OF VARCHAR2(15) ;
v_tab TYP_V_DAY ;
TYPE Clientele IS VARRAY(100) OF Customer;
vips Clientele := Clientele(); -- initialize empty varray
Begin
v_tab := TYP_NT_NUM( Sun,Mon,Tue,Wed,Thu,Fri,Sat ) ; --We can also initialize inside the code
End ;
It is not required to initialize all the elements of a collection. You can either initialize no element. In this case, use an empty constructor.
v_tab := TYP_NT_NUM() ;
This collection is empty, which is different than a NULL collection (not initialized).
Finally, create a relational table MED_STORE which has MEDICINE_ARR as a column type:
CREATE TABLE MED_STORE (
LOCATION VARCHAR2(15),
STORE_SIZE NUMBER(7),
EMPLOYEES NUMBER(6),
MED_ITEMS MEDICINE_ARR);
Each item in column MED_ITEMS is a varray that will several medicines for a given location.
PDFmyURL.com
Advanced_PL_SQL
The following example shows how to insert two rows into the MED_STORE table:
INSERT INTO MED_STORE VALUES ('BELMONT',1000,10, MEDICINE_ARR(MEDICINES(11111,'STOPACHE',SYSDATE)));
INSERT INTO MED_STORE VALUES ('REDWOOD CITY',700,5, MEDICINE_ARR(MEDICINES(12345,'STRESS_BUST',SYSDATE)));
The following example shows how to delete the second row we have inserted in example 6 above:
DELETE FROM MED_STORE WHERE LOCATION = 'REDWOOD CITY';
The following example shows how to update the MED_STORE table and add more medicines to the Belmont store:
UPDATE MED_STORE SET MED_ITEMS = MEDICINE_ARR (
MEDICINES(12346,'BUGKILL',SYSDATE),
MEDICINES(12347,'INHALER',SYSDATE),
MEDICINES(12348,'PAINKILL',SYSDATE));
Another Example:
Below SQL uses TABLE function to display the ORDERS table data in relational format.
To manipulate the individual elements of a collection with SQL, use the TABLE operator. The TABLE operator uses a subquery to extract the varray or nested table,
so that the INSERT, UPDATE, or DELETE statement applies to the nested table rather than the top-level table.
SELECT T1.LOCATION, T1.STORE_SIZE, T1.EMPLOYEES, T2.*
FROM MED_STORE T1, TABLE(T1.MANF_DATE) T2;
-- In the following example, you retrieve the title and cost of the Maintenance Department's fourth project from the varray column projects:
DECLARE
my_cost NUMBER(7,2);
my_title VARCHAR2(35);
BEGIN
SELECT cost, title INTO my_cost, my_title
FROM TABLE(SELECT projects FROM department
WHERE dept_id = 50)
WHERE project_no = 4;
...
END;
/
Example: Performing INSERT, UPDATE, and DELETE Operations on a Varray with SQL
Currently, you cannot reference the individual elements of a varray in an INSERT, UPDATE, or DELETE statement.
You must retrieve the entire varray, use PL/SQL procedural statements to add, delete, or update its elements, and then store the changed varray back in the database table.
In the following example, stored procedure ADD_PROJECT inserts a new project into a department's project list at a given position:
CREATE PROCEDURE add_project (
dept_no IN NUMBER,
new_project IN Project,
position IN NUMBER) AS
my_projects ProjectList;
BEGIN
SELECT projects INTO my_projects FROM department
WHERE dept_no = dept_id FOR UPDATE OF projects;
my_projects.EXTEND; -- make room for new project
PDFmyURL.com
Advanced_PL_SQL
/* Move varray elements forward. */
FOR i IN REVERSE position..my_projects.LAST - 1 LOOP
my_projects(i + 1) := my_projects(i);
END LOOP;
my_projects(position) := new_project; -- add new project
UPDATE department SET projects = my_projects
WHERE dept_no = dept_id;
END add_project;
/
PDFmyURL.com
Advanced_PL_SQL
--Load some elements
someNumbers_nt := nestab(10, 4, 6, 9, 2, 5);
someNames_varr := varr('Fred','Joe','Caesar');
i:=3;
--Ask if Item on position 3 is a 6
if someNumbers_nt(i) = 6 then
dbms_output.put_line ('someNumbers_nt(' || i || ') = 6');
else
dbms_output.put_line ('someNumbers_nt(' || i || ') <> 6');
end if;
Output:
someNumbers_nt(3) = 6
someNumbers_nt(3) <> 6
2: 4
3: 7
5: 2
6: 5
Table Functions
To do this, the PL/SQL code executes a SQL statement passing the local nested table variable to the server. There are two special
functions necessary to achieve this functionality. The TABLE function tells the server to bind over the values of the nested table, perform
the requested SQL operation and return the results back as if the variable was a SQL table in the database. The CAST function is an explicit
directive to the server to map the variable to the SQL type that was defined globally in the previous step. With this capability, many new
operations become possible.. For example, one can take a nested table of objects that have been created in code and send them to the
server for ordering or aggregation. Almost any SQL operation is possible. For example a nested table can be joined with other SQL tables in
the database. The next example shows a simple ordering of an array by the second field.
DECLARE
eml_dmo_nt email_demo_nt_t := email_demo_nt_t();
BEGIN
-- Some logic that populates the nested table
eml_dmo_nt.EXTEND(3);
eml_dmo_nt(1) := email_demo_obj_t(45, 3, '23');
eml_dmo_nt(2) := email_demo_obj_t(22, 3, '41');
eml_dmo_nt(3) := email_demo_obj_t(18, 7, 'over_100k');
PDFmyURL.com
Advanced_PL_SQL
Has Abilit y To Varray Nest ed Associat ive Array
be indexed by non-integer No No Yes
preserve element order Yes No No
be stored in database Yes Yes No
have elements selected indidually in database Yes Yes --
have elements updated indidually in database Yes Yes --
The following guidelines will help you choose an associative array, nested table, or VARRAY:
If you need a sparsely indexed list (for "data-smart" storage, for example), your only practical option is an associative array. True, you
could allocate and then delete elements of a nested table variable, but it is inefficient to do so for anything but the smallest
collections.
If your PL/SQL application requires negative subscripts, you have to use associative arrays.
If you are running Oracle Database 10g and would find it useful to perform high-level set operations on your collections, choose
nested tables over associative arrays.
If you want to enforce a limit on the number of rows stored in a collection, use VARRAYs.
If you intend to store large amounts of persistent data in a column collection, your only option is a nested table. Oracle Database will
then use a separate table behind the scenes to hold the collection data, so you can allow for almost limitless growth.
If you want to preserve the order of elements stored in the collection column and if your data set will be small, use a VARRAY. What is
"small"? I tend to think in terms of how much data you can fit into a single database block; if you span blocks, you get row chaining,
which decreases performance. The database block size is established at database creation time and is typically 2K, 4K, or 8K.
Here are some other indications that a VARRAY would be appropriate: You don't want to worry about deletions occurring in the middle
of the data set; your data has an intrinsic upper bound; or you expect, in general, to retrieve the entire collection simultaneously.
The information in Table 1 will also help you make your choice.
In addition, the following bullet points can be referred to when deciding what collection best suits a particular solution.
Varray
Nested Table
Use when working with an unbounded list that needs to increase dynamically
Use when you need to store in the database and operate on elements individually
Access the collection inside SQL (table functions, columns in tables)
Want to perform set operations
Associative Array
Use when there is no need to store the Collection in the database. Its speed and indexing flexibility make it ideal for internal application use.
Work within PL/SQL code only
Sparsely fill and manipulate the collection
PDFmyURL.com
Advanced_PL_SQL
Take advantage of negative index values
EXISTS
COUNT
LIMIT
FIRST and LAST
PRIOR and NEXT
EXTEND
TRIM
DELETE
A collection method is a built-in function or procedure that operates on collections and is called using dot notation. The syntax follows:
collection_name.method_name[(parameters)]
Collection methods cannot be called from SQL statements. EXTEND and TRIM cannot be used with associative arrays.
Only EXISTS can be used on a null collection. If you apply another method to such collections, PL/SQL raises COLLECTION_IS_NULL.
Some Examples:
PDFmyURL.com
Advanced_PL_SQL
EXISTS(index)
Returns TRUE if the index element exists in the collection, else it returns FALSE. Use this method to be sure you are doing a valid operation
on the collection.
If my_collection.EXISTS(10) Then
My_collection.DELETE(10) ;
End if ;
COUNT
Returns the number of elements in a collection.
Declare
TYPE TYP_TAB IS TABLE OF NUMBER;
my_tab TYP_TAB := TYP_TAB( 1, 2, 3, 4, 5 );
Begin
Dbms_output.Put_line( 'COUNT = ' || To_Char( my_tab.COUNT ) ) ;
my_tab.DELETE(2) ;
Dbms_output.Put_line( 'COUNT = ' || To_Char( my_tab.COUNT ) ) ;
End ;
/
COUNT = 5
COUNT = 4
LIMIT
For nested tables and associative arrays, which have no maximum size, LIMIT returns NULL. For varrays, LIMIT returns the maximum number
of elements that a varray can contain (which you must specify in its type definition, and can change later with the TRIM and EXTEND
methods).
Declare
TYPE TYP_ARRAY IS ARRAY(30) OF NUMBER ;
my_array TYP_ARRAY := TYP_ARRAY( 1, 2, 3 ) ;
Begin
dbms_output.put_line( 'Max array size is ' || my_array.LIMIT ) ;
End;
/
Max array size is 30
Declare
TYPE TYP_TAB IS TABLE OF PLS_INTEGER INDEX BY VARCHAR2(1);
PDFmyURL.com
Advanced_PL_SQL
my_tab TYP_TAB;
TYPE TYP_TAB IS TABLE OF NUMBER;
my_tab TYP_TAB := TYP_TAB( 1, 2, 3, 4, 5 );
Begin
For i in 65 .. 69 Loop
my_tab( Chr(i) ) := i ;
End loop ;
Dbms_Output.Put_Line( 'First= ' || my_tab.FIRST || ' Last= ' || my_tab.LAST ) ;
For associative arrays with VARCHAR2 keys, these methods return the appropriate key value; ordering is based on the binary values of the
characters in the string, unless the NLS_COMP initialization parameter is set to ANSI, in which case the ordering is based on the locale-
specific sort order specified by the NLS_SORT initialization parameter.
These methods are more reliable than looping through a fixed set of subscript values, because elements might be inserted or deleted from
the collection during the loop. This is especially true for associative arrays, where the subscripts might not be in consecutive order and so
the sequence of subscripts might be (1,2,4,8,16) or ('A','E','I','O','U').
PRIOR and NEXT do not wrap from one end of a collection to the other. For example, the following statement assigns NULL to n because
the first element in a collection has no predecessor:
n := courses.PRIOR(courses.FIRST); -- assigns NULL to n
PRIOR is the inverse of NEXT. For instance, if element i exists, the following statement assigns element i to itself:
projects(i) := projects.PRIOR(projects.NEXT(i));
You can use PRIOR or NEXT to traverse collections indexed by any series of subscripts. In the following example, you use NEXT to traverse
a nested table from which some elements have been deleted:
i := courses.FIRST; -- get subscript of first element
WHILE i IS NOT NULL LOOP
-- do something with courses(i)
i := courses.NEXT(i); -- get subscript of next element
END LOOP;
Declare
TYPE TYP_TAB IS TABLE OF PLS_INTEGER INDEX BY VARCHAR2(1) ;
PDFmyURL.com
Advanced_PL_SQL
my_tab TYP_TAB ;
c Varchar2(1) ;
Begin
For i in 65 .. 69 Loop
my_tab( Chr(i) ) := i ;
End loop ;
c := my_tab.FIRST ; -- first element
Loop
Dbms_Output.Put_Line( 'my_tab(' || c || ') = ' || my_tab(c) ) ;
c := my_tab.NEXT(c) ; -- get the successor element
Exit When c IS NULL ; -- end of collection
End loop ;
End ;
/
my_tab(A) = 65
my_tab(B) = 66
my_tab(C) = 67
my_tab(D) = 68
my_tab(E) = 69
Use the PRIOR() or NEXT() method to be sure that you do not access an invalid element:
Declare
TYPE TYP_TAB IS TABLE OF PLS_INTEGER ;
my_tab TYP_TAB := TYP_TAB( 1, 2, 3, 4, 5 );
Begin
my_tab.DELETE(2) ; -- delete an element of the collection
For i in my_tab.FIRST .. my_tab.LAST Loop
Dbms_Output.Put_Line( 'my_tab(' || Ltrim(To_char(i)) || ') = ' || my_tab(i) ) ;
End loop ;
End ;
/
my_tab(1) = 1
Declare
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at line 7
In this example, we get an error because one element of the collection was deleted.
One solution is to use the PRIOR()/NEXT() method:
Declare
TYPE TYP_TAB IS TABLE OF PLS_INTEGER ;
my_tab TYP_TAB := TYP_TAB( 1, 2, 3, 4, 5 );
v Pls_Integer ;
Begin
my_tab.DELETE(2) ;
v := my_tab.first ;
Loop
Dbms_Output.Put_Line( 'my_tab(' || Ltrim(To_char(v)) || ') = ' || my_tab(v) ) ;
v := my_tab.NEXT(v) ; -- get the next valid subscript
Exit When v IS NULL ;
End loop ;
End ;
PDFmyURL.com
Advanced_PL_SQL
/
my_tab(1) = 1
my_tab(3) = 3
my_tab(4) = 4
my_tab(5) = 5
EXTEND[(n[,i])]
Used to extend a collection (add new elements) of a nested table or varray. You cannot use EXTEND with Associative Arrays. The procedure
has 3 forms:
You cannot use EXTEND to initialize an atomically null collection. Also, if you impose the NOT NULL constraint on a TABLE or VARRAY type,
you cannot apply the first two forms of EXTEND to collections of that type.
EXTEND operates on the internal size of a collection, which includes any deleted elements. So, if EXTEND encounters deleted elements, it
includes them in its tally. PL/SQL keeps placeholders for deleted elements so that you can replace them if you wish.
Consider the following example:
DECLARE
TYPE CourseList IS TABLE OF VARCHAR2(10);
courses CourseList;
BEGIN
courses := CourseList('Biol 4412', 'Psyc 3112', 'Anth 3001');
courses.DELETE(3); -- delete element 3
/* PL/SQL keeps a placeholder for element 3. So, the next statement appends element 4, not element 3. */
courses.EXTEND; -- append one null element
/* Now element 4 exists, so the next statement does not raise SUBSCRIPT_BEYOND_COUNT. */
courses(4) := 'Engl 2005';
PDFmyURL.com
Advanced_PL_SQL
When it includes deleted elements, the internal size of a nested table differs from the values returned by COUNT and LAST. For instance, if
you initialize a nested table with five elements, then delete elements 2 and 5, the internal size is 5, COUNT returns 3, and LAST returns 4. All
deleted elements (whether leading, in the middle, or trailing) are treated alike
Declare
TYPE TYP_NES_TAB is table of Varchar2(20) ;
tab1 TYP_NES_TAB ;
i Pls_Integer ;
Procedure PrintAll IS
Begin
Dbms_Output.Put_Line( '* Print all collection *' ) ;
For i IN tab1.FIRST..tab1.LAST Loop
If tab1.EXISTS(i) Then
Dbms_Output.Put_Line( 'tab1(' || ltrim(to_char(i)) ||') = ' || tab1(i) ) ;
End if ;
End loop ;
End ;
Begin
tab1 := TYP_NES_TAB('One') ;
i := tab1.COUNT ;
Dbms_Output.Put_Line( 'tab1.COUNT = ' || i ) ;
Print(i) ;
-- the following line raise an error because the second index does not exists in the collection --
-- tab1(2) := 'Two' ;
-- Add one empty element --
tab1.EXTEND ;
i := tab1.COUNT ;
tab1(i) := 'Two' ;
Printall ;
PDFmyURL.com
Advanced_PL_SQL
tab1(1) = One
tab1(2) = Two
* Print all collection *
tab1(1) = One
tab1(2) = Two
tab1(3) = Three
tab1(4) = Four
* Print all collection *
tab1(1) = One
tab1(2) = Two
tab1(3) = Three
tab1(4) = Four
tab1(5) = One
tab1(6) = One
tab1(7) = One
TRIM[(n)]
Used to decrease the size of a collection
TRIM removes one element from the end of a collection.
TRIM(n) removes n elements from the end of a collection.
Declare
TYPE TYP_TAB is table of varchar2(100) ;
tab TYP_TAB ;
Begin
tab := TYP_TAB( 'One','Two','Three' ) ;
For i in tab.first..tab.last Loop
dbms_output.put_line( 'tab(' || ltrim( to_char( i ) ) || ') = ' || tab(i) ) ;
End loop ;
-- add 3 element with second element value --
dbms_output.put_line( '* add 3 elements *' ) ;
tab.EXTEND(3,2) ;
For i in tab.first..tab.last Loop
dbms_output.put_line( 'tab(' || ltrim( to_char( i ) ) || ') = ' || tab(i) ) ;
End loop ;
-- suppress the last element --
dbms_output.put_line( '* suppress the last element *' ) ;
tab.TRIM ;
For i in tab.first..tab.last Loop
dbms_output.put_line( 'tab(' || ltrim( to_char( i ) ) || ') = ' || tab(i) ) ;
End loop ;
End;
/
tab(1) = One
tab(2) = Two
tab(3) = Three
* add 3 elements *
tab(1) = One
tab(2) = Two
tab(3) = Three
tab(4) = Two
tab(5) = Two
tab(6) = Two
* suppress the last element *
PDFmyURL.com
Advanced_PL_SQL
tab(1) = One
tab(2) = Two
tab(3) = Three
tab(4) = Two
tab(5) = Two
If you try to suppress more elements than the collection contents, you get a SUBSCRIPT_BEYOND_COUNT exception.
DELETE[(n[,m])]
DELETE removes all elements from a collection.
DELETE(n) removes the nth element from an associative array with a numeric key or a nested table. If the associative array has a string
key, the element corresponding to the key value is deleted. If n is null, DELETE(n) does nothing.
DELETE(n,m) removes all elements in the range m..n from an associative array or nested table. If m is larger than n or if m or n is null,
DELETE(n,m) does nothing
Caution :
LAST returns the greatest subscript of a collection and COUNT returns the number of elements of a collection. If you delete some elements,
LAST != COUNT.
Caution:
For Varrays, you can suppress only the last element. If the element does not exists, no exception is raised.
Handle Collections
Checking if a Collect ion Is Null
PDFmyURL.com
Advanced_PL_SQL
Nested tables and varrays can be atomically null, so they can be tested for nullity:
DECLARE
TYPE Staff IS TABLE OF Employee;
members Staff;
BEGIN
-- Condition yields TRUE because we haven't used a constructor.
IF members IS NULL THEN ...
END;
While the collection is not initialized (Nested tables and Varrays), it is not possible to manipulate it. You can test if a collection is initialized:
Declare
TYPE TYP_VAR_TAB is VARRAY(30) of varchar2(100) ;
tab1 TYP_VAR_TAB ; -- declared but not initialized
Begin
If Tab1 IS NULL Then
-- NULL collection, have to initialize it --
Tab1 := TYP_VAR_TAB('','','','','','','','','','');
End if ;
-- Now, we can handle the collection --
End ;
Declare
Type TYPE_TAB_DAYS IS TABLE OF PLS_INTEGER INDEX BY VARCHAR2(20) ;
day_tab TYPE_TAB_DAYS ;
Begin
day_tab( 'Monday' ) := 10 ;
day_tab( 'Tuesday' ) := 20 ;
day_tab( 'Wednesday' ) := 30 ;
End ;
PDFmyURL.com
Advanced_PL_SQL
tab3 := tab1 ; -- Error : types not similar
...
End ;
Another Example:
DECLARE
TYPE clientele IS TABLE OF VARCHAR2 (64);
IF clients1_in = clients2_in
THEN
DBMS_OUTPUT.put_line (' Client list 1 = Client list 2');
ELSIF clients1_in != clients2_in
THEN
DBMS_OUTPUT.put_line (' Client list 1 != Client list 2');
PDFmyURL.com
Advanced_PL_SQL
ELSIF (clients1_in = clients2_in) IS NULL
THEN
DBMS_OUTPUT.put_line (' NULL result');
END IF;
END compare_clients;
BEGIN
compare_clients ('1,2 compared to 1,3', client_list_12, client_list_13);
--
compare_clients ('1,3 compared to 3,1', client_list_13, client_list_31);
--
compare_clients ('1,3,3 compared to 3,1', client_list_133, client_list_31);
--
compare_clients ('1,3,NULL compared to 3,NULL,1', client_list_13n, client_list_3n1);
compare_clients ('1,3,NULL compared to 1,3,NULL', client_list_13n, client_list_13n);
END;
/
submultiset Tests whether a given nested table is a subset of another nested table.
Returns a nested table that contains the values of two inputted nested tables.
multiset union The all or distinct option may be used, as required, to allow for or eliminate
duplicates, including NULL values.
Returns a nested table that contains values that are common to the two nested
multiset intersect tables passed in through the input operators. The all or distinct option may be
used, as required, to allow for or eliminate duplicates, including NULL values.
This operator takes the names of two nested tables as parameters. When
executed it will return a nested table which contains elements listed in the first
multiset except parameter of the function, that do not exist in the parameter. The all or distinct
option may be used, as required, to allow for or eliminate duplicates, including
NULL values.
cardinality Returns the number of elements contained within a given nested table.
Used to convert a nested table into a set of elements that are distinct. This set is
set
returned in the form of a nested table.
PDFmyURL.com
Advanced_PL_SQL
Used to determine whether a nested table is made up of distinct elements. If a
is a set duplicate value is contained in the nested table, then the operator returns FALSE;
otherwise, TRUE is returned.
Used on nested tables to generate multiple sets of nested table entries from a
powermultiset
given nested table.
Used on nested tables to generate multiple sets of nested table entries based on
powermultiset_by_cardinality
a specified cardinality. This restricts the depth of the resulting nested table output.
DECLARE
TYPE nested_typ IS TABLE OF NUMBER;
nt1 nested_typ := nested_typ(1,2,3);
nt2 nested_typ := nested_typ(3,2,1);
nt3 nested_typ := nested_typ(2,3,1,3);
nt4 nested_typ := nested_typ(1,2,4);
reponse BOOLEAN;
combien NUMBER;
PROCEDURE verif(test BOOLEAN DEFAULT NULL, label IN VARCHAR2 DEFAULT NULL, quantity NUMBER DEFAULT NULL) IS
BEGIN
IF test IS NOT NULL THEN
dbms_output.put_line(label || ' -> ' || CASE test WHEN TRUE THEN 'True' WHEN FALSE THEN 'False' END);
END IF;
IF quantity IS NOT NULL THEN
dbms_output.put_line(quantity);
END IF;
END;
BEGIN
reponse := nt1 IN (nt2,nt3,nt4); -- true, nt1 correspond to nt2
verif(test => reponse, label => 'nt1 IN (nt2,nt3,nt4)');
reponse := nt1 SUBMULTISET OF nt3; -- true, all elements correspond
verif(test => reponse, label => 'nt1 SUBMULTISET OF nt3');
reponse := nt1 NOT SUBMULTISET OF nt4; -- true
verif(test => reponse, label => 'nt1 NOT SUBMULTISET OF nt4');
PDFmyURL.com
Advanced_PL_SQL
verif(test => reponse, label => 'nt3 IS NOT A SET' );
reponse := nt1 IS EMPTY; -- false, nt1 have elements
verif(test => reponse, label => 'nt1 IS EMPTY' );
END;
/
nt1 IN (nt2,nt3,nt4) -> True
nt1 SUBMULTISET OF nt3 -> True
nt1 NOT SUBMULTISET OF nt4 -> True
4
3
4 MEMBER OF nt1 -> False
nt3 IS A SET -> False
nt3 IS NOT A SET -> True
nt1 IS EMPTY -> False
Nested tables
CREATE [OR REPLACE] TYPE [schema. .] type_name { IS | AS } TABLE OF datatype;
Varrays
CREATE [OR REPLACE] TYPE [schema. .] type_name { IS | AS } { VARRAY | VARYING ARRAY } ( limit ) OF datatype;
You can query the USER_TYPES view to get information on the types created in the database.
select type_name, typecode, attributes from user_types;
TYPE_NAME TYPECODE ATTRIBUTES
------------------------------ ------------------------------ ----------
TYP_LIG_ENV OBJECT 5
TYP_TAB_LIG_ENV COLLECTION 0
You can query the USER_COLL_TYPES view to get information on the collections created in the database.
select type_name, coll_type, elem_type_owner, elem_type_name from user_coll_types;
TYPE_NAME COLL_TYPE ELEM_TYPE_OWNER ELEM_TYPE_NAME
------------------------- ---------------------- ------------------------- -------
TYP_TAB_LIG_ENV TABLE TEST TYP_LIG_ENV
You can query the USER_TYPE_ATTRS view to get information on the collection attributes.
select type_name, attr_name, attr_type_name, length, precision, scale, attr_no from user_type_attrs;
TYPE_NAME ATTR_NAME ATTR_TYPE_ LENGTH PRECISION SCALE ATTR_NO
--------------- --------------- ---------- ---------- ---------- ---------- ----------
TYP_LIG_ENV LIG_NUM INTEGER 1
TYP_LIG_ENV LIG_CODE VARCHAR2 20 2
TYP_LIG_ENV LIG_PHT NUMBER 6 2 3
TYP_LIG_ENV LIG_TVA NUMBER 3 1 4
TYP_LIG_ENV LIGQTY INTEGER 5
1 Insertion
Add a line in the INVOICE table
Use the INSERT statement with all the constructors needed for the collection
INSERT INTO INVOICE VALUES
(1 ,1000 ,SYSDATE
, TYP_TAB_LIG_ENV -- Table of objects constructor
( TYP_LIG_ENV( 1 ,'COD_01', 1000, 5.0, 1 ) - object constructor
)
);
Multiple inserts
You can add more than one element in a collection by using the SELECT statement instead of the VALUES keyword.
INSERT INTO TABLE (SELECT inv_line FROM INVOICE WHERE inv_num = 1)
SELECT nt.* FROM TABLE (SELECT inv_line FROM INVOICE WHERE inv_num = 1) nt;
2 Update
2.2 Varray
It is not possible to update one element of a VARRAY collection with SQL.
You cannot use the TABLE keyword for this purpose (because Varrays are not stored in particular table like Nested tables).
So, a single VARRAY element of a collection must be updated within a PL/SQL block:
-- varray of invoice lines --
CREATE TYPE TYP_VAR_LIG_ENV AS VARRAY(5) OF TYP_LIG_ENV ;
-- insert a row --
Insert into INVOICE_V
Values ( 1, 1000, SYSDATE,
PDFmyURL.com
Advanced_PL_SQL
Values ( 1, 1000, SYSDATE,
TYP_VAR_LIG_ENV
(
TYP_LIG_ENV( 1, 'COD_01', 1000, 5, 1 ),
TYP_LIG_ENV( 2, 'COD_02', 500, 5, 10 ),
TYP_LIG_ENV( 3, 'COD_03', 10, 5, 100 )
)
) ;
3 Delete
PDFmyURL.com
Advanced_PL_SQL
Delete all the collection rows
DELETE FROM TABLE (SELECT inv_line FROM INVOICE WHERE inv_num = 1) nt;
3.2 Varray
Varrays are more complicated to handle. It is not possible to delete a single element in a Varray collection.
To do the job, you need a PL/SQL block and a temporary Varray that keep only the lines that are not deleted.
Declare
v_table TYP_VAR_LIG_ENV ;
v_tmp v_table%Type := TYP_VAR_LIG_ENV() ;
ind pls_integer := 1 ;
Begin
-- select the collection --
Select inv_line
Into v_table
From INVOICE_V
Where inv_num = 1
For Update of inv_line ;
PDFmyURL.com
Advanced_PL_SQL
For Update of inv_line ;
-- Extend the temporary varray --
v_tmp.EXTEND(v_table.LIMIT) ;
For i IN v_table.FIRST .. v_table.LAST Loop
If v_table(i).lig_num <> 2 Then
v_tmp(ind) := v_table(i) ; ind := ind + 1 ;
End if ;
End loop ;
Update INVOICE_V Set inv_line = v_tmp Where inv_num = 1 ;
End ;
/
4 Query
Query the whole table
select * from INVOICE;
INV_NUM INV_NUMCLI INV_DATE
---------- ---------- --------
INV_LINE(LIG_NUM, LIG_CODE, LIG_PHT, LIG_TVA, LIGQTY)
------------------------------------------------------------------------------------------
3 1001 11/11/05
TYP_TAB_LIG_ENV()
2 1002 12/11/05
PDFmyURL.com
Advanced_PL_SQL
2 1002 12/11/05
TYP_TAB_LIG_ENV(TYP_LIG_ENV(1, 'COD_03', 1000, 5, 1))
1 1000 11/11/05
TYP_TAB_LIG_ENV(TYP_LIG_ENV(1, 'COD_01', 1000, 5, 1), TYP_LIG_ENV(2, 'COD_02', 50, 5, 10))
We can see that the collection is treated as a table with the TABLE keyword.
The collection could be sorted on any column.
SQL Mode
SELECT FROM TABLE
( SELECT the_collection FROM the_table WHERE ... )
PDFmyURL.com
Advanced_PL_SQL
2 COD_02 50 5 10
Another syntax:
Select t2.* from invoice t1,TABLE(t1.inv_line) t2
Where t1.inv_numcli = 1000;
LIG_NUM LIG_CODE LIG_PHT LIG_TVA LIGQTY
---------- -------------------- ---------- ---------- ----------
1 COD_01 1000 5 1
2 COD_02 50 5 10
PL/SQL Mode
Declare
TYPE TYP_REC IS RECORD
(
num INV_LINE_TABLE.LIG_NUM%Type,
code INV_LINE_TABLE.LIG_CODE%Type,
pht INV_LINE_TABLE.LIG_PHT%Type,
tva INV_LINE_TABLE.LIG_TVA%Type,
qty INV_LINE_TABLE.LIGQTY%Type
);
-- Table of records --
TYPE TAB_REC IS TABLE OF TYP_REC ;
t_rec TAB_REC ;
Begin
-- Store the lines into the table of records --
Select * BULK COLLECT INTO t_rec
from TABLE(SELECT inv_line FROM INVOICE WHERE inv_num = 1) nt ;
-- Print the record attributes of each line--
For i IN t_rec.FIRST .. t_rec.LAST Loop
dbms_output.put_line( '** Line = ' || t_rec(i).num || ' **' ) ;
dbms_output.put_line( 'Code = ' || t_rec(i).code ) ;
dbms_output.put_line( 'Price = ' || t_rec(i).pht ) ;
dbms_output.put_line( 'Tax rate = ' || t_rec(i).tva ) ;
dbms_output.put_line( 'Quantity = ' || t_rec(i).qty ) ;
End loop ;
End ;
/
** Line = 1 **
Code = COD_01
Price = 1000
Tax rate = 5
Quantity = 1
** Line = 2 **
Code = COD_02
Price = 50
Tax rate = 5
Quantity = 10
PDFmyURL.com
Advanced_PL_SQL
Quantity = 10
SQL Mode
SELECT nt.lig_code, nt.lig_pht
FROM TABLE (SELECT inv_line FROM INVOICE WHERE inv_num = 1) nt
WHERE nt.lig_num = 1;
LIG_CODE LIG_PHT
-------------------- ----------
COD_01 1000
Another syntax:
Select t2.* from invoice t1,TABLE(t1.inv_line) t2
Where t1.inv_numcli = 1000
And t2.lig_num = 1;
LIG_NUM LIG_CODE LIG_PHT LIG_TVA LIGQTY
---------- -------------------- ---------- ---------- ----------
1 COD_01 1000 5 1
PL/SQL Mode
Declare
TYPE t_rec IS RECORD
(
num INV_LINE_TABLE.LIG_NUM%Type,
code INV_LINE_TABLE.LIG_CODE%Type,
pht INV_LINE_TABLE.LIG_PHT%Type,
tva INV_LINE_TABLE.LIG_TVA%Type,
qty INV_LINE_TABLE.LIGQTY%Type
);
rec t_rec ;
Begin
-- Store the line into the record --
Select *
Into rec
from TABLE(SELECT inv_line FROM INVOICE WHERE inv_num = 1) nt
Where nt.lig_num = 1 ;
-- Print the record attributes --
dbms_output.put_line( 'Code = ' || rec.code ) ;
dbms_output.put_line( 'Price = ' || rec.pht ) ;
dbms_output.put_line( 'Tax rate = ' || rec.tva ) ;
dbms_output.put_line( 'Quantity = ' || rec.qty ) ;
End ;
/
Code = COD_01
PDFmyURL.com
Advanced_PL_SQL
Code = COD_01
Price = 1000
Tax rate = 5
Quantity = 1
SQL Mode
SELECT v.inv_numcli, v.inv_date, nt.lig_code, nt.lig_pht
FROM INVOICE v,
TABLE (SELECT inv_line FROM INVOICE WHERE inv_num = 1) nt
WHERE v.inv_num = 1;
INV_NUMCLI INV_DATE LIG_CODE LIG_PHT
---------- -------- -------------------- ----------
1000 11/11/05 COD_01 1000
1000 11/11/05 COD_02 50
PL/SQL Mode
Declare
invoice_rec INVOICE%ROWTYPE ;
LC$Print Varchar2(512) ;
Begin
-- Select the INVOICE line --
Select *
Into invoice_rec
From INVOICE
Where inv_numcli = 1000 ;
-- Print the parent and collection attributes--
For i IN invoice_rec.inv_line.FIRST .. invoice_rec.inv_line.LAST Loop
LC$Print := invoice_rec.inv_numcli
|| ' - ' || To_Char(invoice_rec.inv_date,'DD/MM/YYYY')
|| ' - ' || invoice_rec.inv_line(i).lig_num
|| ' - ' || invoice_rec.inv_line(i).lig_code
|| ' - ' || invoice_rec.inv_line(i).lig_pht
PDFmyURL.com
Advanced_PL_SQL
|| ' - ' || invoice_rec.inv_line(i).lig_pht
|| ' - ' || invoice_rec.inv_line(i).lig_tva
|| ' - ' || invoice_rec.inv_line(i).ligqty ;
dbms_output.put_line( LC$Print ) ;
End loop ;
End ;
/
1000 - 11/11/2005 - 1 - COD_01 - 1000 - 5 - 1
1000 - 11/11/2005 - 2 - COD_02 - 50 - 5 - 10
You can use NESTED CURSOR to get information on rows where collection is NULL or EMPTY
SELECT v.inv_numcli, v.inv_date,
CURSOR( SELECT nt.lig_code, nt.lig_pht FROM TABLE (inv_line) nt)
FROM INVOICE v;
INV_NUMCLI INV_DATE CURSOR(SELECTNT.LIG_
---------- -------- --------------------
1001 11/11/05 CURSOR STATEMENT : 3
CURSOR STATEMENT : 3
PDFmyURL.com
Advanced_PL_SQL
Let's say that we want to load one table into another one:
DECLARE
BEGIN
FOR x IN (SELECT * FROM all_objects)
LOOP
INSERT INTO t1 (owner, object_name, subobject_name, object_id,
data_object_id, object_type, created, last_ddl_time,
timestamp, status, temporary, generated, secondary)
VALUES (x.owner, x.object_name, x.subobject_name, x.object_id,
x.data_object_id, x.object_type, x.created,
x.last_ddl_time, x.timestamp, x.status, x.temporary, x.generated, x.secondary);
END LOOP;
COMMIT;
END test_proc;
Elapsed: 00:00:20.02
The following solution uses a nested table to hold the data from the ALL_OBJECTS table, and performs BULK COLLECT to load all of the source tables' data
into the nested table.
The next example is a variation on this, that does much the same thing with slightly more compact code, I just removed the cursor.
truncate table t1;
create or replace procedure fast_proc2
is
TYPE My_ARRAY IS TABLE OF all_objects%ROWTYPE;
l_data My_ARRAY;
begin
--Here I put all the rows in memory on this collection
select * BULK COLLECT INTO l_data
from ALL_OBJECTS;
PDFmyURL.com
Advanced_PL_SQL
from ALL_OBJECTS;
-- Now I work with that collection
FORALL x in l_data.First..l_data.Last
INSERT INTO t1 VALUES l_data(x) ;
end;
/
Elapsed: 00:00:09.27
Bulk Binding
Bulk binding improves performance by reducing the context switches between the PL/SQL and SQL engines for execution of SQL
statements. Bulk Collect causes the SQL engine to bulk-bind the entire output collection before sending it to the PL/SQL engine. An in-
bind is when we pass a value from a program to the SQL engine, often either to constraint on a column or to specify a value for a DML
statement
Commonly, in-binds are only of interest because they are essential for SQL statements to be sharable. When DBAs talk of the importance
of applications using bind variables it is in the context of in-binds since, in applications that use dynamic SQL, using literals instead of bind
variables causes each SQL statement to be parsed. This is a critical consideration for overall database performance
An out-bind occurs when values are passed from the SQL engine back to the host language.
When processing a cursor, application developers can choose to either fetch back values one-at-a-time or returned in a batch operation
which will bind back many rows to the host application in a single operation.
Before Oracle 8i values being bound out into PL/SQL host variables had to be fetched one at a time. The following CURSOR FOR-LOOP
construct is a familiar one.
--Archive historical data
DECLARE
CURSOR sales_cur (p_customer_id NUMBER) IS
SELECT * FROM sales
WHERE customer_id = p_customer_id;
v_customer_id NUMBER := 1234;
BEGIN
FOR rec IN sales_cur(v_customer_id) LOOP
INSERT INTO sales_hist(customer_id, detail_id, process_date)
VALUES (v_customer_id, rec.sales_id, sysdate);
END LOOP;
END;
In a CURSOR FOR-LOOP, a record variable is implicitly declared that matches the column list of the cursor. On each iteration of the loop,
the execution context is switched from the PL/SQL engine to the SQL engine, performing an out-bind of the column values into the record
variable once for each loop iteration. Likewise, an in-bind for the insert statement will occur once on each iteration. Although stored PL/SQL
code has the advantage over other host languages of keeping this interaction within the same process, the context switching between the
SQL engine and the PL/SQL engine is relatively expensive making the above code very inefficient.In addition, the cursor is defined as
SELECT * instead of just selecting from the columns to be utilized which is also inefficient. Whether the code references a column or not,
PDFmyURL.com
Advanced_PL_SQL
Oracle will have to fetch and bind over all of the columns in the select list, slowing down code execution
A better way to perform the above task would be to utilize bulk binding, for both the fetch and the insert statements. We have two new
PL/SQL operators to accomplish this. The BULK COLLECT (for SELECT and FETCH) statement is used to specify bulk out-binds; while
the FORALL (for INSERT, UPDATE and DELETE) statement is used to provide bulk in-binds for DML statements.
The index can be referenced only within the FORALL statement and only as a collection subscript. The SQL statement must be an INSERT,
UPDATE, or DELETE statement that references collection elements. And, the bounds must specify a valid range of consecutive index
numbers. The SQL engine executes the SQL statement once for each index number in the range."
Oracle9i Release 2 also allows updates using record definitions by using the ROW keyword:
DECLARE
TYPE test1_tab IS TABLE OF test1%ROWTYPE;
t_tab test1_tab := test1_tab();
BEGIN
FOR i IN 1 .. 10000 LOOP
t_tab.extend;
t_tab(t_tab.last).id := i;
t_tab(t_tab.last).description := 'Description: ' || To_Char(i);
END LOOP;
DECLARE
v_emprecs emp_util.emprec_tab_t;
CURSOR cur IS SELECT * FROM employees
WHERE hire_date < '25-JUN-97';
BEGIN
OPEN cur;
FETCH cur BULK COLLECT INTO v_emprecs LIMIT 10;
CLOSE cur;
emp_util.give_raise (v_emprecs);
END;
PDFmyURL.com
Advanced_PL_SQL
[Note: the clause limit 10 is equivalent to where ro wnum <= 10 .]
Even more wonderful, we can now combine BULK COLLECT fetches into records with NATIVE DYNAMIC SQL. Here is an example, in which we give raises
to employees for a specific schema:
CREATE OR REPLACE PROCEDURE give_raise (schema_in IN VARCHAR2)
IS
v_emprecs emp_util.emprec_tab_t;
cur SYS_REFCURSOR;
BEGIN
OPEN cur FOR 'SELECT * FROM ' || schema_in || '.employees' || 'WHERE hire_date < :date_limit' USING '25-JUN-97';
FETCH cur BULK COLLECT INTO v_emprecs LIMIT 10;
CLOSE cur;
emp_util.give_raise ( schema_in, v_emprecs);
END;
SYS_REFCURSOR is a pre-defined weak REF CURSOR type .
Since Oracle9i Release 2 we can now t ake advance of simple, int uit ive and compact synt ax t o bind an ent ire record t o a row in an insert . T his is
shown below:
DECLARE
v_emprec employees%rowtype := Emp_Util.Get_One_Row;
BEGIN
INSERT INTO employees_retired
VALUES v_emprec;
END;
Notice that we do not put the record inside parentheses. You are, unfortunately, not able to use this technique with Native Dynamic SQL. You can, on the other hand,
insert using a record in the highly efficient FORALL statement. This technique is valuable when you are inserting a large number of rows.
Take a look at the following example. The following table explains the interesting parts of the retire_them_now procedure
CREATE OR REPLACE PROCEDURE retire_them_now
IS
--Declare an Exception
bulk_errors EXCEPTION;
PRAGMA EXCEPTION_INIT (bulk_errors, -24381);
--Declare an associative array as rowtype of the employees table.
TYPE employees_t IS TABLE OF employees%ROWTYPE INDEX BY PLS_INTEGER;
retirees employees_t;
BEGIN
--Load up the array with the information for all employees who are over 40 years of age
FOR rec IN (SELECT * FROM employees
WHERE hire_date < ADD_MONTHS (SYSDATE, -1 * 18 * 40))
LOOP
retirees (SQL%ROWCOUNT) := rec;
END LOOP;
-- Added a clause to "catch" exceptions
FORALL indx IN retirees.FIRST .. retirees.LAST SAVE EXCEPTIONS
INSERT INTO employees
VALUES retirees (indx);
EXCEPTION
--Catch the exception
WHEN bulk_errors THEN
FOR j IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE ( 'Error from element #' ||TO_CHAR(SQL%BULK_EXCEPTIONS(j).error_index) || ': '
||SQLERRM(SQL%BULK_EXCEPTIONS(j).error_code));
END LOOP;
PDFmyURL.com
Advanced_PL_SQL
END LOOP;
END;
PDFmyURL.com
Advanced_PL_SQL
depts NumList := NumList(10,20,30);
TYPE enum_t IS TABLE OF employees.employee_id%TYPE;
TYPE dept_t IS TABLE OF employees.department_id%TYPE;
e_ids enum_t;
d_ids dept_t;
BEGIN
FORALL j IN depts.FIRST..depts.LAST
DELETE FROM emp_temp WHERE department_id = depts(j)
RETURNING employee_id, department_id
BULK COLLECT INTO e_ids, d_ids;
DBMS_OUTPUT.PUT_LINE('Deleted ' || SQL%ROWCOUNT || ' rows:');
FOR i IN e_ids.FIRST .. e_ids.LAST
LOOP
DBMS_OUTPUT.PUT_LINE('Employee #' || e_ids(i) || ' from dept #' || d_ids(i));
END LOOP;
END;
/
NOTE:
You cannot use returning bulk collect into a for INSERT .... INTO ... SELECT.... statement. It can be used for update and delete.
"insert as select" is currently not supported with the returning clause, you can ONLY use insert with the "values" clause.
So the following statement will return errors:
create table emp_temp as select * from emp where 1 = 0;
declare
type num_type is table of number;
l_empno num_type := num_type();
begin
insert into emp_temp(empno,ename)
select empno,ename from emp
returning empno
bulk collect into l_empno;
end;
/
declare
type num_type is table of number;
l_empno num_type;
type var_type is table of varchar(10);
l_empname var_type;
begin
select empno,ename bulk collect into l_empno,l_empname from emp;
forall i in l_empno.first..l_empno.last
insert into emp_temp(empno,ename)
values(l_empno(i),l_empname(i));
end;
/
In-Binding
Both the EXECUTE IMMEDIATE and FORALL (for bulk DML operations) offer a USING clause to bind variable values into the SQL statement. Let's
follow the progression of explicit row-from-row processing to bulk binding to bulk binding in native dynamic DML to see how the USING
clause is deployed.
We start with this kind of explicit FOR loop in our Oracle7 and Oracle8 code base:
FOR indx IN employee_ids.FIRST .. employee_ids.LAST
LOOP
UPDATE employees SET salary = salary * 1.1
WHERE employee_id = employee_ids (indx);
END LOOP;
Then, with Oracle8i, we get rid of most of the context switches by moving to FORALL:
FORALL indx IN employee_ids.FIRST .. employee_ids.LAST
UPDATE employees SET salary = salary * 1.1
WHERE employee_id = employee_ids (indx);
And that handles all of our needs-unless, once again, we need or would like to perform this same operation on different tables, based on
location (or for any other kind of dynamic SQL situation). In this case, we can combine FORALL with EXECUTE IMMEDIATE, with these
wonderful results:
CREATE OR REPLACE PROCEDURE upd_employees (
loc_in IN VARCHAR2,
employees_in IN employees_t )
IS
BEGIN
FORALL indx in employees_in.first..employees_in.last
EXECUTE IMMEDIATE 'UPDATE ' || loc_in || ' employees SET salary = salary*1.1' || ' WHERE employee_id = :the_id'
USING employee_in (indx);
END;
Notice that in the USING clause, we must include both the name of the collection and the subscript for a single row using the same FORALL
loop index variable.
Out-Binding
Let's again follow the progression from individual row updates to bulk bind relying on BULK COLLECT INTO to retrieve information, and
finally the dynamic approach possible in Oracle 9i.
Oracle8 enhanced DML capabilities by providing support for the RETURNING clause. Shown in the following FOR loop, it allows us to obtain
information (in this case, the updated salary) from the DML statement itself (thereby avoiding a separate and expensive query).
BEGIN
FOR indx IN employee_ids.FIRST .. employee_ids.LAST
LOOP
UPDATE employees SET salary = salary * 1.1
WHERE employee_id = employee_ids (indx)
RETURNING salary INTO salaries (indx);
END LOOP;
END;
Starting with Oracle8i, we can take advantage of FORALL to improve performance dramatically:
BEGIN
FORALL indx IN employee_ids.FIRST .. employee_ids.LAST
PDFmyURL.com
Advanced_PL_SQL
UPDATE employees SET salary = salary * 1.1
WHERE employee_id = employee_ids (indx)
RETURNING salary BULK COLLECT INTO salaries;
END;
There's one seemingly odd aspect of this code you should remember: Inside the DML statement, any reference to the collection that drives
the FORALL statement must be subscripted as in:
WHERE employee_id = employee_ids (indx)
In the RETURNING clause, however, you BULK COLLECT INTO the collection and not a single subscripted row of the collection.
That's all well and good, but what if (not to sound like a broken record) we want to execute this same update for any of the employee tables
for different locations? Time to go to NDS and use the RETURNING BULK COLLECT clause:
CREATE OR REPLACE PROCEDURE upd_employees (loc_in IN VARCHAR2, employees_in IN employees_t)
IS
my_salaries salaries_t;
BEGIN
FORALL indx in employees_in.first.. employees_in.last
EXECUTE IMMEDIATE 'UPDATE '|| loc_in || ' employees SET salary = salary*1.1' || ' WHERE employee_id = :the_id
RETURNING salary INTO :salaries'
USING employee_in (indx)
RETURNING BULK COLLECT INTO my_salaries;
END;
Use of SAVE EXCEPTIONS allows the FORALL to continue through all the rows indicated by the collection; it "saves up" the exceptions as it
encounters them. This saving step begs the obvious question: How can you, the developer, get information about the errors that were
"saved"? By taking advantage of the new SQL%BULK_COLLECTIONS pseudo-collection, as demonstrated in the code shown in the
following example:
DECLARE
bulk_errors EXCEPTION;
PRAGMA EXCEPTION_INIT (bulk_errors, -24381);
BEGIN
FORALL indx IN words.FIRST .. words.LAST SAVE EXCEPTIONS
INSERT INTO t (text) VALUES (words (indx));
EXCEPTION
WHEN bulk_errors THEN
--For each error found, try to identify the cause of that error
FOR j IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
LOOP
Dbms_Output.Put_Line ('Iteration Number ' || sql%bulk_exceptions(j).error_index);
Dbms_Output.Put_Line ('Error ' || Sqlerrm(sql%bulk_exceptions(j).error_code*-1));
--Detecting Unique Constraint Violation
if substr(Sqlerrm(SQL%BULK_EXCEPTIONS(J).ERROR_CODE * -1),1,9) = 'ORA-00001' then
v_RowsDuplicated := v_RowsDuplicated + 1;
else
PDFmyURL.com
Advanced_PL_SQL
Dbms_Output.Put_Line ('Other type of Error on Issuedata Import');
end if;
END LOOP;
v_newerrors := SQL%BULK_EXCEPTIONS.COUNT;
v_errors := v_errors + v_newerrors;
dbms_output.put_line('Total Errors= ' || to_char(v_errors));
END;
Each row of this pseudo-collection is a record consisting of two fields: ERROR_INDEX and ERROR_CODE. The ERROR_INDEX shows
which index in the original bulk-load collection causes the failure. ERROR_CODE is the error number encountered.
You must both use the SAVE EXCEPTIONS construct and handle the BULK_ERRORS exception to get the intended benefit (that is, that all
non-erroring rows are inserted).
Implementation restriction
It is not allowed to use the FORALL statement and an UPDATE order that use the SET ROW functionality
Declare
TYPE TAB_EMP is table of EMP%ROWTYPE ;
emp_tab TAB_EMP ;
Cursor CEMP is Select * From EMP ;
Begin
Open CEMP;
Fetch CEMP BULK COLLECT Into emp_tab ;
Close CEMP ;
Forall i in emp_tab.first..emp_tab.last
Update EMP set row = emp_tab(i) where EMPNO = emp_tab(i).EMPNO ; -- ILLEGAL
End ;
/
PDFmyURL.com
Advanced_PL_SQL
Forall i in no_tab.first..no_tab.last
Update EMP set ENAME = na_tab(i) where EMPNO = no_tab(i) ;
End ;
- You cannot bulk collect into an associative array that has a string type for the key.
- You can use the BULK COLLECT clause only in server-side programs (not in client-side programs). Otherwise, you get the error this feature
is not supported in client-side programs.
- All targets in a BULK COLLECT INTO clause must be collections, as the following example shows:
DECLARE
TYPE NameList IS TABLE OF emp.ename%TYPE;
names NameList;
salary emp.sal%TYPE;
BEGIN
SELECT ename, sal BULK COLLECT INTO names, salary -- illegal target
FROM emp WHERE ROWNUM < 50;
...
END;
- Composite targets (such as objects) cannot be used in the RETURNING INTO clause. Otherwise, you get the error unsupported feature
withRETURNING clause.
- When implicit datatype conversions are needed, multiple composite targets cannot be used in the BULK COLLECT INTO clause.
- When an implicit datatype conversion is needed, a collection of a composite target (such as a collection of objects) cannot be used in the
BULKCOLLECT INTO clause.
MULTI-DIMENSIONAL ARRAYS
Another new feature is the capability of multi-dimensional arrays, which Oracle has implemented as collections of collections.Technically, all
collection types support only a single dimension, however by allowing a collection element to become a collection, one has the effectively
the same data structure. The following code shows the way to declare and reference a two-dimensional array of numbers.
DECLARE
TYPE element IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
TYPE twoDimensional IS TABLE OF element INDEX BY BINARY_INTEGER;
twoD twoDimensional;
BEGIN
twoD(1)(1) := 123;
twoD(1)(2) := 456;
END;
At first one would think that, while an interesting capability, it has no potential impact on performance but it will be shown later in this paper
how the combination of this capability along with the use of packaged variables can open up the door to dramatically speeding up PL/SQL
PDFmyURL.com
Advanced_PL_SQL
code.
Another Example
To demonst rat e t his, let 's use t he example of st udent s in a college or universit y.
In t he real world t he inf ormat ion we would want t o know about st udent s would most likely include :
- Addresses (home/holiday, t erm-t ime), more t han one
- Phone numbers (home, t erm-t ime, mobile)
- Email addresses (home, college, ot her)
- First and Last Name
- et c
But let 's keep t hings simple f or t his example and rest rict ourselves t o just t he f irst and last name and 2 addresses simply t o demonst rat e t he
principles of nest ing collect ions.
T his means t hat we have 1 ent it y - st udent - wit h t he f ollowing at t ribut es : f irst & last name and 2 addresses. In a dat abase, ent it ies t ranslat e t o
a t able, so we have our f irst dat a st ruct ure: a t able (or collect ion in PL/SQL t erms).
T he at t ribut es would normally t ranslat e t o columns in t he t able but Oracle's PL/SQL doesn't have t he concept of columns so we have t o use t he
record st ruct ure.
Now we have a problem t hough. Our st udent record needs t o cont ain 2 addresses, how do we handle t his in PL/SQL ? We could just have t wo
dif f erent address at t ribut es (a home address and an t erm-t ime address) but we might need t o add more addresses lat er so we'll use a PLSQL
collect ion f or t he addresses.
T he next problem is t hat each address has many lines, how do we handle t hat ? Again, no problem. We simply def ine an address as a collect ion
of address lines.
Not e t hat we are indexing address_lines by binary_int eger and addresses by varchar2 so t hat we can look up addresses by name (e.g. 'home' or
't erm-t ime') rat her t han by number.
Now we have nest ed our collect ions (associat ive arrays in t his case) - we have a t able of addresses and each address consist s of a collect ion of
address lines. Not e t hat we can't use any of t hese dat a st ruct ures yet , as we've only def ined t he t ypes, not declared any variables of t hose
t ypes.
T he next t hing t o do is t o def ine our record st ruct ure t o hold t he st udent inf ormat ion as f ollows:
So f ar, so good, but we only have t he abilit y t o st ore inf ormat ion about 1 st udent at t he moment so we need t o def ine anot her t ype - a t able
(collect ion) of st udent s:
TYPE students_tab IS TABLE OF student INDEX BY BINARY_INTEGER;
PDFmyURL.com
Advanced_PL_SQL
Not e t hat t his t ime we're indexing by binary int eger, which could be t he numeric st udent id, as we're already st oring t he f irst and last names.
T he f inal dat a st ruct ure declarat ion is t hat of t he variable t o st ore t he dat a.
students students_tab;
Now we have PL/SQL collect ions nest ed 3 deep - we have a collect ion of st udent s, each of which has a collect ion of addresses (home, t erm-
t ime, et c), each of which in t urn has a collect ion of address lines. We could ext end t he nest ing by having say a collect ion of colleges, each of
which has a collect ion of st udent s and so on, but just because we can do t hings it doesn't necessarily mean we should do t hem!
Let 's put t his all t oget her now and writ e some code t o st ore and ret rieve some dat a.
DECLARE
TYPE address_lines IS TABLE OF VARCHAR2(50) INDEX BY BINARY_INTEGER;
TYPE address_tab IS TABLE OF address_lines INDEX BY VARCHAR2(10);
TYPE student IS RECORD (
first_name VARCHAR2(50),
last_name VARCHAR2(50),
addresses address_tab);
TYPE students_tab IS TABLE OF student INDEX BY BINARY_INTEGER;
students students_tab;
BEGIN
students(1).first_name := 'Mike';
students(1).last_name := 'Johnson';
students(1).addresses('HOME')(2) := 'Bristol';
students(1)Addresses('HOME')(1) := '1 The Avenue';
students(1)Addresses('HOME')(3) := 'BS99 3SJ';
students(2)First_name := 'Mike';
students(2)Last_name := 'Bliss';
students(2)Addresses('HOME')(2) := 'Leicester';
students(2)Addresses('HOME')(1) := '11 The Street';
students(2)Addresses('HOME')(3) := 'LE64 50Q';
PDFmyURL.com
Advanced_PL_SQL
St udent (2) home address line(3) LE64 50Q
T he f irst collect ion is a set of records, t he part of t he record we are int erest ed in, is in t urn anot her collect ion each element of which is anot her
collect ion. T he (address_line_no)clause gives us t he access t o t he base level collect ion but we can't access it by name because it doesn't have
one, t he t ype declarat ion is only a place holder in ef f ect .
The advantage of this approach is that its very simple code, easily understood by all PL/SQL developers.
The downsides of this approach include:
The calling environment needs to be able to work with PL/SQL records, which arent universally recognized.
This approach forces you to work with a single row at a time. If the calling program needs to retrieve lots of data, this will be a relatively
slow alternative.
An application can call the function and bind the returned open cursor to a local cursor variable. The application then iterates through the
result set as if it had been defined and opened locally.
The advantages of the cursor variable approach include the following:
The cursor variable can be passed back to a non-PL/SQL host environment such as Java, which can then fetch individual rows through the
cursor variable, and close it when completed.
Within PL/SQL, you can manipulate the cursor variable using traditional and familiar syntax.
The package body contains the implementation of that function by relying on the fast and concise BULK COLLECT query.
Note that with BULK COLLECT, a SELECT INTO will not raise NO_DATA_FOUND if no rows are found. Instead, you must check to see how
many rows are defined in the collection to determine whether the query returned any rows.
CREATE OR REPLACE PACKAGE BODY many_employees IS
FUNCTION allrows RETURN employee_tc
IS
l_employees employee_tc;
BEGIN
SELECT * BULK COLLECT INTO l_employees
FROM employee;
RETURN l_employees;
PDFmyURL.com
Advanced_PL_SQL
END allrows;
END many_employees;
/
From within PL/SQL, I can call this function and then iterate through the returned rows of the collection. In the example shown here, I display
the last name of each employee found in the collection:
DECLARE
l_employees many_employees.employee_tc;
l_row PLS_INTEGER;
BEGIN
l_employees := many_employees.allrows;
l_row := l_employees.FIRST;
WHILE (l_row IS NOT NULL)
LOOP
DBMS_OUTPUT.put_line (l_employees(l_row).last_name);
l_row := l_employees.NEXT(l_row);
END LOOP;
END;
/
We could do row-by-row querying into the collection or bulk processing. Lets start with the oldfashioned row-by-row processing. This
version is shown here:
FUNCTION allrows_by (where_in IN VARCHAR2 DEFAULT NULL)
RETURN employee_tc
PDFmyURL.com
Advanced_PL_SQL
IS
TYPE weak_rc IS REF CURSOR;
allrows_cur weak_rc;
l_rows PLS_INTEGER;
retval employee_tc;
BEGIN
IF where_in IS NULL THEN
RETURN allrows;
ELSE
OPEN allrows_cur FOR 'SELECT * FROM om_EMPLOYEE WHERE ' || where_in;
LOOP
FETCH allrows_cur INTO retval (retval.COUNT + 1);
EXIT WHEN allrows_cur%NOTFOUND;
END LOOP;
RETURN retval;
END IF;
END allrows_by;
Now, having done and seen all of that code, I must also caution you that whenever you face the prospect of querying data on a row-by-row
basis, you should consider switching to BULK COLLECT. This construct generally will allow you to write less code (if you were already using
collections) and execute your code much more efficiently. In this implementation, if my WHERE clause isnt null, then I simply construct the
query, execute within the EXECUTE IMMEDIATE statement, and then BULK COLLECT INTO my collection. With that single statement, all the
data in om_employee is transferred to the collection (and the rows are populated sequentially, starting with 1).
FUNCTION allrows_by (where_in IN VARCHAR2 DEFAULT NULL)
RETURN employee_tc
IS
l_rows PLS_INTEGER;
retval employee_tc;
BEGIN
IF where_in IS NULL THEN
RETURN allrows;
ELSE
EXECUTE IMMEDIATE 'SELECT * FROM om_EMPLOYEE ' ||'WHERE ' || where_in
BULK COLLECT INTO retval;
RETURN retval;
END IF;
END allrows_by;
PDFmyURL.com
Advanced_PL_SQL
program can return its output collection as a cursor using the following syntax. Views can be wrapped around this kind of SQL statement to
make life easier for a calling application.
Note that while the return type of the function is still the collection type, the local variable being assigned is the object type. In this example,
the fetch is performed using the BULK COLLECTfeature. The documents illustrate the much slower row-by-row fetch. Since the signature
of the procedure has not been changed, only the implementation, it can be called the same way as the previous table function using the
TABLE and CAST functions.
Cursor Attributes
More information can be found HERE
For FORALL, %FOUND and %NOTFOUND reflect the overall results, not the results of an individual statement, including the last. In
other words, if any one of the statements executed in the FORALL modified at least one row, %FOUND returns TRUE and
%NOTFOUND returns FALSE.
PDFmyURL.com
Advanced_PL_SQL
For FORALL, %ISOPEN always returns FALSE because the cursor is closed when the FORALL statement terminates.
For FORALL, %ROWCOUNT returns the total number of rows affected by all the FORALL statements executed, not simply the last
statement.
For BULK COLLECT, %FOUND and %NOTFOUND always return NULL and %ISOPEN returns FALSE because the BULK COLLECT has
completed the fetching and closed the cursor. %ROWCOUNT always returns NULL, since this attribute is only relevant for DML
statements.
The nth row in this pseudo index-by table stores the number of rows processed by the n th execution of the DML operation in the
FORALL statement. If no rows are processed, then the value in %BULK_ROWCOUNT is set to 0.
The %BULK_ROWCOUNT attribute is a handy device, but it is also quite limited. Keep the following in mind:
Even though it looks like an index-by table, you cannot apply any methods to it.
%BULK_ROWCOUNT cannot be assigned to other collections. Also, it cannot be passed as a parameter to subprograms.
The only rows defined for this pseudo index-by table are the same rows defined in the collection referenced in the FORALL statement.
l_tab1.delete(301);
l_tab1.delete(601);
l_tab1.delete(901);
PDFmyURL.com
Advanced_PL_SQL
-- INSERT INTO tab1 VALUES l_tab(i);
-- This works fine for collections of indexes pointing to elements of another collection.
FORALL i IN VALUES OF l_tab2
INSERT INTO tab1 VALUES l_tab1(i);
END;
/
SET SERVEROUTPUT ON
DECLARE
TYPE t_colors IS TABLE OF VARCHAR2(10);
l_col_1 t_colors := t_colors('Red', 'Green', 'Blue', 'Green', 'Blue');
l_col_2 t_colors := t_colors('Red', 'Green', 'Yellow', 'Green');
l_col_3 t_colors;
-- Expression assignments.
l_col_3 := l_col_1 MULTISET UNION l_col_2;
display('MULTISET UNION:', l_col_3);
PDFmyURL.com
Advanced_PL_SQL
l_col_3 := l_col_1 MULTISET EXCEPT DISTINCT l_col_2;
display('MULTISET EXCEPT DISTINCT:', l_col_3);
END;
/
Direct Assignment:
Red
Green
Blue
Green
Blue
MULTISET UNION:
Red
Green
Blue
Green
Blue
Red
Green
Yellow
Green
MULTISET INTERSECT:
Red
Green
Green
MULTISET EXCEPT:
Blue
Blue
l_col_3 := l_col_1;
CARDINALITY(l_col_2): 3
Remove Duplicat es
The SET function removes duplicate entries from your nested table, in a similar way to the SQL DISTINCT aggregate function.
SET SERVEROUTPUT ON
DECLARE
TYPE t_colors IS TABLE OF VARCHAR2(10);
PDFmyURL.com
Advanced_PL_SQL
l_col_1 t_colors := t_colors('Red', 'Green', 'Blue', 'Green', 'Blue');
l_col_2 t_colors;
-- SET assignments.
l_col_2 := SET(l_col_1);
display('MULTISET UNION:', l_col_2);
END;
/
Direct Assignment:
Red
Green
Blue
Green
Blue
MULTISET UNION:
Red
Green
Blue
PDFmyURL.com
Advanced_PL_SQL
NESTED TABLE review_date STORE AS emp_tab
NESTED TABLE salary_last_increase STORE AS sup_tab
NESTED TABLE promotion_date STORE AS promo_tab;
-- Insert 2 records.
INSERT INTO t_emp_info values (1,
TY_REVIEW (T_REVIEW_DATES(SYSDATE-365),
T_REVIEW_DATES(SYSDATE-300),
T_REVIEW_DATES(SYSDATE-270),
T_REVIEW_DATES(SYSDATE-200) ),
TY_REVIEW (T_REVIEW_DATES(SYSDATE-365),
T_REVIEW_DATES(SYSDATE-300) ),
TY_REVIEW (T_REVIEW_DATES(SYSDATE)),
SYSDATE, SYSDATE);
Note that nested tables are still not supported in temporary tables.
PDFmyURL.com
Advanced_PL_SQL
Now, to modify the VARRAY so that it will be able to hold more elements, simply issue an alter type command:
alter type ty_test modify limit 1000 cascade;
There are two options available when altering types (neither of which is a default value):
invalidate - Invalidates all dependent objects when the operation takes place and does so without any checks. This can be dangerous,
as it is possible to inadvertently drop an attribute that is critical, so use invalidate carefully.
cascade - Propagates the change to all dependent types and tables, and an error will occur if any errors are found.
Finally, the size of a VARRAY can only be increased. Attempting to make it smaller will result in an error.
It is also possible to alter the existing tablespace of the table of a nested table collection with the alter table command, as shown in this
example:
Alter table test_id_no move tablespace new_users;
Note that it was necessary to create a map method for the object, which is required. After the objects have been created and the data has
been inserted, it is possible to query the collection items in the table, as shown below:
COLUMN review_date FORMAT a20
COLUMN salary_last_increase FORMAT a20
SQL> SELECT emp_id FROM t_emp_info WHERE review_date=salary_last_increase;
EMP_ID
----------
2
PDFmyURL.com
Advanced_PL_SQL
The PL/SQL block below populates a collection with the existing data, amends the data in the collection, then updates the table with the
amended data.
The final query displays the changed data in the table.
DECLARE
TYPE t_forall_test_tab IS TABLE OF forall_test%ROWTYPE;
l_tab t_forall_test_tab;
BEGIN
-- Retrieve the existing data into a collection.
SELECT * BULK COLLECT INTO l_tab
FROM forall_test;
PDFmyURL.com
Advanced_PL_SQL
4 44 Description for 4
5 55 Description for 5
In versions prior to 11g, the lines with the UPDATE would raise PLS-00436 because they reference attributes within the forall_test record
variable.
In Oracle Database 10g and below, you would have had to define separate collection variables for the columns and in the process may have
had to update several times, based on your exact logic.
In Oracle 11g, this is not only makes the program more readable and maintainable; but performant as well, since you may not need to issue
multiple UPDATE statements.
Notice both the SET and WHERE clauses contain references to individual columns in the collection. This makes using bulk-binds for DML
even easier as we no longer need to maintain multiple collections if we need to reference columns in the WHERE clause. It can also improve
performance of updates, as previous versions required updates of the whole row using the ROW keyword, which included potentially
unnecessary updates of primary key and foreign key columns.
PDFmyURL.com