Sunteți pe pagina 1din 36

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. Like 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. Thats 5 Terabytes (5000 Gigabytes) of data to be copied.

Oracle Academy

1 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

2 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 3 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 computers 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

4 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

5 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

6 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

7 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

8 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

9 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 nonprimary-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(primarykey). 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

10 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

11 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 12 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

13 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

14 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

15 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

16 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 Republics 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 17 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 18 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 19 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 20 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 21 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

22 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 23 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 departments 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

24 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

25 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

26 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

27 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 collections 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. 2. 3. 4. A list of all employees last names The character value Chang The populations of all countries in Europe 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

28 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

29 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

30 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

31 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 employees 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

32 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

33 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 34 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

35 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; Oracle Academy 36 Database Programming with PL/SQL Copyright 2007, Oracle. All rights reserved.

S-ar putea să vă placă și