Upload
tranque
View
294
Download
1
Embed Size (px)
Citation preview
Oracle Academy 1 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
Oracle Academy
Introduction to PL/SQL
Instructor Resource Guide
INSTRUCTOR NOTES FOR SLIDES
SECTION 11 LESSON 1
Slide 1: Using Large Object Data Types
No instructor notes for this slide
Slide 2: What Will I Learn?
No instructor notes for this slide
Slide 3: Why Learn It?
Remind students that the RAW datatype is the binary equivalent of VARCHAR2.
Slide 4: Tell Me / Show Me – New Columns for EMPLOYEES
No instructor notes for this slide
Slide 5: Tell Me / Show Me – We need Large Object (LOB) Column Data Types
Be aware that for BFILEs (which are stored outside the database as Operating System files) the
Operating System may impose a restriction on the maximum size. For example, some operating
systems cannot store files larger than 32GB. But this is still very large!
Slide 6: Tell Me / Show Me – Two ways to store large objects: the old and the new way
“Deprecated” means that you can still use them, but Oracle does not recommend their use
because newer and better methods exist. Liıke many other deprecated features in Oracle, LONG
and LONG RAW still exist because some older applications still use them.
Slide 7: Tell Me / Show Me – Example Uses of LOB Columns
The word “LOB” (“Large OBject”) is generally used when talking about CLOBs, BLOBs, and
BFILEs in general.
Slide 8: Tell Me / Show Me – The Old Way The New Way
The rest of this lesson explains only CLOBs and BLOBs. Students will learn more about
BFILEs in the next lesson.
Slide 9: Tell Me / Show Me – Converting LONG to CLOB
Although the SQL syntax is easy, converting LONG to CLOB (or LONG RAW to BLOB) can
take a long time, because the physical formats of the two datatypes are different, so every byte of
data has to be copied. Imagine a table of 1 million rows in which the average size of the LONG
column is 5MB. That’s 5 Terabytes (5000 Gigabytes) of data to be copied.
Oracle Academy 2 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
Slide 10: Tell Me / Show Me – CLOB column
No instructor notes for this slide
Slide 11: Tell Me / Show Me – How and where is LOB data stored?
Components of a LOB
There are two distinct parts to a LOB:
• LOB value: The data that constitutes the real object being stored
• LOB locator: A pointer to the location of the LOB value stored in the table row
Regardless of where the value of a LOB is stored, a locator is stored in the row. You can think of
a LOB locator as a pointer to the actual location of the LOB value.
A LOB column does not contain the data; it contains the locator of the LOB value.
When a user creates an internal LOB (CLOB or BLOB), the value is stored elsewhere in the
database and a locator to the out-of-line LOB value is placed in the LOB column of the
corresponding row in the table. External LOBs (BFILES) store the data outside the database, so
only a locator to the LOB value is stored in the database.
Slide 12: Tell Me / Show Me – Adding a LOB column to a table
No instructor notes for this slide
Slide 13: Tell Me / Show Me – Initializing a LOB Column
Despite the word EMPTY_ in the function names, these two functions do not set the LOB
column to null (it is automatically null when the column is first added). These functions place a
real non-null value in the column. This value is a locator (pointer) to the space elsewhere in the
database where the large LOB value will be stored.
Slide 14: Tell Me / Show Me – Populating a CLOB Column with Data
Note that this and the next few slides shows only how to populate and manipulate CLOB data.
BLOBs are usually populated either by converting existing LONG RAW data (as shown earlier
in this lesson) or by loading the BLOB data from an external BFILE.
Slide 15: Tell Me / Show Me – Reading CLOB data from the table
We can use normal SQL character functions – UPPER, LOWER, INITCAP, SUBSTR, INSTR
and so on – when SELECTing CLOB columns.
Slide 16: Tell Me / Show Me – Updating CLOB data
We have to use PL/SQL because calls to DBMS_LOB require passing the locator as a parameter.
Therefore we need to declare a PL/SQL variable to store the locator value.
Oracle Academy 3 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
Slide 17: Tell Me / Show Me – Updating CLOB data using DBMS_LOB
This PL/SQL block finds the length of the existing LOB value, then appends ‘NEW TEXT’ to
the end of it, preceded by a space.
A CLOB or BLOB variable in PL/SQL holds the locator, not the data value.
• First, the SELECT loads the locator into the CLOB variable. This locator is used as the
first parameter in all calls to DBMS_LOB.
• Then, the GETLENGTH function returns the length in bytes of the existing LOB data
value.
• Then, the WRITE function modifies the LOB value in the database.
There are many other procedures and functions in the DBMS_LOB package. Students will see
some more of these in the next lesson.
Slide 18: Tell Me / Show Me – Populating a CLOB column with a large value using
DBMS_LOB:
No instructor notes for this slide
Slide 19: Tell Me / Show Me – Populating a CLOB column using DBMS_LOB:
This PL/SQL block loops round, each time appending a new piece of text to the existing CLOB
value, until the next piece is found to be null (LENGTH = 0).
The actual values of the pieces of text in V_TEXT would be inserted programmatically, not
coded as a literal as shown here. In fact, if you execute the anonymous block shown in the slide,
it will loop forever – or until 128TB of data has been loaded.
Slide 20: Tell Me / Show Me – Reading BLOB column data using DBMS_LOB
This block retrieves and displays the country_id, country_name and length in bytes of the flag (a
BLOB column) for countries whose names begin with ‘A’. The output is shown on the next
slide.
Slide 21: Tell Me / Show Me – Reading BLOB column data using DBMS_LOB (continued)
No instructor notes for this slide
Slide 22: Tell Me / Show Me – Terminology
CLOB – Character Large Objects, such as resumes, text articles, source code files.
BLOB – Binary Large Objects, such as sound (MP3), photos (JPEG, BMP), proprietary formats
(PDF, DOC, XLS), and executables (EXE, DDL).
BFILE – Binary Files, just like BLOB but stored outside the database, often on separate media
(CD, DVD, HD-DVD).
Slide 23: Summary
No instructor notes for this slide
Slide 24: Try It / Solve It
No instructor notes for this slide
Oracle Academy 4 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
SECTION 11 LESSON 2
Slide 1: Managing BFiles
No instructor notes for this slide
Slide 2: What Will I Learn?
No instructor notes for this slide
Slide 3: Why Learn It?
No instructor notes for this slide
Slide 4: Tell Me / Show Me – What is a BFILE?
A single BFILE cannot span more than one device, for example a movie must be all on a single
DVD.
Slide 5: Tell Me / Show Me – How is a BFILE different fromCLOBs and BLOBs?
“Created outside Oracle”: for example, we could take a set of DVDs each containing a movie,
and copy them to our computer’s hard disk using normal operating system commands, creating a
set of files in one or more operating system directories.
For example on Windows:
C:\mymovies\titanic.avi
C:\mymovies\eight_mile.avi and so on.
Slide 6: Tell Me / Show Me – When to use a BFILE?
No instructor notes for this slide
Slide 7: Tell Me / Show Me – A New Database Object: DIRECTORY
Directories can be used in other cases where Oracle needs a pointer to files outside the database,
for example when using the UTL_FILE_DIR package. This was mentioned in Section 9 Lesson
6.
Slide 8: Tell Me / Show Me – Creating and Managing Directories
ALTER DIRECTORY only updates the pointer. The files themselves must be moved by
Operating System commands, for example cut/paste in Windows Explorer.
Slide 9: Tell Me / Show Me – Viewing Directories in the Data Dictionary
No instructor notes for this slide
Slide 10: Tell Me / Show Me – Adding and Populating a BFILE column for a Table
A BFILE locator column has two components: the directory alias for the Operating System
directory where the file is stored, and the name of the file ıtself.
Oracle Academy 5 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
Slide 11: Tell Me / Show Me – Adding and Populating a BFILE column: Example
Go through this carefully:
Step 1: declares a PL/SQL variable of type BFILE to hold a locator value
Step 2: populates the BFILE variable with the location and name of a specific BFILE (ie a
specific movie) using the BFILENAME function.
Step 3: the DBMS_LOB.FILEEXISTS function checks the Operating System to see if the file is
really there. It returns 1 if the file exists, and 0 if it does not. If the file exists, we must
open it before use using DBMS_LOB.FILEOPEN.
Step 4: updates the table column with the locator value and then closes the file.
NOTE: If the Oracle directory object MOVIE_DIR does not exist it will give an ORA-22285 (a
non-predefined Oracle server error)
Slide 12: Tell Me / Show Me – Reading BFILE Locator and Data Values
A locator value in a BFILE column has two components: the DIRECTORY alias and the
filename. However, it is stored in a binary format which cannot be SELECTed directly. We use
the FILEGETNAME procedure to extract its two components into VARCHAR2 variables.
Slide 13: Tell Me / Show Me – Terminology
BFILE – Is like a CLOB or BLOB, except that its value is stored outside the database in a
separate file. The database holds a pointer to the external file.
DIRECTORY – Is a pointer from the database to an operating system directory (Windows
folder) where BFILEs are stored.
Slide 14: Summary
No instructor notes for this slide
Slide 15: Try It / Solve It
No instructor notes for this slide
Oracle Academy 6 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
SECTION 11 LESSON 3
Slide 1: User-Defined Records
No instructor notes for this slide
Slide 2: What Will I Learn?
No instructor notes for this slide
Slide 3: Why Learn It?
No instructor notes for this slide
Slide 4: Tell Me / Show Me – A Problem Scenario
This and the next few slides show that a record declared with %ROWTYPE can be based on a
table rather than on a cursor. Students will probably find this easy to understand, but it is a
necessary prerequisite for the more complex record structures later in the lesson.
Slide 5: Tell Me / Show Me – A Problem Scenario: PL/SQL Code
Point out that this is long-winded and cumbersome code. And what happens if a twelfth column
is added to the EMPLOYEES table? Or an existing column is dropped? Imagine a table with
forty or fifty columns (these are not uncommon in production databases).
Slide 6: Tell Me / Show Me – And how can we return the results to the calling environment
No instructor notes for this slide
Slide 7: Tell Me / Show Me – Using a PL/SQL Record
PL/SQL allows any named variable – scalar or composite - to be passed as a parameter.
Slide 8: Tell Me / Show Me – PL/SQL Records
No instructor notes for this slide
Slide 9: Tell Me / Show Me – Defining Our Own Records
Obviously we cannot declare a record as: p_record_name
bits_of_table1_plus_bits_of_table2_plus….%ROWTYPE ! We could create a database view to
implement the join, and then use %ROWTYPE on the view. But as we have seen, views have
limitations, for example complex views may not be updateable.
Stress that records are not the same as rows in a table, even if (using %ROWTYPE) their
structure exactly matches a table row. Table rows are stored on disk and are permanent; the data
in a record is held in a memory structure (like any other program variable) and persists only for
the duration of the session.
Oracle Academy 7 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
Slide 10: Tell Me / Show Me – Creating a User-Defined PL/SQL Record
The Oracle-predefined scalar data types such as VARCHAR2, DATE, NUMBER and so on, are
actually TYPEs declared automatically (with global scope) in every Oracle database. If you
have DBA privileges, try the following:
SELECT type_name
FROM dba_types
WHERE predefined = ‘YES’;
Slide 11: Tell Me / Show Me – User-Defined PL/SQL Records Example
The slide example declares two record types and a record based on each of the types.
Slide 12: Tell Me / Show Me – User-Defined PL/SQL Records: Example (continued)
No instructor notes for this slide
Slide 13: Tell Me / Show Me – Where Can Types and Records be Declared and Used?
Remind students that declarations in a package specification are visible to the calling
environment, not just within the package itself.
Slide 14: Tell Me / Show Me
The package specification declares a record type person_type. Two records are declared based
on this type: p_pers_rec is an OUT formal parameter from the procedure, and v_pers_rec is a
local variable within the procedure.
Note: The EXCEPTION section has been omitted from the package body to save space on the
slide.
Slide 15: Tell Me / Show Me – Visibility and Scope of records Example (continued)
Step 1: declares a record based on the record type declared globally in the package specification
Step 2: passes the record-name as an actual parameter to the package procedure.
Slide 16: Tell Me / Show Me – Terminology
PL/SQL record – is a composite data type consisting of a group of related data items stored as
fields, each with its own name and data type.
Slide 17: Summary
No instructor notes for this slide
Slide 18: Try It / Solve It
No instructor notes for this slide
Oracle Academy 8 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
SECTION 11 LESSON 4
Slide 1: Indexing Tables of Records
No instructor notes for this slide
Slide 2: What Will I Learn?
No instructor notes for this slide
Slide 3: Why Learn It?
This lesson discusses INDEX BY tables and tables of records. There are other types of
collection variable in PL/SQL: Nested Tables and Varrays. They are outside the scope of this
course.
In Oracle 10g, INDEX BY tables are called “Associative Arrays”.
Slide 4: Tell Me / Show Me – What is a Collection?
Stress that an INDEX BY Table is NOT a database table. Their data is stored in memory, not on
disk in the database. Therefore transaction control statements such as COMMIT and
ROLLBACK have no meaning for INDEX BY tables.
Slide 5: Tell Me / Show Me – An INDEX BY Table Has a Primary Key
BINARY_INTEGER is faster than PLS_INTEGER and is therefore recommended. The
magnitude range of a BINARY_INTEGER is -2147483647 to +2147483647, so we can store
many millions of entries in an INDEX BY table … as long as we have enough memory to hold
them!
In Oracle 10g, INDEX BY tables can be string-indexed by VARCHAR2.
Slide 6: Tell Me / Show Me – INDEX BY Table Structure
Point out that the primary key is not like a sequence. Values are not generated automatically, but
must be specifically inserted. Therefore some values can be missing, as the slide shows.
Slide 7: Tell Me / Show Me – Declaring an INDEX BY Table
The slide example uses an anonymous block, but INDEX BY tables can be declared in any kind
of PL/SQL subprogram.
Oracle Academy 9 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
Slide 8: Tell Me / Show Me
If we wanted to assign the primary keys incrementally (like a sequence) instead of using the
employee_id, we would code:
DECLARE
TYPE t_names IS TABLE OF VARCHAR2(50)
INDEX BY BINARY_INTEGER;
last_names_tab t_names;
v_count BINARY_INTEGER := 0;
BEGIN
FOR emp_rec IN (SELECT employee_id, last_name
FROM employees) LOOP
v_count := v_count + 1;
last_names_tab(v_count) := emp_rec.last_name;
END LOOP;
END;
Slide 9: Tell Me / Show Me – Using INDEX BY Table Methods
No instructor notes for this slide
Slide 10: Tell Me / Show Me – Using INDEX BY Table Methods (continued)
The COUNT method returns the number of elements in the table. FIRST and LAST return the
lowest and highest primary key values in the table. EXISTS(n) returns TRUE if an element with
primary key = n exists.
Ask students: what output would be produced when this block is executed? Answer: all the
employee last names, in ascending employee_id sequence.
Note: A full discussion of methods is beyond the scope of this course.
Oracle Academy 10 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
EXAMPLE WORKING CODE:
DECLARE
TYPE t_names IS TABLE OF VARCHAR2(50)
INDEX BY BINARY_INTEGER;last_names_tab t_names;
last_names_tab t_names;
v_count BINARY_INTEGER := 0;
BEGIN
FOR emp_rec IN (SELECT employee_id, last_name
FROM employees) LOOP
v_count := v_count + 1;
last_names_tab(v_count) := emp_rec.last_name;
END LOOP;
v_count := last_names_tab.COUNT --1
FOR i IN last_names_tab.FIRST .. last_names_tab.LAST --2
LOOP
IF last_names_tab.EXISTS(i) THEN --3
DBMS_OUTPUT.PUT_LINE(last_names_tab(i));
END IF;
END LOOP;
END;
Slide 11: Tell Me / Show Me – INDEX BY TABLE OF RECORDS
An INDEX BY table of records is just like any other INDEX BY table, except that the non-
primary-key field is a composite (a record) rather than a scalar. We populate it and use methods
such as COUNT, EXISTS and FIRST on it exactly as before.
Slide 12: Tell Me / Show Me –Using an INDEX BY Table of Records
--1 populates the table with whole EMPLOYEE rows.
--2 displays the first_name field from each table element in turn.
Note the syntax: table_name(primary-key).field-name, NOT table_name.field-name(primary-
key).
Slide 13: Tell Me / Show Me – Terminology
Collection – A set of occurrences of the same kind of data.
INDEX BY TABLE – A collection which is based on a single field or column, for example on
the last_name column of EMPLOYEES
INDEX BY TABLE OF RECORDS – A collection which is based on a composite record type,
for example on the whole DEPARTMENTS row.
Slide 14: Summary
No instructor notes for this slide
Slide 15: Try It / Solve It
No instructor notes for this slide
Oracle Academy 11 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
PRACTICE SOLUTIONS
SECTION 11 LESSON 1 - Using Large Object Data Types
Terminology
1. ___BLOB_________________ Binary Large Objects, such as sound (MP3), photos
(JPEG, BMP), proprietary formats (PDF, DOC, XLS), and executables (EXE, DDL).
2. ___CLOB_________________ Character Large Objects, such as resumes, text articles,
source code files.
3. ___BFILE_________________ Binary Files, just like BLOB but stored outside the
database, often on separate media (CD, DVD, HD-DVD).
Try It / Solve It
1. State the datatypes of the three kinds of LOB. Which are internal and which are external?
What kinds of data can be stored in each one?
CLOB: internal, stores text data such as resumes, text articles, source code files
BLOB: internal, stores binary data such as sound (MP3) and photos (JPEG, BMP)
BFILE: external, stores binary data like a BLOB but outside the database.
2. You will use a partial copy of the employees table. Create this copy by executing:
CREATE TABLE lob_emps
AS SELECT employee_id, last_name
FROM employees
WHERE employee_id IN (103,104);
A. You need to add a column to the table to store employees’ annual performance
evaluations, which are stored in text format. Some evaluations may be very large. Why
would you not use a VARCHAR2 datatype for this?
A VARCHAR2 column can store a maximum of 4096 bytes.
B. Add a LONG column named annual_eval to the copy table.
ALTER TABLE lob_emps ADD (annual_eval LONG);
C. Populate the LONG column for the two employees using the following values:
employee_id 103, value “Programs Java and HTML well.” Employee_id 104, value
“Useless at both HTML and Java.”
Oracle Academy 12 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
UPDATE lob_emps
SET annual_eval = 'Programs Java and HTML well.'
WHERE employee_id = 103;
UPDATE lob_emps
SET annual_eval = 'Useless at both HTML and Java.'
WHERE employee_id = 104;
D. Why would it be better if this column were CLOB, not LONG? Convert the column to
CLOB using an ALTER TABLE statement.
A CLOB column can store at least 4GB in each row, while a LONG column can store a
maximum of 2GB. Also a table can contain as many LOB columns as needed, but only
one LONG column.
ALTER TABLE lob_emps MODIFY (annual_eval CLOB);
E. Describe the table to check that the column is now CLOB. Then SELECT the CLOB
data for the two employees.
DESCRIBE lob_emps
SELECT employee_id, annual_eval
FROM lob_emps;
3. Use the partial copy of the employee table from question 2 above for these next questions.
A. What is a LOB locator and what is its purpose?
Because LOB data values are stored out-of-line (in a different area of the database from
the rest of the table) there must be a pointer to it from the table row. This pointer is
called a locator.
B. Write and execute an anonymous block which displays the employee_ids and the length
in bytes of the CLOB column values. Declare a cursor to fetch the rows. Use
DBMS_LOB.GETLENGTH to retrieve the length of the CLOB values. Save your code.
DECLARE
CURSOR emp_curs IS
SELECT employee_id, annual_eval
FROM lob_emps;
v_emprec emp_curs%ROWTYPE;
v_length NUMBER;
BEGIN
OPEN emp_curs;
LOOP
Oracle Academy 13 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
FETCH emp_curs INTO v_emprec;
EXIT WHEN emp_curs%NOTFOUND;
v_length := DBMS_LOB.GETLENGTH(v_emprec.annual_eval);
DBMS_OUTPUT.PUT_LINE('Employee: ' || v_emprec.employee_id ||
' Length: ' || v_length );
END LOOP;
CLOSE emp_curs;
END;
C. Modify your anonymous block to add extra text to the end of the CLOB values. Use the
DBMS_LOB.GETLENGTH function to find how long the existing data is; then use
DBMS_LOB.WRITE to add the text “Next evaluation is due after one year.” to the end
of the text. Your cursor will need to be FOR UPDATE because you are modifying the
table. Save your code.
DECLARE
CURSOR emp_curs IS
SELECT employee_id, annual_eval
FROM lob_emps
FOR UPDATE;
v_emprec emp_curs%ROWTYPE;
v_length NUMBER;
v_new_text VARCHAR2(32767)
:= 'Next evaluation is due after one year.';
v_amount_to_add INTEGER;
BEGIN
OPEN emp_curs;
LOOP
FETCH emp_curs INTO v_emprec;
EXIT WHEN emp_curs%NOTFOUND;
v_length := DBMS_LOB.GETLENGTH(v_emprec.annual_eval);
v_amount_to_add := LENGTH(v_new_text);
DBMS_LOB.WRITE(v_emprec.annual_eval,v_amount_to_add,
v_length + 2,v_new_text);
END LOOP;
CLOSE emp_curs;
END;
D. SELECT the CLOB data again (as in question 1e) to check that the values have been
updated correctly.
SELECT employee_id, annual_eval
FROM lob_emps;
Oracle Academy 14 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
4. The wf_countries table contains a column named flag of datatype BLOB.
A. Write and execute a SQL statement which attempts to display the country_name and flag
for countries whose names begin with “M”.
SELECT country_name, flag
FROM wf_countries
WHERE country_name LIKE ‘M%’;
B. BLOB data cannot be displayed directly in Application Express, but we can display the
length of the BLOB data. Write and execute an anonymous block which uses a cursor to
fetch and display the country name and the length of the BLOB column value, again for
all countries whose names begin with “M”. Save your code.
DECLARE
CURSOR country_curs IS
SELECT country_name, flag
FROM wf_countries
WHERE country_name LIKE 'M%';
v_country_rec country_curs%ROWTYPE;
v_length NUMBER;
BEGIN
OPEN country_curs;
LOOP
FETCH country_curs INTO v_country_rec;
EXIT WHEN country_curs%NOTFOUND;
v_length := DBMS_LOB.GETLENGTH(v_country_rec.flag);
DBMS_OUTPUT.PUT_LINE(v_country_rec.country_name
||' '||v_length);
END LOOP;
CLOSE country_curs;
END;
Extension Exercises
1. From your lob_emps table from question 2:
A. Add a third row to by executing the following:
INSERT INTO lob_emps (employee_id, last_name)
VALUES (105, ‘Smith’);
B. Now re-execute your anonymous block from question 2c to try to update all three rows.
What happens and why?
Oracle Academy 15 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
An ORA-22275 error (invalid LOB locator specified) is returned because the CLOB
locator in the new row has not been initialized. This was not a problem for employees
103 and 104 because the ALTER TABLE lob_emps MODIFY (annual_eval CLOB); in
question 1d initialized the locators automatically.
C. What must be done to correct the error? Correct it, then re-execute your anonymous
block to check that it works.
UPDATE lob_emps
SET annual_eval = EMPTY_CLOB()
WHERE employee_id = 105;
2. We can display the value of BLOB data by copying it into a PL/SQL variable of datatype
RAW and then displaying the RAW column value. Modify your block from question 3b to
declare a variable of datatype RAW(100) and a NUMBER variable initialized to a value of
50. Use DBMS_LOB.READ to copy the first 50 bytes of each BLOB value into the RAW
variable. Then display the country name and the value of the RAW variable.
Here are the formal parameters of the DBMS_LOB.READ procedure:
DBMS_LOB.READ (
lob_loc IN BLOB,
amount IN OUT NUMBER,
offset IN INTEGER,
buffer OUT RAW);
DECLARE
CURSOR country_curs IS
SELECT country_name, flag
FROM wf_countries
WHERE country_name LIKE 'M%';
v_country_rec country_curs%ROWTYPE;
v_length NUMBER;
v_blob_value RAW(50);
v_amount_to_read NUMBER := 50;
BEGIN
OPEN country_curs;
LOOP
FETCH country_curs INTO v_country_rec;
EXIT WHEN country_curs%NOTFOUND;
DBMS_LOB.READ(v_country_rec.flag, v_amount_to_read, 1, v_blob_value);
DBMS_OUTPUT.PUT_LINE(v_country_rec.country_name || ' ' || v_blob_value);
END LOOP;
CLOSE country_curs;
END;
Oracle Academy 16 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
SECTION 11 LESSON 2 - Managing Bfiles
Terminology
Directions: Identify the vocabulary word for each definition below.
1. ___BFILE__________________ Is like a CLOB or BLOB, except that its value is stored
outside the database in a separate file. The database holds a pointer to the external file.
2. ___DIRECTORY_____________ Is a pointer from the database to an operating system
directory (Windows folder) where BFILEs are stored.
Try It / Solve It
1. How is BFILE data stored differently from other types of LOB data (CLOB and BLOB)?
BFILE data is stored outside the database in separate operating system files. The database
contains a pointer to the external file.
2. List three restrictions of using BFILEs.
1) BFILEs can be read by Oracle, but not modified. Therefore they must be created outside
Oracle.
2) Normal database object privileges (for example SELECT) cannot be granted on
BFILEs.
3) Normal SQL statements cannot be used with BFILEs; DBMS_LOB must be used.
3. BFILES:
A. What is a DIRECTORY database object? State two reasons why a DIRECTORY is
needed when using BFILEs.
A DIRECTORY is a pointer to an operating system directory (or Windows folder).
It is needed when using BFILEs because:
(a) the database needs to know where the BFILEs are
(b) it controls privileges: which Oracle users are allowed to read the BFILEs.
B. What two SQL statements would you use to create a directory called MYDIR pointing to
an operating system directory called ‘/u01/mybfiles’, and to allow Oracle user TOM to
read BFILEs stored in that directory?
CREATE DIRECTORY mydir AS '/u01/mybfiles';
GRANT READ ON DIRECTORY mydir TO tom;
C. You do not have the privilege to create your own directories. Query the dictionary to see
what directory has already been created for you.
SELECT * FROM all_directories;
Oracle Academy 17 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
4. Every country in the world has a two-character FIPS (Federal Information Processing
Standard) code which is stored in the fips_id column of wf_countries. The directory
WF_FLAGS points to an operating system directory which contains a BFILE for each
country. Each BFILE contains the national flag of that country. The operating system file
name of the BFILE is: lower_case_fips_id-lgflag.gif. For example, the fips id of Canada is
“CA”, so its BFILE file name is: ca-lgflag.gif
A. Execute a SELECT statement to display the country id, country name and fips id of all
countries whose name begins with “C”.
SELECT country_id, country_name, fips_id
FROM wf_countries
WHERE country_name LIKE ‘C%’;
B. Write down the complete path name (operating system directory and file name) of the
Czech Republic’s national flag.
/u02/webapps/oa1bprd_dir/ez-lgflags.gif
5. In the rest of this Practice, you will be working on a partial copy of wf_countries called
copy_countries. This table is created by you, you add two LOB columns to it and then work
with those LOB columns..
A. Create a partial copy of wf_countries by executing the following statement:
CREATE TABLE copy_countries
AS SELECT country_id, country_name, fips_id
FROM wf_countries
WHERE country_name LIKE 'C%';
B. Change the copy_countries table. Add a new BFILE column called external_flag by
running the following statement:
Alter table copy_countries ADD external_flag BFILE;
C. The following code shows part of an anonymous block which checks whether the
external BFILEs actually exist. Copy and complete the block. For each row, use the
BFILENAME function to populate the BFILE variable with the directory and correct file
name of its external BFILE, then use DBMS_LOB.FILEEXISTS to check the existence
of the file, and display a suitable message to show whether the file exists or not. Execute
the block and save your code.
DECLARE
CURSOR flag_curs IS
SELECT country_name, fips_id
FROM copy_countries;
Oracle Academy 18 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
v_flag_rec flag_curs%ROWTYPE;
v_dir_alias VARCHAR2(30):= 'WF_FLAGS';
v_file_name VARCHAR2(50);
v_locator BFILE;
BEGIN
OPEN flag_curs;
LOOP
FETCH flag_curs INTO v_flag_rec;
EXIT WHEN flag_curs%NOTFOUND;
...
...
...
END LOOP;
CLOSE flag_curs;
END;
DECLARE
CURSOR flag_curs IS
SELECT country_name, fips_id
FROM copy_countries;
v_flag_rec flag_curs%ROWTYPE;
v_dir_alias VARCHAR2(30):= 'WF_FLAGS';
v_file_name VARCHAR2(50);
v_locator BFILE;
BEGIN
OPEN flag_curs;
LOOP
FETCH flag_curs INTO v_flag_rec;
EXIT WHEN flag_curs%NOTFOUND;
v_file_name := LOWER(v_flag_rec.fips_id) || '-lgflag.gif';
v_locator := BFILENAME(v_dir_alias,v_file_name);
IF DBMS_LOB.FILEEXISTS(v_locator)= 1 THEN
DBMS_OUTPUT.PUT_LINE(v_flag_rec.country_name ||
' flag exists and is called: ' ||
v_file_name);
ELSE
DBMS_OUTPUT.PUT_LINE(v_flag_rec.country_name ||
' flag does not exist');
END IF;
END LOOP;
CLOSE flag_curs;
END;
6. Modify your anonymous block from question 5 to populate the external_flag column of
copy_countries with the locator of its BFILE. You will need to change your cursor to FOR
UPDATE because you are updating the table. Execute the block. Save your work.
Oracle Academy 19 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
DECLARE
CURSOR flag_curs IS
SELECT country_name, fips_id
FROM copy_countries FOR UPDATE;
v_length NUMBER;
v_dir_alias VARCHAR2(50):= 'WF_FLAGS';
v_file_name VARCHAR2(50);
v_locator BFILE;
BEGIN
FOR flag_rec IN flag_curs LOOP
v_file_name := LOWER(flag_rec.fips_id) || '-lgflag.gif';
v_locator := BFILENAME(v_dir_alias,v_file_name);
IF dbms_lob.FILEEXISTS(v_locator)= 1 THEN
UPDATE copy_countries SET external_flag = v_locator
WHERE CURRENT OF flag_curs;
DBMS_LOB.FILEOPEN(v_locator);
v_length := DBMS_LOB.GETLENGTH(v_locator);
DBMS_LOB.FILECLOSE(v_locator);
ELSE
v_length := 0;
END IF;
DBMS_OUTPUT.PUT_LINE(flag_rec.country_name || ' ' || v_dir_alias || ' '
|| v_file_name || ' ' || v_length);
END LOOP;
END;
7. Modify the block from question 6 again to display the country names and the size of its
BFILE in bytes. Save your code. Your output should look similar to this:
DECLARE
Oracle Academy 20 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
CURSOR flag_curs IS
SELECT country_name, fips_id
FROM copy_countries;
v_flag_rec flag_curs%ROWTYPE;
v_dir_alias VARCHAR2(30):= 'WF_FLAGS';
v_file_name VARCHAR2(50);
v_locator BFILE;
v_bfile_length NUMBER;
BEGIN
OPEN flag_curs;
LOOP
FETCH flag_curs INTO v_flag_rec;
EXIT WHEN flag_curs%NOTFOUND;
v_file_name := LOWER(v_flag_rec.fips_id) || '-lgflag.gif';
v_locator := BFILENAME(v_dir_alias,v_file_name);
IF DBMS_LOB.FILEEXISTS(v_locator)= 1 THEN
DBMS_LOB.FILEOPEN(v_locator);
v_bfile_length := DBMS_LOB.GETLENGTH(v_locator);
DBMS_LOB.FILECLOSE(v_locator);
ELSE
v_bfile_length := 0;
END IF;
DBMS_OUTPUT.PUT_LINE(v_flag_rec.country_name || ' ' ||
v_dir_alias || ' ' ||
v_file_name || ' ' ||
v_bfile_length);
END LOOP;
CLOSE flag_curs;
END;
Extension Exercise
1. In this question you will add a BLOB column to your copy_countries table and populate it
with a copy of the corresponding BFILE data.
A. Add a BLOB column called internal_flag to the copy_countries table.
ALTER TABLE copy_countries
ADD (internal_flag BLOB);
B. Initialize the BLOB column locators by using EMPTY_BLOB().
UPDATE copy_countries
SET internal_flag = EMPTY_BLOB();
C. Copy the external_flag BFILE value to the BLOB column. To do this, modify your code
from question 6 to read the BFILE value into a RAW variable (using
Oracle Academy 21 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
DBMS_LOB.READ), then writing this value into the BLOB column (using
DBMS_LOB.WRITE).
You will also need to exclude country_id 1670 from your cursor because the size of its flag is
34196 bytes, and the maximum size of a RAW variable is 32767 bytes.
DECLARE
CURSOR flag_curs IS
SELECT *
FROM copy_countries
WHERE country_id <> 1670
FOR UPDATE;
v_flag_rec flag_curs%ROWTYPE;
v_dir_alias VARCHAR2(30):= 'WF_FLAGS';
v_file_name VARCHAR2(50);
v_locator BFILE;
v_bfile_length NUMBER;
v_bfile_value RAW(32767);
BEGIN
OPEN flag_curs;
LOOP
FETCH flag_curs INTO v_flag_rec;
EXIT WHEN flag_curs%NOTFOUND;
IF DBMS_LOB.FILEEXISTS(v_flag_rec.external_flag)= 1 THEN
DBMS_LOB.FILEOPEN(v_flag_rec.external_flag);
v_bfile_length := DBMS_LOB.GETLENGTH(v_flag_rec.external_flag);
DBMS_LOB.READ(v_flag_rec.external_flag,
v_bfile_length,
1,
v_bfile_value);
DBMS_LOB.FILECLOSE(v_flag_rec.external_flag);
DBMS_LOB.WRITE(v_flag_rec.internal_flag,
v_bfile_length,
1,
v_bfile_value);
END IF;
END LOOP;
CLOSE flag_curs;
END;
D. For each row, display the sizes of the BLOB value and the BFILE value. Examine the
output and check that the two sizes are equal.
DECLARE
CURSOR flag_curs IS
SELECT *
Oracle Academy 22 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
FROM copy_countries
WHERE country_id <> 1670;
v_flag_rec flag_curs%ROWTYPE;
v_dir_alias VARCHAR2(30):= 'WF_FLAGS';
v_file_name VARCHAR2(50);
v_locator BFILE;
v_bfile_length NUMBER;
v_blob_length NUMBER;
BEGIN
OPEN flag_curs;
LOOP
FETCH flag_curs INTO v_flag_rec;
EXIT WHEN flag_curs%NOTFOUND;
IF DBMS_LOB.FILEEXISTS(v_flag_rec.external_flag)= 1 THEN
DBMS_LOB.FILEOPEN(v_flag_rec.external_flag);
v_bfile_length := DBMS_LOB.GETLENGTH(v_flag_rec.external_flag);
DBMS_LOB.FILECLOSE(v_flag_rec.external_flag);
v_blob_length := DBMS_LOB.GETLENGTH(v_flag_rec.internal_flag);
DBMS_OUTPUT.PUT_LINE(v_flag_rec.country_name ||
': BFILE size: ' ||
v_bfile_length ||
' BLOB size: ' ||
v_blob_length);
END IF;
END LOOP;
CLOSE flag_curs;
END;
Oracle Academy 23 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
SECTION 11 LESSON 3 - User-Defined Records
Terminology
1. ___ROWTYPE_____________ is a composite data type consisting of a group of related
data items stored as fields, each with its own name and data type.
Try It / Solve It
1. Copy and execute the following anonymous block. Then modify it to declare and use a
single record instead of a scalar variable for each column. Make sure that your code will still
work if an extra column is added to the departments table later. Execute your modified block
and save your code.
DECLARE
v_dept_id departments.department_id%TYPE;
v_dept_name departments.department_name%TYPE;
v_mgr_id departments.manager_id%TYPE;
v_loc_id departments.location_id%TYPE;
BEGIN
SELECT department_id, department_name,
manager_id, location_id
INTO v_dept_id, v_dept_name,
v_mgr_id, v_loc_id
FROM departments
WHERE department_id = 80;
DBMS_OUTPUT.PUT_LINE(v_dept_id || ' ' || v_dept_name
|| ' ' || v_mgr_id
|| ' ' || v_loc_id);
EXCEPTION
WHEN no_data_found THEN
DBMS_OUTPUT.PUT_LINE('This department does not exist');
END;
DECLARE
v_dept_rec departments%ROWTYPE;
BEGIN
SELECT * INTO v_dept_rec
FROM departments
WHERE department_id = 80;
DBMS_OUTPUT.PUT_LINE(v_dept_rec.department_id
|| ' ' || v_dept_rec.department_name
|| ' ' || v_dept_rec.manager_id
|| ' ' || v_dept_rec.location_id);
EXCEPTION
WHEN no_data_found THEN
DBMS_OUTPUT.PUT_LINE('This department does not exist');
END;
Oracle Academy 24 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
2. Procedure get_dept:
A. Convert your anonymous block from question 1 to a procedure called get_dept which
accepts a department_id as an IN parameter and (instead of displaying the row) returns a
complete department row in a single composite OUT parameter. If the department does
not exist, the procedure should not display message but should instead return a null value
in the department_id field of the OUT parameter. Execute the code to create the
procedure. Save your code.
CREATE OR REPLACE PROCEDURE get_dept
(p_dept_id IN departments.department_id%TYPE,
p_dept_rec OUT departments%ROWTYPE) IS
BEGIN
SELECT * INTO p_dept_rec
FROM departments
WHERE department_id = p_dept_id;
EXCEPTION
WHEN no_data_found THEN
p_dept_rec.department_id := NULL;
END;
B. Write and execute an anonymous block which calls the get_dept procedure and displays
the department’s details. If a null value is returned, display an error message. Execute
the block twice using department_ids 80 and 72. Save your code.
DECLARE
v_dept_rec departments%ROWTYPE;
BEGIN
get_dept(80, v_dept_rec); -- or (72, v_dept_rec)
IF v_dept_rec.department_id IS NOT NULL THEN
DBMS_OUTPUT.PUT_LINE(v_dept_rec.department_id
|| ' ' || v_dept_rec.department_name
|| ' ' || v_dept_rec.manager_id
|| ' ' || v_dept_rec.location_id);
ELSE
DBMS_OUTPUT.PUT_LINE('This department does not exist');
END IF;
END;
Oracle Academy 25 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
3. Answer the following questions:
A. Modify your anonymous block from question 1 so that it SELECTs from an equijoin of
departments and locations into a single record, and displays the department id,
department name and the city where the department is located. You will need to declare
a type consisting of three fields, and a record structure of that type. Execute the block
using department_id 80.
DECLARE
TYPE dept_loc_type IS RECORD
(dept_id departments.department_id%TYPE,
dept_name departments.department_name%TYPE,
city locations.city%TYPE);
v_dept_loc_rec dept_loc_type;
BEGIN
SELECT d.department_id, d.department_name, l.city
INTO v_dept_loc_rec
FROM departments d, locations l
WHERE d.location_id = l.location_id
AND d.department_id = 80;
DBMS_OUTPUT.PUT_LINE(v_dept_loc_rec.dept_id
|| ' ' || v_dept_loc_rec.dept_name
|| ' ' || v_dept_loc_rec.city);
EXCEPTION
WHEN no_data_found THEN
DBMS_OUTPUT.PUT_LINE('This department does not exist');
END;
B. Now modify your get_dept procedure from question 2 so that it tries to return the same
joined data as step 3a in an OUT parameter record structure based on a type. Try to
recreate the procedure. What happens and why?
Oracle Academy 26 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
CREATE OR REPLACE PROCEDURE get_dept
(p_dept_id IN departments.department_id%TYPE,
p_dept_loc_rec OUT dept_loc_type) IS
TYPE dept_loc_type IS RECORD
(dept_id departments.department_id%TYPE,
dept_name departments.department_name%TYPE,
city locations.city%TYPE);
BEGIN
SELECT d.department_id, d.department_name, l.city
INTO p_dept_loc_rec
FROM departments d, locations l
WHERE d.location_id = l.location_id
AND d.department_id = p_dept_id;
EXCEPTION
WHEN no_data_found THEN
p_dept_loc_rec.dept_id := NULL;
END;
An error is returned because the procedure code is trying to reference the type in the
OUT parameter before declaring the type within the procedure. We cannot use
forward declarations in procedures or functions, only in packages.
C. Modify your get_dept procedure so that it is the only procedure in a package called
get_dept_pkg. Declare the type as a global variable in the package specification. Create
the package specification and body, and save your code.
CREATE OR REPLACE PACKAGE get_dept_pkg IS
TYPE dept_loc_type IS RECORD
(dept_id departments.department_id%TYPE,
dept_name departments.department_name%TYPE,
city locations.city%TYPE);
PROCEDURE get_dept
(p_dept_id IN departments.department_id%TYPE,
p_dept_loc_rec OUT dept_loc_type);
END get_dept_pkg;
Oracle Academy 27 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
CREATE OR REPLACE PACKAGE BODY get_dept_pkg IS
PROCEDURE get_dept
(p_dept_id IN departments.department_id%TYPE,
p_dept_loc_rec OUT dept_loc_type) IS
BEGIN
SELECT d.department_id, d.department_name, l.city
INTO p_dept_loc_rec
FROM departments d, locations l
WHERE d.location_id = l.location_id
AND d.department_id = p_dept_id;
EXCEPTION
WHEN no_data_found THEN
p_dept_loc_rec.dept_id := NULL;
END get_dept;
END get_dept_pkg;
D. Describe get_dept_pkg and observe the datatype of its OUT parameter. Then, modify
your anonymous block from question 2b to call the packaged procedure. The block
should not declare the type, but should use the global type declaration from the package
specification. Execute the block twice using department_ids 80 and 72.
DESCRIBE get_dept_pkg
DECLARE
v_dept_rec get_dept_pkg.dept_loc_type;
BEGIN
get_dept_pkg.get_dept(80, v_dept_rec); -- or (72, v_dept_rec)
IF v_dept_rec.dept_id IS NOT NULL THEN
DBMS_OUTPUT.PUT_LINE(v_dept_rec.dept_id
|| ' ' || v_dept_rec.dept_name
|| ' ' || v_dept_rec.city);
ELSE
DBMS_OUTPUT.PUT_LINE('This department does not exist');
END IF;
END;
Oracle Academy 28 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
SECTION 11 LESSON 4 - Index By Tables of Records
Terminology
1. _Collection _____________________ A set of occurrences of the same kind of data.
2. _INDEX BY TABLE______________ A collection which is based on a single field or
column, for example on the last_name column of EMPLOYEES
3. _INDEX BY TABLE OF RECORDS_ A collection which is based on a composite record
type, for example on the whole DEPARTMENTS row.
Try It / Solve It
1. Pl/SQL collections:
A. In your own words, describe what a PL/SQL collection is.
A PL/SQL collection is a set of two or more (usually) many occurrences of the same
kind of data. It is a named variable in PL/SQL. The collection’s data is stored in a
private memory area, like any other PL/SQL variable.
B. Which of the following are collections and which are not?
1. A list of all employees’ last names
2. The character value “Chang”
3. The populations of all countries in Europe
4. All the data stored in the employees table about a specific employee.
1 and 3 are collections. 2 is a scalar. 4 is a composite record structure but not a
collection, since each data item occurs only once.
C. What is the difference between an INDEX BY table and a database table such as
employees or wf_countries?
Database tables are stored in the database, ie on disk, and are therefore permanent
(until DROPped). Their data can be seen and used by any database user with the
correct privileges. INDEX BY tables are PL/SQL variables stored in a memory area,
and are not permanent. Their contents are private to the creating session and cannot
be seen by any other session or user.
D. Describe the difference between an INDEX BY table and an INDEX BY table of records.
In an INDEX BY table, each element or “member” of the table is a single scalar value
such as a last name. In an INDEX BY table of records, each element is a record
structure such as a whole employee row. Both kinds of INDEX BY table also have a
numeric primary key which serves as an index into the table.
Oracle Academy 29 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
E. Look at the following code. Describe the difference between t_pops and v_pops_tab. Is
v_pops_tab an INDEX BY table or an INDEX BY table of records? How do you know?
DECLARE
TYPE t_pops IS TABLE OF wf_countries.population%TYPE
INDEX BY BINARY_INTEGER;
v_pops_tab t_pops;
t_pops declares a type and v_pops_tab declares a variable of that type. v_pops_tab is
an INDEX BY table (not of records) because each element is a single scalar variable (a
population value).
2. Tables of countries in South America:
A. Write and execute an anonymous block which declares and populates an INDEX BY
table of countries in South America (region_id = 5). The table should use country_id as a
primary key, and should store the country names as the element values. The data should
be stored in the table in ascending sequence of country_id. The block should not display
any output. Save your code.
DECLARE
TYPE t_country_names IS TABLE OF
wf_countries.country_name%TYPE
INDEX BY BINARY_INTEGER;
v_country_names t_country_names;
CURSOR country_curs IS
SELECT country_id, country_name
FROM wf_countries
WHERE region_id = 5
ORDER BY country_id;
v_country_rec country_curs%ROWTYPE;
BEGIN
OPEN country_curs;
LOOP
FETCH country_curs INTO v_country_rec;
EXIT WHEN country_curs%NOTFOUND;
v_country_names(v_country_rec.country_id) :=
v_country_rec.country_name;
END LOOP;
CLOSE country_curs;
END;
Oracle Academy 30 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
B. Modify the block so that after populating the INDEX BY table, it uses a FOR loop to
display the contents of the INDEX BY table. You will need to use the FIRST, LAST and
EXISTS table methods. Execute the block and check the displayed results. Save your
code.
DECLARE
TYPE t_country_names IS TABLE OF
wf_countries.country_name%TYPE
INDEX BY BINARY_INTEGER;
v_country_names t_country_names;
CURSOR country_curs IS
SELECT country_id, country_name
FROM wf_countries
WHERE region_id = 5
ORDER BY country_id;
v_country_rec country_curs%ROWTYPE;
BEGIN
OPEN country_curs;
LOOP
FETCH country_curs INTO v_country_rec;
EXIT WHEN country_curs%NOTFOUND;
v_country_names(v_country_rec.country_id) :=
v_country_rec.country_name;
END LOOP;
CLOSE country_curs;
FOR i IN v_country_names.FIRST .. v_country_names.LAST
LOOP
IF v_country_names.EXISTS(i) THEN
DBMS_OUTPUT.PUT_LINE(i || ' ' || v_country_names(i));
END IF;
END LOOP;
END;
Oracle Academy 31 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
C. Modify the block again so that instead of displaying all the contents of the table, it
displays only the first and last elements and the number of elements in the table. Execute
the block and check the displayed results.
DECLARE
TYPE t_country_names IS TABLE OF
wf_countries.country_name%TYPE
INDEX BY BINARY_INTEGER;
v_country_names t_country_names;
CURSOR country_curs IS
SELECT country_id, country_name
FROM wf_countries
WHERE region_id = 5
ORDER BY country_id;
v_country_rec country_curs%ROWTYPE;
BEGIN
OPEN country_curs;
LOOP
FETCH country_curs INTO v_country_rec;
EXIT WHEN country_curs%NOTFOUND;
v_country_names(v_country_rec.country_id) :=
v_country_rec.country_name;
END LOOP;
CLOSE country_curs;
DBMS_OUTPUT.PUT_LINE(v_country_names.FIRST || ' ' ||
v_country_names(v_country_names.FIRST));
DBMS_OUTPUT.PUT_LINE(v_country_names.LAST || ' ' ||
v_country_names(v_country_names.LAST));
DBMS_OUTPUT.PUT_LINE('Number of countries is: ' ||
v_country_names.COUNT);
END;
Oracle Academy 32 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
3. Table of Records:
A. Write and execute an anonymous block which declares and populates an INDEX by table
of records containing employee data. The table of records should use the employee id as
a primary key, and each element should contain an employee’s last name, job id and
salary. The data should be stored in the table of records in ascending sequence of
employee id. The block should not display any output. Hint: declare a cursor to fetch the
employee data, then declare the INDEX BY table as cursor-name%ROWTYPE Save
your code.
DECLARE
CURSOR emp_curs IS
SELECT employee_id, last_name, job_id, salary
FROM employees
ORDER BY employee_id;
v_emp_rec emp_curs%ROWTYPE;
TYPE t_emp_data IS TABLE OF
emp_curs%ROWTYPE
INDEX BY BINARY_INTEGER;
v_emp_data t_emp_data;
BEGIN
OPEN emp_curs;
LOOP
FETCH emp_curs INTO v_emp_rec;
EXIT WHEN emp_curs%NOTFOUND;
v_emp_data(v_emp_rec.employee_id) :=
v_emp_rec;
END LOOP;
CLOSE emp_curs;
END;
Oracle Academy 33 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
B. Modify the block so that after populating the table of records, it uses a FOR loop to
display to display the contents. You will need to use the FIRST, LAST and EXISTS
table methods. Execute the block and check the displayed results. Save your code.
DECLARE
CURSOR emp_curs IS
SELECT employee_id, last_name, job_id, salary
FROM employees
ORDER BY employee_id;
v_emp_rec emp_curs%ROWTYPE;
TYPE t_emp_data IS TABLE OF
emp_curs%ROWTYPE
INDEX BY BINARY_INTEGER;
v_emp_data t_emp_data;
BEGIN
OPEN emp_curs;
LOOP
FETCH emp_curs INTO v_emp_rec;
EXIT WHEN emp_curs%NOTFOUND;
v_emp_data(v_emp_rec.employee_id) :=
v_emp_rec;
END LOOP;
CLOSE emp_curs;
FOR i IN v_emp_data.FIRST .. v_emp_data.LAST
LOOP
IF v_emp_data.EXISTS(i) THEN
DBMS_OUTPUT.PUT_LINE(v_emp_data(i).employee_id || ' '
|| v_emp_data(i).last_name || ' '
|| v_emp_data(i).job_id || ' '
|| v_emp_data(i).salary);
END IF;
END LOOP;
END;
C. Convert the anonymous block from step 3a to a package named tab_rec_pkg which
declares both the cursor and the type as global variables in the package specification.
The block code should be converted to a public packaged procedure named
pop_emp_tab, which does not display anything, but declares the INDEX BY table of
records as an OUT parameter. Execute your code to create the package specification and
body.
Oracle Academy 34 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
CREATE OR REPLACE PACKAGE tab_rec_pkg IS
CURSOR g_emp_curs IS
SELECT employee_id, last_name, job_id, salary
FROM employees
ORDER BY employee_id;
TYPE t_emp_data IS TABLE OF
g_emp_curs%ROWTYPE
INDEX BY BINARY_INTEGER;
PROCEDURE pop_emp_tab
(p_emp_data OUT t_emp_data);
END tab_rec_pkg;
CREATE OR REPLACE PACKAGE BODY tab_rec_pkg IS
PROCEDURE pop_emp_tab
(p_emp_data OUT t_emp_data) IS
v_emp_rec g_emp_curs%ROWTYPE;
BEGIN
OPEN g_emp_curs;
LOOP
FETCH g_emp_curs INTO v_emp_rec;
EXIT WHEN g_emp_curs%NOTFOUND;
p_emp_data(v_emp_rec.employee_id) :=
v_emp_rec;
END LOOP;
CLOSE g_emp_curs;
END;
END tab_rec_pkg;
D. Write and execute an anonymous block which calls the pop_emp_tab packaged
procedure and displays the returned table of records of employee data using a FOR loop.
Save your code.
DECLARE
v_emp_data tab_rec_pkg.t_emp_data;
BEGIN
tab_rec_pkg.pop_emp_tab(v_emp_data);
FOR i IN v_emp_data.FIRST .. v_emp_data.LAST
LOOP
IF v_emp_data.EXISTS(i) THEN
DBMS_OUTPUT.PUT_LINE(v_emp_data(i).employee_id || ' '
|| v_emp_data(i).last_name || ' '
|| v_emp_data(i).job_id || ' '
|| v_emp_data(i).salary);
END IF;
END LOOP;
END;
Oracle Academy 35 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
Extension Exercise
1. Create an empty partial copy of the employees table by executing the following statement:
CREATE TABLE part_emps AS
SELECT employee_id, last_name, job_id, salary
FROM employees
WHERE 0 = 1;
2. Add a second public procedure named ins_new_emp to the tab_rec_pkg from step 3b. The
procedure should accept an IN parameter which is a record structure (not a collection)
consisting of the employee id, last name, job id and salary for a single employee. (Hint:
declare the IN parameter as global-cursor-name%ROWTYPE). The procedure should insert
this data as a row into the part_emps table. Execute your code to recreate the package
specification and body. Save your code.
CREATE OR REPLACE PACKAGE tab_rec_pkg IS
CURSOR g_emp_curs IS
SELECT employee_id, last_name, job_id, salary
FROM employees
ORDER BY employee_id;
TYPE t_emp_data IS TABLE OF
g_emp_curs%ROWTYPE
INDEX BY BINARY_INTEGER;
PROCEDURE pop_emp_tab
(p_emp_data OUT t_emp_data);
PROCEDURE ins_new_emp
(p_emp_rec IN g_emp_curs%ROWTYPE);
END tab_rec_pkg;
Oracle Academy 36 Database Programming with PL/SQL
Copyright © 2007, Oracle. All rights reserved.
CREATE OR REPLACE PACKAGE BODY tab_rec_pkg IS
PROCEDURE pop_emp_tab
(p_emp_data OUT t_emp_data) IS
v_emp_rec g_emp_curs%ROWTYPE;
BEGIN
OPEN g_emp_curs;
LOOP
FETCH g_emp_curs INTO v_emp_rec;
EXIT WHEN g_emp_curs%NOTFOUND;
p_emp_data(v_emp_rec.employee_id) := v_emp_rec;
END LOOP;
CLOSE g_emp_curs;
END;
PROCEDURE ins_new_emp
(p_emp_rec IN g_emp_curs%ROWTYPE) IS
BEGIN
INSERT INTO part_emps (employee_id, last_name,
job_id, salary)
VALUES (p_emp_rec.employee_id,
p_emp_rec.last_name,
p_emp_rec.job_id,
p_emp_rec.salary);
END;
END tab_rec_pkg;
3. Modify your anonymous block from step 3d so that instead of displaying each element of the
INDEX BY table of records, it calls the ins_new_emp procedure to insert the element data
into the part_emps table. Execute your modified block.
DECLARE
v_emp_data tab_rec_pkg.t_emp_data;
BEGIN
tab_rec_pkg.pop_emp_tab(v_emp_data);
FOR i IN v_emp_data.FIRST .. v_emp_data.LAST
LOOP
IF v_emp_data.EXISTS(i) THEN
tab_rec_pkg.ins_new_emp(v_emp_data(i));
END IF;
END LOOP;
END;
4. Query the part_emps table to check that it has been populated correctly. It should contain 20
rows.
SELECT *
FROM part_emps;