A record variable is a PL/SQL variable that holds a row of data. Each record variable has named fields that you access using dot notation (record.field), similar to how you reference a table column. Because record variables map directly to table rows, they simplify INSERT, UPDATE, and query operations that work on entire rows at once.
Create a record variable — define a custom record type, mirror a table row, or copy an existing record type
Initialize a record variable — set default values and NOT NULL constraints at declaration time
Assign values to a record variable — field-by-field, record-to-record, and query-based assignment
Test a record variable — NULL, equality, and inequality comparisons
Use a record variable with a table — insert and update rows using a record variable
Limitations — where record variables can and cannot appear
Syntax
Define a record type:
TYPE record_type IS RECORD ( {field_definition [, ...]} );Where field_definition is:
field datatype [ [NOT NULL] {:= | DEFAULT} expression ]Declare a record variable:
record1 {record_type | {table | view | cursor}%ROWTYPE | record2%TYPE};Create a record variable
Three methods are available:
| Method | Syntax | When to use |
|---|---|---|
| Define a record type | TYPE r_type IS RECORD(...); v1 r_type; | Custom field names and types |
Use %ROWTYPE | v2 table%ROWTYPE; | Mirror a full or partial table/view/cursor row |
Use %TYPE | v3 v2%TYPE; | Declare a variable of the same type as an existing record variable |
CREATE TABLE test(id INT, name VARCHAR(10));
DECLARE
TYPE r_type IS RECORD(id INT, name VARCHAR(10));
v1 r_type; -- Method 1: custom record type
v2 test%ROWTYPE; -- Method 2: full table row
v3 v2%TYPE; -- Method 3: same type as v2
BEGIN
...
END;Package-scoped record types
A record type defined inside a PL/SQL block is a local type, available only within that block. A record type is stored in the database only if it is declared in a package. To share a record type across multiple blocks, declare it in a package:
CREATE PACKAGE pkg AS
TYPE r_type IS RECORD(id INT, name VARCHAR(10));
END;
CREATE PACKAGE BODY pkg AS
END;
DECLARE
r pkg.r_type := pkg.r_type(1, 'a');
BEGIN
RAISE NOTICE 'id: %, name: %', r.id, r.name;
END;Output:
NOTICE: id: 1, name: aUsing %ROWTYPE
table%ROWTYPE captures the complete row definition. Use a cursor or view to capture a partial row:
CREATE TABLE test(id INT, name VARCHAR(10));
DECLARE
r1 test%ROWTYPE; -- Full row: id and name columns
CURSOR cur IS SELECT id FROM test;
r2 cur%ROWTYPE; -- Partial row: id column only
BEGIN
r1.id := 1;
r1.name := 'a';
r2.id := 2;
RAISE NOTICE 'r1.id: %, r1.name: %', r1.id, r1.name;
RAISE NOTICE 'r2.id: %', r2.id;
END;Output:
NOTICE: r1.id: 1, r1.name: a
NOTICE: r2.id: 2When using%ROWTYPEor%TYPE, the record variable's default value isNULL. Table constraints are not inherited — only field names and types are copied. Hidden columns and therowidcolumn are excluded.
Initialize a record variable
Set field constraints and default values when defining the record type:
DECLARE
TYPE r_type IS RECORD(id INT NOT NULL := 1,
name VARCHAR(10) DEFAULT 'name');
BEGIN
...
END;Declare a record variable as CONSTANT to prevent modification after initialization:
DECLARE
TYPE r_type IS RECORD(id INT, name VARCHAR(10));
r CONSTANT r_type := r_type(1, 'a');
BEGIN
r := r_type(2, 'b'); -- Error
END;Output:
ERROR: variable "r" is declared CONSTANTAssign values to a record variable
Field-by-field assignment
Assign a value to each field individually:
DECLARE
TYPE r_type IS RECORD(id INT, name VARCHAR(10));
r r_type;
BEGIN
r.id := 1;
r.name := 'name';
RAISE NOTICE 'r.id: %, r.name: %', r.id, r.name;
END;Output:
NOTICE: r.id: 1, r.name: nameAssign one record variable to another
You can directly assign the values of one record variable to those of another record variable if the attributes of their corresponding fields allow implicit type conversions.
DECLARE
TYPE r_type1 IS RECORD(id INT, name VARCHAR(10));
TYPE r_type2 IS RECORD(id VARCHAR(10), name INT);
r1 r_type1;
r2 r_type2 := r_type2('1', 2);
BEGIN
r1 := r2; -- Implicit conversion: VARCHAR '1' -> INT, INT 2 -> VARCHAR
RAISE NOTICE 'r.id: %, r.name: %', r1.id, r1.name;
END;Output:
NOTICE: r.id: 1, r.name: 2If a conversion fails, an error is returned:
ERROR: invalid input syntax for type integer: "a"Assign from a query result
Use SELECT INTO to populate a record variable from a table. Include LIMIT 1 to ensure at most one row is returned:
CREATE TABLE test(id INT, name VARCHAR(10));
INSERT INTO test VALUES(1, 'a');
INSERT INTO test VALUES(2, 'b');
DECLARE
TYPE r_type IS RECORD(id INT, name TEXT);
r r_type;
BEGIN
SELECT * INTO r FROM test LIMIT 1;
RAISE NOTICE '%', r;
END;Output:
NOTICE: (1,a)If the query returns more than one row without a LIMIT, an error is thrown:
ERROR: query returned more than one row
HINT: Make sure the query returns a single row, or use LIMIT 1.FETCH INTO and UPDATE/INSERT/DELETE RETURNING INTO follow the same rules as SELECT INTO.
NULL assignment
Set a record variable to NULL only when it has no NOT NULL constraint. Setting a field that has a NOT NULL constraint to NULL raises a runtime error.
Test a record variable
Record variables support IS NULL, equality (=), and inequality (>, <) testing. For equality and inequality, fields are compared in order using the comparison functions of the record type.
DECLARE
TYPE r_type IS RECORD(id INT, name VARCHAR(10));
r1 r_type := r_type(1, 2);
r2 r_type := r_type(1, 2);
BEGIN
RAISE NOTICE '%', r1 IS NULL; -- false
RAISE NOTICE '%', r1 = r2; -- true
RAISE NOTICE '%', r1 > r2; -- false
r2.id := 10;
RAISE NOTICE '%', r1 < r2; -- true
END;Output:
NOTICE: f
NOTICE: t
NOTICE: f
NOTICE: tUse a record variable with a table
Insert a row using a record variable
CREATE TABLE test(id INT, name VARCHAR(10));
DECLARE
TYPE r_type IS RECORD(id INT, name VARCHAR(10));
r r_type := r_type(1, 'a');
BEGIN
INSERT INTO test VALUES r;
END;
SELECT id, name FROM test;Output:
id | name
----+------
1 | a
(1 row)Update a row using a record variable
Use SET ROW = record_variable to update an entire row:
DECLARE
TYPE r_type IS RECORD(id INT, name VARCHAR(10));
r r_type := r_type(2, 'b');
BEGIN
UPDATE test SET ROW = r WHERE id = 1;
END;
SELECT id, name FROM test;Output:
id | name
----+------
2 | b
(1 row)Limitations
Where record variables can be used
Record variables are valid only in these positions:
The right side of the SET clause in an
UPDATEstatementThe VALUES clause of an
INSERTstatementThe INTO clause of a
RETURNINGclause
Record variables cannot appear in:
SELECTlistsWHEREclausesGROUP BYclausesORDER BYclauses
ROW keyword constraints
ROWcan appear only on the left side of a SET clause:SET ROW = record_variableROWcannot be used with subqueriesAn
UPDATEstatement usingROWcan have only one SET clause
INSERT and RETURNING INTO constraints
If the VALUES clause of an
INSERTstatement contains a record variable, no other variables or values can appear in the same clauseIf the INTO clause of a
RETURNINGclause contains a record variable, no other variables or values can appear in the same clause