PL/SQL User Defined types - Record and Table

RECORD and TABLE are user-defined composite types that can be defined by the programmer. The record type allows the defination of a type that consists of specified fields. Once the type has been defined, you can then define a specific name for the type as it is being used, a variable name that references the type. The following PL/SQL coding examples show some uses of TYPE IS RECORD. In the examples, a_rec_donor is the variable dame for the type rec_donor which is defined as having three fields: rec_id, rec_name and rec_yrgoal. Notice in the first I name the fields in the INTO of the SELECT and in the second example, I use the variable name for the record type in the INTO.

SQL CODE:

SQL> edit atsd1

PL/SQL CODE:

SET VERIFY OFF
ACCEPT in_idno PROMPT 'Please enter the donor idno: '
DECLARE
   TYPE rec_donor IS RECORD
     (rec_id VARCHAR2(5), rec_name VARCHAR2(15), rec_yrgoal NUMBER(7,2));
   a_rec_donor rec_donor; -- Note: this declares a variable of the defined type
BEGIN
   SELECT idno, name, yrgoal INTO a_rec_donor.rec_id, a_rec_donor.rec_name, a_rec_donor.rec_yrgoal
   FROM new_donor WHERE idno = &in_idno;
   INSERT INTO some_donor
   VALUES(a_rec_donor.rec_id, a_rec_donor.rec_name, a_rec_donor.rec_yrgoal);
END;
/
SET VERIFY ON

SQL CODE:

SQL> SELECT * FROM new_donor;

IDNO  NAME            STADR           CITY       ST ZIP   DATEFST      YRGOAL CONTACT
----- --------------- --------------- ---------- -- ----- --------- --------- ------------
11111 Stephen Daniels 123 Elm St      Seekonk    MA 02345 03-JUL-98       500 John Smith
12121 Jennifer Ames   24 Benefit St   Providence RI 02045 24-MAY-97       400 Susan Jones
22222 Carl Hersey     24 Benefit St   Providence RI 02045 03-JAN-98           Susan Jones
23456 Susan Ash       21 Main St      Fall River MA 02720 04-MAR-92       100 Amy Costa
33333 Nancy Taylor    26 Oak St       Fall River MA 02720 04-MAR-92        50 John Adams
34567 Robert Brooks   36 Pine St      Fall River MA 02720 04-APR-98        50 Amy Costa

6 rows selected.

SQL> SELECT * FROM some_donor;

no rows selected

SQL> @ atsd1
Please enter the donor idno: 23456

PL/SQL procedure successfully completed.

Input truncated to 13 characters
SQL> SELECT * FROM some_donor;

IDNO  NAME               YRGOAL
----- --------------- ---------
23456 Susan Ash             100

SQL> edit atsd2

PL/SQL CODE:

SET VERIFY OFF
ACCEPT in_idno PROMPT 'Please enter the donor idno: '
DECLARE
   TYPE rec_donor IS RECORD
     (rec_id VARCHAR2(5), rec_name VARCHAR2(15), rec_yrgoal NUMBER(7,2));
   a_rec_donor rec_donor; -- Note: this declares a variable of the defined type
BEGIN
   SELECT idno, name, yrgoal INTO a_rec_donor
   FROM new_donor WHERE idno = &in_idno;
   INSERT INTO some_donor
   VALUES(a_rec_donor.rec_id, a_rec_donor.rec_name, a_rec_donor.rec_yrgoal);
END;
/
SET VERIFY ON

SQL> @ atsd2
Please enter the donor idno: 11111

PL/SQL procedure successfully completed.

Input truncated to 13 characters
SQL> SELECT * FROM some_donor;

IDNO  NAME               YRGOAL
----- --------------- ---------
23456 Susan Ash             100
11111 Stephen Daniels       500
The following code uses %ROWTYPE in a similar way that we have used %TYPE. In this case we are simply declaring the variable v_donor as having the same layout and types as the row in the some_donor table. ROWTYPE is useful because it allows the underlying database to be modified without having to go in and change the PL/SQL code.

