PL/SQL Tables
(Index by tables):
PL/SQL Tables are not the same as a database table. They are essentially a one dimensional
array that has two columns. The
first column is the primary key column of type BINARY_INTEGER, the second column
is the data column of any data-type.
Declaring tables in PL/SQL is a two step
process. First Declare a table
TYPE, then declare a variable of that type.
Syntax:
TYPE type_name IS TABLE OF data-type INDEX BY BINARY_INTEGER
Variable type_name
Example:
DECLARE
TYPE sal_table_type IS TABLE OF NUMBER(8,2) INDEX BY BINARY_INTEGER;
TYPE
EmpListType IS TABLE OF emp%ROWTYPE INDEX BY BINARY_INTEGER;
sal_table sal_table_type;
BEGIN
. . .
Note the indentation
above, failure to indent for readability will result in one full letter grade
deduction and your work being returned to you to fix.
PL/SQL
Records:
Not the same as a database row. Composed of one or more fields that can
be of any data-type. Once again
declaring a PL/SQL Record is a two step process. First declare a record TYPE, next
declare a variable of that type.
This is similar to the type command in Visual Basic and the
struct command in C.
Syntax:
TYPE type_name IS RECORD (col1 data-type, col2 data-type, ?);
Variable type_name;
Example:
DECLARE
TYPE comp_record_type IS RECORD (
Sal NUMBER (8,2) NOT NULL,
Comm NUMBER (8,2) NULL,
Total_comp NUMBER(8,2) NOT NULL);
TYPE comp_list_type IS TABLE OF comp_record_type INDEX BY BINARY_INTEGER
BEGIN
. . .
Declare PL/SQL Record Type
from a Table Definition (%ROWTYPE):
The %ROWTYPE construct
allows you to create a PL/SQL record with the same fields as the table it
references. This much simplifies
record declaration when you want to emulate a table inside your PL/SQL
code.
Syntax:
Sales_rep_record sales_rep%ROWTYPE;
BEGIN
. .
.
Note the indentation
above, failure to indent for readability will result in one full letter grade
deduction and your work being returned to you to fix.
-
Assigning Variables in
Code
V_variable :=
expression;
Table_name(index) :=
expression;
Record_name.field_name :=
expression;
empList (index).column_name := value;
CURSORS:
Cursors are
used for processing multiple rows being returned from a SELECT
statement.
Creating a cursor:
-
Declare
the cursor.
-
Open the
cursor.
-
Fetch
data from the cursor.
-
Close
the cursor.
Declaring Cursors,
Syntax:
DECLARE
CURSOR cursor_name IS
SELECT columns
FROM tables
WHERE condition;
Notes:
-
Do not include the INTO clause in the SELECT.
-
Declare
variables before cursors.
Opening Cursors,
Syntax:
Note: This is done in the BEGIN section and is
used to prepare the cursor for execution.
Notes:
-
Include the same number of variables as is included in the SELECT clause
of the cursor.
-
Variables are mapped to
columns in the SELECT in the order they are listed.
Closing Cursors,
Syntax:
CLOSE cursor_name;
Notes:
-
Close the cursor after all the rows are processed.
-
You
can't fetch data from the cursor once you have closed it.
Cursor Status
Attributes:
-
%ISOPEN: True if cursor is OPEN.
-
%NOTFOUND: True if the most recent FETCH returns
nothing.
-
%FOUND: True until most recent FETCH returns
nothing.
-
%ROWCOUNT: Total rows fetched so far.
Example:
set
serveroutput on;
ACCEPT p_order_nr PROMPT 'Enter Order #:';
DECLARE
v_order_nr order_line.order_number%TYPE := &p_order_nr;
v_part order_line.part_number%TYPE;
v_total totals.total%TYPE;
v_order_total totals.total%TYPE;
CURSOR order_line_cursor IS
SELECT part_number, quoted_price * number_ordered
FROM order_line
WHERE order_number= v_order_nr;
BEGIN
OPEN order_line_cursor;
LOOP
FETCH order_line_cursor INTO v_part, v_order_total;
EXIT WHEN order_line_cursor%NOTFOUND;
v_total := v_total + v_order_total;
dbms_output.put_line ('Order total for part ' ||v_part || ' is ' || v_order_total);
INSERT INTO totals (part, total)
VALUES (v_part, v_order_total);
END LOOP;
CLOSE order_line_cursor;
COMMIT WORK;
END;
/
Note the indentation
above, failure to indent for readability will result in one full letter grade
deduction and your work being returned to you to fix.
Cursor FOR Loop:
A cursor for loop is specifically designed
to stream line cursor processing.
A cursor For
Loop:
- Explicitly
Declare
-
Implicitly Opens
-
Implicitly Fetches
-
Implicitly Declares Record for returned row
-
Implicitly Closes Cursor when for loop ends.
Syntax:
FOR record_name IN cursor_name LOOP
Statement1;
Statement2;
END LOOP;
Note the indentation
above, failure to indent for readability will result in one full letter grade
deduction and your work being returned to you to fix.
Example
ACCEPT p_order_nr PROMPT
'Enter Order
#:';
DECLARE
v_order_nr order_line.order_number%TYPE :=
&p_order_nr;
v_total
totals.total%TYPE := 0;
CURSOR c_order_line
IS
SELECT part_number, quoted_price,
number_ordered
FROM
order_line
WHERE order_number=
v_order_nr;
BEGIN
FOR r_order_line IN c_order_line
LOOP
v_total := v_total + (r_order_line.quoted_price
*
r_order_line.number_ordered);
dbms_output.put_line ('Order
total for part ' ||
r_order_line.part_number || ' is ' ||
v_total);
INSERT INTO totals (part,
total)
VALUES (r_order_line.part_number,
v_total);
END
LOOP;
COMMIT
WORK;
END;
/
Note the indentation above, failure to
indent for readability will result in one full letter grade deduction and your
work being returned to you to fix.
Advanced Cursors:
1. Using Nested For Loops
2. Using Parameters
with cursors
3. For Update Cursors
4. Where Current of
Clause
Click here to download the set up script for the tables for the next few examples
After unzipping them in a folder (assume c:\temp). Execute the following from sqlplus
SQL>@ c:\temp\createStudent.sql
This should create all the necessary tables and data.
Processing Nested Cursors
SET SERVEROUTPUT ON
DECLARE
v_zip zipcode.zip%TYPE;
CURSOR
c_zip
IS
SELECT zip, city, state FROM zipcode WHERE state =
'CT';
CURSOR c_student
IS
SELECT first_name, last_name FROM student WHERE zip =
v_zip;
BEGIN
FOR r_zip IN c_zip
LOOP
v_zip :=
r_zip.zip;
DBMS_OUTPUT.PUT_LINE
(CHR(10));
DBMS_OUTPUT.PUT_LINE ('Students living in ' ||
r_zip.city);
FOR r_student IN c_student
LOOP
DBMS_OUTPUT.PUT_LINE (r_student.first_name||'
'||r_student.last_name);
END LOOP;
END
LOOP;
END;
/
SET SERVEROUTPUT ON
DECLARE
v_amount course.cost%TYPE;
v_instructor_id
instructor.instructor_id%TYPE;
CURSOR c_inst IS
SELECT first_name, last_name,
instructor_id
FROM
instructor;
CURSOR c_cost
IS
SELECT
c.cost
FROM course c, section s, enrollment
e
WHERE s.instructor_id =
v_instructor_id
AND c.course_no =
s.course_no
AND s.section_id =
e.section_id;
BEGIN
FOR r_inst
IN c_inst
LOOP
v_instructor_id :=
r_inst.instructor_id;
v_amount :=
0;
DBMS_OUTPUT.PUT_LINE ('Generated by instructor '
||
r_inst.first_name || ' ' ||
r_inst.last_name);
FOR r_cost IN c_cost
LOOP
v_amount := v_amount + NVL(r_cost.cost,
0);
END
LOOP;
DBMS_OUTPUT.PUT_LINE (TO_CHAR(v_amount,
'$999,999'));
END
LOOP;
END;
/
Using Parameters with cursors
SET SERVEROUTPUT ONFor Update Cursors
DECLARE
CURSOR c_course
IS
SELECT course_no,
cost
FROM
course
FOR UPDATE;
BEGIN
FOR r_course
IN c_course
LOOP
IF r_course.cost < 2500
THEN
UPDATE
course
SET cost = r_course.cost + 10
WHERE course_no =
r_course.course_no;
END IF;
END
LOOP;
END;
/
DECLARE
CURSOR c_stud_zip
IS
SELECT s.student_id,
z.city
FROM student s, zipcode
z
WHERE z.city =
'Brooklyn'
AND s.zip =
z.zip
FOR UPDATE OF phone;
BEGIN
FOR
r_stud_zip IN c_stud_zip
LOOP
UPDATE student SET phone = '718'||SUBSTR(phone, 4)
WHERE student_id = r_stud_zip.student_id;
END LOOP;
END;
/
Where Current of Clause
DECLARE
CURSOR c_stud_zip
IS
SELECT s.student_id,
z.city
FROM student s, zipcode
z
WHERE z.city =
'Brooklyn'
AND s.zip =
z.zip
FOR UPDATE OF s.phone;
BEGIN
FOR r_stud_zip IN c_stud_zip
LOOP
DBMS_OUTPUT.PUT_LINE
(r_stud_zip.student_id);
UPDATE
student
SET phone = '718'||SUBSTR(phone, 4)
WHERE CURRENT OF c_stud_zip;
END
LOOP;
END;
/
Implicit Cursors with Sub-Queries
You don't have to declare a cursor to deal with a SQL statement returning more than one row. In PL/SQL you can embed a SQL-Select statement inside a for loop.
BEGIN FOR r_cust IN (SELECT * FROM customer ORDER BY lname, fname) LOOP dbms_output.put_line (r_cust.fname || ' ' || r_cust.lname); dbms_output.put_line (r_cust.street); dbms_output.put_line (r_cust.city || ', ' || r_cust.state || ' ' || r_cust.zip_code); END LOOP; END; /
The Next Example is Sourced from Oracle's Web-Site:
Like a cursor, a cursor variable points to the current row in the result set of a multi-row query. But, cursors differ from cursor variables the way constants differ from variables. Whereas a cursor is static, a cursor variable is dynamic because it is not tied to a specific query. You can open a cursor variable for any type-compatible query. This gives you more flexibility.
Also, you can assign new values to a cursor variable and pass it as a parameter to local and stored subprograms. This gives you an easy way to centralize data retrieval.
Cursor variables are available to every PL/SQL client. For example, you can declare a cursor variable in a PL/SQL host environment such as an OCI or Pro*C program, then pass it as input host variable (bind variable) to PL/SQL. Moreover, application development tools such as Oracle Forms and Oracle Reports, which have a PL/SQL engine, can use cursor variables entirely on the client side.
The Oracle server also has a PL/SQL engine. So, you can pass cursor variables back and forth between an application and server via remote procedure calls (RPCs).
Cursor variables are like C or Pascal pointers, which hold the
memory location (address) of some item instead of the item itself. So, declaring
a cursor variable creates a pointer, not an item. In
PL/SQL, a pointer has datatype REF X, where
REF is short for REFERENCE and X stands
for a class of objects. Therefore, a cursor variable has datatype
REF CURSOR.
To execute a multi-row query, Oracle opens an unnamed work area that stores processing information. To access the information, you can use an explicit cursor, which names the work area. Or, you can use a cursor variable, which points to the work area. Whereas a cursor always refers to the same query work area, a cursor variable can refer to different work areas. So, cursors and cursor variables are not interoperable; that is, you cannot use one where the other is expected.
Mainly, you use cursor variables to pass query result sets between PL/SQL stored subprograms and various clients. Neither PL/SQL nor any of its clients owns a result set; they simply share a pointer to the query work area in which the result set is stored. For example, an OCI client, Oracle Forms application, and Oracle server can all refer to the same work area.
A query work area remains accessible as long as any cursor variable points to it. Therefore, you can pass the value of a cursor variable freely from one scope to another. For example, if you pass a host cursor variable to a PL/SQL block embedded in a Pro*C program, the work area to which the cursor variable points remains accessible after the block completes.
If you have a PL/SQL engine on the client side, calls from client to server impose no restrictions. For example, you can declare a cursor variable on the client side, open and fetch from it on the server side, then continue to fetch from it back on the client side. Also, you can reduce network traffic by having a PL/SQL block open (or close) several host cursor variables in a single round trip.
To create cursor variables, you take two steps. First, you define a
REF CURSOR type, then declare cursor variables of that
type. You can define REF CURSOR types in any PL/SQL
block, subprogram, or package using the syntax
TYPE ref_type_name IS REF CURSOR RETURN return_type;
where ref_type_name is a type specifier used in
subsequent declarations of cursor variables and return_type must
represent a record or a row in a database table. In the following example, you
specify a return type that represents a row in the database table
dept:
DECLARE TYPE DeptCurTyp IS REF CURSOR RETURN dept%ROWTYPE;
REF CURSOR types can be strong (restrictive) or weak
(nonrestrictive). As the next example shows, a strong REF
CURSOR type definition specifies a return type, but a weak
definition does not:
DECLARE TYPE EmpCurTyp IS REF CURSOR RETURN emp%ROWTYPE; -- strong TYPE GenericCurTyp IS REF CURSOR; -- weak
Strong REF CURSOR types are less error
prone because the PL/SQL compiler lets you associate a strongly typed cursor
variable only with type-compatible queries. However, weak REF
CURSOR types are more flexible because the compiler lets you
associate a weakly typed cursor variable with any query.
Once you define a REF CURSOR type, you can
declare cursor variables of that type in any PL/SQL block or subprogram. In the
following example, you declare the cursor variable dept_cv:
DECLARE TYPE DeptCurTyp IS REF CURSOR RETURN dept%ROWTYPE; dept_cv DeptCurTyp; -- declare cursor variable
Note: You cannot declare cursor variables in a package. Unlike packaged variables, cursor variables do not have persistent state. Remember, declaring a cursor variable creates a pointer, not an item. So, cursor variables cannot be saved in the database.
Cursor variables follow the usual scoping and instantiation rules. Local PL/SQL cursor variables are instantiated when you enter a block or subprogram and cease to exist when you exit.
In the RETURN clause of a REF
CURSOR type definition, you can use %ROWTYPE to
specify a record type that represents a row returned by a strongly (not weakly)
typed cursor variable, as follows:
DECLARE TYPE TmpCurTyp IS REF CURSOR RETURN emp%ROWTYPE; tmp_cv TmpCurTyp; -- declare cursor variable TYPE EmpCurTyp IS REF CURSOR RETURN tmp_cv%ROWTYPE; emp_cv EmpCurTyp; -- declare cursor variable
Likewise, you can use %TYPE to provide the datatype of
a record variable, as the following example shows:
DECLARE dept_rec dept%ROWTYPE; -- declare record variable TYPE DeptCurTyp IS REF CURSOR RETURN dept_rec%TYPE; dept_cv DeptCurTyp; -- declare cursor variable
In the final example, you specify a user-defined RECORD
type in the RETURN clause:
DECLARE
TYPE EmpRecTyp IS RECORD (
empno NUMBER(4),
ename VARCHAR2(1O),
sal NUMBER(7,2));
TYPE EmpCurTyp IS REF CURSOR RETURN EmpRecTyp;
emp_cv EmpCurTyp; -- declare cursor variable
You can declare cursor variables as the formal parameters of
functions and procedures. In the following example, you define the
REF CURSOR type EmpCurTyp, then declare a
cursor variable of that type as the formal parameter of a procedure:
DECLARE TYPE EmpCurTyp IS REF CURSOR RETURN emp%ROWTYPE; PROCEDURE open_emp_cv (emp_cv IN OUT EmpCurTyp) IS ...
Caution: Like all pointers, cursor variables increase the possibility of parameter aliasing. For more information, see "Parameter Aliasing".
You use three statements to control a cursor variable:
OPEN-FOR, FETCH, and CLOSE. First, you
OPEN a cursor variable FOR a multi-row query. Then,
you FETCH rows from the result set one at a time. When all the rows
are processed, you CLOSE the cursor variable.
The OPEN-FOR statement associates a cursor variable
with a multi-row query, executes the query, and identifies the result set. Here
is the syntax:
OPEN {cursor_variable_name | :host_cursor_variable_name}
FOR select_statement;
where host_cursor_variable_name identifies a cursor
variable declared in a PL/SQL host environment such as an OCI or Pro*C
program.
Unlike cursors, cursor variables take no parameters. However, no
flexibility is lost because you can pass whole queries (not just parameters) to
a cursor variable. The query can reference host variables and PL/SQL variables,
parameters, and functions but cannot be FOR
UPDATE.
In the example below, you open the cursor variable
emp_cv. Notice that you can apply cursor attributes
(%FOUND, %NOTFOUND, %ISOPEN, and
%ROWCOUNT) to a cursor variable.
IF NOT emp_cv%ISOPEN THEN /* Open cursor variable. */ OPEN emp_cv FOR SELECT * FROM emp; END IF;
Other OPEN-FOR statements can open the same cursor
variable for different queries. You need not close a cursor variable before
reopening it. (Recall that consecutive OPENs of a static cursor
raise the predefined exception CURSOR_ALREADY_OPEN.) When you
reopen a cursor variable for a different query, the previous query is
lost.
Typically, you open a cursor variable by passing it to a stored
procedure that declares a cursor variable as one of its formal parameters. For
example, the following packaged procedure opens the cursor variable
emp_cv:
CREATE PACKAGE emp_data AS
...
TYPE EmpCurTyp IS REF CURSOR RETURN emp%ROWTYPE;
PROCEDURE open_emp_cv (emp_cv IN OUT EmpCurTyp);
END emp_data;
CREATE PACKAGE BODY emp_data AS
...
PROCEDURE open_emp_cv (emp_cv IN OUT EmpCurTyp) IS
BEGIN
OPEN emp_cv FOR SELECT * FROM emp;
END open_emp_cv;
END emp_data;
When you declare a cursor variable as the formal parameter of a
subprogram that opens the cursor variable, you must specify the IN
OUT mode. That way, the subprogram can pass an open cursor back to
the caller.
Alternatively, you can use a stand-alone procedure to open the
cursor variable. Simply define the REF CURSOR type in
a separate package, then reference that type in the stand-alone procedure. For
instance, if you create the following bodiless package, you can create
stand-alone procedures that reference the types it defines:
CREATE PACKAGE cv_types AS TYPE GenericCurTyp IS REF CURSOR; TYPE EmpCurTyp IS REF CURSOR RETURN emp%ROWTYPE; TYPE DeptCurTyp IS REF CURSOR RETURN dept%ROWTYPE; ... END cv_types;
In the next example, you create a stand-alone procedure that
references the REF CURSOR type EmpCurTyp,
which is defined in the package cv_types:
CREATE PROCEDURE open_emp_cv (emp_cv IN OUT cv_types.EmpCurTyp) AS BEGIN OPEN emp_cv FOR SELECT * FROM emp; END open_emp_cv;
To centralize data retrieval, you can group type-compatible queries
in a stored procedure. In the example below, the packaged procedure declares a
selector as one of its formal parameters. (In this context, a selector is a variable used to select one of several
alternatives in a conditional control statement.) When called, the procedure
opens the cursor variable emp_cv for the chosen query.
CREATE PACKAGE emp_data AS
TYPE GenericCurTyp IS REF CURSOR;
TYPE EmpCurTyp IS REF CURSOR RETURN emp%ROWTYPE;
PROCEDURE open_emp_cv (emp_cv IN OUT EmpCurTyp, choice NUMBER);
END emp_data;
CREATE PACKAGE BODY emp_data AS
PROCEDURE open_emp_cv (
emp_cv IN OUT EmpCurTyp,
choice NUMBER) IS
BEGIN
IF choice = 1 THEN
OPEN emp_cv FOR SELECT * FROM emp WHERE comm IS NOT NULL;
ELSIF choice = 2 THEN
OPEN emp_cv FOR SELECT * FROM emp WHERE sal > 2500;
ELSIF choice = 3 THEN
OPEN emp_cv FOR SELECT * FROM emp WHERE deptno = 20;
END IF;
END open_emp_cv;
END emp_data;
For more flexibility, you can pass a cursor variable and a selector to a stored procedure that executes queries with different return types. Consider this example:
CREATE PACKAGE BODY emp_data AS
PROCEDURE open_cv (
generic_cv IN OUT GenericCurTyp,
choice NUMBER) IS
BEGIN
IF choice = 1 THEN
OPEN generic_cv FOR SELECT * FROM emp;
ELSIF choice = 2 THEN
OPEN generic_cv FOR SELECT * FROM dept;
ELSIF choice = 3 THEN
OPEN generic_cv FOR SELECT * FROM salgrade;
END IF;
END open_cv;
END emp_data;
SQL> CREATE TABLE emp AS SELECT * FROM rtimlin.emp;
CREATE OR REPLACE PACKAGE cv_types AS TYPE GenericCurTyp IS REF CURSOR; TYPE EmpCurTyp IS REF CURSOR RETURN emp%ROWTYPE; TYPE emp_list_type IS TABLE OF emp%ROWTYPE INDEX BY BINARY_INTEGER; END cv_types; / CREATE OR REPLACE PROCEDURE open_emp_cv (emp_cv IN OUT cv_types.EmpCurTyp) AS BEGIN OPEN emp_cv FOR SELECT * FROM emp; END open_emp_cv; / SET SERVEROUTPUT ON; DECLARE emp_rec emp%ROWTYPE; test_cv cv_types.EmpCurTyp; BEGIN open_emp_cv (test_cv); LOOP FETCH test_cv INTO emp_rec; EXIT WHEN test_cv%NOTFOUND; DBMS_OUTPUT.PUT_LINE (emp_rec.empname); END LOOP; CLOSE test_cv; END; /
-- Example 2: Using a Table of Records
DECLARE
test_cv cv_types.EmpCurTyp;
emp_list cv_types.emp_list_type;
i BINARY_INTEGER := 0;
BEGIN
open_emp_cv (test_cv);
LOOP
FETCH test_cv INTO emp_list(i);
EXIT WHEN test_cv%NOTFOUND;
DBMS_OUTPUT.PUT_LINE (emp_list(i).empname);
i := i +1;
END LOOP;
CLOSE test_cv;
END;
/