PL/SQL - (Nested) Record

> Procedural Languages > PL/SQL - (Procedure Language|PL) SQL

1 - About

Records are simply a row representation in PL/SQL.

Records (as row) contain uniquely named fields (column name for row), which can have different datatypes whereas collection contains element of the same datatype.

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.

Advertising

3 - Advantage/Restriction

You can define records that contain:

However, records cannot be attributes of object types.

4 - Syntax

Best:

  1. Use <cursor>%ROWTYPE instead of <table>%ROWTYPE because you only want some columns.
  2. use %ROWTYPE instead of specifying each column.
  3. Use %TYPE instead of defining your datatype

4.1 - Row-defined

You can use the %ROWTYPE attribute to declare a record that represents:

  • a row fetched from a cursor.
  • or a row in a table
Advertising

4.1.1 - Cursor

Declaring cursor doesn't run query or affect performance.

CURSOR c1 IS
     SELECT department_id, department_name, location_id
     FROM departments;
rec1 c1%ROWTYPE;

4.1.2 - (Table|View)

To declare a record that represents a row in a database table or a view, without listing the columns, use the %ROWTYPE attribute

myTable%ROWTYPE;
myView%ROWTYPE;

4.2 - User-defined

With a user-defined record, you can declare fields of your own.

4.2.1 - Column Datatype

Use <column>%TYPE in field declarations to avoid problems if the column types change. You can use %TYPE to specify a field type corresponding to a table column type. Your code keeps working even if the column type is changed (for example, to increase the length of a VARCHAR2 or the precision of a NUMBER).

TYPE DeptRec2 IS RECORD
     (dept_id   departments.department_id%TYPE,
      dept_name departments.department_name%TYPE,
      dept_loc  departments.location_id%TYPE);
rec2 DeptRec2;

4.2.2 - Specific Datatype

Write each field name, specifying type directly (clumsy and unmaintainable for working with table data use only for all-PL/SQL code).

TYPE DeptRec3 IS RECORD (dept_id NUMBER,
                         dept_name VARCHAR2(14),
                         dept_loc VARCHAR2(13));
rec3 DeptRec3;
Advertising

5 - Management

5.1 - Initialization

5.1.1 - Single

  • Assigning an empty record to rec1 resets fields to their default values.
DECLARE
   TYPE RecordTyp IS RECORD (field1 NUMBER, 
                             field2 VARCHAR2(32) DEFAULT 'something');
   rec1 RecordTyp;
   rec2 RecordTyp;
BEGIN
-- At first, rec1 has the values you assign.
   rec1.field1 := 100; 
   rec1.field2 := 'something else';
 
-- Assigning an empty record to rec1
-- resets fields to their default values.
-- Field1 is NULL and field2 is 'something'
-- due to the DEFAULT clause
   rec1 := rec2;
   DBMS_OUTPUT.PUT_LINE
     ('Field1 = ' || NVL(TO_CHAR(rec1.field1),'<NULL>') || ', field2 = ' || rec1.field2);
END;
/

Output:

Field1 = <NULL>, field2 = something

5.1.2 - SELECT INTO

  • Specific datatype
DECLARE
   TYPE RecordTyp IS RECORD (LAST employees.last_name%TYPE, 
                             id employees.employee_id%TYPE);
   rec1 RecordTyp;
BEGIN
  SELECT last_name, employee_id INTO rec1
     FROM employees WHERE ROWNUM < 2;
     WHERE ROWNUM < 2;
   DBMS_OUTPUT.PUT_LINE
     ('Employee #' || rec1.id || ' = ' || rec1.LAST);
END;
/
  • With %ROWTYPE
DECLARE
  dept_info departments%ROWTYPE;
BEGIN
  -- department_id, department_name, and location_id
  -- are the table columns
  -- The record picks up these names from the %ROWTYPE
     dept_info.department_id := 300;
     dept_info.department_name := 'Personnel';
     dept_info.location_id := 1700;
  -- Using the %ROWTYPE means you can leave out the column list
  -- (department_id, department_name, and location_id)
  -- from the INSERT statement
  INSERT INTO departments VALUES dept_info;
END;
/

5.1.3 - Fetch