Note that in the select I am taking certain columns from new_donor and inserting them in the variable v_donor which is of the row type some_donor. The fields that I have selected are fields that correspond correctly with the layout of some_donor. When I do the insert, I need to name the columns and since v_donor has the same layout as some_donor, the columns have the same name and I can refer to the as v_donor.column name. Note that in the values, I cannot use v_donor successfully.

SQL CODE:

SQL> edit atsd4

PL/SQL CODE:

SET VERIFY OFF
ACCEPT in_idno PROMPT 'Please enter the donor idno: '
DECLARE
   v_donor some_donor%ROWTYPE;
BEGIN
   SELECT idno, name, yrgoal INTO v_donor
   FROM new_donor WHERE idno = &in_idno;
   INSERT INTO some_donor
   VALUES(v_donor.idno, v_donor.name, v_donor.yrgoal);
END;
/
SET VERIFY ON

SQL CODE:

SQL> @ atsd4
Please enter the donor idno: 22222

PL/SQL procedure successfully completed.

SQL> SELECT * FROM some_donor;

IDNO  NAME               YRGOAL
----- --------------- ---------
23456 Susan Ash             100
11111 Stephen Daniels       500
22222 Carl Hersey

SQL> @ atsd4
Please enter the donor idno: 12121

PL/SQL procedure successfully completed.

Input truncated to 13 characters
SQL> SELECT * FROM some_donor;

IDNO  NAME               YRGOAL
----- --------------- ---------
12121 Jennifer Ames         400
23456 Susan Ash             100
11111 Stephen Daniels       500
22222 Carl Hersey
In the example above, please note that 12121 was the last record entered and it shows up as the first once again verifying that if you leave ordering up to Oracle you get an interesting order.

Tables

PL/SQL tables defined as TYPE IS TABLE are similar to arrays but significantly different in their implementation. Think of a PL/SQL table as similar to a database table that contains two columns. The first column would be the KEY or primary key column and would be of type binary_integer and the second column would be the VALUE column that contains the data we are putting into the table. Tables in Oracle have no limit on the number of rows, the elements in the table are not ordered in any specified way, specifically, they do not have to be sequential.

A table is defined as a TYPE field in the Declare section using the syntax:

TYPE table type name IS TABLE OF data type INDEX BY BINARY_INTEGER

Note that the data type has to be a scalar or record datatype and the index has to be binary_integer, at least in the current version.

Examples of declaring table:

TYPE t_first_table IS TABLE of whatever.first%TYPE
     INDEX BY BINARY_INTEGER;
TYPE t_second_table is TABLE of whatever.datesnd%TYPE
     INDEX BY BINARY_INTEGER;
v_first    t_first_table;
v_second   t_second_table;
In the examples above, the first table and the second table are defined as having similar types to the field first and the field datesnd on the table whatever. Then, variables are set up from each of the defined table types. The index to both of them is the required binary integer. You could also define a table using a record layout. In this case, you can also use the column names when you are putting data into the tables if you want to put data into a specific column in the table.

TYPE t_third_table IS TABLE OF whatever%ROWTYPE
     INDEX BY BINARY_INTEGER;
v_third    t_third_table
To put elements into the tables, the following assignment statements could be used.

v_first(1) := 'Snow shovel';
v_first(8) := 'Trowel';
v_first(4) := 'Sprinkler';
v_second(2) := SYSDATE;
v_third(12).city := 'Rhode Island'
The first table would now contain the following:

KEY	VALUE
1 Snow Shovel
8 Trowel
4 Sprinkler
The second table would contain the following:

KEY	VALUE
2	21-JUL-99
The third table would contain the following:

KEY	VALUE
12 RHODE ISLAND
When dealing with tables there are several attributes available that will help in the effective handling of the table data: In the example below, I have created a table called t_table_1 and then added 4 variables to that table. Notice that the numbers are not in order or in sequence. For my own sense of order and control, I would prefer to plan for more orderly numbering when possible. The next thing I did was use COUNT to tell me how many rows are in the table. COUNT returned the count of 4.

