As you might expect, objects of type RECORD are called records. Records contain uniquely named fields, which can have different datatypes. Suppose you have various data about an employee such as name, salary, and hire date. These items are dissimilar in type but logically related. A record containing a field for each item lets you treat the data as a logical unit.
TYPE record_type_name IS RECORD (field[, field]...);
where record_type_name is a type specifier used in subsequent declarations of records and field stands for the following syntax:
field_name datatype [[NOT NULL] {:= | DEFAULT} expr]
You can use the attributes %TYPE and %ROWTYPE to specify field types. In the following example, you define a RECORD type named DeptRecTyp:
DECLARE TYPE DeptRecTyp IS RECORD ( deptno NUMBER(2), dname dept.dname%TYPE, loc dept.loc%TYPE);
Notice that the field declarations are like variable declarations. Each field has a unique name and specific datatype.
The next example shows that you can initialize a RECORD type. When you declare a record of type TimeTyp, its three fields assume an initial value of zero.
DECLARE TYPE TimeTyp IS RECORD ( seconds SMALLINT := 0, minutes SMALLINT := 0, hours SMALLINT := 0);
You can add the NOT NULL constraint to any field declaration and so prevent the assigning of nulls to that field. Fields declared as NOT NULL must be initialized.
DECLARE TYPE TimeTyp IS RECORD ( seconds SMALLINT, minutes SMALLINT, hours SMALLINT); TYPE MeetingTyp IS RECORD ( day DATE, time TimeTyp, -- nested record place VARCHAR2(20), purpose VARCHAR2(50));
DECLARE TYPE EmpRecTyp IS RECORD (emp_id INTEGER, salary REAL); ... FUNCTION nth_highest_salary (n INTEGER) RETURN EmpRecTyp IS ...
DECLARE TYPE EmpRecTyp IS RECORD ( emp_id NUMBER(4), emp_name CHAR(10), job_title CHAR(9) hire_date DATE)); emp_rec EmpRecTyp; -- declare user-defined record
The identifier emp_rec represents an entire record.
Like scalar variables, user-defined records can be declared as the formal parameters of procedures and functions. An example follows:
CREATE PACKAGE emp_actions AS TYPE EmpRecTyp IS RECORD ( emp_id NUMBER(4), last_name CHAR(10), job_title CHAR(14), ...); ... PROCEDURE hire_employee (emp_rec EmpRecTyp);
record_name.field_name
For example, you reference the field hire_date in the record emp_rec as follows:
emp_rec.hire_date ...
You can assign the value of an expression to a specific field using the following syntax:
record_name.field_name := expression;
In the next example, you convert an employee name to upper case:
emp_rec.ename := UPPER(emp_rec.ename);
Instead of assigning values separately to each field in a record, you can assign values to all fields at once. This can be done in two ways. First, you can assign one record to another if they have the same datatype, as the following example shows:
DECLARE TYPE DeptRecTyp IS RECORD(...); dept_rec1 DeptRecTyp; dept_rec2 DeptRecTyp; BEGIN ... dept_rec1 := dept_rec2; -- assign one record to another
Records that have different datatypes cannot be assigned to each other even if their fields match exactly.
Note: A user-defined record and a %ROWTYPE record always have different datatypes.
Second, you can use the SELECT or FETCH statement to fetch column values into a record, as the example below shows. The column names must appear in the same order as the fields in your record.
DECLARE TYPE DeptRecTyp IS RECORD( dept_no NUMBER(2), dept_name CHAR(14), location CHAR(13)); dept_rec DeptRecTyp; BEGIN SELECT deptno, dname, loc INTO dept_rec FROM dept WHERE ...
However, you cannot use the INSERT statement to insert user-defined records into a database table. So, the following statement is illegal:
INSERT INTO dept VALUES (dept_rec); -- illegal
Also, you cannot assign a list of values to a record using an assignment statement. Therefore, the following syntax is illegal:
record_name := (value1, value2, value3, ...); -- illegal
Finally, records cannot be tested for equality, inequality, or nullity. For instance, the following IF conditions are illegal:
IF dept_rec1 = dept_rec2 THEN ... -- illegal IF emp_rec IS NULL THEN ... -- illegal
DECLARE TYPE TimeTyp IS RECORD (minutes SMALLINT, hours SMALLINT); TYPE MeetingTyp IS RECORD ( day DATE, time TimeTyp, -- nested record room INTEGER(4), subject VARCHAR2(35)); TYPE PartyTyp IS RECORD ( day DATE, time TimeTyp, -- nested record place VARCHAR2(15)); meeting MeetingTyp; seminar MeetingTyp; party PartyTyp; ... BEGIN ... seminar.time := meeting.time; -- same parent type party.time := meeting.time; -- different parent types ... END;
function_name(parameters).field_name
For example, the following call to the function nth_highest_sal references the field salary in the user-defined record emp_rec:
DECLARE TYPE EmpRecTyp IS RECORD ( emp_id NUMBER(4), job_title CHAR(14), salary REAL); middle_sal REAL; FUNCTION nth_highest_sal (n INTEGER) RETURN EmpRecTyp IS emp_rec EmpRecTyp; BEGIN ... RETURN emp_rec; -- return user-defined record END; BEGIN ... middle_sal := nth_highest_sal(10).salary; -- call function
To reference nested fields in a record returned by a function, you use the following syntax:
function_name(parameters).field_name.nested_field_name
For example, the following call to the function calendar_item references the nested field hours in the user-defined record meeting:
DECLARE TYPE TimeTyp IS RECORD (minutes SMALLINT, hours SMALLINT); TYPE MeetingTyp IS RECORD ( day DATE, duration TimeTyp, -- nested record room INTEGER(4), subject VARCHAR2(35)); ... FUNCTION calendar_item (priority INTEGER) RETURN MeetingTyp IS meeting MeetingTyp; BEGIN ... RETURN meeting; -- return user-defined record END; BEGIN ... IF calendar_item(3).duration.hours > 2 THEN ...
Restriction Currently, you cannot use the syntax above to call a parameterless function because PL/SQL does not allow empty parameter lists. That is, the following syntax is illegal:
function_name().field_name -- illegal; empty parameter list
You cannot just drop the empty parameter list because the following syntax is also illegal:
function_name.field_name -- illegal; no parameter list
Instead, declare a local user-defined record to which you can assign the function result, then reference its fields directly, as shown in the following example:
DECLARE TYPE EmpRecTyp IS RECORD (..., salary REAL); emp_rec EmpRecTyp; -- declare record median REAL; FUNCTION median_sal RETURN EmpRecTyp IS ... BEGIN ... emp_rec := median_sal; -- assign function result median := emp_rec.salary; -- reference field
DECLARE TYPE FiguresTyp IS RECORD (cash REAL, notes REAL, ...); sub1_figs FiguresTyp; sub2_figs FiguresTyp; ... FUNCTION acid_test (figs FiguresTyp) RETURN REAL IS ... BEGIN SELECT cash, notes, ... INTO sub1_figs FROM assets, liabilities WHERE assets.sub = 1 AND liabilities.sub = 1; SELECT cash, notes, ... INTO sub2_figs FROM assets, liabilities WHERE assets.sub = 2 AND liabilities.sub = 2; IF acid_test(sub1_figs) > acid_test(sub2_figs) THEN ... ... END;
Notice how easy it is to pass the collected figures to the function acid_test, which computes a financial ratio.