PostgreSQL composite types let you bundle related fields into a single named structure. Once defined, a composite type can be used as the data type of a table column, returned from a function, or passed as a function argument—just like any built-in type.
Defining a composite type
Use CREATE TYPE ... AS (...) to declare a composite type with its field names and data types:
CREATE TYPE address_type AS (
street TEXT,
city TEXT,
state TEXT,
zip_code INTEGER,
country TEXT
);
You can now use address_type as the type of any table column:
CREATE TABLE contacts (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(255) NOT NULL,
address address_type
);
Note: when you create a table, PostgreSQL automatically creates a corresponding composite type with the same name as the table. So CREATE TABLE contacts (...) also implicitly creates a contacts composite type.
Inserting and querying composite values
Construct a composite value using the ROW() expression (the ROW keyword is optional when multiple fields are listed):
INSERT INTO contacts (name, address)
VALUES (
'John Smith',
ROW('123 Main St', 'Houston', 'TX', 77001, 'USA')
);
-- Insert using individual field notation (dot syntax)
INSERT INTO contacts (name, address.street, address.city, address.state, address.zip_code)
VALUES ('Jane Doe', '4000 N. 1st Street', 'San Jose', 'CA', 95134);
To read individual fields from a composite column, wrap the column name in parentheses and append the field name with a dot:
SELECT
id,
name,
(address).city,
(address).state,
(address).zip_code
FROM contacts;
Output:
id | name | city | state | zip_code
----+------------+----------+-------+----------
1 | John Smith | Houston | TX | 77001
2 | Jane Doe | San Jose | CA | 95134
To expand all fields of a composite column, use the .* shorthand:
SELECT id, name, (address).* FROM contacts;
Updating composite columns
Update individual fields of a composite column using dot notation after SET (no parentheses around the column name in SET):
UPDATE contacts
SET address.country = 'USA'
WHERE id = 2
RETURNING *;
Output:
id | name | address
----+----------+------------------------------------------------
2 | Jane Doe | ("4000 N. 1st Street","San Jose",CA,95134,USA)
Practical tips
- Composite types are best for logically grouped fields that always travel together, such as addresses, coordinates, or monetary amounts with currency.
- Querying individual fields requires the
(column_name).fieldsyntax—forgetting the parentheses causes a parse error. - When using composite types in
WHEREclauses, compare individual fields rather than the whole composite value for clarity and index usage. - Altering a composite type with
ALTER TYPEis possible but can be disruptive—plan schema changes carefully in production.