System Catalogs
PostgreSQL stores its own schema in system catalogs (pg_catalog). The SQL-standard information_schema views wrap a subset for portability. Operators and automation should know both: catalogs are complete; information_schema is familiar.
Recipe
-- Tables in current database (catalog style)
SELECT n.nspname, c.relname, pg_total_relation_size(c.oid) AS bytes
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','p','m')
AND n.nspname NOT IN ('pg_catalog','information_schema')
ORDER BY bytes DESC
LIMIT 20;When to reach for this: You need to introspect schemas, columns, constraints, or sizes without external tools.
Working Example
-- Column detail with types and nullability
SELECT table_schema, table_name, column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'app' AND table_name = 'customers'
ORDER BY ordinal_position;
-- Constraints on a table
SELECT conname, contype, pg_get_constraintdef(oid) AS definition
FROM pg_constraint
WHERE conrelid = 'app.customers'::regclass;What this demonstrates:
information_schema.columnslists user-facing column metadatapg_constraintstores CHECK, UNIQUE, PRIMARY KEY, and FOREIGN KEY definitionspg_total_relation_sizeincludes indexes and toast for capacity planning
Deep Dive
Key Catalogs
| Catalog | Holds |
|---|---|
| pg_namespace | schemas |
| pg_class | tables, indexes, views (relkind) |
| pg_attribute | columns |
| pg_constraint | constraints |
| pg_index | index columns and flags |
| pg_proc | functions and procedures |
information_schema vs pg_catalog
- information_schema filters to SQL-standard objects and hides implementation details.
- pg_catalog is authoritative for partitions, policies, triggers, and storage parameters.
- Join catalogs when you need OIDs, relfilenode, or amname (access method).
Discovery Tips
- Regclass casts ('app.customers'::regclass) resolve names using search_path.
- psql \d+ tablename is a thin wrapper over catalog queries.
Gotchas
- OID wraps on long-lived clusters - Very old clusters rarely hit this; still use names in app code.. Fix: Prefer regclass/regtype casts in admin scripts.
- information_schema omits partitions detail - Declarative partition children may look like ordinary tables.. Fix: Query pg_inherits and pg_partitioned_table.
- pg_attribute.attnum gaps - Dropped columns leave holes; filter attnum > 0 and not dropped.. Fix: Use information_schema for portable column lists.
- Stats views are not catalogs - pg_stat_* are views over shared memory snapshots.. Fix: Reset stats with pg_stat_reset() cautiously in prod.
- search_path affects regclass - Same unqualified name can resolve differently per role.. Fix: Schema-qualify in tooling and migrations.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| psql \d family | Interactive exploration | Automation or CI schema diff |
| pg_dump --schema-only | Full DDL export | Lightweight column lookup |
| ORM migrations | App-owned schema evolution | DBA-wide governance across services |
FAQs
Can I query catalogs from any database?
Yes. Catalogs describe the cluster; most queries filter to current_database() objects.
Are catalog changes transactional?
DDL that changes user tables updates catalogs in the same transaction.
What is pg_description?
Optional comment storage tied to catalog OIDs via objoid/classoid.
How do I find foreign keys?
pg_constraint with contype = 'f' or information_schema.table_constraints.
Is information_schema slower?
Slightly more view layers. For heavy introspection, pg_catalog joins are fine.
Related
- PostgreSQL Basics - first catalog queries
- Schema Design Basics - naming and keys
- DDL Transactions - catalog updates during migrations
Stack versions: This page was written for PostgreSQL 18.4 (stable 18, maintenance 17), pgvector 0.8+, PgBouncer 1.x, Patroni 3.x, and PostGIS 3.5+.