You are on page 1of 31

PL/SQL TABLES

PL/SQL tables are PL/SQL’s way of providing arrays. Arrays are like temporary
tables in memory and thus are processed very quickly. It is important for you to
realize that they are not database tables, and DML statements cannot be issued
against them. This type of table is indexed by a binary integer counter (it cannot
be indexed by another type of number) whose value can be referenced using the
number of the index. Remember that PL/SQL tables exist in memory only, and
therefore don’t exist in any persistent way, disappearing after the session ends.
A PL/SQL TABLE DECLARATION
There are two steps in the declaration of a PL/SQL table. First, you must define
the table structure using the TYPE statement. Second, once a table type is
created, you then declare the actual table.

create or replace procedure table_type_test


is
cursor c is select ename, job, sal from emp;
TYPE EMP_TAB IS TABLE OF EMP.ENAME%TYPE INDEX BY
BINARY_INTEGER;
EMP_TAB_TYPE EMP_TAB;
COUNTER NUMBER:=0;
BEGIN
FOR I IN C
LOOP
COUNTER:=COUNTER+1;
EMP_TAB_TYPE(COUNTER):=I.ENAME;
END LOOP;
FOR J IN 1..COUNTER
LOOP
DBMS_OUTPUT.PUT_LINE('EMPLOYEE NAME IS '||EMP_TAB_TYPE(J));
END LOOP;
END;
/

PL/SQL TABLE ATTRIBUTES


Here are seven PL/SQL table attributes you can use to gain information
about a PL/SQL table or to modify a row in a PL/SQL table:
• DELETE—Deletes rows in a table.
• EXISTS—Return TRUE if the specified entry exists in the table.
• COUNT—Returns the number of rows in the table.
• FIRST—Returns the index of the first row in the table.
• LAST—Returns the index of the last row in the table.
• NEXT—Returns the index of the next row in the table after the
specified row.
• PRIOR—Returns the index of the previous row in the table be-fore
the specified row.
The DELETE and EXISTS attributes function differently than the other attributes.
These two generally operate on one row at a time, so you must
add the following syntax:
<TableName>.<attribute>(<IndexNumber>[,<IndexNumber>])
t.customer.delete deletes all rows from the t_customer table, whereas
t_customer.delete(15) deletes the fifteenth row of the t_customer table.
Likewise, t_customer.exists(10) returns a value of true if there is a one hundredth
row and a value of false if there is not.
The EXISTS attribute can be used to determine if a particular index value exists
in the PL/SQL table or not.

FOR EXAMPLE

DECLARE
TYPE t_czip_type IS TABLE OF
customers.post_code%TYPE
INDEX BY BINARY_INTEGER;
t_czip t_czip_type;
v_czip_index BINARY_INTEGER;
BEGIN
t_czip(11203) := ‘nyc’;
t_czip(11201) := ‘Brkl’;
t_czip(49341) := ‘SF’;
BEGIN
v_czip_index := t_czip.first;
LOOP
DBMS_OUTPUT.PUT_LINE(t_czip(v_czip_index));
EXIT WHEN v_czip_index = t_czip.LAST;
v_czip_index := t_czip.NEXT(v_czip_index);
END LOOP;
RAISE NO_DATA_FOUND;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.PUT_LINE(‘The last zipcode has been reached.’);
END;
END;

create or replace procedure table_type_test


is
cursor c is select ename, job, sal from emp;
TYPE EMP_TAB IS TABLE OF C%ROWTYPE INDEX BY BINARY_INTEGER;
EMP_TAB_TYPE EMP_TAB;
COUNTER NUMBER:=0;
EMP_REC C%ROWTYPE;
BEGIN
FOR I IN C
LOOP
COUNTER:=COUNTER+1;
EMP_TAB_TYPE(COUNTER):=I;
END LOOP;
FOR J IN emp_tab_type.FIRST .. EMP_TAB_TYPE.LAST
LOOP
EMP_REC:=EMP_TAB_TYPE(J);
DBMS_OUTPUT.PUT_LINE(EMP_REC.ENAME||' '||EMP_REC.JOB||' '||
EMP_REC.SAL);
END LOOP;
END;
/

CREATE OR REPLACE PROCEDURE TABLE_TYPE_TEST


