Summary: In this tutorial, you’ll learn how to use the PostgreSQL composite types and effectively manipulate composite columns.
Introduction to PostgreSQL composite types #
In PostgreSQL, a composite type represents the structure of a row or record. A composite type is a list of field names and their data types.
PostgreSQL allows you to use composite types in the same way as simple types.
For example, you can use composite types for table columns, function arguments, and function return types.
In this tutorial, you will learn how to:
- Create a new composite type.
- Use a composite type for a table column.
- Construct, access, and update composite values.
- Inspect the fields of a composite type.
- Understand how
NULLworks with composite values. - Apply constraints to fields using a domain over a composite type.
Creating composite types #
To create a new composite type, you use the CREATE TYPE statement as follows:
CREATE TYPE type_name AS (
field1 data_type,
field2 data_type,
...
);
In this syntax:
- First, provide the composite type name after the
CREATE TYPEkeywords. - Second, list field names with their corresponding data types within parentheses after the
ASkeyword.
Note that a composite type defines the structure of a value. It does not create a table or store any rows.
For example, the following statement creates a composite type called coordinate that includes latitude and longitude:
CREATE TYPE coordinate AS (
latitude DEC,
longitude DEC
);
Using composite types #
After creating a composite type, you can use it like a simple type. For example, you can use a composite type as the type of a table column.
The following statement creates a table called warehouse_locations that uses the coordinate composite type:
CREATE TABLE warehouse_locations(
warehouse_id INT PRIMARY KEY,
location coordinate
);
Constructing composite values #
To create a composite value as a literal constant, you can use the ROW keyword and enclose comma-separated field values within parentheses as follows:
ROW(value1, value2, ...)
For example, you can construct the value for the coordinate composite type like this:
ROW(37.318686, -121.871019)
In this value, 37.318686 is the latitude, and -121.871019 is the longitude.
The following statement inserts a new row into the warehouse_locations table:
INSERT INTO warehouse_locations(warehouse_id, location)
VALUES(1, ROW(37.318686, -121.871019))
RETURNING *;
Output:
warehouse_id | location
--------------+-------------------------
1 | (37.318686,-121.871019)
It’s possible to explicitly specify the fields of the composite column you want to insert using the dot notation:
composite_column.field_name
For example, the following statement inserts a new row into the warehouse_locations table:
INSERT INTO
warehouse_locations (
warehouse_id,
location.latitude,
location.longitude
)
VALUES
(2, 37.650972, -122.398659)
RETURNING
*;
Output:
warehouse_id | location
--------------+-------------------------
2 | (37.650972,-122.398659)
Accessing fields of composite types #
When you want to read a field from a composite-valued column or expression, you use the following syntax:
(composite_column).field_name
For exampl,e you can access the fields of the location field as follows:
(location).latitude
(location).longitude
The parentheses group location as the composite expression before PostgreSQL applies the field selector.
To access all fields of a composite column, you can use the asterisk shorthand (*):
(composite_column).*
For example, the following statement retrieves the latitude and longitude together with warehouse_id from the warehouse_locations table:
SELECT
warehouse_id,
(location).latitude,
(location).longitude
FROM
warehouse_locations;
Output:
warehouse_id | latitude | longitude
--------------+-----------+-------------
1 | 37.318686 | -121.871019
2 | 37.650972 | -122.398659
In the SELECT statement, if you don’t use the parentheses, PostgreSQL might misinterpret the location type as a table name and issue an error.
The above query is equivalent to the following query that uses the shorthand (*):
SELECT
warehouse_id,
(location).*
FROM
warehouse_locations;
Output:
warehouse_id | latitude | longitude
--------------+-----------+-------------
1 | 37.318686 | -121.871019
2 | 37.650972 | -122.398659
Updating composite values #
You can use an UPDATE statement to modify either individual iedl ỏ the complete composite value. To assign a value to an invididual field, you can place the field selector on the left side of the assignement:
For example, the following statement updates the latitude and longitude of the location of the warehouse with id 1:
UPDATE warehouse_locations
SET
location.latitude = 37.650971,
location.longitude = -122.398658
WHERE
warehouse_id = 1
RETURNING *;
Output:
warehouse_id | location
--------------+-------------------------
1 | (37.650971,-122.398658)
You cannot use write (location).latitude as an assignment target in the SET clause.
You use parenthesized composite syntax when you want to read a field from an expression; For the assignment target, you use:
composite_column.field_name
To update all fields of the location column at once, you can assign a new composite to the column:
UPDATE warehouse_locations
SET
location = ROW (37.650971, -122.398658)
WHERE
warehouse_id = 1
RETURNING *;
Output:
warehouse_id | location
--------------+-------------------------
1 | (37.650971,-122.398658)
Deleting rows based on composite values #
When using the DELETE statement to delete a row based on a field of a composite type, you do need to use the parentheses in the WHERE clause. The reason is that the WHERE clause qualifies the column with the table name. If you don’t use the parentheses, PostgreSQL may misinterpret the composite as a table.
For example, the following statement deletes a row from the warehouse_locations table where the latitude of a location equals 37.650971:
DELETE FROM warehouse_locations
WHERE
(location).latitude = 37.650971
RETURNING *;
Output:
warehouse_id | location
--------------+-------------------------
1 | (37.650971,-122.398658)
Inspecting fields of a composite type #
You can query the information_schema.attributes view to inspect the fields of a standalone composite type.
For example, the following query returns the fields of the coordinate type in their declared order:
SELECT
attribute_name,
data_type,
attribute_udt_schema,
attribute_udt_name,
ordinal_position
FROM
information_schema.attributes
WHERE
udt_schema = 'public'
AND udt_name = 'coordinate'
ORDER BY
ordinal_position;
In this query:
attribute_nameis the name of the field.data_typeis the general SQL data type of the field.attribute_udt_schemaandattribute_udt_nameidentify the underlying PostgreSQL type.ordinal_positionis the field’s position in the composite type.
It’s a good practice to include udt_schema in the WHERE clause because different schemas can contain types with the same name.
The information_schema.attributes view provides information about fields of composite data types. To inspect ordinary table columns, you use the information_schema.columns view instead.
Applying constraints #
Composite types are flexible. However, they do not directly support constraints such as NOT NULL and CHECK on individual fields.
For example, if you apply the NOT NULL constraint to the warehouse_locations table:
ALTER TABLE warehouse_locations
ALTER COLUMN location
SET NOT NULL;
The NOT NULL constraint ensures that the whole composite value is NOT NULL, not individual fields.
This is a column-level NOT NULL constraint on the location column. It prevents the complete column value from being a null composite. But it does not independently make latitude and longitude non-null.
Therefore, the following statement fails to insert NULL into the location column:
INSERT INTO warehouse_locations(warehouse_id, location)
VALUES(3, NULL);
Error:
null value in column "location" of relation "warehouse_locations" violates not-null constraint
But you can insert NULL into latitude and longitude individually within a ROW:
INSERT INTO
warehouse_locations (warehouse_id, location)
VALUES
(3, ROW (NULL, NULL));
To apply constraints like NOT NULL or CHECK to individual fields of a composite column, you can create a domain over the composite type and apply constraints to the domain. For example:
First, remove the row with id 3 from the warehouse_locations table (if it exists):
DELETE FROM warehouse_locations
WHERE warehouse_id = 3;
Second, create a domain coordinate_domain over the coordinate type:
CREATE DOMAIN coordinate_domain
AS coordinate
CHECK (
(VALUE).latitude IS NOT NULL
AND (VALUE).longitude IS NOT NULL
);
Third, change the type of the location column to coordinate_domain:
ALTER TABLE warehouse_locations
ALTER COLUMN location TYPE coordinate_domain;
Finally, attempt to insert NULL into individual fields of the location:
INSERT INTO
warehouse_locations (warehouse_id, location)
VALUES
(3, ROW (NULL, NULL));
It will result in an error as expected:
ERROR: value for domain coordinate_domain violates check constraint "coordinate_domain_check"
Implicit composite types #
When you create a table, PostgreSQL automatically creates a corresponding composite type.
CREATE TABLE product_serials (
serial_no VARCHAR(25) PRIMARY KEY,
product_id INT NOT NULL
);
PostgreSQL automatically creates a product_serials composite type called product_serials with two fields serial_no and product_id. However, it does not carry the constraints to the type.
Summary #
- A composite type defines a structured value containing named fields.
- Use
CREATE TYPE ... AS (...)to create a standalone composite type. - Use
ROW(...)to construct a composite value. - Use
(composite_expression).field_nameto read an individual field. - Use
(composite_expression).*to expand all fields. - Use
composite_column.field_namein anUPDATE SETassignment target. - Query
information_schema.attributesto inspect the fields of a standalone composite type. - A column-level
NOT NULLconstraint prevents the entire composite column from being null, but it does not make each field non-null. - Use a domain over a composite type when reusable field validation is required.
- Every table has an automatically created composite row type, but the table’s constraints are not carried as constraints on that type outside the table.
Quiz #
PostgreSQL Composite Types
Thank you for your feedback!