Is there a way using SQL to list all foreign keys for a given table? I know the table name / schema and I can plug that in.
-
I suggest to use [@Magnus' answer](http://stackoverflow.com/a/1154078/939860). Simplest, cleanest, fastest. – Erwin Brandstetter Oct 10 '13 at 15:04
-
@ErwinBrandstetter However, the accepted answer gives columns and table names directly, so we don't need to parse them. It would be nice if Magnus' answer could do that too. – Rodrigo Jul 14 '20 at 21:00
31 Answers
You can do this via the information_schema tables. For example:
SELECT
tc.table_schema,
tc.constraint_name,
tc.table_name,
kcu.column_name,
ccu.table_schema AS foreign_table_schema,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name
FROM
information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
AND ccu.table_schema = tc.table_schema
WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name='mytable';

- 19,655
- 10
- 58
- 76

- 5,589
- 1
- 17
- 8
-
9table_name='mytable' should be tc.table_name='mytable' or else it throws an ambiguous error – intrepion Jul 15 '11 at 23:50
-
21+1, very helpful. To make the query more robust it should probably join on constraint_schema as well, since it's possible for two schemas to have constraints with the same name. Something like: `FROM information_schema.table_constraints AS tc JOIN information_schema.key_column_usage AS kcu USING (constraint_schema, constraint_name) JOIN information_schema.constraint_column_usage AS ccu USING (constraint_schema, constraint_name)` – EMP Aug 26 '11 at 06:41
-
9This breaks when there are several columns in a constraint, doesn't it? There seems to be no proper way to associate pk columns with fk columns using information_schema BTW. – fionbio Jun 01 '12 at 18:54
-
5It indeed breaks with more than one column in constraint. For Postgres, there is a way of getting this information from the pg_catalog schema. See my answer below. – martin Jun 08 '12 at 14:07
-
18The query is wrong. It assumes that constraint names cannot repeat, which is false. Constraints with the same name can exist in different namespaces. You are using constraint_name to make the join. Also joinning on both constraint_name and schema name won't work since you are not sure the two constraints are the same. The only option is going for pg_constraints, pg_class etc. using oids to join. Postgres' ANSI catalog is only there for compliance but it's flawed. pg_catalog is the way to go. The correct answer is here http://dba.stackexchange.com/questions/36979/retrieving-all-pk-and-fk – Tulains Córdova Nov 24 '15 at 18:18
-
For some reason this works but is very slow for me, it takes 5-6 seconds to run on a schema with 1 table and 2 constraints – Alexandru Severin Jul 01 '16 at 12:19
-
w/ schemas: `SELECT tc.constraint_name, tc.table_schema||'.'||tc.table_name table_name , kcu.column_name, ccu.table_schema||'.'||ccu.table_name AS foreign_table_name, ccu.column_name AS foreign_column_name FROM information_schema.table_constraints AS tc JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name WHERE constraint_type = 'FOREIGN KEY' ~ – gerardw Jun 01 '17 at 11:40
-
1@Jan'sp͇̦̮l͈͕̰i͔te'K. it does work if the user is the owner of the tables. The user doesn't need to be a superuser. – jakubiszon Feb 16 '18 at 09:31
-
@fionbio There is a proper way to join multiple columns, even when they have different names in the two tables, just look at my answer https://stackoverflow.com/a/48824659/523898 – jakubiszon Feb 16 '18 at 10:25
-
You need to join on schema too. If you have constraints with the same name over multiple schemas you might get false positives. – André C. Andersen Jun 19 '18 at 08:20
-
This query is gotten really slow (10-100 times slower) in PostgreSQL 12. Do you have any idea how it could be improved? – Csuki Aug 07 '20 at 12:27
-
I agree with @fionbio, so I upgraded this query to join with row order. Please see my [answer](https://stackoverflow.com/a/64868754/2958717) – doctorgu Nov 17 '20 at 03:11
-
psql does this, and if you start psql with:
psql -E
it will show you exactly what query is executed. In the case of finding foreign keys, it's:
SELECT conname,
pg_catalog.pg_get_constraintdef(r.oid, true) as condef
FROM pg_catalog.pg_constraint r
WHERE r.conrelid = '16485' AND r.contype = 'f' ORDER BY 1
In this case, 16485 is the oid of the table I'm looking at - you can get that one by just casting your tablename to regclass like:
WHERE r.conrelid = 'mytable'::regclass
Schema-qualify the table name if it's not unique (or the first in your search_path
):
WHERE r.conrelid = 'myschema.mytable'::regclass

- 605,456
- 145
- 1,078
- 1,228

- 23,890
- 5
- 56
- 43
-
2This is very handy! Postgres seems to have a million little functions like this that make everything simpler. Now how to remember them? – epic_fil Feb 06 '14 at 22:22
-
6@Phil: You only need a general idea. [Let the manual remember the rest.](http://www.postgresql.org/docs/current/interactive/functions-info.html#FUNCTIONS-INFO-CATALOG-TABLE) – Erwin Brandstetter Feb 07 '14 at 13:42
-
8to list all foreign keys targeting a table: `SELECT conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef FROM pg_catalog.pg_constraint r WHERE r.confrelid = 'myschema.mytable'::regclass;` – regilero Oct 20 '15 at 12:23
-
1@ErwinBrandstetter how do i do to get a foreign table name? – Wellington Silva Ribeiro Jul 10 '18 at 20:26
-
3I don't get it, what command should be use ? `psql -E -U username -d database ThenWHAT` ? – Poutrathor Jan 17 '19 at 18:52
-
1@Poutrathor: `psql -E` directs `psql` to display the actual queries behind psql commands like `\dt`, `\d`, etc. So it's a way to explore what those commands do. It's not needed to run the actual query in the answer. – user1071847 Jan 14 '20 at 18:17
-
If you want to see the foreign table as well: `SELECT r.conname, r.conrelid, pc.relname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef FROM pg_catalog.pg_constraint r INNER JOIN pg_class pc ON pc.oid = r.conrelid WHERE r.confrelid = 'my_schema.my_table'::regclass;` – Bruce Pierson Aug 27 '21 at 23:21
Issue \d+ tablename
on PostgreSQL prompt, in addition to showing table column's data types it'll show the indexes and foreign keys.
-
Sorry didn't notice my comment was cropped. If you could at least try it once, you'd see the foreign key mappings are displayed as well. – Gre Hahn Nov 22 '15 at 05:12
-
At least as of 12.5, `\d` is sufficient but `\d+` will show additional (non-FK information) e.g. column `Stats target`, `Description` – xlm Apr 17 '23 at 04:19
Ollyc's answer is good as it is not Postgres-specific, however, it breaks down when the foreign key references more than one column. The following query works for arbitrary number of columns but it relies heavily on Postgres extensions:
select
att2.attname as "child_column",
cl.relname as "parent_table",
att.attname as "parent_column",
conname
from
(select
unnest(con1.conkey) as "parent",
unnest(con1.confkey) as "child",
con1.confrelid,
con1.conrelid,
con1.conname
from
pg_class cl
join pg_namespace ns on cl.relnamespace = ns.oid
join pg_constraint con1 on con1.conrelid = cl.oid
where
cl.relname = 'child_table'
and ns.nspname = 'child_schema'
and con1.contype = 'f'
) con
join pg_attribute att on
att.attrelid = con.confrelid and att.attnum = con.child
join pg_class cl on
cl.oid = con.confrelid
join pg_attribute att2 on
att2.attrelid = con.conrelid and att2.attnum = con.parent

- 2,520
- 22
- 29
-
before 8.4 the function unnest has to be created at first. http://wiki.postgresql.org/wiki/Array_Unnest – maletin Oct 04 '12 at 08:12
-
Where does one insert the table name into this query? Entered verbatim the above returns 0 rows on my PSQL DB that has tens of foreign keys. – Phrogz Mar 10 '13 at 16:33
-
4You replace 'child_table' and 'child_schema' with the names of the table and its schema – martin Apr 09 '13 at 10:47
-
-
-
2I think that `'child_table'` is in fact `'parent_table'`, because `cl.relname` is in the `cl` table which is joined over `confrelid` that is the referenced (parent) table. If you wanna filter on child table you should add `join pg_class cl2 on cl2.oid = con.conrelid` and then use `cl2.relname = 'child_table'`. – Teejay Aug 01 '17 at 15:38
-
Oh.. I see that you are using **parent** and **child** in the opposite way respect to me. Normally, parent table is the referenced one, because if the record in it does not exist, neither the record in the child could exist. – Teejay Aug 01 '17 at 15:48
-
1
-
Extension to ollyc recipe :
CREATE VIEW foreign_keys_view AS
SELECT
tc.table_name, kcu.column_name,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name
FROM
information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage
AS kcu ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage
AS ccu ON ccu.constraint_name = tc.constraint_name
WHERE constraint_type = 'FOREIGN KEY';
Then:
SELECT * FROM foreign_keys_view WHERE table_name='YourTableNameHere'
;
check the ff post for your solution and don't forget to mark this when you fine this helpful
http://errorbank.blogspot.com/2011/03/list-all-foreign-keys-references-for.html
SELECT
o.conname AS constraint_name,
(SELECT nspname FROM pg_namespace WHERE oid=m.relnamespace) AS source_schema,
m.relname AS source_table,
(SELECT a.attname FROM pg_attribute a WHERE a.attrelid = m.oid AND a.attnum = o.conkey[1] AND a.attisdropped = false) AS source_column,
(SELECT nspname FROM pg_namespace WHERE oid=f.relnamespace) AS target_schema,
f.relname AS target_table,
(SELECT a.attname FROM pg_attribute a WHERE a.attrelid = f.oid AND a.attnum = o.confkey[1] AND a.attisdropped = false) AS target_column
FROM
pg_constraint o LEFT JOIN pg_class f ON f.oid = o.confrelid LEFT JOIN pg_class m ON m.oid = o.conrelid
WHERE
o.contype = 'f' AND o.conrelid IN (SELECT oid FROM pg_class c WHERE c.relkind = 'r');
-
Offers two SQLs that work on PostgreSQL 9.1 (once you correct the wrong escaping put your 'tablename' (without schema-prefix) into the SQL). – alfonx Jun 14 '12 at 23:59
-
2
-
-
2This solution will only show the first column of any multi-column foreign keys... but looks so much simpler than the one I just posted that will do multiples. – dewin Apr 22 '16 at 17:38
This query works correct with composite keys also:
select c.constraint_name
, x.table_schema as schema_name
, x.table_name
, x.column_name
, y.table_schema as foreign_schema_name
, y.table_name as foreign_table_name
, y.column_name as foreign_column_name
from information_schema.referential_constraints c
join information_schema.key_column_usage x
on x.constraint_name = c.constraint_name
join information_schema.key_column_usage y
on y.ordinal_position = x.position_in_unique_constraint
and y.constraint_name = c.unique_constraint_name
order by c.constraint_name, x.ordinal_position

- 347
- 3
- 4
-
3You're joining the columns on "constraint_name", so this will only work if all of your constraint names are unique (across all tables in all schemas). This is not usually a requirement, and thus not enforced by the database. – Zilk Sep 11 '13 at 17:33
-
4Thanks. This is the only answer that shows how to use information_schema to properly handle multiple columns. – Samuel Danielson Mar 01 '17 at 09:14
-
2This solution works. It doesn't produce duplicates and it handles multiple fields in the FK. – Igor Jul 29 '17 at 17:39
-
Most of the other answers here return duplicates if a foreign key consists of 2 or more columns. This sql can handle this - and is using the information_schema, which makes it actually easy to understand. – Andreas Aug 19 '21 at 19:25
Just replace 'your table name' in below query with your table name.
short but sweet upvote if it works for you.
select * from information_schema.key_column_usage
where constraint_catalog=current_catalog and table_name='your_table_name'
and position_in_unique_constraint notnull;

- 1,508
- 14
- 23
-
1
-
Just update "table_name='your_table_name'" in above query and you are done. – NikhilP Mar 24 '22 at 17:40
I think what you were looking for and very close to what @ollyc wrote is this:
SELECT
tc.constraint_name, tc.table_name, kcu.column_name,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name
FROM
information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
WHERE constraint_type = 'FOREIGN KEY' AND ccu.table_name='YourTableNameHere';
This will list all the tables that use your specified table as a foreign key

- 78,363
- 46
- 261
- 468

- 773
- 7
- 15
One another way:
WITH foreign_keys AS (
SELECT
conname,
conrelid,
confrelid,
unnest(conkey) AS conkey,
unnest(confkey) AS confkey
FROM pg_constraint
WHERE contype = 'f' -- AND confrelid::regclass = 'your_table'::regclass
)
-- if conrelid, conname pair shows up more than once then it is multicolumn foreign key
SELECT
fk.conname as constraint_name,
fk.conrelid::regclass as referencing_table, a.attname as fkcol,
fk.confrelid::regclass as referenced_table, af.attname as ukcol
FROM foreign_keys fk
JOIN pg_attribute af ON af.attnum = fk.confkey AND af.attrelid = fk.confrelid
JOIN pg_attribute a ON a.attnum = conkey AND a.attrelid = fk.conrelid
ORDER BY fk.conrelid, fk.conname;

- 630,263
- 148
- 957
- 1,375

- 231
- 3
- 4
-
This is the nicest answer so far. It is concise, works properly with duplicate constraint names, and JOINs easily against the `pg_attribute` tables so that attribute properties can be tested – Bergi Mar 15 '23 at 23:10
None of the existing answers gave me results in the form that I actually wanted them in. So here is my (gargantuan) query for finding information about foreign keys.
A few notes:
- The expressions used to generate
from_cols
andto_cols
could be vastly simplified on Postgres 9.4 and later usingWITH ORDINALITY
rather than the window-function-using hackery I'm using. - Those same expressions are relying on the query planner not altering the returned order of results from
UNNEST
. I don't think it will, but I don't have any multiple-column foreign keys in my dataset to test with. Adding the 9.4 niceties eliminates this possibility altogether. - The query itself requires Postgres 9.0 or later (8.x didn't allow
ORDER BY
in aggregate functions) - Replace
STRING_AGG
withARRAY_AGG
if you want an array of columns rather than a comma-separated string.
-
SELECT
c.conname AS constraint_name,
(SELECT n.nspname FROM pg_namespace AS n WHERE n.oid=c.connamespace) AS constraint_schema,
tf.name AS from_table,
(
SELECT STRING_AGG(QUOTE_IDENT(a.attname), ', ' ORDER BY t.seq)
FROM
(
SELECT
ROW_NUMBER() OVER (ROWS UNBOUNDED PRECEDING) AS seq,
attnum
FROM
UNNEST(c.conkey) AS t(attnum)
) AS t
INNER JOIN pg_attribute AS a ON a.attrelid=c.conrelid AND a.attnum=t.attnum
) AS from_cols,
tt.name AS to_table,
(
SELECT STRING_AGG(QUOTE_IDENT(a.attname), ', ' ORDER BY t.seq)
FROM
(
SELECT
ROW_NUMBER() OVER (ROWS UNBOUNDED PRECEDING) AS seq,
attnum
FROM
UNNEST(c.confkey) AS t(attnum)
) AS t
INNER JOIN pg_attribute AS a ON a.attrelid=c.confrelid AND a.attnum=t.attnum
) AS to_cols,
CASE confupdtype WHEN 'r' THEN 'restrict' WHEN 'c' THEN 'cascade' WHEN 'n' THEN 'set null' WHEN 'd' THEN 'set default' WHEN 'a' THEN 'no action' ELSE NULL END AS on_update,
CASE confdeltype WHEN 'r' THEN 'restrict' WHEN 'c' THEN 'cascade' WHEN 'n' THEN 'set null' WHEN 'd' THEN 'set default' WHEN 'a' THEN 'no action' ELSE NULL END AS on_delete,
CASE confmatchtype::text WHEN 'f' THEN 'full' WHEN 'p' THEN 'partial' WHEN 'u' THEN 'simple' WHEN 's' THEN 'simple' ELSE NULL END AS match_type, -- In earlier postgres docs, simple was 'u'nspecified, but current versions use 's'imple. text cast is required.
pg_catalog.pg_get_constraintdef(c.oid, true) as condef
FROM
pg_catalog.pg_constraint AS c
INNER JOIN (
SELECT pg_class.oid, QUOTE_IDENT(pg_namespace.nspname) || '.' || QUOTE_IDENT(pg_class.relname) AS name
FROM pg_class INNER JOIN pg_namespace ON pg_class.relnamespace=pg_namespace.oid
) AS tf ON tf.oid=c.conrelid
INNER JOIN (
SELECT pg_class.oid, QUOTE_IDENT(pg_namespace.nspname) || '.' || QUOTE_IDENT(pg_class.relname) AS name
FROM pg_class INNER JOIN pg_namespace ON pg_class.relnamespace=pg_namespace.oid
) AS tt ON tt.oid=c.confrelid
WHERE c.contype = 'f' ORDER BY 1;

- 161
- 1
- 3
You can use the PostgreSQL system catalogs. Maybe you can query pg_constraint to ask for foreign keys. You can also use the Information Schema

- 46,642
- 28
- 120
- 174
Here is a solution by Andreas Joseph Krogh from the PostgreSQL mailing list: http://www.postgresql.org/message-id/200811072134.44750.andreak@officenet.no
SELECT source_table::regclass, source_attr.attname AS source_column,
target_table::regclass, target_attr.attname AS target_column
FROM pg_attribute target_attr, pg_attribute source_attr,
(SELECT source_table, target_table, source_constraints[i] source_constraints, target_constraints[i] AS target_constraints
FROM
(SELECT conrelid as source_table, confrelid AS target_table, conkey AS source_constraints, confkey AS target_constraints,
generate_series(1, array_upper(conkey, 1)) AS i
FROM pg_constraint
WHERE contype = 'f'
) query1
) query2
WHERE target_attr.attnum = target_constraints AND target_attr.attrelid = target_table AND
source_attr.attnum = source_constraints AND source_attr.attrelid = source_table;
This solution handles foreign keys that reference multiple columns, and avoids duplicates (which some of the other answers fail to do). The only thing I changed were the variable names.
Here is an example that returns all employee
columns that reference the permission
table:
SELECT source_column
FROM foreign_keys
WHERE source_table = 'employee'::regclass AND target_table = 'permission'::regclass;

- 86,244
- 97
- 390
- 689
To expand upon Martin's excellent answer here is a query that lets you filter based on the parent table and shows you the name of the child table with each parent table so you can see all of the dependent tables/columns based upon the foreign key constraints in the parent table.
select
con.constraint_name,
att2.attname as "child_column",
cl.relname as "parent_table",
att.attname as "parent_column",
con.child_table,
con.child_schema
from
(select
unnest(con1.conkey) as "parent",
unnest(con1.confkey) as "child",
con1.conname as constraint_name,
con1.confrelid,
con1.conrelid,
cl.relname as child_table,
ns.nspname as child_schema
from
pg_class cl
join pg_namespace ns on cl.relnamespace = ns.oid
join pg_constraint con1 on con1.conrelid = cl.oid
where con1.contype = 'f'
) con
join pg_attribute att on
att.attrelid = con.confrelid and att.attnum = con.child
join pg_class cl on
cl.oid = con.confrelid
join pg_attribute att2 on
att2.attrelid = con.conrelid and att2.attnum = con.parent
where cl.relname like '%parent_table%'

- 3,112
- 1
- 24
- 27
-
1The query in the accepted answer adds 1.2 secs to a ~ 0.03 query, yours adds only 0.01, thanks! – AVProgrammer Jan 06 '17 at 19:00
Proper solution to the problem, using information_schema
, working with multi column keys, joining columns of different names in both tables correctly and also compatible with ms sqlsever:
select fks.TABLE_NAME as foreign_key_table_name
, fks.CONSTRAINT_NAME as foreign_key_constraint_name
, kcu_foreign.COLUMN_NAME as foreign_key_column_name
, rc.UNIQUE_CONSTRAINT_NAME as primary_key_constraint_name
, pks.TABLE_NAME as primary_key_table_name
, kcu_primary.COLUMN_NAME as primary_key_column_name
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS fks -- foreign keys
inner join INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu_foreign -- the columns of the above keys
on fks.TABLE_CATALOG = kcu_foreign.TABLE_CATALOG
and fks.TABLE_SCHEMA = kcu_foreign.TABLE_SCHEMA
and fks.TABLE_NAME = kcu_foreign.TABLE_NAME
and fks.CONSTRAINT_NAME = kcu_foreign.CONSTRAINT_NAME
inner join INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc -- referenced constraints
on rc.CONSTRAINT_CATALOG = fks.CONSTRAINT_CATALOG
and rc.CONSTRAINT_SCHEMA = fks.CONSTRAINT_SCHEMA
and rc.CONSTRAINT_NAME = fks.CONSTRAINT_NAME
inner join INFORMATION_SCHEMA.TABLE_CONSTRAINTS pks -- primary keys (referenced by fks)
on rc.UNIQUE_CONSTRAINT_CATALOG = pks.CONSTRAINT_CATALOG
and rc.UNIQUE_CONSTRAINT_SCHEMA = pks.CONSTRAINT_SCHEMA
and rc.UNIQUE_CONSTRAINT_NAME = pks.CONSTRAINT_NAME
inner join INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu_primary
on pks.TABLE_CATALOG = kcu_primary.TABLE_CATALOG
and pks.TABLE_SCHEMA = kcu_primary.TABLE_SCHEMA
and pks.TABLE_NAME = kcu_primary.TABLE_NAME
and pks.CONSTRAINT_NAME = kcu_primary.CONSTRAINT_NAME
and kcu_foreign.ORDINAL_POSITION = kcu_primary.ORDINAL_POSITION -- this joins the columns
where fks.TABLE_SCHEMA = 'dbo' -- replace with schema name
and fks.TABLE_NAME = 'your_table_name' -- replace with table name
and fks.CONSTRAINT_TYPE = 'FOREIGN KEY'
and pks.CONSTRAINT_TYPE = 'PRIMARY KEY'
order by fks.constraint_name, kcu_foreign.ORDINAL_POSITION
Note: There are some differences between potgresql and sqlserver implementations of information_schema
which make the top answer give different results on the two systems - one shows column names for the foreign key table the other for the primary key table. For this reason I decided to use KEY_COLUMN_USAGE view instead.

- 3,229
- 1
- 27
- 41
-
The information schema seems like the right answer, but really you want the pg_catalog tables: pg_constraint etc. We got bitten hard by this. if your database has large amounts of constraints there can be performance issues... – hajikelist Mar 16 '18 at 03:03
-
1The above condition on `ORDINAL_POSITION` can yield incorrect result when the order of columns in foreign key is different from the order of columns in the unique constraint. I believe you should have joined on `kcu_foreign.POSITION_IN_UNIQUE_CONSTRAINT = kcu_primary.ORDINAL_POSITION` **Update**: Also, a foreign key may depend on a UNIQUE constraint as well, so I think you should remove the `pks.CONSTRAINT_TYPE` condition and can just join `rc` to `kcu_primary` directly – easd Jun 09 '20 at 11:06
-
I have made a similar answer here: https://stackoverflow.com/a/62260908/9093051 – easd Jun 09 '20 at 11:12
Use the name of the Primary Key to which the Keys are referencing and query the information_schema:
select table_name, column_name
from information_schema.key_column_usage
where constraint_name IN (select constraint_name
from information_schema.referential_constraints
where unique_constraint_name = 'TABLE_NAME_pkey')
Here 'TABLE_NAME_pkey' is the name of the Primary Key referenced by the Foreign Keys.

- 11,116
- 8
- 73
- 109
SELECT r.conname
,ct.table_name
,pg_catalog.pg_get_constraintdef(r.oid, true) as condef
FROM pg_catalog.pg_constraint r, information_schema.constraint_table_usage ct
WHERE r.contype = 'f'
AND r.conname = ct.constraint_name
ORDER BY 1

- 605,456
- 145
- 1,078
- 1,228

- 41
- 1
SELECT
conrelid::regclass AS table_from,
conname,
pg_get_constraintdef(oid) as condef
FROM pg_catalog.pg_constraint r

- 12,950
- 7
- 74
- 95
I created little tool to query and then compare database schema: Dump PostgreSQL db schema to text
There is info about FK, but ollyc response gives more details.

- 53,067
- 18
- 70
- 114
This is what I'm currently using, it will list a table and it's fkey constraints [remove table clause and it will list all tables in current catalog]:
SELECT
current_schema() AS "schema",
current_catalog AS "database",
"pg_constraint".conrelid::regclass::text AS "primary_table_name",
"pg_constraint".confrelid::regclass::text AS "foreign_table_name",
(
string_to_array(
(
string_to_array(
pg_get_constraintdef("pg_constraint".oid),
'('
)
)[2],
')'
)
)[1] AS "foreign_column_name",
"pg_constraint".conindid::regclass::text AS "constraint_name",
TRIM((
string_to_array(
pg_get_constraintdef("pg_constraint".oid),
'('
)
)[1]) AS "constraint_type",
pg_get_constraintdef("pg_constraint".oid) AS "constraint_definition"
FROM pg_constraint AS "pg_constraint"
JOIN pg_namespace AS "pg_namespace" ON "pg_namespace".oid = "pg_constraint".connamespace
WHERE
--fkey and pkey constraints
"pg_constraint".contype IN ( 'f', 'p' )
AND
"pg_namespace".nspname = current_schema()
AND
"pg_constraint".conrelid::regclass::text IN ('whatever_table_name')

- 1,136
- 9
- 9
-
Cool, but this `"pg_constraint".conindid::regclass::text` gives you the PK name of the referenced table, the constraint name is `"pg_constraint".conname` – Tiago Oliveira de Freitas Dec 29 '21 at 17:11
I wrote a solution that like and use frequently. The code is at http://code.google.com/p/pgutils/. See the pgutils.foreign_keys view.
Unfortunately, the output is too wordy to include here. However, you can try it on a public version of the database here, like this:
$ psql -h unison-db.org -U PUBLIC -d unison -c 'select * from pgutils.foreign_keys;
This works with 8.3 at least. I anticipate updating it, if needed, in the next few months.
-Reece

- 7,616
- 4
- 30
- 46
-
1
-
@pimlottc: Moved to https://bitbucket.org/reece/pgutils. Thanks for pointing this out. – Reece Sep 27 '14 at 23:59
I upgraded answer of @ollyc which is currently at top.
I agree with @fionbio because key_column_usage
and constraint_column_usage
has no relative information at column level.
If constraint_column_usage
has ordinal_positon
column like key_column_usage
, it can be joined with this column. So I made a ordinal_position
to constraint_column_usage
as below.
I cannot confirm this manually created ordinal_position
is exactly in same order with key_column_usage
. But I checked it is exactly same order at least in my case.
SELECT
tc.table_schema,
tc.constraint_name,
tc.table_name,
kcu.column_name,
ccu.table_schema AS foreign_table_schema,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name
FROM
information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN (select row_number() over (partition by table_schema, table_name, constraint_name order by row_num) ordinal_position,
table_schema, table_name, column_name, constraint_name
from (select row_number() over (order by 1) row_num, table_schema, table_name, column_name, constraint_name
from information_schema.constraint_column_usage
) t
) AS ccu
ON ccu.constraint_name = tc.constraint_name
AND ccu.table_schema = tc.table_schema
AND ccu.ordinal_position = kcu.ordinal_position
WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = 'mytable'

- 616
- 6
- 9
SELECT conrelid::regclass AS table_name,
conname AS foreign_key,
pg_get_constraintdef(oid)
FROM pg_constraint
WHERE contype = 'f'
AND connamespace = 'public'::regnamespace
ORDER BY conrelid::regclass::text, contype DESC;
https://soft-builder.com/how-to-list-all-foreign-keys-in-postgresql-database

- 561
- 8
- 10
My own contribution. The goal is to backup the definition of all the foreign keys:
SELECT
'ALTER TABLE ' || tc.table_schema || '.' || tc.table_name || E'\n
ADD FOREIGN KEY (' || kcu.column_name || ')' || E'\n
REFERENCES ' || ccu.table_schema || '.' || ccu.table_name ||
' (' || ccu.column_name || ') ' || E'\n ' ||
CASE WHEN rc.match_option <> 'NONE' THEN E'\n
MATCH ' || rc.match_option ELSE '' END ||
CASE WHEN rc.update_rule <> 'NO ACTION' THEN E'\n
ON UPDATE ' || rc.update_rule || ' ' ELSE '' END ||
CASE WHEN rc.delete_rule <> 'NO ACTION'
THEN 'ON DELETE ' || rc.delete_rule ELSE '' END || ';'
AS add_constraint
FROM
information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
AND ccu.table_schema = tc.table_schema
JOIN information_schema.referential_constraints AS rc
ON tc.constraint_name=rc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
\t\a\g\a\ta
improving the query from the most popular answer
because for postgresql 12+ information_schema is very slow
it helped me:
SELECT sh.nspname AS table_schema,
tbl.relname AS table_name,
col.attname AS column_name,
referenced_sh.nspname AS foreign_table_schema,
referenced_tbl.relname AS foreign_table_name,
referenced_field.attname AS foreign_column_name
FROM pg_constraint c
INNER JOIN pg_namespace AS sh ON sh.oid = c.connamespace
INNER JOIN (SELECT oid, unnest(conkey) as conkey FROM pg_constraint) con ON c.oid = con.oid
INNER JOIN pg_class tbl ON tbl.oid = c.conrelid
INNER JOIN pg_attribute col ON (col.attrelid = tbl.oid AND col.attnum = con.conkey)
INNER JOIN pg_class referenced_tbl ON c.confrelid = referenced_tbl.oid
INNER JOIN pg_namespace AS referenced_sh ON referenced_sh.oid = referenced_tbl.relnamespace
INNER JOIN (SELECT oid, unnest(confkey) as confkey FROM pg_constraint) conf ON c.oid = conf.oid
INNER JOIN pg_attribute referenced_field ON (referenced_field.attrelid = c.confrelid AND referenced_field.attnum = conf.confkey)
WHERE c.contype = 'f'

- 131
- 1
- 4
The selected answer didn't work for me, so posting my sql that worked.
select
con.conname as constraint_name,
src_schema.nspname as source_schema,
source.relname as source_table,
source_col.attname as source_column,
trg_schema.nspname as target_schema,
target.relname as target_table,
target_col.attname as target_column
from
pg_constraint con
inner join
pg_class source on source.oid = con.conrelid
inner join
pg_attribute source_col on source_col.attrelid = con.conrelid and source_col.attnum = con.conkey[1] and source_col.attisdropped = false
inner join
pg_namespace src_schema on src_schema.oid = source.relnamespace
inner join
pg_class target on target.oid = con.confrelid
inner join
pg_attribute target_col on target_col.attrelid = con.confrelid and target_col.attnum = con.confkey[1] and source_col.attisdropped = false
inner join
pg_namespace trg_schema on trg_schema.oid = target.relnamespace

- 314
- 1
- 12
Note: Do not forget column's order while reading constraint columns!
SELECT conname, attname
FROM pg_catalog.pg_constraint c
JOIN pg_catalog.pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY (c.conkey)
WHERE attrelid = 'schema.table_name'::regclass
ORDER BY conname, array_position(c.conkey, a.attnum)

- 490
- 4
- 10
the fastest to verify straight in bash answer based entirely on this answer
IFS='' read -r -d '' sql_code << EOF_SQL_CODE
SELECT
o.oid
, o.conname AS constraint_name
, (SELECT nspname FROM pg_namespace WHERE oid=m.relnamespace) AS source_schema
, m.relname AS source_table
, (SELECT a.attname FROM pg_attribute a
WHERE a.attrelid = m.oid AND a.attnum = o.conkey[1] AND a.attisdropped = false) AS source_column
, (SELECT nspname FROM pg_namespace
WHERE oid=f.relnamespace) AS target_schema
, f.relname AS target_table
, (SELECT a.attname FROM pg_attribute a
WHERE a.attrelid = f.oid AND a.attnum = o.confkey[1] AND a.attisdropped = false) AS target_column
, ROW_NUMBER () OVER (ORDER BY o.oid) as rowid
FROM pg_constraint o
LEFT JOIN pg_class f ON f.oid = o.confrelid
LEFT JOIN pg_class m ON m.oid = o.conrelid
WHERE 1=1
AND o.contype = 'f'
AND o.conrelid IN (SELECT oid FROM pg_class c WHERE c.relkind = 'r')
EOF_SQL_CODE
psql -d my_db -c "$sql_code"

- 5,114
- 1
- 56
- 53
Where $1 ('my_schema') is the schema and $2 ('my_table') is the name of the table:
SELECT ss.conname constraint_name, a.attname column_name, ss.refnamespace fk_table_schema, ss.reflname fk_table_name, af.attname fk_column_name
FROM pg_attribute a, pg_attribute af,
(SELECT r.oid roid, c.conname, rf.relname reflname, information_schema._pg_expandarray(c.conkey) x,
nrf.nspname refnamespace, rf.oid rfoid, information_schema._pg_expandarray(cf.confkey) xf
FROM pg_namespace nr, pg_class r, pg_constraint c,
pg_namespace nrf, pg_class rf, pg_constraint cf
WHERE nr.oid = r.relnamespace
AND r.oid = c.conrelid
AND rf.oid = cf.confrelid
AND c.conname = cf.conname
AND nrf.oid = rf.relnamespace
AND nr.nspname = $1
AND r.relname = $2) ss
WHERE ss.roid = a.attrelid AND a.attnum = (ss.x).x AND NOT a.attisdropped
AND ss.rfoid = af.attrelid AND af.attnum = (ss.xf).x AND NOT af.attisdropped
ORDER BY ss.conname, a.attname;

- 56
- 4
IF someone want to use store procedures then here is the way to retreive FKs
--command-1
DROP TYPE IF EXISTS returnFkType CASCADE;
--command-2
DROP FUNCTION IF EXISTS returnFks;
--command-3
CREATE TYPE returnFkType as (
constraint_name varchar,
source_schema varchar,
target_schema varchar,
source_table varchar,
target_table varchar,
source_column varchar,
target_column varchar
);
--command-4
CREATE OR REPLACE FUNCTION returnFks(_tbl varchar)
RETURNS SETOF returnFkType
LANGUAGE sql
as $$
select
c.constraint_name,
x.table_schema as source_schema,
y.table_schema as target_schema,
x.table_name as source_table,
y.table_name as target_table,
x.column_name as source_column,
y.column_name as target_column
from information_schema.referential_constraints c
join information_schema.key_column_usage x on x.constraint_name = c.constraint_name
join information_schema.key_column_usage y on y.ordinal_position = x.position_in_unique_constraint
and y.constraint_name = c.unique_constraint_name
and x.table_name = $1
order by c.constraint_name, x.ordinal_position
$$
--command-5
select * from returnFks($_TABLE);

- 316
- 3
- 7
in case someone wants to know the foreign_keys and the columns they reference use this:
select
c.TABLE_SCHEMA,
c.TABLE_NAME,
c.COLUMN_NAME,
kcu.TABLE_SCHEMA AS references_schema,
kcu.TABLE_NAME AS references_table,
kcu.COLUMN_NAME AS references_column
from information_schema.columns as c
inner join information_schema.constraint_column_usage as ccu
on c.TABLE_SCHEMA = ccu.TABLE_SCHEMA
AND c.COLUMN_NAME = ccu.COLUMN_NAME
AND c.TABLE_NAME = ccu.TABLE_NAME
INNER JOIN information_schema.referential_constraints AS rc
ON ccu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
AND ccu.TABLE_SCHEMA = rc.CONSTRAINT_SCHEMA
INNER JOIN information_schema.key_column_usage AS kcu
ON rc.UNIQUE_CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA
AND rc.UNIQUE_CONSTRAINT_NAME = kcu.CONSTRAINT_NAME;
this is a fast one, sry for bad formatting, maybe some join conditions are missing.

- 51
- 1
- 3