DECLARE
   -- The record definition
   TYPE EmpRecTyp IS RECORD (
     emp_id       NUMBER(6),
     salary       NUMBER(8,2));
 
   -- The cursor supporting the record definition
   CURSOR desc_salary RETURN EmpRecTyp IS
      SELECT emp_id, salary
      FROM employees
      ORDER BY salary DESC;
 
   -- The record variable
   emp_rec     EmpRecTyp;
 
   -- The function
   FUNCTION nth_highest_salary (n INTEGER) RETURN EmpRecTyp IS
   BEGIN
      OPEN desc_salary;
      FOR i IN 1..n LOOP
         FETCH desc_salary INTO emp_rec;
      END LOOP;
      CLOSE desc_salary;
      RETURN emp_rec;
   END nth_highest_salary;
 
BEGIN
   emp_rec := nth_highest_salary(1);
   DBMS_OUTPUT.PUT_LINE('First Salary of ' || emp_rec.salary || ' is for the employee (' || emp_rec.emp_id || ')');
   emp_rec := nth_highest_salary(5);
   DBMS_OUTPUT.PUT_LINE('Fifth Salary of ' || emp_rec.salary || ' is for the employee (' || emp_rec.emp_id || ')');
END;
/

5.2 - Parameter

5.2.1 - Input

DECLARE
   TYPE EmpRecTyp IS RECORD (
      emp_id       NUMBER(6),
      emp_sal      NUMBER(8,2) );
   PROCEDURE raise_salary (emp_info EmpRecTyp) IS
   BEGIN
      UPDATE employees SET salary = salary + salary * .10
             WHERE employee_id = emp_info.emp_id;
   END raise_salary;
BEGIN
   NULL;
END;
/

5.2.2 - Return

-- The function
FUNCTION nth_highest_salary (n INTEGER) RETURN EmpRecTyp IS
BEGIN
  OPEN desc_salary;
  FOR i IN 1..n LOOP
	 FETCH desc_salary INTO emp_rec;
  END LOOP;
  CLOSE desc_salary;
  RETURN emp_rec;
END nth_highest_salary;

5.3 - DML

5.3.1 - Insert

DECLARE
  dept_info departments%ROWTYPE;
BEGIN
  -- department_id, department_name, and location_id
  -- are the table columns
  -- The record picks up these names from the %ROWTYPE
     dept_info.department_id := 300;
     dept_info.department_name := 'Personnel';
     dept_info.location_id := 1700;
  -- Using the %ROWTYPE means you can leave out the column list
  -- (department_id, department_name, and location_id)
  -- from the INSERT statement
  INSERT INTO departments VALUES dept_info;
END;
/

5.3.2 - Update

DECLARE
   dept_info departments%ROWTYPE;
BEGIN
-- department_id, department_name, and location_id
-- are the table columns
-- The record picks up these names from the %ROWTYPE.
  dept_info.department_id := 300;
  dept_info.department_name := 'Personnel';
  dept_info.location_id := 1700;
-- The fields of a %ROWTYPE
-- can completely replace the table columns
-- The row will have values for the filled-in columns, and null
-- for any other columns
   UPDATE departments SET ROW = dept_info WHERE department_id = 300;
END;
/

where:

  • The keyword ROW represents an entire row,

5.3.3 - Returning

The INSERT, UPDATE, and DELETE statements can include a RETURNING clause, which returns column values from the affected row into a PL/SQL record variable. This eliminates the need to SELECT the row after an insert or update, or before a delete.

By default, you can use this clause only when operating on exactly one row. When you use bulk SQL, you can use the form RETURNING BULK COLLECT INTO to store the results in one or more collections. See PL/SQL - Bulk Collect - Fetch collection of (records|Collection)

DECLARE
   TYPE EmpRec IS RECORD (last_name  employees.last_name%TYPE, 
                          salary     employees.salary%TYPE);
   emp_info EmpRec;
   emp_id   NUMBER := 100;
BEGIN
   UPDATE employees SET salary = salary * 1.1
     WHERE employee_id = emp_id
     RETURNING last_name, salary INTO emp_info;
   DBMS_OUTPUT.PUT_LINE
     ('Just gave a raise to ' || emp_info.last_name ||
      ', who now makes ' || emp_info.salary);
   ROLLBACK;
END;
/

6 - Document / Reference

lang/plsql/record.txt · Last modified: 2017/09/06 22:18 by gerardnico