Oracle® Database Object-Relational Developer's Guide 11g Release 1 (11.1) Part Number B28371-01 |
|
|
View PDF |
This chapter describes how to use object types with PL/SQL
This chapter contains these topics:
Using object types in a PL/SQL block, subprogram, or package is a two-step process.
You must define object types using the SQL statement CREATE TYPE
, in SQL*Plus or other similar programs.
For information on the CREATE
TYPE
and CREATE
TYPE
BODY
SQL statements, see Oracle Database SQL Language Reference.
After an object type is defined and installed in the schema, you can use it in any PL/SQL block, subprogram, or package.
In PL/SQL, you then declare a variable whose data type is the ADT that you just defined.
Objects or ADTs follow the usual scope and instantiation rules.
Example 4-1 shows how to create an object type, object body type, and a table of object types. Then, the next example shows how to declare a variable of that data type in PL/SQL.
Example 4-1 Working With Object Types
CREATE TYPE address_typ AS OBJECT ( street VARCHAR2(30), city VARCHAR2(20), state CHAR(2), postal_code VARCHAR2(6) ); / CREATE TYPE employee_typ AS OBJECT ( employee_id NUMBER(6), first_name VARCHAR2(20), last_name VARCHAR2(25), email VARCHAR2(25), phone_number VARCHAR2(20), hire_date DATE, job_id VARCHAR2(10), salary NUMBER(8,2), commission_pct NUMBER(2,2), manager_id NUMBER(6), department_id NUMBER(4), address address_typ, MAP MEMBER FUNCTION get_idno RETURN NUMBER, MEMBER PROCEDURE display_address ( SELF IN OUT NOCOPY employee_typ ) ); / CREATE TYPE BODY employee_typ AS MAP MEMBER FUNCTION get_idno RETURN NUMBER IS BEGIN RETURN employee_id; END; MEMBER PROCEDURE display_address ( SELF IN OUT NOCOPY employee_typ ) IS BEGIN DBMS_OUTPUT.PUT_LINE(first_name || ' ' || last_name); DBMS_OUTPUT.PUT_LINE(address.street); DBMS_OUTPUT.PUT_LINE(address.city || ', ' || address.state || ' ' || address.postal_code); END; END; / CREATE TABLE employee_tab OF employee_typ;
You can use objects or ADTs wherever built-in types such as CHAR
or NUMBER
can be used. In Example 4-2, you declare object emp
of type employee_typ
. Then, you call the constructor for object type employee_typ
to initialize the object.
Example 4-2 Declaring Objects in a PL/SQL Block
DECLARE emp employee_typ; -- emp is atomically null BEGIN -- call the constructor for employee_typ emp := employee_typ(315, 'Francis', 'Logan', 'FLOGAN', '555.777.2222', '01-MAY-04', 'SA_MAN', 11000, .15, 101, 110, address_typ('376 Mission', 'San Francisco', 'CA', '94222')); DBMS_OUTPUT.PUT_LINE(emp.first_name || ' ' || emp.last_name); -- display details emp.display_address(); -- call object method to display details END; /
The formal parameter of a PL/SQL subprogram may have data type of ADT. Therefore, you can pass objects to stored subprograms and from one subprogram to another. In the next example, you use object type employee_typ
to specify the datatype of a formal parameter:
PROCEDURE open_acct (new_acct IN OUT employee_typ) IS ...
In the following example, you use object type employee_typ
to specify the return type of a function:
FUNCTION get_acct (acct_id IN NUMBER) RETURN employee_typ IS ...
ADTs, just like collections are atomically null, until you initialize the object by calling the constructor for its object type. That is, the object itself is null, not just its attributes.
Comparing a null object with any other object always yields NULL
. Also, if you assign an atomically null object to another object, the other object becomes atomically null (and must be reinitialized). Likewise, if you assign the non-value NULL
to an object, the object becomes atomically null.
In an expression, attributes of an uninitialized object evaluate to NULL
. When applied to an uninitialized object or its attributes, the IS
NULL
comparison operator yields TRUE
.
Example 4-3 illustrates null objects and objects with null attributes.
Example 4-3 Null Objects in a PL/SQL Block
DECLARE emp employee_typ; -- emp is atomically null BEGIN IF emp IS NULL THEN DBMS_OUTPUT.PUT_LINE('emp is NULL #1'); END IF; IF emp.employee_id IS NULL THEN DBMS_OUTPUT.PUT_LINE('emp.employee_id is NULL #1'); END IF; emp.employee_id := 330; IF emp IS NULL THEN DBMS_OUTPUT.PUT_LINE('emp is NULL #2'); END IF; IF emp.employee_id IS NULL THEN DBMS_OUTPUT.PUT_LINE('emp.employee_id is NULL #2'); END IF; emp := employee_typ(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, address_typ(NULL, NULL, NULL, NULL)); -- emp := NULL; -- this would have made the following IF statement TRUE IF emp IS NULL THEN DBMS_OUTPUT.PUT_LINE('emp is NULL #3'); END IF; IF emp.employee_id IS NULL THEN DBMS_OUTPUT.PUT_LINE('emp.employee_id is NULL #3'); END IF; EXCEPTION WHEN ACCESS_INTO_NULL THEN DBMS_OUTPUT.PUT_LINE('Cannot assign value to NULL object'); END; /
The output is:
emp is NULL #1
emp.employee_id is NULL #1
emp is NULL #2
emp.employee_id is NULL #3
Calls to methods of an uninitialized object raise the predefined exception NULL_SELF_DISPATCH
. When passed as arguments to IN
parameters, attributes of an uninitialized object evaluate to NULL
. When passed as arguments to OUT
or IN
OUT
parameters, they raise an exception if you try to write to them.
This section describes how to manipulate object attributes and methods in PL/SQL.
You refer to an attribute by name. To access or change the value of an attribute, you use dot notation. Attribute names can be chained, which lets you access the attributes of a nested object type. For example:
Example 4-4 Accessing Object Attributes
DECLARE emp employee_typ; BEGIN emp := employee_typ(315, 'Francis', 'Logan', 'FLOGAN', '555.777.2222', '01-MAY-04', 'SA_MAN', 11000, .15, 101, 110, address_typ('376 Mission', 'San Francisco', 'CA', '94222')); DBMS_OUTPUT.PUT_LINE(emp.first_name || ' ' || emp.last_name); DBMS_OUTPUT.PUT_LINE(emp.address.street); DBMS_OUTPUT.PUT_LINE(emp.address.city || ', ' ||emp. address.state || ' ' || emp.address.postal_code); END; /
Calls to a constructor are allowed wherever function calls are allowed. Like all functions, a constructor is called as part of an expression, as shown in Example 4-4 and Example 4-5.
Example 4-5 Inserting Rows in an Object Table
DECLARE emp employee_typ; BEGIN INSERT INTO employee_tab VALUES (employee_typ(310, 'Evers', 'Boston', 'EBOSTON', '555.111.2222', '01-AUG-04', 'SA_REP', 9000, .15, 101, 110, address_typ('123 Main', 'San Francisco', 'CA', '94111')) ); INSERT INTO employee_tab VALUES (employee_typ(320, 'Martha', 'Dunn', 'MDUNN', '555.111.3333', '30-SEP-04', 'AC_MGR', 12500, 0, 101, 110, address_typ('123 Broadway', 'Redwood City', 'CA', '94065')) ); END; /
When you pass parameters to a constructor, the call assigns initial values to the attributes of the object being instantiated. When you call the default constructor to fill in all attribute values, you must supply a parameter for every attribute; unlike constants and variables, attributes cannot have default values. You can call a constructor using named notation instead of positional notation.
Like packaged subprograms, methods are called using dot notation. In Example 4-6, the display_address
method is called to display attributes of an object. Note the use of the VALUE
function which returns the value of an object. VALUE
takes as its argument a correlation variable. In this context, a correlation variable is a row variable or table alias associated with a row in an object table.
Example 4-6 Accessing Object Methods
DECLARE emp employee_typ; BEGIN SELECT VALUE(e) INTO emp FROM employee_tab e WHERE e.employee_id = 310; emp.display_address(); END; /
In SQL statements, calls to a parameterless method require an empty parameter list. In procedural statements, an empty parameter list is optional unless you chain calls, in which case it is required for all but the last call. You cannot chain additional method calls to the right of a procedure call because a procedure is called as a statement, not as part of an expression. Also, if you chain two function calls, the first function must return an object that can be passed to the second function.
For static methods, calls use the notation type_name
.
method_name
rather than specifying an instance of the type.
When you call a method using an instance of a subtype, the actual method that is executed depends on the exact declarations in the type hierarchy. If the subtype overrides the method that it inherits from its supertype, the call uses the subtype's implementation. Or, if the subtype does not override the method, the call uses the supertype's implementation. This capability is known as dynamic method dispatch.
From inside a PL/SQL block you can modify and delete rows in an object table.
Example 4-7 Updating and Deleting Rows in an Object Table
DECLARE emp employee_typ; BEGIN INSERT INTO employee_tab VALUES (employee_typ(370, 'Robert', 'Myers', 'RMYERS', '555.111.2277', '07-NOV-04', 'SA_REP', 8800, .12, 101, 110, address_typ('540 Fillmore', 'San Francisco', 'CA', '94011')) ); UPDATE employee_tab e SET e.address.street = '1040 California' WHERE e.employee_id = 370; DELETE FROM employee_tab e WHERE e.employee_id = 310; END; /
You can retrieve refs using the function REF
, which takes as its argument a correlation variable.
Example 4-8 Updating Rows in an Object Table With a REF Modifier
DECLARE emp employee_typ; emp_ref REF employee_typ; BEGIN SELECT REF(e) INTO emp_ref FROM employee_tab e WHERE e.employee_id = 370; UPDATE employee_tab e SET e.address = address_typ('8701 College', 'Oakland', 'CA', '94321') WHERE REF(e) = emp_ref; END; /
You can declare refs as variables, parameters, fields, or attributes. You can use refs as input or output variables in SQL data manipulation statements.
You cannot navigate through refs in PLSQL. For example, the assignment in Example 4-9 using a ref is not allowed. Instead, use the function DEREF
or make calls to the package UTL_REF
to access the object. For information on the REF
function, see Oracle Database SQL Language Reference.
Example 4-9 Using DEREF in a SELECT INTO Statement
DECLARE emp employee_typ; emp_ref REF employee_typ; emp_name VARCHAR2(50); BEGIN SELECT REF(e) INTO emp_ref FROM employee_tab e WHERE e.employee_id = 370; -- the following assignment raises an error, not allowed in PL/SQL -- emp_name := emp_ref.first_name || ' ' || emp_ref.last_name; -- emp := DEREF(emp_ref); not allowed, cannot use DEREF in procedural statements SELECT DEREF(emp_ref) INTO emp FROM DUAL; -- use dummy table DUAL emp_name := emp.first_name || ' ' || emp.last_name; DBMS_OUTPUT.PUT_LINE(emp_name); END; /
For detailed information on the DEREF
function, see Oracle Database SQL Language Reference.
To store nested tables and varrays inside database tables, you must also declare SQL types using the CREATE TYPE
statement. The SQL types can be used as columns or as attributes of SQL object types.
For more information on object types, see "Object Types".
You can declare equivalent types within PL/SQL, or use the SQL type name in a PL/SQL variable declaration.
Example 4-10 shows how you might declare a nested table in SQL, and use it as an attribute of an object type.
Example 4-10 Declaring a Nested Table in SQL
CREATE TYPE CourseList AS TABLE OF VARCHAR2(10) -- define type / CREATE TYPE student AS OBJECT ( -- create object id_num INTEGER(4), name VARCHAR2(25), address VARCHAR2(35), status CHAR(2), courses CourseList); -- declare nested table as attribute / CREATE TABLE sophomores of student NESTED TABLE courses STORE AS courses_nt;
The identifier courses
represents an entire nested table. Each element of courses
stores the name of a college course such as 'Math 1020'
.
Example 4-11 creates a database column that stores varrays. Each varray element contains a VARCHAR2
.
Example 4-11 Creating a Table with a Varray Column
-- Each project has a 16-character code name. -- We will store up to 50 projects at a time in a database column. CREATE TYPE ProjectList AS VARRAY(50) OF VARCHAR2(16); / CREATE TABLE dept_projects ( -- create database table dept_id NUMBER(2), name VARCHAR2(15), budget NUMBER(11,2), -- Each department can have up to 50 projects. projects ProjectList);
In Example 4-12, you insert a row into database table dept_projects
. The varray constructor ProjectList()
provides a value for column projects
.
Example 4-12 Varray Constructor Within a SQL Statement
BEGIN INSERT INTO dept_projects VALUES(60, 'Security', 750400, ProjectList('New Badges', 'Track Computers', 'Check Exits')); END; /
In Example 4-13, you insert several scalar values and a CourseList
nested table into the sophomores
table.
Example 4-13 Nested Table Constructor Within a SQL Statement
CREATE TABLE sophomores of student NESTED TABLE courses STORE AS courses_nt; BEGIN INSERT INTO sophomores VALUES (5035, 'Janet Alvarez', '122 Broad St', 'FT', CourseList('Econ 2010', 'Acct 3401', 'Mgmt 3100')); END; /
By default, SQL operations store and retrieve whole collections rather than individual elements. To manipulate the individual elements of a collection with SQL, use the TABLE
operator. The TABLE
operator uses a subquery to extract the varray or nested table, so that the INSERT
, UPDATE
, or DELETE
statement applies to the nested table rather than the top-level table.
To perform DML operations on a PL/SQL nested table, use the operators TABLE
and CAST
. This way, you can do set operations on nested tables using SQL notation, without actually storing the nested tables in the database.
The operands of CAST
are PL/SQL collection variable and a SQL collection type (created by the CREATE TYPE
statement). CAST
converts the PL/SQL collection to the SQL type.
Example 4-14 Performing Operations on PL/SQL Nested Tables With CAST
CREATE TYPE Course AS OBJECT (course_no NUMBER, title VARCHAR2(64), credits NUMBER); / CREATE TYPE CourseList AS TABLE OF course; / -- create department table CREATE TABLE department ( name VARCHAR2(20), director VARCHAR2(20), office VARCHAR2(20), courses CourseList) NESTED TABLE courses STORE AS courses_tab; INSERT INTO department VALUES ('English', 'June Johnson', '491C', CourseList(Course(1002, 'Expository Writing', 4), Course(2020, 'Film and Literature', 4), Course(4210, '20th-Century Poetry', 4), Course(4725, 'Advanced Workshop in Poetry', 4))); DECLARE revised CourseList := CourseList(Course(1002, 'Expository Writing', 3), Course(2020, 'Film and Literature', 4), Course(4210, '20th-Century Poetry', 4), Course(4725, 'Advanced Workshop in Poetry', 5)); num_changed INTEGER; BEGIN SELECT COUNT(*) INTO num_changed FROM TABLE(CAST(revised AS CourseList)) new, TABLE(SELECT courses FROM department WHERE name = 'English') old WHERE new.course_no = old.course_no AND (new.title != old.title OR new.credits != old.credits); DBMS_OUTPUT.PUT_LINE(num_changed); END; /
Collections let you manipulate complex datatypes within PL/SQL. Your program can compute subscripts to process specific elements in memory, and use SQL to store the results in database tables.
In SQL*Plus, you can create SQL object types whose definitions correspond to PL/SQL nested tables and varrays, as shown in Example 4-15. Each item in column dept_names
is a nested table that will store the department names for a specific region. The NESTED
TABLE
clause is required whenever a database table has a nested table column. The clause identifies the nested table and names a system-generated store table, in which Oracle stores the nested table data.
Within PL/SQL, you can manipulate the nested table by looping through its elements, using methods such as TRIM
or EXTEND
, and updating some or all of the elements. Afterwards, you can store the updated table in the database again. You can insert table rows containing nested tables, update rows to replace its nested table, and select nested tables into PL/SQL variables. You cannot update or delete individual nested table elements directly with SQL; you have to select the nested table from the table, change it in PL/SQL, then update the table to include the new nested table.
Example 4-15 Using INSERT, UPDATE, DELETE, and SELECT Statements With Nested Tables
CREATE TYPE dnames_tab AS TABLE OF VARCHAR2(30); / CREATE TABLE depts (region VARCHAR2(25), dept_names dnames_tab) NESTED TABLE dept_names STORE AS dnames_nt; BEGIN INSERT INTO depts VALUES('Europe', dnames_tab('Shipping','Sales','Finance')); INSERT INTO depts VALUES('Americas', dnames_tab('Sales','Finance','Shipping')); INSERT INTO depts VALUES('Asia', dnames_tab('Finance','Payroll')); COMMIT; END; / DECLARE -- Type declaration is not needed, because PL/SQL can access the SQL object type -- TYPE dnames_tab IS TABLE OF VARCHAR2(30); not needed -- Declare a variable that can hold a set of department names v_dnames dnames_tab; -- Declare a record that can hold a row from the table -- One of the record fields is a set of department names v_depts depts%ROWTYPE; new_dnames dnames_tab; BEGIN -- Look up a region and query just the associated department names SELECT dept_names INTO v_dnames FROM depts WHERE region = 'Europe'; FOR i IN v_dnames.FIRST .. v_dnames.LAST LOOP DBMS_OUTPUT.PUT_LINE('Department names: ' || v_dnames(i)); END LOOP; -- Look up a region and query the entire row SELECT * INTO v_depts FROM depts WHERE region = 'Asia'; -- Now dept_names is a field in a record, so we access it with dot notation FOR i IN v_depts.dept_names.FIRST .. v_depts.dept_names.LAST LOOP -- Because we have all the table columns in the record, we can refer to region DBMS_OUTPUT.PUT_LINE(v_depts.region || ' dept_names = ' || v_depts.dept_names(i)); END LOOP; -- We can replace a set of department names with a new collection -- in an UPDATE statement new_dnames := dnames_tab('Sales','Payroll','Shipping'); UPDATE depts SET dept_names = new_dnames WHERE region = 'Europe'; -- Or we can modify the original collection and use it in the UPDATE. -- We'll add a new final element and fill in a value v_depts.dept_names.EXTEND(1); v_depts.dept_names(v_depts.dept_names.COUNT) := 'Finance'; UPDATE depts SET dept_names = v_depts.dept_names WHERE region = v_depts.region; -- We can even treat the nested table column like a real table and -- insert, update, or delete elements. The TABLE operator makes the statement -- apply to the nested table produced by the subquery. INSERT INTO TABLE(SELECT dept_names FROM depts WHERE region = 'Asia') VALUES('Sales'); DELETE FROM TABLE(SELECT dept_names FROM depts WHERE region = 'Asia') WHERE column_value = 'Payroll'; UPDATE TABLE(SELECT dept_names FROM depts WHERE region = 'Americas') SET column_value = 'Payroll' WHERE column_value = 'Finance'; COMMIT; END; /
Example 4-16 shows how you can manipulate SQL varray object types with PL/SQL statements. In this example, varrays are transferred between PL/SQL variables and SQL tables. You can insert table rows containing varrays, update a row to replace its varray, and select varrays into PL/SQL variables. You cannot update or delete individual varray elements directly with SQL; you have to select the varray from the table, change it in PL/SQL, then update the table to include the new varray.
Example 4-16 Using INSERT, UPDATE, DELETE, and SELECT Statements With Varrays
-- By using a varray, we put an upper limit on the number of elements -- and ensure they always come back in the same order CREATE TYPE dnames_var IS VARRAY(7) OF VARCHAR2(30); / CREATE TABLE depts (region VARCHAR2(25), dept_names dnames_var); BEGIN INSERT INTO depts VALUES('Europe', dnames_var('Shipping','Sales','Finance')); INSERT INTO depts VALUES('Americas', dnames_var('Sales','Finance','Shipping')); INSERT INTO depts VALUES('Asia', dnames_var('Finance','Payroll','Shipping','Sales')); COMMIT; END; / DECLARE new_dnames dnames_var := dnames_var('Benefits', 'Advertising', 'Contracting', 'Executive', 'Marketing'); some_dnames dnames_var; BEGIN UPDATE depts SET dept_names = new_dnames WHERE region = 'Europe'; COMMIT; SELECT dept_names INTO some_dnames FROM depts WHERE region = 'Europe'; FOR i IN some_dnames.FIRST .. some_dnames.LAST LOOP DBMS_OUTPUT.PUT_LINE('dept_names = ' || some_dnames(i)); END LOOP; END; /
In Example 4-17, PL/SQL BULK
COLLECT
is used with a multilevel collection that includes an object type.
Example 4-17 Using BULK COLLECT with Nested Tables
CREATE TYPE dnames_var IS VARRAY(7) OF VARCHAR2(30); / CREATE TABLE depts (region VARCHAR2(25), dept_names dnames_var); BEGIN INSERT INTO depts VALUES('Europe', dnames_var('Shipping','Sales','Finance')); INSERT INTO depts VALUES('Americas', dnames_var('Sales','Finance','Shipping')); INSERT INTO depts VALUES('Asia', dnames_var('Finance','Payroll','Shipping','Sales')); COMMIT; END; / DECLARE TYPE dnames_tab IS TABLE OF dnames_var; v_depts dnames_tab; BEGIN SELECT dept_names BULK COLLECT INTO v_depts FROM depts; DBMS_OUTPUT.PUT_LINE(v_depts.COUNT); -- prints 3 END; /
Example 4-18 illustrates the use of objects and collections with dynamic SQL. First, define object type person_typ
and VARRAY
type hobbies_var
, then write a package that uses these types.
Example 4-18 TEAMS Package Using Dynamic SQL for Object Types and Collections
CREATE TYPE person_typ AS OBJECT (name VARCHAR2(25), age NUMBER); / CREATE TYPE hobbies_var AS VARRAY(10) OF VARCHAR2(25); / CREATE OR REPLACE PACKAGE teams AUTHID CURRENT_USER AS PROCEDURE create_table (tab_name VARCHAR2); PROCEDURE insert_row (tab_name VARCHAR2, p person_typ, h hobbies_var); PROCEDURE print_table (tab_name VARCHAR2); END; / CREATE OR REPLACE PACKAGE BODY teams AS PROCEDURE create_table (tab_name VARCHAR2) IS BEGIN EXECUTE IMMEDIATE 'CREATE TABLE ' || tab_name || ' (pers person_typ, hobbs hobbies_var)'; END; PROCEDURE insert_row ( tab_name VARCHAR2, p person_typ, h hobbies_var) IS BEGIN EXECUTE IMMEDIATE 'INSERT INTO ' || tab_name || ' VALUES (:1, :2)' USING p, h; END; PROCEDURE print_table (tab_name VARCHAR2) IS TYPE refcurtyp IS REF CURSOR; v_cur refcurtyp; p person_typ; h hobbies_var; BEGIN OPEN v_cur FOR 'SELECT pers, hobbs FROM ' || tab_name; LOOP FETCH v_cur INTO p, h; EXIT WHEN v_cur%NOTFOUND; -- print attributes of 'p' and elements of 'h' DBMS_OUTPUT.PUT_LINE('Name: ' || p.name || ' - Age: ' || p.age); FOR i IN h.FIRST..h.LAST LOOP DBMS_OUTPUT.PUT_LINE('Hobby(' || i || '): ' || h(i)); END LOOP; END LOOP; CLOSE v_cur; END; END; /
From an anonymous block, you might call the procedures in package TEAMS
:
Example 4-19 Calling Procedures from the TEAMS Package
DECLARE team_name VARCHAR2(15); BEGIN team_name := 'Notables'; TEAMS.create_table(team_name); TEAMS.insert_row(team_name, person_typ('John', 31), hobbies_var('skiing', 'coin collecting', 'tennis')); TEAMS.insert_row(team_name, person_typ('Mary', 28), hobbies_var('golf', 'quilting', 'rock climbing', 'fencing')); TEAMS.print_table(team_name); END; /