Documente Academic
Documente Profesional
Documente Cultură
rogramming with the new Object constructions in Oracle8 can be challenging. This chapter shows how to work with Objects in PL/SQL blocks and with JDBC.
CrossReference
25
C H A P T E R
In This Chapter
Writing PL/SQL code for querying Objects Updating Objects with PL/SQL Inserting data into Objects with PL/SQL Using JDBC to query Objects Updating Objects with JDBC
This chapter uses the Objects and Object Tables created in Chapter 24, which examines the concepts behind Oracle8 Objects and illustrates some sample PL/SQL code. Review Chapter 24 before you tackle this chapter. Chapter 18 discusses basic construction techniques for PL/SQL programming. Refer to this chapter if you need to brush up on PL/SQL programming techniques. Each section in this chapter has sample code that runs using the sample Tables included on the CD-ROM. Follow the instructions in Appendix B to install the Tables in your database.
CrossReference
648
BOOK TABLE T (table of BOOKS T) BOOK_ID integer (PK) BOOK_PUBLISHER varchar2(50) BOOK_AUTHOR_NAME
FIRST_NAME LAST_NAME varchar2(25) varchar2(25)
books_t name_t
Figure 25-1: The Object Table BOOK_TABLE_T is one of the sample Tables.
The BOOK_TABLE_T contains a combination of regular Columns with Oracle datatypes and User-defined datatypes. The Column BOOK_AUTHOR_NAME is of the type name_t, which is a User-defined Object type. The name_t Object type is defined as containing two VARCHAR2 Columns: FIRST_NAME and LAST_NAME. The BOOK_TABLE_T also contains a collection. The Column BOOK_RESERVED_LIST is a User-defined datatype called reserved_list_t. Reserved_list_t is a VARRAY containing ten rows of another User-defined datatype called books_ reserved_t. The books_reserved_t Object type contains two Columns: PERSON_ID, a reference to the PERSON_T datatype; and WAIT_NO, an integer. The PERSON_ID Column connects a record in the VARRAY to a row in the PERSON_TABLE_T Table, similar to a Foreign Key Column. The other Object Table is called PERSON_TABLE_T. Figure 25-2 shows a diagram of this Object Table structure. The PERSON_TABLE_T Object Table has a variety of Column structures. PERSON_ID is the Primary Key. PERSON_NAME is a User-defined datatype of name_t. PERSON_ADDRESS is another User-defined datatype of address_t. PERSON_TABLE_T contains the following two collections: 3 PERSON_PHONE. This collection is a VARRAY of ten records of the Object type phone_t. The phone_t type itself consists of two VARCHAR2 Columns: PHONE_TYPE and PHONE_NUMBER. 3 PERSON_BK_LOANED. This collection is a nested Table of records of the Object type book_loaned_t. The book_loaned_t type consists of three Columns: BOOK_ID, a reference to BOOKS_T (which acts as a Foreign Key to the BOOK_ TABLE_T); LOAN__DATE, a date Column; and FINE, a number Column.
649
integer (PK)
varchar2(25) varchar2(25)
name_t
person_t
PERSON ADDRESS
STREET CITY STATE ZIP varchar2(50) varchar2(25) varchar2(25) number
address_t
PERSON_BK_LOANED books_loan_list_t
BOOK_ID LOAN_DATE FINE ref BOOKS_T date number(5,2) table of book_loaned_t book_loaned_t
Figure 25-2: The Object Table PERSON_TABLE_T is one of the sample Tables.
650
In the preceding query command, the Columns FIRST_NAME and LAST_NAME are qualified with the Object type Column BOOK_AUTHOR_NAME. The two Columns are also qualified with a Table alias. Both components are necessary to execute the query successfully. The two Columns appear in the query as follows:
... B.BOOK_AUTHOR_NAME.FIRST_NAME, B.BOOK_AUTHOR_NAME.LAST_NAME ...
The results of this query appear as follows. The headings are removed to compress the Column listing into single lines:
1 IDG Rough Guide to British Columbia Travel Amy Chandi 2 IDG Hiking Maui Travel Iolani Kalani 3 SCIFI Press Foundation Science Fiction Isaac Asimov 4 SCIFI Press Best of Science Fiction Science Fiction Isaac Asimov 5 SCIFI Press The Illustrated Man Science Fiction Ray Bradbury 6 Time Life The Technical Drummer Music Joe Banks 7 Time Life Slide Guitar Music Joe Dieter 16 HARDY PRESS Moonlight Science Fiction James Tiptree 8 rows returned.
Again, the Object datatype Columns, PERSON_NAME and PERSON_ADDRESS, are split into their component Columns using the Column name and the component Column name. The Table alias is also required for this kind of Column, such as in the following statement:
651
The following lists the results of the query. The Column headings are removed to compress to data into single lines per data row:
1 Tony Prem 78759 Y 2 Amy Chandi 78759 Y 3 Cortney Dumas 78799 Y 4 Patrick Mohyde 12345 Y 4 rows selected. 11316 Jollyville 100 West Main St 215 E Dewey St 80 Kealani St Austin Dallas Austin TX TX HI TX
Honolulu
652
INTO V_PERSON_BK_LOANED, V_FIRST_NAME, V_LAST_NAME FROM PERSON_TABLE_T P WHERE P.PERSON_ID = I_PERSON_ID; EXCEPTION WHEN NO_DATA_FOUND THEN V_PERSON_BK_LOANED := BOOKS_LOAN_LIST_T(NULL); END; DBMS_OUTPUT.PUT_LINE(BOOKS ON LOAN TO:); DBMS_OUTPUT.PUT_LINE(PERSON# ||I_PERSON_ID || ||V_FIRST_NAME || || V_LAST_NAME || *****************************); FOR V_COUNTER IN 1..V_PERSON_BK_LOANED.COUNT LOOP /* ---- LOOP THROUGH EACH ROW IN THE NESTED TABLE. ---*/ V_BOOK_LOANED := V_PERSON_BK_LOANED(V_COUNTER); V_REF_BOOK := V_BOOK_LOANED.BOOK_ID; /* ---------- USE DEREF TO RETRIEVE THE BOOK_TABLE_T ROW THAT IS REFERENCED ------------- */ SELECT DEREF(V_REF_BOOK) INTO V_BOOK FROM DUAL; /* ----- DISPLAY DETAILS ABOUT THE BOOK. ----------- */ DBMS_OUTPUT.PUT_LINE(BOOK# ||V_BOOK.BOOK_ID || ||V_BOOK.BOOK_TITLE); DBMS_OUTPUT.PUT_LINE( LOAN DATE: || V_BOOK_LOANED.LOAN_DATE); DBMS_OUTPUT.PUT_LINE( FINE: || V_BOOK_LOANED.FINE); END LOOP; DBMS_OUTPUT.PUT_LINE(DONE); END; END;
To run the procedure after creation, type the following SQL command in SQL Worksheet or SQL*Plus:
EXECUTE LIST_LOANED_BOOKS(n);
Replace n with the PERSON_ID you wish to list. For example, the following command lists the loaned books of Tony Prem (PERSON_ID=1):
EXECUTE LIST_LOANED_BOOKS(1);
653
BOOK# 2 Hiking Maui LOAN DATE: 12-MAR-98 FINE: 1.5 BOOK# 4 Best of Science Fiction LOAN DATE: 16-MAR-98 FINE: .5 BOOK# 5 The Illustrated Man LOAN DATE: 25-MAR-98 FINE: 0 DONE PL/SQL procedure successfully completed.
When using PL/SQL to reach data within a nested Table, you must set up variables with datatypes that match the datatypes in the Object Table. For example, the PERSON_BK_LOANED Column is of datatype books_loan_List_t. Therefore, you can define a variable, V_PERSON_BK_LOANED of datatype BOOKS_LOAN_LIST_T. Now that you have a PL/SQL variable containing the nested Table, you can use the usual collection processing commands within PL/SQL: 3 To find out how many rows are in the nested Table, use the COUNT method. In the preceding sample Procedure, the COUNT method appears in the LOOP command:
FOR V_COUNTER IN 1..V_PERSON_BK_LOANED.COUNT LOOP
3 Use a subscript to refer to an individual row in the nested Table. In the preceding Procedure, the V_COUNTER variable is used as a subscript within the LOOP. The following command in the Procedure assigns one row of the nested Table to a PL/SQL variable defined as datatype BOOK_LOANED_T:
V_BOOK_RESERVED := V_PERSON_BK_LOANED(V_COUNTER);
Now you have a final challenge: deciphering the BOOK_ID Column of this record. Because BOOK_ID in the nested Table row has a datatype of REF BOOKS_T, you must use the DEREF function to extract the corresponding row in the BOOK_TABLE_T. This sequence involves the following two steps: 3 Define a PL/SQL variable with the BOOK_T datatype. In the preceding Procedure, the variable is declared in the following manner:
V_BOOK BOOKS_T;
3 Use the DEREF command to populate the variable with a row from the referenced Table. The following command is found in the preceding Procedure. The DEREF command can only be used in the context of a SQL command, so the command is a SELECT command:
SELECT DEREF(V_REF_BOOK) INTO V_BOOK FROM DUAL;
654
Finally, display information about the book by extracting it from the variable (V_BOOK) containing the BOOK_TABLE_T record. The preceding Procedure displays the books BOOK_ID and BOOK_TITLE in the following command:
DBMS_OUTPUT.PUT_LINE(BOOK# ||V_BOOK.BOOK_ID || ||V_BOOK.BOOK_TITLE);
Tip
VARRAYs are handled like nested Tables in a PL/SQL block. The only difference: VARRAYs have a limited number of records.
655
FROM DUAL; IF TRUNC(SYSDATE) - 7 > V_BOOK_RESERVED.LOAN_DATE THEN /* --------- FINE IS 25 CENTS A DAY AFTER 7 DAYS ---- */ /* ---------- UPDATE ROW VARIABLE --- */ V_BOOK_RESERVED.FINE := .25 * (TRUNC(SYSDATE) TRUNC(V_BOOK_RESERVED.LOAN_DATE) - 7); /* ---------- UPDATE NESTED TABLE VARIABLE --- */ V_PERSON_BK_LOANED(V_COUNTER) := V_BOOK_RESERVED; DBMS_OUTPUT.PUT_LINE(FINE IS || V_BOOK_RESERVED.FINE); ELSE /* ------- BOOK IS NOT OVERDUE -------------- */ DBMS_OUTPUT.PUT_LINE(NOT A FINE! BOOK ID: || V_BOOK.BOOK_ID || LOAN_DATE = || V_BOOK_RESERVED.LOAN_DATE); END IF; END LOOP; /* ------- UPDATE PERSON_TABLE_T ROW WITH NESTED TABLE VARIABLE ---*/ UPDATE PERSON_TABLE_T SET PERSON_BK_LOANED = V_PERSON_BK_LOANED WHERE CURRENT OF C1; END LOOP; COMMIT; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(ERROR #|| SQLCODE || MSG: || SQLERRM); END; END;
To execute the Procedure, type the following command in SQL Worksheet or SQL*Plus:
EXECUTE UPDATE_FINES
656
The trick to this Procedure is extracting the data into ever-simpler variables until you are at a data manipulation level. Then you can rebuild the modified data back up to the complex datatype and update the whole record. The UPDATE command uses the usual SQL syntax. It can only be executed within a PL/SQL block because it needs a variable with an Object datatype, however. The nested Table variable is declared as follows:
V_PERSON_BK_LOANED BOOKS_LOAN_LIST_T;
The first sub_query must select the nested Table. The VALUES section or the second sub_query must contain Columns that match the Columns in one occurrence of the nested Table. The following PL/SQL Procedure loans a library book to a person. It checks to see if the person already has borrowed the book. If so, a message is sent. If not, the book is added to that persons list of loaned books.
/* -------- LOAN_A_BOOK.SQL --------------- */ SET SERVEROUTPUT ON CREATE OR REPLACE PROCEDURE LOAN_A_BOOK ( I_PERSON_ID IN NUMBER DEFAULT NULL, I_BOOK_ID IN INTEGER DEFAULT NULL ) AS BEGIN DECLARE ERROR_FOUND EXCEPTION; V_ERROR VARCHAR2(20); V_PERSON_BK_LOANED BOOKS_LOAN_LIST_T; V_ID NUMBER; V_BOOK_SUBSCRIPT NUMBER; V_BOOK_RESERVED BOOK_LOANED_T;
657
V_REF_BOOK REF BOOKS_T; V_BOOK BOOKS_T; BEGIN IF I_PERSON_ID IS NULL OR I_BOOK_ID IS NULL THEN RAISE ERROR_FOUND; END IF; SELECT PERSON_BK_LOANED INTO V_PERSON_BK_LOANED FROM PERSON_TABLE_T WHERE PERSON_ID = I_PERSON_ID; /* ------------ IS THE BOOK ALREADY CHECKED OUT? ------- */ FOR V_COUNTER IN 1..V_PERSON_BK_LOANED.COUNT LOOP V_BOOK_RESERVED := V_PERSON_BK_LOANED(V_COUNTER); V_REF_BOOK := V_BOOK_RESERVED.BOOK_ID; SELECT DEREF(V_REF_BOOK) INTO V_BOOK FROM DUAL; IF V_BOOK.BOOK_ID = I_BOOK_ID THEN /* ------------- BOOK ALREADY LOANED TO THIS PERSON ---- */ V_BOOK_SUBSCRIPT := V_COUNTER; END IF; END LOOP; IF V_BOOK_SUBSCRIPT IS NOT NULL THEN DBMS_OUTPUT.PUT_LINE(BOOK ALREADY LOANED. PERSON: || I_PERSON_ID || , BOOK: || I_BOOK_ID); ELSE /* ------------------- LOAN A BOOK ------- */ INSERT INTO THE( SELECT A.PERSON_BK_LOANED FROM PERSON_TABLE_T A WHERE A.PERSON_ID = I_PERSON_ID ) SELECT REF(C), SYSDATE, 0 FROM BOOK_TABLE_T C WHERE C.BOOK_ID = I_BOOK_ID; DBMS_OUTPUT.PUT_LINE(BOOK LOANED. PERSON: || I_PERSON_ID || , BOOK: || I_BOOK_ID); END IF; EXCEPTION WHEN ERROR_FOUND THEN DBMS_OUTPUT.PUT_LINE(MISSING PARAMETER. PLEASE ENTER PERSON_ID AND BOOK_ID); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(ERROR #|| SQLCODE || MSG: || SQLERRM); END; END;
658
Replace person_id with the actual PERSON_ID borrowing the book and replace book_id with the actual BOOK_ID to be loaned. The results follow:
BOOK LOANED. PERSON: 1, BOOK: 6 PL/SQL procedure successfully completed.
Replace nested_table_variable with the PL/SQL variable with a nested Table datatype. Replace subscript with an integer. For example, to delete the third row, replace subscript with 3. The following PL/SQL Procedure enables a person to return a book to the library. The Procedure performs this action by removing a row from the PERSON_BK_ LOANED Column. The Procedure reads one persons row from the PERSON_TABLE_T Object Table using the parameter I_PERSON_ID. The Procedure looks for the row that matches the BOOK_ID passed as the incoming parameter, I_BOOK_ID. The Procedure code follows:
/* ---------- RETURN_A_BOOK.SQL --------- */ SET SERVEROUTPUT ON CREATE OR REPLACE PROCEDURE RETURN_A_BOOK (I_PERSON_ID IN NUMBER, I_BOOK_ID IN NUMBER) AS BEGIN DECLARE
659
ERROR_FOUND EXCEPTION; V_ERROR VARCHAR2(20); V_REF_BOOK REF BOOKS_T; V_BOOK_RESERVED BOOK_LOANED_T; V_PERSON_BK_LOANED BOOKS_LOAN_LIST_T; V_ID NUMBER; V_BOOK BOOKS_T; V_DELETE_BOOK_SUBSCRIPT NUMBER; CURSOR C1 IS SELECT A.PERSON_ID, A.PERSON_BK_LOANED FROM PERSON_TABLE_T A WHERE PERSON_ID = I_PERSON_ID FOR UPDATE; BEGIN FOR C1REC IN C1 LOOP V_PERSON_BK_LOANED := C1REC.PERSON_BK_LOANED; DBMS_OUTPUT.PUT_LINE(PERSON_ID= || C1REC.PERSON_ID); /* ------------------- FIND THE BOOK ------- */ FOR V_COUNTER IN 1..V_PERSON_BK_LOANED.COUNT LOOP V_BOOK_RESERVED := V_PERSON_BK_LOANED(V_COUNTER); V_REF_BOOK := V_BOOK_RESERVED.BOOK_ID; SELECT DEREF(V_REF_BOOK) INTO V_BOOK FROM DUAL; IF V_BOOK.BOOK_ID = I_BOOK_ID THEN /* ---------- SAVE THE SUBSCRIPT FOR THIS BOOK ----*/ V_DELETE_BOOK_SUBSCRIPT := V_COUNTER; ELSE DBMS_OUTPUT.PUT_LINE(DID NOT REMOVE BOOK: || V_BOOK.BOOK_ID); END IF; END LOOP; IF V_DELETE_BOOK_SUBSCRIPT IS NULL THEN DBMS_OUTPUT.PUT_LINE(BOOK NOT ON LOAN); ELSE V_PERSON_BK_LOANED.DELETE(V_DELETE_BOOK_SUBSCRIPT); DBMS_OUTPUT.PUT_LINE(REMOVED BOOK: || V_BOOK.BOOK_ID || SUBSCRIPT: || V_DELETE_BOOK_SUBSCRIPT); UPDATE PERSON_TABLE_T SET PERSON_BK_LOANED = V_PERSON_BK_LOANED WHERE CURRENT OF C1; END IF; END LOOP; COMMIT; DBMS_OUTPUT.PUT_LINE( ************* DONE ******************* ); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(ERROR #|| SQLCODE || MSG: || SQLERRM); END; END;
660
Replace person_id and book_id with the person returning the book and the returned book, respectively. The results follow:
PERSON_ID=1 DID NOT REMOVE BOOK: 1 DID NOT REMOVE BOOK: 2 DID NOT REMOVE BOOK: 4 REMOVED BOOK:5 SUBSCRIPT: 4 ************* DONE ******************* PL/SQL procedure successfully completed.
The Procedure uses a multiple step process: 1. Read the PERSON_TABLE_T rows entire nested Table (PERSON_BK_LOANED) into a PL/SQL variable (V_PERSON_BK_LOANED). 2. Loop through all rows in the nested Table variable until you find the BOOK_ID to be removed. Remember the subscript of this row. 3. Delete the appropriate row from the nested Table variable. 4. Update the PERSON_TABLE_T rows nested Table (PERSON_BK_LOANED) with the nested Table variable (V_PERSON_BK_LOANED).
See Reference Section
Chapter 24 shows how to insert rows into Object Tables. The next section shows how to perform the same functions described in these sections (display, update, and delete records in a nested Table) using JDBC.
Chapter 25 3 Summary
661
The JDBC program shows all loaned books for one person. The program requires an input parameter: the PERSON_ID.
On the CD-ROM
The code for this JDBC program is on the CD-ROM that accompanies this book.
The code for this JDBC program is on the CD-ROM that accompanies this book.
The code for this JDBC program is on the CD-ROM that accompanies this book.
The code for this JDBC program is on the CD-ROM that accompanies this book.
Summary
Oracle8 Object Tables contain Columns that consist of User-defined datatypes. This chapter reviews the two Object Tables created and described in Chapter 24. The BOOK_TABLE_T Table contains rows of the datatype BOOKS_T. The BOOKS_T datatype includes regular Columns, Object type Columns, and a VARRAY Column. The PERSON_TABLE_T Table contains rows of the datatype PERSON_T. The PERSON_T datatype contains normal Columns (such as the PERSON_ID) and Object type Columns (such as PERSON_ADDRESS). The PERSON_T Object type also contains a VARRAY of telephone numbers and a nested Table of loaned books.
662
View all books in the Object Table BOOK_TABLE_T and PERSON_TABLE_T with SQL unless you need to list details contained in the VARRAY. To list the data in the nested Table in the PERSON_TABLE_T Object Table, you must use a PL/SQL block. The Procedure LIST_LOANED_BOOKS has variables with Userdefined datatypes that match those in the Object Table. After placing the entire nested Table into the PL/SQL variable, use regular collection processing (subscripts and loops) to retrieve and display data from the nested Table. A new extension to the SQL INSERT command enables you to insert a row into a nested Table without resorting to complex PL/SQL block. The extension, called THE, enables you to use a subquery in place of the Object Table name in the INSERT command. When deleting a row from a nested Table, you must use a PL/SQL block where you have declared a variable of the same Object type as the nested Table.