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.
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,
...
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
In this syntax:
- First, provide the composite type name after the
CREATE TYPE
keywords. - Second, list field names with their corresponding data types within parentheses after the
AS
keyword.
For example, the following statement creates a composite type called coordinate
that includes latitude
and longitude
:
CREATE TYPE coordinate AS (
latitude DEC,
longitude DEC
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
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
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
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, ...)
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
For example, you can construct the value for the coordinate
composite type like this:
ROW(37.318686, -121.871019)
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
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 *;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
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
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
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
*;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Output:
warehouse_id | location
--------------+-------------------------
2 | (37.650972,-122.398659)
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Accessing fields of composite types #
You can access the fields of a composite column using the dot notation:
composite_column.field_name
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Sometimes, you need to place the composite column within parentheses like this:
(composite_column).field_name
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
To access all fields of a composite column, you can use the asterisk shorthand (*
):
(composite_column).*
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
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;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
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;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Output:
warehouse_id | latitude | longitude
--------------+-----------+-------------
1 | 37.318686 | -121.871019
2 | 37.650972 | -122.398659
Updating composite values #
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 *;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Output:
warehouse_id | location
--------------+-------------------------
1 | (37.650971,-122.398658)
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
You do not use parentheses when you access the field in the SET
clause of the UPDATE
statement because the dot notation is sufficient to identify the individual fields within the composite type. Using parentheses here will result in an error.
To update all fields of the location
column at once, you can also use the following syntax:
UPDATE warehouse_locations
SET
location = ROW (37.650971, -122.398658)
WHERE
warehouse_id = 1
RETURNING *;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Output:
warehouse_id | location
--------------+-------------------------
1 | (37.650971,-122.398658)
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
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 *;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Output:
warehouse_id | location
--------------+-------------------------
1 | (37.650971,-122.398658)
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
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;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
The NOT NULL
constraint ensures that the whole composite value is NOT NULL
, not individual fields.
Therefore, the following statement fails to insert NULL
into the location
column:
INSERT INTO warehouse_locations(warehouse_id, location)
VALUES(3, NULL);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Error:
null value in column "location" of relation "warehouse_locations" violates not-null constraint
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
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));
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
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;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
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
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Third, change the type of the location
column to coordinate_domain
:
ALTER TABLE warehouse_locations
ALTER COLUMN location TYPE coordinate_domain;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Finally, attempt to insert NULL
into individual fields of the location
:
INSERT INTO
warehouse_locations (warehouse_id, location)
VALUES
(3, ROW (NULL, NULL));
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
It will result in an error as expected:
ERROR: value for domain coordinate_domain violates check constraint "coordinate_domain_check"
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
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
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
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 represents a record or a row.
- A composite type contains a list of fields and their corresponding data types.
- Use a composite type similarly to a simple type.
- Composite types do not directly support constraints on individual fields, but you can create a domain over the composite type and apply constraints to the domain.
- PostgreSQL automatically creates a corresponding implicit composite type for each table.