IS
CURSOR C IS SELECT ENAME, JOB, SAL FROM EMP;
TYPE EMP_TAB IS TABLE OF C%ROWTYPE;
EMP_TAB_TYPE EMP_TAB:=EMP_TAB();
COUNTER NUMBER:=0;
EMP_REC C%ROWTYPE;
BEGIN
FOR I IN C
LOOP
EMP_TAB_TYPE.EXTEND;
EMP_TAB_TYPE(EMP_TAB_TYPE.LAST):=I;
END LOOP;
FOR J IN emp_tab_type.FIRST .. EMP_TAB_TYPE.LAST
LOOP
EMP_REC:=EMP_TAB_TYPE(J);
DBMS_OUTPUT.PUT_LINE(EMP_REC.ENAME||' '||EMP_REC.JOB||' '||
EMP_REC.SAL);
END LOOP;
END;
/
Nested Tables:
Create type members is table of varchar2(10);
/
Create table family_details(family_head varchar2(10), dependents members)
Nested table dependents store as members_tab;
Insert into family_details
values('Sudheer',members('RAMESH','SUMA','SRIDEVI'));
Select * from table(select dependents from family_details where
family_head='Sudhher');
NESTED TABLE with OBJECT TYPE
CREATE TYPE t_experience AS OBJECT
(
CompanyName
varchar2(20),
Position
varchar2(20),
NoOfYears
number(2)
);
/
The above script just creates an OBJECT TYPE (which is almost similar to the
one present in part-4).
CREATE TYPE t_experience_tbl AS TABLE OF t_experience;
If we observe the above carefully, we are still creating another TYPE (not a
concrete table), but it is of TABLE TYPE within
database. For our convenience, I named it as ‘t_experience_tbl’.
CREATE TABLE employees
(
Name
varchar2(20),
Experiences
t_experience_tbl
)
NESTED TABLE Experiences STORE AS Experiences_tab;
The above statement creates a new table ‘employees’ (make sure to drop an old
one if it exists) with only two
fields, ‘name’ and ‘experiences’. The field ‘Experiences’ is created based on the
TABLE TYPE ‘t_experience_tbl’. This
means that every employee can now store his experience list in the same row.
Indirectly, experience list itself is a
table of information (CompanyName, Position, NoOfYears). This table is being
stored as a part of single row in the
table ‘employees’, which is what the NESTED TABLE is.
Even though the NESTED TABLE is logically part of the ‘employees’ table, it is
stored externally (with a different table
name ‘experiences_tab’) from the main table. The following statement inserts a
row into that table.
insert into employees values
(
'jag',
t_experience_tbl
(
t_experience('abc company','Software
Engineer',3),
t_experience('xyz company','System Analyst',2),
t_experience('mnp
company','Research fellow',4)
)
);
Better not confuse ourselves with the example above. ‘t_experience_tbl’ is a
TABLE TYPE based on ‘t_experience’ OBJECT
TYPE. So, ‘t_experience_tbl’ can have any number of OBJECTs of TYPE
‘t_experience’. The same concept is practiced
above. And you can insert any number of experiences for a single row.
The following SELECT statement gives the respective details of NESTED TABLE
available in a single row of parent table:
select a.CompanyName,a.NoOfYears from
table(select experiences from employees where name='jag') a
You can also replace the word ‘table’ with another keyword ‘the’ as follows:
select a.CompanyName,a.NoOfYears from
the(select experiences from employees where name='jag') a
Any of the above two examples performs two operations. First, it reads an
‘employees’ row with the given name. Then,
it reads the elements stored in that row’s nested table. For better readability, I
used ‘table’ instead of ‘the’. You can even
replace the field names with ‘*’ as follows:
select a.* from
table(select experiences from employees where name='jag') a
By now, we already know how to retrieve information from NESTED TABLEs.
Now we need to know how to insert rows to
the NESTED TABLE (not to the main table ‘employees’)? This means I wanted to
add another ‘experience’ row to the same
employee ‘jag’. The following statement does it.
INSERT INTO
TABLE(SELECT experiences FROM employees WHERE name = 'jag')
VALUES
(
t_experience('efg company','Professor',2)
);
If you compare the above example with previous SELECT statements, you
should be able to understand and there is
nothing new from any angle. Actually it is not necessary to use ‘t_experience’ in
the above statement, you can also insert
it as follows:
INSERT INTO
TABLE(SELECT experiences FROM employees WHERE name = 'jag')
VALUES
(
'efg company','Professor',2
);
But for better readability, I suggest you to use the TYPE name along with values.
You can even modify the existing rows
using UPDATE statement as follows:
UPDATE
TABLE(SELECT experiences FROM employees WHERE name = 'jag')
set NoOfYears=5
where CompanyName='abc company';
You can even use DELETE statement on NESTED TABLE as follows:
DELETE FROM
TABLE(SELECT experiences FROM employees WHERE name = 'jag')
where CompanyName='abc company';
If you don’t use any OBJECT type in the NESTED TABLE (like a NESTED
TABLE of varchar2, like in part-3), you can use a
keyword ‘column_value’ for UPDATE or DELETE statements. It is necessary
because the NESTED TABLE does not have any
specific columns to address.
PL/SQL nested tables represent sets of values. You can think of them as one-
dimensional arrays with no upper bound.
You can model multi-dimensional arrays by creating nested tables whose
elements are also nested tables. Within
the database, nested tables are column types that hold sets of values. Oracle
stores the rows of a nested table in
no particular order. When you retrieve a nested table from the database into a
PL/SQL variable, the rows are given
consecutive subscripts starting at 1. That gives you array-like access to individual
rows.
The PL/SQL would be almost similar to the example presented in part-3 of my
article, but with some simple
enhancements. Consider the following example:
declare
v_experiences
t_experience_tbl;
v_name
varchar2(20) := '&name';
begin
select experiences into v_experiences
from employees where name=v_name;
dbms_output.put_line('Experience list of ' || v_name);
dbms_output.put_line('-----------
------------');
for i in v_experiences.first .. v_experiences.last
loop
dbms_output.put(v_experiences(i).Position);
dbms_output.put_line (',' ||
v_experiences(i).NoOfYears);
end loop;
end;
I don’t think there exists anything new from the above program. You are just
creating a TABLE based
variable ‘v_experiences’, storing all the values into that variable using the
SELECT statement and displaying them using a
FOR loop. It seems quite comfortable to work with single row of information (as
above). Let us consider another issue.
How to display all employees with all their experiences? The following example
shows the solution:
declare
v_experiences
t_experience_tbl;
TYPE t_name is TABLE OF varchar2(20);
v_name
t_name;
begin
select name bulk collect into v_name from employees;
for j in v_name.first ..
v_name.last
loop
select experiences into v_experiences
from employees where
name=v_name(j);
dbms_output.put_line('-----------------------');
dbms_output.put_line('Experience list of ' || v_name);
dbms_output.put_line('-------
----------------');
for i in v_experiences.first .. v_experiences.last
loop
dbms_output.put(v_experiences(i).Position);
dbms_output.put_line (',' ||
v_experiences(i).NoOfYears);
end loop;
end loop;
end;
In the above program, I declared a new TABLE type ‘t_name’ and a variable
‘v_name’ based on it to hold all employee
names as a bulk. I populated all the employee names into that variable using the
first SELECT statement. I am using two
FOR loops. One loop for the employee names and the other for experience list of
employee retrieved by the outer FOR
loop. It is something like the parent-child display.
The above program is NOT the only solution. We can develop in several other
ways based on the appropriate situation.
Can we INSERT, UPDATE or DELETE from NESTED TABLES using PL/SQL?
Why not? We can do it in several ways. We
can use RECORD types, TABLE types or even individual variables to do all such
tasks. The syntax will be almost similar
to the examples given in the previous section (SQL statements). But to be bit
enhanced with PL/SQL based variables in
whatever way we like. I leave it to you to try and investigate the maximum
number of ways to achieve them.

VARRAY: Will have a limitation in size.


Create type members as varray(5) of varchar2(10);

How To Retrieve Typical Resultsets From Oracle Stored Procedures

SUMMARY

This article shows how to create a Remote Data Object (RDO) project that
returns a typical Resultset from an Oracle stored procedure.

Step-by-Step Example
1. Run the following DDL script on your Oracle server:
2. DROP TABLE person;
3.
4. CREATE TABLE person
5. (ssn NUMBER(9) PRIMARY KEY,
6. fname VARCHAR2(15),
7. lname VARCHAR2(20));
8.
9. INSERT INTO person VALUES(555662222,'Sam','Goodwin');
10.
11. INSERT INTO person VALUES(555882222,'Kent','Clark');
12.
13. INSERT INTO person VALUES(666223333,'Sally','Burnett');
14.
15. COMMIT;
16. /
17.

18. Create the following package on your Oracle server:


19. CREATE OR REPLACE PACKAGE packperson
20. AS
21. TYPE tssn is TABLE of NUMBER(10)
22. INDEX BY BINARY_INTEGER;
23. TYPE tfname is TABLE of VARCHAR2(15)
24. INDEX BY BINARY_INTEGER;
25. TYPE tlname is TABLE of VARCHAR2(20)
26. INDEX BY BINARY_INTEGER;
27.
28. PROCEDURE allperson
29. (ssn OUT tssn,
30. fname OUT tfname,
31. lname OUT tlname);
32. PROCEDURE oneperson
33. (onessn IN NUMBER,
34. ssn OUT tssn,
35. fname OUT tfname,
36. lname OUT tlname);
37. END packperson;
38. /
39.

40. Create the following package body on your Oracle server:


41. CREATE OR REPLACE PACKAGE BODY packperson
42. AS
43.
44. PROCEDURE allperson
45. (ssn OUT tssn,
46. fname OUT tfname,
47. lname OUT tlname)
48. IS
49. CURSOR person_cur IS
50. SELECT ssn, fname, lname
51. FROM person;
52.
53. percount NUMBER DEFAULT 1;
54.
55. BEGIN
56. FOR singleperson IN person_cur
57. LOOP
58. ssn(percount) := singleperson.ssn;
59. fname(percount) := singleperson.fname;
60. lname(percount) := singleperson.lname;
61. percount := percount + 1;
62. END LOOP;
63. END;
64.
65. PROCEDURE oneperson
66. (onessn IN NUMBER,
67. ssn OUT tssn,
68. fname OUT tfname,
69. lname OUT tlname)
70. IS
71. CURSOR person_cur IS
72. SELECT ssn, fname, lname
73. FROM person
74. WHERE ssn = onessn;
75.
76. percount NUMBER DEFAULT 1;
77.
78. BEGIN
79. FOR singleperson IN person_cur
80. LOOP
81. ssn(percount) := singleperson.ssn;
82. fname(percount) := singleperson.fname;
83. lname(percount) := singleperson.lname;
84. percount := percount + 1;
85. END LOOP;
86. END;
87. END;
88. /
89.

90. Open a new project in Visual Basic Enterprise edition. Form1 is created
by default.
91. Place the following controls on the form:
92. Control Name Text/Caption
93. -----------------------------------------
94. Button cmdGetEveryone Get Everyone
95. Button cmdGetOne Get One
96.

97. From the Tools menu, select the Options item. Click the "Default Full
Module View" option, and then click OK. This allows you to view all of
the code for this project.
98. Paste the following code into your code window:
99. Option Explicit
100. Dim Cn As rdoConnection
101. Dim En As rdoEnvironment
102. Dim CPw1 As rdoQuery
103. Dim CPw2 As rdoQuery
104. Dim Rs As rdoResultset
105. Dim Conn As String
106. Dim QSQL As String
107. Dim tempcnt As Integer
108.
109. Private Sub cmdGetEveryone_Click()
110.
111. Set Rs = CPw1.OpenResultset(rdOpenStatic,
rdConcurReadOnly)
112.
113. While Not Rs.EOF
114.
115. MsgBox "Person data: " & Rs(0) & ", " & Rs(1) & ", " &
Rs(2)
116. Rs.MoveNext
117.
118. Wend
119.
120. Rs.Close
121.
122. Set Rs = Nothing
123.
124. End Sub
125.
126. Private Sub cmdGetOne_Click()
127.
128. Dim inputssn As Long
129.
130. inputssn = InputBox("Enter an SSN number:")
131.
132. CPw2(0) = inputssn
133.
134. Set Rs = CPw2.OpenResultset(rdOpenStatic,
rdConcurReadOnly)
135.
136. MsgBox "Person data: " & Rs(0) & ", " & Rs(1) & ", " &
Rs(2)
137.
138. Rs.Close
139.
140. Set Rs = Nothing
141.
142. End Sub
143.
144. Private Sub Form_Load()
145.
146. 'Change the text in <> to the appropriate logon
147. 'information.
148. Conn = "UID=<your user ID>;PWD=<your password>;" _
149. & "DRIVER={Microsoft ODBC for Oracle};" _
150. & "SERVER=<your database alias>;"
151.
152. Set En = rdoEnvironments(0)
153. En.CursorDriver = rdUseOdbc
154. Set Cn = En.OpenConnection("", rdDriverNoPrompt,
False, Conn)
155.
156. QSQL = "{call packperson.allperson({resultset 9, ssn,
fname, " _
157. & "lname})}"
158.
159. Set CPw1 = Cn.CreateQuery("", QSQL)
160.
161. QSQL = "{call packperson.oneperson(?,{resultset 2, ssn,
fname, " _
162. & "lname})}"
163.
164. Set CPw2 = Cn.CreateQuery("", QSQL)
165.
166. End Sub
167.
168. Private Sub Form_Unload(Cancel As Integer)
169.
170. En.Close
171.
172. End Sub
173.

174. Run the project.

When you click the "Get Everyone" button, it executes the following query:
QSQL = "{call packperson.allperson({resultset 9, ssn, fname, "_ & "lname})}"
This query is executing the stored procedure "allperson," which is in the package
"packperson" (referenced as "packperson.allperson"). There are no input
parameters and the procedure is returning three arrays (ssn, fname, and lname),
each with 9 or fewer records. As stated in 174679  , you must specify the
maximum number of rows you will be returning. Please refer to the Microsoft
ODBC Driver for Oracle Help File and 174679  for more information on this issue.

When you click on the "Get One" button, you see an input box that prompts you
for an SSN. Once you input a valid SSN and click OK, this query is executed:
QSQL = "{call packperson.oneperson(?,{resultset 2, ssn, fname, "_ &
"lname})}"

The stored procedure, packperson.oneperson, uses a single input parameter as


the selection criteria for the Resultset it creates. Just like packperson.allperson,
the Resultset is constructed using the table types defined in packperson. (See
174679  for more information.)

NOTE: You can only define input parameters for Oracle stored procedures that
return a Resultset. You cannot define output parameters for these stored
procedures.

These two stored procedures cover the basic uses of stored procedures that
return Resultsets. The first one gives you a predefined set of records (such as
everyone) and the second will gives you a set of records (or just one record)
based on one or more input parameters. Once you have these Resultsets, you
can do inserts, updates, and deletes either through stored procedures or SQL
that you create on the client.
Dynamic SQL:

 Is a SQL statement that contains variables that may change at run time.
 Is a SQL statement with place holders and is stored as a character string.
 Enables general purpose code to be written
 Enables data definition and data control or session control statements to
be written and executed from PLSQL.
 Is written using either DBMS_SQL package or native dynamic SQL

In Oracle 8 and earlier, you have to use DBMS_SQL package. From 8i we can
use both DBMS_SQL or DYNAMIC SQL

STEPS to process a SQL statement:


Parse – Every SQL statement must be parsed. Parsing the statement
includes checking the statement syntax, checking references and object
privileges.
Binding – after parsing, the Oracle server knows the meaning of the
statement but still may not have enough information to execute the
statement. The oracle server may need values for any bind variables in
the statement. The process of obtaining these values is called BINDING.
Execute – At this point Oracle server have necessary information and
resources, and the statement is executed.
Fetch – In the fetch stage, rows are selected and ordered, and each
successive fetch retrieves another row of the result, until the last row is
fetched. You can fetch the queries but not the DML statements.

USING DBMS_SQL Package:


Procedures and functions in DBMS_SQL package:
 OPEN_CURSOR – Opens a new cursor and assigns a cursor id number
 PARSE - Used to parse DDL and DML command.
 BIND_VARIABLE – Binds the given value in the variable
 EXECUTE - executes the SQL statement
 FETCH_ROWS - retrieves a row for the specified cursor
 CLOSE_CURSOR – closes the specified cursor.

Examples:
Procedure to delete rows from a table:
create or replace procedure delete_all_rows
(tabname in varchar2)
is
cursor_name integer;
rows number;
begin
cursor_name:=dbms_sql.open_cursor;
dbms_sql.parse(cursor_name,'delete from '||tabname, dbms_sql.native);
rows:=dbms_sql.execute(cursor_name);
dbms_sql.close_cursor(cursor_name);
dbms_output.put_line('rows deleted are '||rows);
end;
/

Using native Dynamic SQL [Execute Immediate]:


Syn: EXECUTE IMMEDIATE <DYNAMIC_STRING>;

create or replace procedure delete_all_rows


(tabname in varchar2)
is
begin
EXECUTE IMMEDIATE 'DELETE FROM '||TABNAME;
dbms_output.put_line('rows deleted are '||SQL%ROWCOUNT);
end;
/

How Bulk Binds in PL/SQL Boost Performance

Introduction

A new feature called "bulk binds" was added to PL/SQL back in Oracle 8i. Bulk
binds enable a PL/SQL program to fetch many rows from a cursor in one call
instead of fetching one row at a time. Bulk binds also allow many similar DML
statements to be executed with one call instead of requiring a separate call for
each. For certain types of PL/SQL programs, using bulk binds will reduce CPU
usage and make the code run faster.

A context switch occurs every time the PL/SQL engine calls the SQL engine to
parse, execute, or fetch from a cursor. Since context switches use CPU time,
reducing the number of context switches will reduce the amount of CPU time
used. In addition, the SQL engine can often reduce the number of logical reads
required when multiple rows are fetched in one call. Reducing logical reads also
saves CPU time.

To test bulk binds using records we first create a test table:

CREATE TABLE test1(


id NUMBER(10),
description VARCHAR2(50));
ALTER TABLE test1 ADD (
CONSTRAINT test1_pk PRIMARY KEY (id));

SET TIMING ON

The time taken to insert 10,000 rows using regular FOR..LOOP statements is
approximately 9 seconds on my test server:

TRUNCATE TABLE test1;

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;

FOR i IN t_tab.first .. t_tab.last LOOP


INSERT INTO test1 (id, description)
VALUES (t_tab(i).id, t_tab(i).description);
END LOOP;

COMMIT;
END;
/

PL/SQL procedure successfully completed.

Elapsed: 00:00:09.03

Using the FORALL construct to bulk bind the inserts this time is reduced to less
than 1/10 of a second:

TRUNCATE TABLE test1;


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;

FORALL i IN t_tab.first .. t_tab.last


INSERT INTO test1 VALUES t_tab(i);

COMMIT;
END;
/

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.07

Since no columns are specified in the insert statement the record structure of the
collection must match the table exactly.

Bulk binds can also improve the performance when loading collections from a
queries. The BULK COLLECT INTO construct binds the output of the query to
the collection. Populating two collections with 10,000 rows using a FOR..LOOP
takes approximately 0.05 seconds:

DECLARE
TYPE test1_tab IS TABLE OF test1%ROWTYPE;

t_tab test1_tab := test1_tab();

CURSOR c_data IS
SELECT *
FROM test1;
BEGIN
FOR cur_rec IN c_data LOOP
t_tab.extend;

t_tab(t_tab.last).id := cur_rec.id;
t_tab(t_tab.last).description := cur_rec.description;
END LOOP;
END;
/

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.05
Using the BULK COLLECT INTO construct reduces this time to less than 0.01
seconds:

DECLARE
TYPE test1_tab IS TABLE OF test1%ROWTYPE;

t_tab test1_tab := test1_tab();


BEGIN
SELECT id, description
BULK COLLECT INTO t_tab
FROM test1;
END;
/

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00

The select list must match the collections record definition exactly for this to be
successful.

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;

FOR i IN t_tab.first .. t_tab.last LOOP


UPDATE test1
SET ROW = t_tab(i)
WHERE id = t_tab(i).id;
END LOOP;

COMMIT;
END;
/
PL/SQL procedure successfully completed.

Elapsed: 00:00:06.08
The reference to the ID column within the WHERE clause means that the this
statement cannot use a bulk bind directly. In order to use a bulk bind a separate
collection must be defined for the id column:
DECLARE
TYPE id_tab IS TABLE OF test1.id%TYPE;
TYPE test1_tab IS TABLE OF test1%ROWTYPE;

t_id id_tab := id_tab();


t_tab test1_tab := test1_tab();
BEGIN
FOR i IN 1 .. 10000 LOOP
t_id.extend;
t_tab.extend;

t_id(t_id.last) := i;
t_tab(t_tab.last).id := i;
t_tab(t_tab.last).description := 'Description: ' || To_Char(i);
END LOOP;

FORALL i IN t_tab.first .. t_tab.last


UPDATE test1
SET ROW = t_tab(i)
WHERE id = t_id(i);

COMMIT;
END;
/

PL/SQL procedure successfully completed.

Elapsed: 00:00:04.01
REF Cursors:
A REF CURSOR is basically a data type. A variable created based on such a
data type is
generally called a cursor variable. A cursor variable can be associated with
different queries at
run-time. The primary advantage of using cursor variables is their capability to
pass result sets
between sub programs (like stored procedures, functions, packages etc.).
Let us start with a small sub-program as follows:
declare
type r_cursor is REF CURSOR;
c_emp r_cursor;
en emp.ename%type;
begin
open c_emp for select ename from emp;
loop
fetch c_emp into en;
exit when c_emp%notfound;
dbms_output.put_line(en);
end loop;
close c_emp;
end;
/
Let me explain step by step. The following is the first statement you need to
understand:
type r_cursor is REF CURSOR;
The above statement simply defines a new data type called "r_cursor," which is
of the type
REF CURSOR. We declare a cursor variable named "c_emp" based on the type
"r_cursor" as
follows:
c_emp r_cursor;
Every cursor variable must be opened with an associated SELECT statement as
follows:
open c_emp for select ename from emp;
To retrieve each row of information from the cursor, I used a loop together with a
FETCH
statement as follows:
loop
fetch c_emp into en;
exit when c_emp%notfound;
dbms_output.put_line(en);
end loop;
I finally closed the cursor using the following statement:
close c_emp;
%ROWTYPE with REF CURSOR
In the previous section, I retrieved only one column (ename) of information using
REF
CURSOR. Now I would like to retrieve more than one column (or entire row) of
information
using the same. Let us consider the following example:
declare
type r_cursor is REF CURSOR;
c_emp r_cursor;
er emp%rowtype;
begin
open c_emp for select * from emp;
loop
fetch c_emp into er;
exit when c_emp%notfound;
dbms_output.put_line(er.ename || ' - ' || er.sal);
end loop;
close c_emp;
end;
In the above example, the only crucial declaration is the following:
er emp%rowtype;
The above declares a variable named "er," which can hold an entire row from the
"emp" table.
To retrieve the values (of each column) from that variable, we use the dot
notation as follows:
dbms_output.put_line(er.ename || ' - ' || er.sal);
Let us consider that a table contains forty columns and I would like to retrieve
fifteen columns.
In such scenarios, it is a bad idea to retrieve all forty columns of information. At
the same time,
declaring and working with fifteen variables would be bit clumsy. The next section
will explain
how to solve such issues.
Working with REF CURSOR in PL/SQL - Working with RECORD and REF
CURSOR
Until now, we have been working either with %TYPE or %ROWTYPE. This
means we are
working with either one value or one complete record. How do we create our own
data type,
with our own specified number of values to hold? This is where TYPE and
RECORD come in.
Let us consider the following example:
declare
type r_cursor is REF CURSOR;
c_emp r_cursor;
type rec_emp is record
(
name varchar2(20),
sal number(6)
);
er rec_emp;
begin
open c_emp for select ename,sal from emp;
loop
fetch c_emp into er;
exit when c_emp%notfound;
dbms_output.put_line(er.name || ' - ' || er.sal);
end loop;
close c_emp;
end;
The most confusing aspect from the above program is the following:
type rec_emp is record
(
name varchar2(20),
sal number(6)
);
The above defines a new data type named "rec_emp" (just like %ROWTYPE
with limited
specified fields) which can hold two fields, namely "name" and "sal."
er rec_emp;
The above statement declares a variable "er" based on the datatype "rec_emp."
This means
that "er" internally contains the fields "name" and "job."
fetch c_emp into er;
The above statement pulls out a row of information (in this case "ename" and
"sal") and places
the same into the fields "name" and "sal" of the variable "er." Finally, I display
both of those
values using the following statement:
dbms_output.put_line(er.name || ' - ' || er.sal);
Working with REF CURSOR in PL/SQL - Working with more than one query with
the same REF
CURSOR
As defined earlier, a REF CURSOR can be associated with more than one
SELECT statement
at run-time. Before associating a new SELECT statement, we need to close the
CURSOR. Let
us have an example as follows:
declare
type r_cursor is REF CURSOR;
c_emp r_cursor;
type rec_emp is record
(
name varchar2(20),
sal number(6)
);
er rec_emp;
begin
open c_emp for select ename,sal from emp where deptno = 10;
dbms_output.put_line('Department: 10');
dbms_output.put_line('--------------');
loop
fetch c_emp into er;
exit when c_emp%notfound;
dbms_output.put_line(er.name || ' - ' || er.sal);
end loop;
close c_emp;
open c_emp for select ename,sal from emp where deptno = 20;
dbms_output.put_line('Department: 20');
dbms_output.put_line('--------------');
loop
fetch c_emp into er;
exit when c_emp%notfound;
dbms_output.put_line(er.name || ' - ' || er.sal);
end loop;
close c_emp;
end;
Working with REF CURSOR inside loops
Sometimes, it may be necessary for us to work with REF CURSOR within loops.
Let us
consider the following example:
declare
type r_cursor is REF CURSOR;
c_emp r_cursor;
type rec_emp is record
(
name varchar2(20),
sal number(6)
);
er rec_emp;
begin
for i in (select deptno,dname from dept)
loop
open c_emp for select ename,sal from emp where deptno = i.deptno;
dbms_output.put_line(i.dname);
dbms_output.put_line('--------------');
loop
fetch c_emp into er;
exit when c_emp%notfound;
dbms_output.put_line(er.name || ' - ' || er.sal);
end loop;
close c_emp;
end loop;
end;
As you can observe from the above program, I implemented a FOR loop as
follows:
for i in (select deptno,dname from dept)
loop
.
.
end loop;
The above loop iterates continuously for each row of the "dept" table. The details
of each row
in "dept" (like deptno, dname etc.) will be available in the variable "i." Using that
variable (as
part of the SELECT statement), I am working with REF CURSOR as follows:
open c_emp for select ename,sal from emp where deptno = i.deptno;
The rest of the program is quite commonplace.
Passing REF CURSOR as parameters to sub-programs
In the previous section, we already started working with sub-programs (or sub-
routines). In
this section, I shall extend the same with the concept of "parameters" (or
arguments). Every
sub-program (or sub-routine) can accept values passed to it in the form of
"parameters" (or
arguments). Every parameter is very similar to a variable, but gets declared as
part of a sub-
program.
Let us consider the following program:
declare
type r_cursor is REF CURSOR;
c_emp r_cursor;
type rec_emp is record
(
name varchar2(20),
sal number(6)
);
procedure PrintEmployeeDetails(p_emp r_cursor) is
er rec_emp;
begin
loop
fetch p_emp into er;
exit when p_emp%notfound;
dbms_output.put_line(er.name || ' - ' || er.sal);
end loop;
end;
begin
for i in (select deptno,dname from dept)
loop
open c_emp for select ename,sal from emp where deptno = i.deptno;
dbms_output.put_line(i.dname);
dbms_output.put_line('--------------');
PrintEmployeeDetails(c_emp);
close c_emp;
end loop;
end;
From the above program, you can observe the following declaration:
procedure PrintEmployeeDetails(p_emp r_cursor) is
In the above declaration, "PrintEmployeeDetails" is the name of the sub-routine
which
accepts "p_emp" as a parameter (of type "r_cursor") and we can use that
parameter throughout
that sub-routine.
PRAGMAS:

Different types of pragma

Pragma is a keyword in Oracle PL/SQL that is used to provide an instruction to


the compiler.

The syntax for pragmas are as follows

PRAMA

The instruction is a statement that provides some instructions to the compiler.

Pragmas are defined in the declarative section in PL/SQL.

The following pragmas are available:

AUTONOMOUS_TRANSACTION:
Prior to Oracle 8.1, each Oracle session in PL/SQL could have at most one
active transaction at a given time. In other words, changes were all or nothing.
Oracle8i PL/SQL addresses that short comings with the
AUTONOMOUS_TRANSACTION pragma. This pragma can perform an
autonomous transaction within a PL/SQL block between a BEGIN and END
statement without affecting the entire transaction. For instance, if rollback or
commit needs to take place within the block without effective the transaction
outside the block, this type of pragma can be used.

AUTONOMOUS_TRANSACTION can also be used to overcome trigger


mutation. A trigger mutation is an error, occurs when we try to retrive or perform
dml operations in a trigger on the same table on which trigger is created.

Example 1: USING COMMIT/ROLLABACK IN A BLOCK WITH OUT


EFFECTING REMAINING TRANSACTIONS.

CREATE TABLE SAMPLE1(NAME VARCHAR2(10));

CREATE TABLE SAMPLE2(NAME VARCHAR2(10));

CREATE OR REPLACE PROCEDURE PROC1


IS
BEGIN
INSERT INTO SAMPLE1 VALUES('JOHN');
COMMIT;
END;
/

CREATE OR REPLACE PROCEDURE PROC2


IS
BEGIN
INSERT INTO SAMPLE2 VALUES('ADAMS');
PROC1;
ROLLBACK;
END;
/

EXEC PROC2;

NOW ‘COMMIT’ IN PROC1 WILL COMMIT’S INSERT STATEMENT IN BOTH


PROC1 AND PROC2. TO MAKE COMMIT WORK ONLY FOR ‘PROC1’ WITH
OUT EFFECTING OUTSIDE TRANSACTIONS WRITE PROC1 AS FOLLOWS:
CREATE OR REPLACE PROCEDURE PROC1
IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO SAMPLE1 VALUES('JOHN');
COMMIT;
END;
/

EXAMPLE 2: A TRIGGER WHICH WILL NOT ACCEPT TO UPDATE


EMPLOYEE SALARY IF IT IS MORE THAN PRESIDENT SALARY

CREATE OR REPLACE TRIGGER INVALID_SAL


BEFORE UPDATE
OF SAL
ON EMP
FOR EACH ROW
WHEN (NEW.JOB<>’PRESIDENT’)
DECLARE
V_SAL NUMBER(8,2);
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
SELECT SAL INTO V_SAL FROM EMP WHERE JOB=’PRESIDENT’;
IF :NEW.SAL > V_SAL THEN
RAISE_APPLICATION_ERROR(-20000,’EMP SALARY SHOULD
NOT BE MORE THAN PRESIDENT’);
END IF;
END INVALID_SAL;
/
EXCEPTION_INIT:
The most commonly used pragma, this is used to bind a user defined exception
to a particular error number.

For example:

Declare
I_GIVE_UP EXCEPTION;
PRAGMA EXCEPTION_INIT(I_give_up, -20000);

BEGIN
..

EXCEPTION WHEN I_GIVE_UP


do something..

END;

RESTRICT_REFERENCES:
Defines the purity level of a packaged program. This is not required starting with
Oracle8i.

Prior to Oracle8i if you were to invoke a function within a package specification


from a SQL statement, you would have to provide a RESTRICT_REFERENCE
directive to the PL/SQL engine for that function. This pragma confirms to Oracle
database that the function as the specified side-effects or ensures that it lacks
any such side-effects.

Usage is as follows:

PRAGMA RESTRICT_REFERENCES(function_name, WNDS [, WNPS] [,


RNDS], [, RNPS])

WNDS: Writes No Database State. States that the function will not perform any
DMLs.

WNPS: Writes No Package State. States that the function will not modify any
Package variables.
RNDS: Reads No Database State. Analogous to Write. This pragma affirms that
the function will not read any database tables.

RNPS: Reads No Package State. Analogous to Write. This pragma affirms that
the function will not read any package variables.

SERIALLY_REUSABLE:
This pragma lets the PL/SQL engine know that package-level data should not
persist between reference to that data.

Package data (global variables in package specification etc.) by default persists


for an entire session (or until a package is recompiled). Globally accessible data
structures can cause some side effects. For instance, what if a cursor is left open
in a package. In addition, a program can use up lots of real memory (UGA) and
then not release it if the data is stored in a package-level structure.

In order to manage this, Oracle8i introduced the SERIALLY_REUSABLE


pragma. This pragma is used in packages only and must be defined BOTH in
specification and in the body.

The advantage is that based on the pragma, a package state can be reduced to
a single call of a program unit in the package as opposed to the package being
available for the whole session.

CREATE OR REPLACE PACKAGE EMP_PACK


IS
CURSOR C(V_DEPTNO NUMBER) RETURN EMP%ROWTYPE;
PROCEDURE GET_DATA(VV_DEPTNO NUMBER);
PEOCEDURE GET_DATA(VV_DNAME VARCHAR2);
R EMP%ROWTYPE;
END;
/

CREATE OR REPLACE PACKAGE BODY EMP_PACK


IS
CURSOR C(V_DEPTNO NUMBER) RETURN EMP%RFOWTYPE IS
SELECT * FROM EMP WHERE DEPTNO=V_DEPTNO;
PROCEDURE GET_DATA(VV_DEPTNO NUMBER)
IS
BEGIN
OPEN C(VV_DEPTNO);
LOOP
FETCH C INTO R;
EXIT WHEN C%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(R.ENAME);
END LOOP;
END GET_DATA;
PROCEDURE GET_DATA(VV_DNAME VARCHAR2)
IS
VV_DEPTNO NUMBER;
BEGIN
SELECT DEPTNO INTO VV_DEPTNO FROM DEPT WHERE
DNAME=VV_DNAME;
OPEN C(VV_DEPTNO);
LOOP
FETCH C INTO R;
EXIT WHEN C%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(R.ENAME);
END LOOP;
END GET_DATA;
END EMP_PACK;

EXECUTE BOTH PROCEDURES, FROM SECOND PROCEDURE EXCEPTION


WILL BE RAISED STATING CURSOR IS ALREADY OPEN.
TO OVERCOME THAT ERROR USE ‘PRAGMA SERIALLY_REUSABLE’ IN
BOTH PACKAGE AND PACKAGE BODY.

You might also like