6. User-Defined Types#
In this chapter, the user-defined types that can be used with stored procedures and functions will be described.
Overview#
RECORD types and Associative Arrays, the user-defined types provided for use with store procedures, make it possible to organize data into logical units for processing. They can also be used as parameters or return values when stored procedures and functions call other stored procedures and functions. Note however that values that have user-defined types cannot be passed to clients.
RECORD Types#
A RECORD type is a user-defined type that consists of a set of columns. It can be used to configure data of different types into logical units for processing. For example, different data types corresponding to "Name", "Salary" and "Department" can be combined into a single data type called "Employee", which is easy to process. A RECORD type defined in a block is local in scope; that is, it is available only in the block in which the type is defined.
For information on defining RECORD types, please refer to "Defining a User-Defined Type" in Chapter 6. Aside from the difference in how they are declared, the use of a RECORD type variable declared using the %ROWTYPE keyword is the same as for other RECORD type variables.
Associative Arrays#
An Associative Array is similar to a hash table. An Associative Array is a set of key-value pairs. The keys are unique indexes that are used to locate the associated values with the syntax:
variable_name[index]
variable_name(index)
The data type of index can be either VARCHAR or INTEGER. It can be used to combine data items of the same type into a single data item for processing, regardless of the amount of data. For example, suppose that it is desired to process the data pertaining to employees having employee numbers from 1 to 100. These 100 data items can be processed using an Associative Array.
Refer to "Defining a User-Defined Type" in Chapter 6 for in-depth information on defining Associative Arrays.
Square brackets "[ ]" or parenthesis "()" are used to access the elements in an Associative Array variable, as shown below:
V1[ 1 ] := 1;
V2( 1 ) := 1;
REF CURSOR (Cursor Variable)#
A cursor variable is intended for use with dynamic SQL statements that are expected to return multiple records. A cursor variable is more flexible than a regular cursor (i.e. an explicit cursor) because it is not associated with a particular query. Cursor variables can be passed as parameters between stored procedures and functions, and can even be passed to clients.
The difference between a cursor variable and a regular cursor is that a cursor variable, while it is open, can refer to different queries, while a regular cursor can only refer to the query with which it was declared.
Defining a User-Defined Type#
Syntax#
type_name#
The name of the user-defined type is specified here.
associative_array_type_spec#
The associative_array_type_spec defines the type of Associative Array comprised of data_type. The basic data type is an integer if INDEX BY clause was omitted.
record_type_spec#
This defines a RECORD type that consists of multiple columns, each having its own sql_data_type. sql_data_type can be any data type that is available for use in SQL statements. Note that sql_data_type cannot be an Associative Array or another RECORD type.
ref_cursor_type_spec#
This clause defines RECORD type, which is comprised of data_type. Any data type applicable to SQL statements can be associated with data_type
Example#
Exampl 1#
Define a RECORD type called employee that consists of the name (VARCHAR(20)), department (INTEGER) and salary (NUMBER(8)) elements.
DECLARE
TYPE employee IS RECORD( name VARCHAR(20),
dept INTEGER,
salary NUMBER(8));
…
BEGIN
…
Example 2#
Define an Associative Array called "namelist" that uses the VARCHAR(20) type for its elements and has an INTEGER type index.
DECLARE
TYPE namelist IS TABLE OF VARCHAR(20)
INDEX BY INTEGER;
…
BEGIN
…
Example 3#
Define an Associative Array called employeelist that uses the employee user-defined record type for its elements and has a VARCHAR(10) type index.
DECLARE
TYPE employee IS RECORD( name VARCHAR(20),
dept INTEGER,
salary NUMBER(8));
TYPE employeelist IS TABLE OF employee
INDEX BY VARCHAR(10));
…
BEGIN
…
Functions for Use with Associative Arrays#
Syntax#
Purpose#
Various functions are provided for manipulating Associative Array elements. Unlike SQL functions, parentheses "()" cannot be omitted when using these functions.
COUNT#
This returns the number of elements in an Associative Array.
DELETE#
DELETE() removes all elements and returns the number of elements that were removed.
DELETE(n) removes the element whose index is n, and returns the number of element(s) that were removed, i.e. 0 or 1.
DELETE(m, n) removes all elements whose indexes are in the range from m to n inclusive and returns the number of the elements that were removed. Note that if the value of m is greater than the value of n, then no elements will be removed. If they are the same, then only that element will be removed.
EXISTS#
EXISTS(n) checks whether the element whose index is n exists. Returns the boolean value TRUE if it exists, or FALSE if it does not.
FIRST#
For an array indexed by integers, FIRST returns the smallest index number. For an array indexed by strings, FIRST returns the lowest key value. If the array does not contain any elements, it returns NULL.
LAST#
For an array indexed by integers, LAST returns the largest index number. For an array indexed by strings, LAST returns the highest key value. If the array does not contain any elements, it returns NULL.
NEXT#
NEXT(n) returns the index number that follows index n. For Associative Arrays with VARCHAR keys, NEXT returns the next key value. The binary values of the characters in the string determine the order. If there is no index at this position, it returns NULL.
PRIOR#
PRIOR(n) returns the index number that precedes index n. For Associative Arrays with VARCHAR keys, PRIOR returns the preceding key value. The binary values of the characters in the string determine the order. If there is no index at this position, it returns NULL.
Examples#
Example 1#
Delete elements from the Associative Array variable "V1".
CREATE OR REPLACE PROCEDURE PROC1(
P1 IN VARCHAR(10),
P2 IN VARCHAR(10) )
AS
TYPE MY_ARR IS TABLE OF INTEGER
INDEX BY VARCHAR(10);
V1 MY_ARR;
V2 INTEGER;
BEGIN
V1['FSDGADS'] := 1;
V1['AA'] := 2;
V1['7G65'] := 3;
V1['N887K'] := 4;
V1['KU'] := 5;
V1['34'] := 6;
PRINTLN( 'V1 COUNT IS : '||V1.COUNT() );
V2 := V1.DELETE(P1, P2);
PRINTLN( 'DELETED COUNT IS : '||V2);
PRINTLN( 'V1 COUNT IS : '||V1.COUNT() );
END;
/
The result of execution:
EXEC PROC1('005T34', 'BC35'); -- The elements whose indexes fall in this range are V1['34'], V1['7G65'] and V1['AA']. Three elements will be deleted.
V1 COUNT IS : 6
DELETED COUNT IS : 3
V1 COUNT IS : 3
Execute success.
Example 2#
Output the elements in the Associative Array variable "V1" in ascending and descending order.
CREATE OR REPLACE PROCEDURE PROC1
AS
TYPE MY_ARR1 IS TABLE OF INTEGER INDEX BY INTEGER;
V1 MY_ARR1;
V1_IDX INTEGER;
BEGIN
V1[435754] := 1;
V1[95464] := 2;
V1[38] := 3;
V1[57334] := 4;
V1[138] := 5;
V1[85462] := 6;
PRINTLN( 'ASCENDING ORDER V1');
V1_IDX := V1.FIRST();
LOOP
IF V1_IDX IS NULL
THEN
EXIT;
ELSE
PRINTLN( 'V1 IDX IS : '||V1_IDX||' VALUE IS : '||V1[V1_IDX] );
V1_IDX := V1.NEXT(V1_IDX);
END IF;
END LOOP;
PRINTLN( 'DESCENDING ORDER V1');
V1_IDX := V1.LAST();
LOOP
IF V1_IDX IS NULL
THEN
EXIT;
ELSE
PRINTLN( 'V1 IDX IS : '||V1_IDX||' VALUE IS : '||V1[V1_IDX] );
V1_IDX := V1.PRIOR(V1_IDX);
END IF;
END LOOP;
END;
/
The result of execution:
EXEC PROC1;
ASCENDING ORDER V1
V1 IDX IS : 38 VALUE IS : 3
V1 IDX IS : 138 VALUE IS : 5
V1 IDX IS : 57334 VALUE IS : 4
V1 IDX IS : 85462 VALUE IS : 6
V1 IDX IS : 95464 VALUE IS : 2
V1 IDX IS : 435754 VALUE IS : 1
DESCENDING ORDER V1
V1 IDX IS : 435754 VALUE IS : 1
V1 IDX IS : 95464 VALUE IS : 2
V1 IDX IS : 85462 VALUE IS : 6
V1 IDX IS : 57334 VALUE IS : 4
V1 IDX IS : 138 VALUE IS : 5
V1 IDX IS : 38 VALUE IS : 3
Execute success.
Using RECORD Type Variables and Associative Array Variables#
This section outlines the rules governing the use of user-defined types in stored procedures, with reference to examples. For information on using user-defined types as parameters and return values, please refer to Chapter7: Typesets.
Compatibility between User-Defined Types#
L_VALUE := R_VALUE;
The inter-compatibility between user-defined types when used in assignment statements such as that shown above is set forth in the following table:
Type of L_VALUE | Type of R_VALUE | Compatibility |
---|---|---|
RECROD Type | RECORD Type | Only RECORD type variables that are the same user-defined type (i.e. that have the same type name) are compatible. Two different record types are not inter-compatible even when they have the same internal structure. |
RECORD Type | %ROWTYPE | Compatible, as long as they comprise the same number and type of columns. |
%ROWTYPE | RECORD Type | Compatible, as long as they comprise the same number and type of columns. |
Associative Array | Associative Array | Only Associative Array variables that are the same user-defined type (i.e. that have the same type name) are compatible. |
In the following example, the last assignment statement fails even though the two user-defined types have the same internal structure.
Example 1#
Assigning values to RECORD type variables.
CREATE OR REPLACE PROCEDURE PROC1
AS
TYPE emp_rec_type1 IS RECORD (
name VARCHAR(20),
job_id VARCHAR(10),
salary NUMBER(8) );
TYPE emp_rec_type2 IS RECORD (
name VARCHAR(20),
job_id VARCHAR(10),
salary NUMBER(8) );
v_emp1 emp_rec_type1;
v_emp2 emp_rec_type2;
BEGIN
v_emp1.name := 'smith';
v_emp1.job_id := 'RND1069';
v_emp1.salary := '10000000';
v_emp2 := v_emp1; -- failed.
Even though the two variables have the same structure, the assignment operation fails because they refer to different user-defined types. However, assignment operations between individual elements whose types match, as shown below, will be successful:
v_emp2.name := v_emp1.name;
RECORD Type Variable Example#
Example 1#
Create a RECORD type for storing the name, salary, and department of employees.
iSQL> CREATE OR REPLACE PROCEDURE PROC1
AS
TYPE emp_rec_type IS RECORD (
name VARCHAR(20),
job_id VARCHAR(10),
salary NUMBER(8) );
v_emp emp_rec_type;
BEGIN
v_emp.name := 'smith';
v_emp.job_id := 'RND1069';
v_emp.salary := '10000000';
PRINTLN('NAME : '||v_emp.name||' '||
'JOB ID : '||v_emp.job_id||' '||
'SALARY : '||v_emp.salary );
END;
/
Associative Array Type Examples#
Example 1#
Output the last names of all employees whose ID numbers range from 1 to 20 inclusive.
iSQL> CREATE OR REPLACE PROCEDURE PROC1
AS
TYPE emp_array_type IS TABLE OF VARCHAR(20) INDEX BY INTEGER;
v_emp emp_array_type;
BEGIN
FOR I IN 1 .. 20 LOOP
SELECT e_lastname INTO v_emp[I] FROM employees WHERE eno = I;
END LOOP;
FOR I IN v_emp.FIRST() .. v_emp.LAST() LOOP
PRINTLN( v_emp[I] );
END LOOP;
END;
/
iSQL> EXEC PROC1;
Moon
Davenport
Kobain
Foster
Ghorbani
Momoi
Fleischer
Wang
Diaz
Bae
Liu
Hammond
Jones
Miura
Davenport
Chen
Fubuki
Huxley
Marquez
Blake
Execute success.
Example 2#
Output the name, salary and department of all employees whose ID numbers range from 1 to 20 inclusive.
iSQL> CREATE OR REPLACE PROCEDURE PROC1
AS
TYPE emp_rec_type IS RECORD (
first_name VARCHAR(20),
last_name VARCHAR(20),
emp_job VARCHAR(15),
salary NUMBER(8) );
TYPE emp_array_type IS TABLE OF emp_rec_type
INDEX BY INTEGER;
v_emp emp_array_type;
BEGIN
FOR I IN 1 .. 20 LOOP
SELECT e_firstname, e_lastname, emp_job, salary INTO v_emp[I]
FROM employees
WHERE eno = I;
END LOOP;
FOR I IN v_emp.FIRST() .. v_emp.LAST() LOOP
PRINTLN( v_emp[I].first_name||' '||
v_emp[I].last_name||' '||
v_emp[I].emp_job||' '||
v_emp[I].salary );
END LOOP;
END;
/
Create success.
iSQL> EXEC PROC1;
Chan-seung Moon CEO
Susan Davenport designer 1500
Ken Kobain engineer 2000
Aaron Foster PL 1800
Farhad Ghorbani PL 2500
Ryu Momoi programmer 1700
Gottlieb Fleischer manager 500
Xiong Wang manager
Curtis Diaz planner 1200
Elizabeth Bae programmer 4000
Zhen Liu webmaster 2750
Sandra Hammond sales rep 1890
Mitch Jones PM 980
Yuu Miura PM 2003
Jason Davenport webmaster 1000
Wei-Wei Chen manager 2300
Takahiro Fubuki PM 1400
John Huxley planner 1900
Alvar Marquez sales rep 1800
William Blake sales rep
Execute success.
Overlapping RECORD Type Variables#
Example#
Create a RECORD type storing the name of employees, and then create a overlapping RECORD type variable storing its type, department, and salary.
iSQL> CREATE OR REPLACE PROCEDURE PROC1
AS
TYPE emp_name_type IS RECORD ( first_name VARCHAR(20),last_name VARCHAR(20) );
TYPE emp_rec_type IS RECORD ( name emp_name_type,d_num NUMBER(8),salary NUMBER(8) );
TYPE emp_array_type IS TABLE OF emp_rec_type INDEX BY INTEGER;
v_emp emp_array_type;
v_emp_name emp_name_type;
BEGIN
FOR I IN 1 .. 10 LOOP
SELECT e_firstname, e_lastname INTO v_emp_name FROM employees WHERE eno = I;
SELECT dno, salary INTO v_emp[i].d_num, v_emp[I].salary FROM employees WHERE eno = I;
v_emp[I].name := v_emp_name;
END LOOP;
FOR I IN v_emp.FIRST() .. v_emp.LAST() LOOP
v_emp_name := v_emp[I].name;
PRINTLN( v_emp_name.first_name || ' ' ||
v_emp_name.last_name || ' ' ||
v_emp[I].d_num || ' ' ||
v_emp[I].salary );
END LOOP;
END;
/
Create success.
iSQL> EXEC PROC1;
Chan-seung Moon 3002
Susan Davenport 1500
Ken Kobain 1001 2000
Aaron Foster 3001 1800
Farhad Ghorbani 3002 2500
Ryu Momoi 1002 1700
Gottlieb Fleischer 4002 500
Xiong Wang 4001
Curtis Diaz 4001 1200
Elizabeth Bae 1003 4000
Execute success.
Multidimensional ASSOCIATIVE ARRAY Type Variables#
Example#
Create variables of multidimensional Associative Array type storing the customer name and order number.
iSQL> CREATE OR REPLACE PROCEDURE PROC1
AS
TYPE order_array_type IS TABLE OF INTEGER INDEX BY INTEGER;
TYPE customer_order_rec_type IS RECORD ( first_name VARCHAR(20), last_name VARCHAR(20), orders order_array_type );
TYPE customer_order_array_type IS TABLE OF customer_order_rec_type;
v_cust_order customer_order_array_type;
v_order_array NOCOPY order_array_type;
BEGIN
FOR I IN 1 .. 5 LOOP
v_order_array := v_cust_order[I].orders;
SELECT c_firstname, c_lastname INTO v_cust_order[I].first_name, v_cust_order[I].last_name FROM customers WHERE cno = I;
SELECT ono BULK COLLECT INTO v_order_array FROM orders WHERE cno = I;
END LOOP;
FOR i in 1 .. 5 LOOP
println ( v_cust_order[I].first_name || ' ' || v_cust_order[I].last_name );
v_order_array := v_cust_order[I].orders;
FOR J IN v_order_array.FIRST() .. v_order_array.LAST() LOOP
PRINTLN ( ' order no : ' || v_order_array[J] );
END LOOP;
END LOOP;
END;
/
Create success.
iSQL> EXEC PROC1;
Estevan Sanchez
order no : 12300001
order no : 12310008
order no : 12310012
Pierre Martin
order no : 12300002
order no : 12310006
Gabriel Morris
order no : 11290007
order no : 12300012
Soo-jung Park
order no : 12300005
James Stone
order no : 12100277
order no : 12310004
order no : 12310009
Execute success
REF CURSOR#
A stored procedure can pass a result set, resulting from execution of a SQL statement, to a client using a cursor variable (REF CURSOR).
Opening a cursor variable with the OPEN FOR statement and then passing the cursor to a client using an OUT parameter makes it possible for the client to access the result set. If multiple cursors are sent, the client can access multiple result sets. Except for the fact that the OPEN FOR statement is used to open a cursor variable, the use of cursor-related statements is the same as for regular cursors.
A cursor variable can only be passed as an OUT or IN/OUT parameter of a stored procedure. It cannot be returned with the RETURN statement.
In order for a client to be able to fetch a result set, a cursor variable must be open when it is passed from the stored procedure to the client. In other words, if the cursor is closed when it is passed, it will be impossible to fetch the result set.
When an UPDATE or INSERT statement is executed inside a stored procedure, the number of affected records (the affected row count) is not passed to the client.
The way that the client receives the result set using the cursor variable varies depending on the type of client. Using a cursor variable to pass the result set to a client is possible only in ODBC and JDBC. It is not possible in embedded SQL (Precompiler, APRE).
Examples#
Create a stored procedure that uses a REF CURSOR
-
Create emp and staff tables, and insert values into them.
CREATE TABLE EMP (ENO INTEGER, ENAME CHAR(20), DNO INTEGER); CREATE TABLE STAFF (NAME CHAR(20), DEPT CHAR(20), JOB CHAR(20), SALARY INTEGER); INSERT INTO EMP VALUES (10, 'DULGI PAPA', 100); INSERT INTO EMP VALUES (20, 'KUNHAN' , 200); INSERT INTO EMP VALUES (30, 'OKASA' , 300); INSERT INTO STAFF VALUES ('DULGI PAPA' , '100' , 'PAPA', 100); INSERT INTO STAFF VALUES ('SHINCHA' , '200' , 'ENGINEER' , 200); INSERT INTO STAFF VALUES ('JI HYUNG', '300', '', 0);
-
Create the user-defined type MY_CUR, which is a REF CURSOR, and create a typeset called MY_TYPE containing type MY_CUR
CREATE TYPESET MY_TYPE AS TYPE MY_CUR IS REF CURSOR; END; /
-
Create the stored procedure PROC1, which has two OUT parameters, P1 and P2, of type MY_CUR, and one IN parameter, SAL, of type INTEGER.
CREATE OR REPLACE PROCEDURE PROC1 (P1 OUT MY_TYPE.MY_CUR, P2 OUT MY_TYPE.MY_CUR, SAL IN INTEGER) AS SQL_STMT VARCHAR2(200); BEGIN SQL_STMT := 'SELECT NAME,DEPT,JOB FROM STAFF WHERE SALARY > ?'; OPEN P1 FOR 'SELECT ENO, ENAME, DNO FROM EMP'; OPEN P2 FOR SQL_STMT USING SAL; END; /
-
After connecting to the database, execute procedure PROC1.
SQLRETURN execute_proc() { SQLCHAR errMsg[MSG_LEN]; char sql[1000]; SQLHSTMT stmt = SQL_NULL_HSTMT; int sal; int sal_len; int eno; int eno_len; int dno; int dno_len; SQLCHAR ename[ENAME_LEN+1]; SQLCHAR name[NAME_LEN+1]; SQLCHAR dept[DEPT_LEN+1]; SQLCHAR job[JOB_LEN+1]; int job_ind; SQLRETURN rc = SQL_SUCCESS; if (SQL_ERROR == SQLAllocStmt(dbc, &stmt)) { printf("SQLAllocStmt error!!\n"); return SQL_ERROR; } /* Prepare SQL statements for execution */ sprintf(sql, "EXEC proc1(?)"); if ( SQLPrepare(stmt,(SQLCHAR *)sql,SQL_NTS) == SQL_ERROR ) { printf("ERROR: prepare stmt\n"); } else { printf("SUCCESS: prepare stmt\n"); } /* Specify sal as 100. */ sal = 100; /* Bind sal as a parameter into SQL statements. */ if ( SQLBindParameter( stmt, 1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &sal, 0, NULL) == SQL_ERROR ) { printf("ERROR: Bind Parameter\n"); } else { printf("SUCCESS: 1 Bind Parameter\n"); } /* Execute the SQL statements (execute procedure PROC1). The procedure passes the results of 'SELECT eno, ename, dno FROM emp' and those of 'SELECT name,dept,job FROM staff WHERE salary > ?'(USING SAL) using OUT parameters, p1 and p2, to the client. */ if (SQL_ERROR == SQLExecute(stmt)) { printf("ERROR: Execute Procedure\n"); } /* Store the results of 'SELECT eno, ename, dno FROM emp' in variables (eno, ename and dno). */ if (SQL_ERROR == SQLBindCol(stmt, 1, SQL_C_SLONG, &eno, 0, (long *)&eno_len)) { printf("ERROR: Bind 1 Column\n"); } if (SQL_ERROR == SQLBindCol(stmt, 2, SQL_C_CHAR, ename, sizeof(ename), NULL)) { printf("ERROR: Bind 2 Column\n"); } if (SQL_ERROR == SQLBindCol(stmt, 3, SQL_C_SLONG, &dno, 0, (long *)&dno_len)) { printf("ERROR: Bind 3 Column\n"); } /* Retrieve the results and then display them on the screen while they exist in P1. */ while (SQL_SUCCESS == rc) { rc = SQLFetch(stmt); if (SQL_SUCCESS == rc) { printf("Result Set 1 : %d,%s,%d\n" ,eno, ename, dno); } else { if (SQL_NO_DATA == rc) { break; } else { printf("ERROR: SQLFetch [%d]\n", rc); execute_err(dbc, stmt, sql); break; } } } /* Move to the next result (P2) */ rc = SQLMoreResults(stmt); if (SQL_ERROR == rc) { printf("ERROR: SQLMoreResults\n"); } else { /* Store the results of 'SELECT name,dept,job FROM staff WHERE salary > ?'(USING SAL) in variables(name, dept and job). */ if (SQL_ERROR == SQLBindCol(stmt, 1, SQL_C_CHAR, name, sizeof(name), NULL)) { printf("ERROR: Bind 1 Column\n"); } if (SQL_ERROR == SQLBindCol(stmt, 2, SQL_C_CHAR, dept, sizeof(dept), NULL)) { printf("ERROR: Bind 2 Column\n"); } if (SQL_ERROR == SQLBindCol(stmt, 3, SQL_C_CHAR, job, sizeof(job), (long *)&job_ind)) { printf("ERROR: Bind 3 Column\n"); } /* Retrieve the results and then display them on the screen while they exist in P2. */ while (SQL_SUCCESS == rc) { rc = SQLFetch(stmt); if (SQL_SUCCESS == rc) { if( job_ind == -1 ) printf("Result Set 2 : %s,%s,NULL\n" ,name, dept); else printf("Result Set 2 : %s,%s,%s\n" ,name, dept, job); } else { if (SQL_NO_DATA == rc) { break; } else { printf("ERROR: SQLFetch [%d]\n", rc); execute_err(dbc, stmt, sql); break; } } } } if (SQL_ERROR == SQLFreeStmt( stmt, SQL_DROP )) { printf("sql free stmt error\n"); } }