Introspection

Find foreign keys for a table

Short answer: join information_schema.key_column_usage with information_schema.referential_constraints to get every foreign key with its source column and referenced table. Engine-specific catalogs are often easier to read (Postgres' \d table shows FKs inline; SQL Server's sys.foreign_keys). Useful when figuring out which tables to JOIN against a given table, or when auditing data integrity.

Canonical SQL #

sql
-- Portable: works on Postgres / MySQL / SQL Server / Snowflake / BigQuery.
SELECT
  rc.constraint_name,
  kcu.table_schema   AS source_schema,
  kcu.table_name     AS source_table,
  kcu.column_name    AS source_column,
  ccu.table_schema   AS target_schema,
  ccu.table_name     AS target_table,
  ccu.column_name    AS target_column,
  rc.update_rule,
  rc.delete_rule
FROM   information_schema.referential_constraints rc
JOIN   information_schema.key_column_usage kcu
  ON   rc.constraint_name   = kcu.constraint_name
JOIN   information_schema.constraint_column_usage ccu
  ON   rc.unique_constraint_name = ccu.constraint_name
WHERE  kcu.table_name = 'orders';                      -- the child / source table

-- Postgres shortcut: \d orders shows FKs inline.
\d orders

Example #

Loading SQL editor...

Common variants #

sql
-- Find tables that reference a given parent (the "what cascades from this?" query).
SELECT
  kcu.table_name        AS child_table,
  kcu.column_name       AS child_column,
  rc.delete_rule
FROM   information_schema.referential_constraints rc
JOIN   information_schema.key_column_usage kcu
  ON   rc.constraint_name = kcu.constraint_name
JOIN   information_schema.constraint_column_usage ccu
  ON   rc.unique_constraint_name = ccu.constraint_name
WHERE  ccu.table_name = 'users';                       -- parent table

-- Postgres: full FK details using pg_catalog (richer, faster).
SELECT
  conrelid::regclass        AS child_table,
  a.attname                  AS child_column,
  confrelid::regclass        AS parent_table,
  af.attname                 AS parent_column,
  confdeltype                AS on_delete,
  confupdtype                AS on_update
FROM   pg_constraint c
JOIN   pg_attribute  a  ON a.attrelid  = c.conrelid AND a.attnum = ANY(c.conkey)
JOIN   pg_attribute  af ON af.attrelid = c.confrelid AND af.attnum = ANY(c.confkey)
WHERE  c.contype = 'f';

-- SQL Server.
SELECT
  OBJECT_NAME(parent_object_id)             AS child_table,
  OBJECT_NAME(referenced_object_id)         AS parent_table,
  name                                      AS constraint_name
FROM   sys.foreign_keys;

Dialect comparison #

EngineSyntaxGotcha
PostgreSQLpg_constraint with contype = 'f'\d table in psql shows FKs inline
MySQLinformation_schema.referential_constraintsenforced on InnoDB tables only
SQL Serversys.foreign_keys
BigQueryinformation_schema constraint viewsFKs are declarative only, not enforced
Snowflakeinformation_schema constraint viewsFKs are declarative only, not enforced
OracleALL_CONSTRAINTS with CONSTRAINT_TYPE = 'R'
SQLitePRAGMA foreign_key_list('orders')one table at a time; enforcement needs PRAGMA foreign_keys = ON

Dialect notes #

  • Postgres: \d table shows incoming and outgoing FKs. pg_constraint is the lower-level catalog with everything; information_schema.referential_constraints is the portable view.
  • MySQL: information_schema.referential_constraints works. Note FKs are only enforced on InnoDB tables.
  • SQL Server: sys.foreign_keys and sys.foreign_key_columns are the system views; sp_help table_name shows constraints.
  • Snowflake / BigQuery / Redshift: FKs are declarative only — they appear in information_schema but are not enforced. Useful for documentation and BI tools (which often use them to suggest joins) but offer no integrity guarantee.
  • Oracle: USER_CONSTRAINTS / ALL_CONSTRAINTS filtered to CONSTRAINT_TYPE = 'R' (referential).
  • SQLite: no information_schema. PRAGMA foreign_key_list('table') lists outgoing FKs of a single table.

FAQ #

How do I find all foreign keys for a table?

Join information_schema.referential_constraints with key_column_usage and constraint_column_usage. The portable form returns the source column, target table, target column, and ON DELETE/UPDATE rules. Postgres shortcut: \d table_name in psql shows FKs inline alongside columns.

How do I find tables that reference a specific parent?

Filter key_column_usage (or equivalent) by the target table. The "what cascades from this?" query. Useful before deleting a parent row, or when planning to change a primary key.

How do I know which engines enforce foreign keys?

Postgres / MySQL InnoDB / SQL Server / Oracle / SQLite (with PRAGMA foreign_keys = ON): yes. Snowflake / BigQuery / Redshift: declared but not enforced. MySQL MyISAM: not enforced. The information_schema query returns the constraint metadata regardless of whether enforcement is active.

How can I generate an ERD from foreign key data?

Pull the FK metadata via information_schema, format as DBML, and feed to dbdiagram.io / dbdocs.io. Or generate a Graphviz DOT file from the same data. The diagram updates as the schema changes, with no hand-drawing.

Are foreign keys auto-indexed?

On the parent (PK) side: yes, the primary key index covers it. On the child side: usually NOT. MySQL InnoDB is the exception: it auto-creates an index on the FK column. Other engines need an explicit CREATE INDEX on the FK column for parent-delete queries to perform well.

Why do my schema audits show FKs that don't actually enforce?

On Snowflake / BigQuery / Redshift, FK constraints are declarative metadata only. They appear in information_schema but the engine doesn't enforce them. They're still useful — BI tools (Looker, Tableau) read them to suggest joins; dbt tests can validate referential integrity in your tests rather than runtime.

Tip

Use FK introspection to auto-generate JOIN suggestions and schema diagrams. A small script that pulls FK metadata produces a graph of "what connects to what" in your database — feed it to dbdiagram.io, dbdocs.io, or even Graphviz, and you get a wall-poster ERD without hand-drawing one. Even better: for warehouses where FKs aren't enforced, declaring them anyway gives the BI tool (Looker, Tableau, Hex) the join graph it needs to suggest correct joins to analysts. The constraint costs nothing if not enforced; the documentation value is permanent.