Skip to content

Guide

PostgreSQL data dictionary query and template

PostgreSQL / data dictionary / database documentation / COMMENT ON

Generate a PostgreSQL data dictionary with one query on pg_catalog and COMMENT ON descriptions, export it with psql, and keep it current as the schema changes.

PostgreSQL is unusually good at holding its own documentation. Every table and column can carry a comment, \d+ shows it in psql, and the system catalog exposes everything else a data dictionary needs. The work is mostly in pulling it together into something a person who does not use psql can read.

The query

One row per column, with the type formatted the way PostgreSQL prints it, nullability, default, primary key and foreign key flags and the column comment:

SELECT
    n.nspname                                   AS schema_name,
    c.relname                                   AS table_name,
    a.attnum                                    AS position,
    a.attname                                   AS column_name,
    format_type(a.atttypid, a.atttypmod)        AS data_type,
    NOT a.attnotnull                            AS is_nullable,
    pg_get_expr(d.adbin, d.adrelid)             AS default_value,
    EXISTS (SELECT 1 FROM pg_constraint k
            WHERE k.conrelid = c.oid AND k.contype = 'p'
              AND a.attnum = ANY (k.conkey))    AS is_primary_key,
    (SELECT string_agg(f.confrelid::regclass::text, ', ')
       FROM pg_constraint f
      WHERE f.conrelid = c.oid AND f.contype = 'f'
        AND a.attnum = ANY (f.conkey))          AS references,
    col_description(c.oid, a.attnum)            AS description
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum > 0 AND NOT a.attisdropped
LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = a.attnum
WHERE c.relkind IN ('r', 'p')
  AND n.nspname NOT IN ('pg_catalog', 'information_schema')
  AND n.nspname NOT LIKE 'pg_toast%'
ORDER BY n.nspname, c.relname, a.attnum;

obj_description(c.oid, 'pg_class') gives the table comment in the same way, and adding 'v' and 'm' to the relkind list includes views and materialized views.

Exporting it

From psql, the dictionary becomes a CSV with headers in one command:

psql "service=sales" -c "\copy (SELECT ... ) TO 'data_dictionary.csv' WITH (FORMAT csv, HEADER)"

psql can also print Markdown or HTML tables directly with \pset format markdown or \pset format html, which is handy for pasting into a wiki page.

Writing the descriptions

COMMENT ON TABLE  orders       IS 'One row per customer order, including cancelled ones.';
COMMENT ON COLUMN orders.total IS 'Order total in EUR including VAT, after discounts.';

Comments live in the database, survive pg_dump and restore, and can be shipped with migrations, which makes them the best home for descriptions that belong to the schema. Their limit is social rather than technical: product managers, analysts and support staff will not write COMMENT ON statements, so the knowledge they have about what a status value means ends up in a separate document that drifts.

Why exports go stale

The CSV is correct until the next migration. Migrations add columns without comments, rename tables, change types, and the exported file has no way of noticing. Teams end up re-running the query before each audit and reconciling by hand the descriptions that were typed into the file.

A data dictionary that follows the schema

Taavik's documentation regenerates the dictionary from pg_catalog after every scan. An agent inside the network reads the catalog with a read-only login, and the document shows, for each table, every column with its type, nullability, default, COMMENT ON text and the date of its last change, plus the indexes. Notes added in the workspace sit next to the comments, attached to a database, schema, table or column, and survive every regeneration, so the analyst's explanation of a status column does not have to become a COMMENT ON to be kept.

Each schema change produces a new revision with a diff. The document can be shared by link or copied as HTML, and the schema map draws foreign keys and exports to Mermaid, DBML or SQL. There is no Excel or PDF export, and partitioned tables are not part of the PostgreSQL scan, which matters for databases that partition their largest tables.

Guides for the other engines: SQL Server data dictionary and MySQL data dictionary.