In PostgreSQL, domains are basically data types with optional constraints. We can create them as a kind of user-defined data type, and then reuse them in our columns going forward.
As with any user-created object, we sometimes need to see a list of existing domains in a PostgreSQL database. Listing all domains can be useful for database management, documentation, or troubleshooting.
This article will show you how to retrieve a list of all domains in your PostgreSQL database.
Using psql
If you’re using the psql
command-line interface, you can use the \dD
command:
\dD
Example result:
Schema | Name | Type | Collation | Nullable | Default | Check
--------+---------------+------------------------+-----------+----------+---------+-----------------------------------------------------------------------------------------------------------------
public | age_years | integer | | | | CHECK (VALUE >= 0 AND VALUE <= 150)
public | email_address | character varying(255) | | | | CHECK (VALUE::text ~ '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'::text) CHECK (length(VALUE::text) >= 5)
public | money_usd | numeric(10,2) | | | 0.00 | CHECK (VALUE >= 0::numeric)
(3 rows)
This command returns a lot of useful information about the domains in the current database, including base type, the CHECK
constraints, default value, etc.
SQL Query
If you’re not using psql
, you can run the following query:
SELECT
n.nspname AS schema,
t.typname AS name,
pg_catalog.format_type(t.typbasetype, t.typtypmod) AS base_type,
(SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type bt
WHERE c.oid = t.typcollation AND bt.oid = t.typbasetype AND t.typcollation <> bt.typcollation) AS collation,
t.typnotnull AS not_null,
t.typdefault AS default,
pg_catalog.array_to_string(ARRAY(
SELECT pg_catalog.pg_get_constraintdef(r.oid, true) FROM pg_catalog.pg_constraint r
WHERE t.oid = r.contypid
), ' ') AS check
FROM pg_catalog.pg_type t
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
WHERE t.typtype = 'd'
ORDER BY 1, 2;
Example result:
schema | name | base_type | collation | not_null | default | check
--------------------+-----------------+-----------------------------+-----------+----------+----------------------+-----------------------------------------------------------------------------------------------------------------
information_schema | cardinal_number | integer | | f | | CHECK (VALUE >= 0)
information_schema | character_data | character varying | C | f | |
information_schema | sql_identifier | name | | f | |
information_schema | time_stamp | timestamp(2) with time zone | | f | CURRENT_TIMESTAMP(2) |
information_schema | yes_or_no | character varying(3) | C | f | | CHECK (VALUE::text = ANY (ARRAY['YES'::character varying, 'NO'::character varying]::text[]))
public | age_years | integer | | f | | CHECK (VALUE >= 0 AND VALUE <= 150)
public | email_address | character varying(255) | | f | | CHECK (VALUE::text ~ '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'::text) CHECK (length(VALUE::text) >= 5)
public | money_usd | numeric(10,2) | | f | 0.00 | CHECK (VALUE >= 0::numeric)
(8 rows)
This time we also got the information schema domains. Every column in the information schema has one of these five types. The PostgreSQL documentation states:
You should not use these types for work outside the information schema, but your applications must be prepared for them if they select from the information schema.
The following query excludes information schema domains:
SELECT
n.nspname AS schema_name,
t.typname AS domain_name,
pg_catalog.format_type(t.typbasetype, t.typtypmod) AS base_type,
t.typnotnull AS not_null,
t.typdefault AS default_value,
pg_catalog.array_to_string(ARRAY(
SELECT pg_catalog.pg_get_constraintdef(r.oid, true)
FROM pg_catalog.pg_constraint r
WHERE t.oid = r.contypid
), ' AND ') AS check_constraints,
d.description AS comment
FROM pg_catalog.pg_type t
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
LEFT JOIN pg_catalog.pg_description d ON d.objoid = t.oid AND d.classoid = 'pg_type'::regclass
WHERE t.typtype = 'd'
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
ORDER BY schema_name, domain_name;
Result:
schema_name | domain_name | base_type | not_null | default_value | check_constraints | comment
-------------+---------------+------------------------+----------+---------------+---------------------------------------------------------------------------------------------------------------------+---------
public | age_years | integer | f | | CHECK (VALUE >= 0 AND VALUE <= 150) |
public | email_address | character varying(255) | f | | CHECK (VALUE::text ~ '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'::text) AND CHECK (length(VALUE::text) >= 5) |
public | money_usd | numeric(10,2) | f | 0.00 | CHECK (VALUE >= 0::numeric) |
(3 rows)
This query provides the following information for each domain:
schema_name
: The schema in which the domain is defined.domain_name
: The name of the domain.base_type
: The underlying data type of the domain.not_null
: Indicates whether the domain has aNOT NULL
constraint.default_value
: The default value for the domain, if any.check_constraints
: AnyCHECK
constraints defined for the domain.comment
: Any comment associated with the domain.
Here’s a breakdown of what the query does:
- It selects from
pg_type
, which contains information about data types, including domains. - It joins with
pg_namespace
to get the schema name. - It left joins with
pg_description
to get any comments associated with the domain. - The subquery within
array_to_string
retrieves anyCHECK
constraints associated with the domain frompg_constraint
. - The
WHERE
clause filters for domain types (typtype = 'd'
) and excludes system schemas. - The results are ordered by schema name and domain name.