SQL CODE:

SQL> edit table1

PL/SQL CODE:

SET SERVEROUTPUT ON
DECLARE
  TYPE t_table1 IS TABLE OF VARCHAR2(12)
    INDEX BY BINARY_INTEGER;
  v_table_var   t_table1;
  v_count_result NUMBER;
BEGIN
  v_table_var(5) := 'Fifth week';
  v_table_var(2) := 'Second week';
  v_table_var(12) := 'Twelfth week';
  v_table_var(6) := 'Sixth week';
  v_count_result:= v_table_var.COUNT;
  dbms_output.put_line('The count is: '||v_count_result);
END;
/
SET SERVEROUTPUT OFF

SQL CODE:

SQL> @ table1
The count is: 4

PL/SQL procedure successfully completed.
In the code below, I have added an IF statement to test to see if a particular record exists. The user will be prompted to enter a number (the index number) which will come in as in_num and will be stored as v_num. The IF statement asks if there is an element in the table with the index number that was keyed in. This is asked in the code IF v_table_var.EXISTS(v_num) where v_table_var is the variable name and v_num is the keyed in index. If it is, the message the row exists will display otherwise the row does not exist will display.

SQL CODE:

SQL> edit table2

PL/SQL CODE:

SET SERVEROUTPUT ON
DECLARE
  TYPE t_table1 IS TABLE OF VARCHAR2(12)
    INDEX BY BINARY_INTEGER;
  v_table_var   t_table1;
  v_count_result NUMBER;
  v_num NUMBER :=&in_num;
BEGIN
   v_table_var(5) := 'Fifth week';
   v_table_var(2) := 'Second week';
   v_table_var(12) := 'Twelfth week';
   v_table_var(6) := 'Sixth week';
   v_count_result:= v_table_var.COUNT;
   dbms_output.put_line('The count is: '||v_count_result);
   IF v_table_var.EXISTS(v_num) THEN
      dbms_output.put_line('The row exists');
   ELSE
      dbms_output.put_line('The row does not exist');
   END IF;
END;
/
SET SERVEROUTPUT OFF

SQL CODE:

SQL> @ table2
Enter value for in_num: 6
old   6:   v_num NUMBER :=&in_num;
new   6:   v_num NUMBER :=6;
The count is: 4
The row exists
PL/SQL procedure successfully completed.

Input truncated to 20 characters
SQL> @ table2
Enter value for in_num: 3
old   6:   v_num NUMBER :=&in_num;
new   6:   v_num NUMBER :=3;
The count is: 4
The row does not exist

PL/SQL procedure successfully completed.
In this example, I am using the first, next, prior and last. Notice first the syntax. FIRST and LAST stand alone while NEXT and PRIOR need to have an index defined in parenthesis.

SQL CODE:

SQL> edit table3

PL/SQL CODE:

SET SERVEROUTPUT ON
DECLARE
  TYPE t_table1 IS TABLE OF VARCHAR2(12)
    INDEX BY BINARY_INTEGER;
  v_table_var   t_table1;
  v_count_result NUMBER;
  v_index_first BINARY_INTEGER;
  v_index_next BINARY_INTEGER;
  v_index_prior BINARY_INTEGER;
  v_index_last BINARY_INTEGER;
BEGIN
   v_table_var(5) := 'Fifth week';
   v_table_var(2) := 'Second week';
   v_table_var(12) := 'Twelfth week';
   v_table_var(6) := 'Sixth week';
   v_count_result:= v_table_var.COUNT;
   dbms_output.put_line('The count is: '||v_count_result);
   v_index_first := v_table_var.FIRST;
   dbms_output.put_line('The first record is: '||v_index_first);
   v_index_next := v_table_var.NEXT(v_index_first);
   dbms_output.put_line('The next record is: '||v_index_next);
   v_index_prior := v_table_var.PRIOR(v_index_next);
   dbms_output.put_line('The prior record is: '||v_index_prior);
   v_index_last := v_table_var.last;
   dbms_output.put_line('The last record is: '||v_index_last);
   v_index_prior := v_table_var.PRIOR(v_index_last);
   dbms_output.put_line('The prior record is: '||v_index_prior);
   v_index_first := v_table_var.FIRST;
   dbms_output.put_line('The first record is: '||v_index_first);
