PLSQL Concepts
PLSQL Concepts
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.
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;
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.
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.
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})}"
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
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;
/
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.
SET TIMING ON
The time taken to insert 10,000 rows using regular FOR..LOOP statements is
approximately 9 seconds on my test server:
DECLARE
TYPE test1_tab IS TABLE OF test1%ROWTYPE;
t_tab(t_tab.last).id := i;
t_tab(t_tab.last).description := 'Description: ' || To_Char(i);
END LOOP;
COMMIT;
END;
/
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:
t_tab(t_tab.last).id := i;
t_tab(t_tab.last).description := 'Description: ' || To_Char(i);
END LOOP;
COMMIT;
END;
/
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;
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;
/
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;
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(t_tab.last).id := i;
t_tab(t_tab.last).description := 'Description: ' || To_Char(i);
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(t_id.last) := i;
t_tab(t_tab.last).id := i;
t_tab(t_tab.last).description := 'Description: ' || To_Char(i);
END LOOP;
COMMIT;
END;
/
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:
PRAMA
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.
EXEC PROC2;
For example:
Declare
I_GIVE_UP EXCEPTION;
PRAGMA EXCEPTION_INIT(I_give_up, -20000);
BEGIN
..
END;
RESTRICT_REFERENCES:
Defines the purity level of a packaged program. This is not required starting with
Oracle8i.
Usage is as follows:
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.
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.