END;
/
SET SERVEROUTPUT OFF
The execution is shown below. Compare the order of the table as I created and the results. The first and last return the first and last rows, however, you need to note that the first row is defined by Oracle as the row that has the lowest index while the last row is defined by Oracle as the row that has the highest index. When I worked with next and prior, I had to provide an index. After I displayed first, I then used the index on first to come up with next. Next is the next row in order by index. I then used the next index (which is what I was currently looking at) to display prior - it went back to the first. I then showed the last and used the last index to show prior. This showed the row with the index that put it sequentially above last. Finally I showed the first again.

SQL CODE:

SQL> @ table3
The count is: 4
The first record is: 2
The next record is: 5
The prior record is: 2
The last record is: 12
The prior record is: 6
The first record is: 2

PL/SQL procedure successfully completed.
The following example shows the use of the delete and then demonstrates the results using first and next. The user inputs the index of the row in the table to be deleted. That number is assigned to an index field of binary integer and then it is used to delete the row from the table. In my example, I deleted the row with index 5. Note that when I display first I get 2, then next gets me 6, next again gets me 12 and the request for next again returns nothing since 5 was deleted and there are now only 3 rows in the table. This is confirmed by the display of the record count both before and after the delete. Before the delete it reads 4, after the delete it reads 3.

SQL CODE:

SQL> edit table4

PL/SQL CODE:

SET SERVEROUTPUT ON
DECLARE
  TYPE t_table1 IS TABLE OF VARCHAR2(12)
    INDEX BY BINARY_INTEGER;
  v_table_var   t_table1;
  v_count_result NUMBER;
  v_num NUMBER :=&in_num;
  v_index_first BINARY_INTEGER;
  v_index_next BINARY_INTEGER;
  v_index_in BINARY_INTEGER;
BEGIN
   v_table_var(5) := 'Fifth week';
   v_table_var(2) := 'Second week';
   v_table_var(12) := 'Twelfth week';
   v_table_var(6) := 'Sixth week';
   v_count_result:= v_table_var.COUNT;
   dbms_output.put_line('The count is: '||v_count_result);
   v_index_in := v_num;
   v_table_var.DELETE(v_index_in);
   v_index_first := v_table_var.FIRST;
   dbms_output.put_line('The first record is: '||v_index_first);
   v_index_next := v_table_var.NEXT(v_index_first);
   dbms_output.put_line('The next record is: '||v_index_next);
   v_index_next := v_table_var.NEXT(v_index_next);
   dbms_output.put_line('The next record is: '||v_index_next);   
   v_index_next := v_table_var.NEXT(v_index_next);
   dbms_output.put_line('The next record is: '||v_index_next); 
   v_count_result:= v_table_var.COUNT;
   dbms_output.put_line('The count is: '||v_count_result);  
END;
/
SET SERVEROUTPUT OFF

SQL CODE:

SQL> @ table4
Enter value for in_num: 5
old   6:   v_num NUMBER :=&in_num;
new   6:   v_num NUMBER :=5;
The count is: 4
The first record is: 2
The next record is: 6
The next record is: 12
The next record is:
The count is: 3

PL/SQL procedure successfully completed.
When using tables in Oracle, it is a good idea to use count as I did simply because the size of the table is unconstrained. You could also choose to keep your own row count that you increment everytime you add a row to the table. If it is possible using indexes of 1 and then 2, 3, 4 etc. will allow you a little more control in processing. Sometimes it isn't possible and as we have seen in the examples above, Oracle accommodates the indexes you choose. If you want to delete a whole table for some reason, you can assign null to it. This would be done with the statement: v_table_var := NULL;.