0

I have this schema:

create table "cat" (
    "name" varchar(64),
    "owner" varchar(64),
    primary key ("name", "owner")
);

create table "comment" (
    "name" varchar(45),
    "owner" varchar(45),
    "id" uuid,
    "comment" text,
    primary key ("id"),
    foreign key ("name", "owner") references "cat"("name", "owner")
);

I want to get a list of foreign keys from table "comment" to "cat", so I use:

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 got almost what I want:

  constraint_name  | table_name | column_name | foreign_table_name | foreign_column_name 
-------------------+------------+-------------+--------------------+---------------------
 comment_name_fkey | comment    | owner       | cat                | name
 comment_name_fkey | comment    | name        | cat                | name
 comment_name_fkey | comment    | owner       | cat                | owner
 comment_name_fkey | comment    | name        | cat                | owner

But there are row 1 and row 4, which I would like to eliminate in the result, because it does not mirror the dependencies of column. How can I do it in Postgresql?

lazyboy
  • 301
  • 3
  • 12
  • This should be simple enough with `WHERE column_name = foreign_column_name`, wrap the whole query above as a subquery, if necessary. – Mike May 30 '22 at 15:38

3 Answers3

4

I think you can't do that using only information_schema, but you can do it querying directly tables:

SELECT conname AS constraint_name, conrelid::regclass AS table_name, ta.attname AS column_name,
       confrelid::regclass AS foreign_table_name, fa.attname AS foreign_column_name
  FROM (
   SELECT conname, conrelid, confrelid,
          unnest(conkey) AS conkey, unnest(confkey) AS confkey
     FROM pg_constraint
    WHERE conname = 'comment_name_fkey'
      --and contype = 'f'
  ) sub
  JOIN pg_attribute AS ta ON ta.attrelid = conrelid AND ta.attnum = conkey
  JOIN pg_attribute AS fa ON fa.attrelid = confrelid AND fa.attnum = confkey

result

  constraint_name  | table_name | column_name | foreign_table_name | foreign_column_name
-------------------+------------+-------------+--------------------+---------------------
 comment_name_fkey | comment    | name        | cat                | name
 comment_name_fkey | comment    | owner       | cat                | owner
Łukasz Kamiński
  • 5,630
  • 1
  • 19
  • 32
  • Use this query since I didn't had access to "constraint_column_usage" table. My final query: SELECT DISTINCT conrelid::regclass AS table_name, confrelid::regclass AS foreign_table_name FROM pg_constraint WHERE contype = 'f' ORDER BY table_name, foreign_table_name; – shlomiLan Nov 15 '21 at 10:38
  • It's also important to note that `information_schema` is significantly slower than retrieving (and joining) `pg_catalog`. – Mike May 30 '22 at 15:34
1

I think you should select a list of column names used in a reference as a whole:

SELECT
    tc.constraint_name, 
    tc.table_name, 
    string_agg(distinct kcu.column_name, ', ') AS column_names, 
    ccu.table_name AS foreign_table_name,
    string_agg(distinct ccu.column_name, ', ') AS foreign_column_names 
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 tc.table_name = 'comment'
GROUP BY 1, 2, 4;

  constraint_name  | table_name | column_names | foreign_table_name | foreign_column_names 
-------------------+------------+--------------+--------------------+----------------------
 comment_name_fkey | comment    | name, owner  | cat                | name, owner
(1 row) 

However, it is not sure that the column names will be in proper order, it depends on the way they are listed in information_schema.

The more reliable solution is to query the system catalog pg_constraint. The function get_col_names() is defined here: List all foreign keys PostgresSQL.

select 
    conname as constraint_name,
    conrelid::regclass as table_name,
    get_col_names(conrelid, conkey) as column_names,
    confrelid::regclass as foreign_table_name,
    get_col_names(confrelid, confkey) as foreing_column_names
from pg_constraint
where contype ='f'
and conrelid = 'comment'::regclass;

  constraint_name  | table_name | column_names | foreign_table_name | foreing_column_names 
-------------------+------------+--------------+--------------------+----------------------
 comment_name_fkey | comment    | name, owner  | cat                | name, owner
(1 row) 
klin
  • 112,967
  • 15
  • 204
  • 232
  • It's also important to note that `information_schema` is significantly slower than retrieving (and joining) `pg_catalog`. – Mike May 30 '22 at 15:33
1

The referential_constraints.unique_constraint_* and key_column_usage.ordinal_position columns can be used to properly join the foreign columns to their referenced columns. See this answer here https://stackoverflow.com/a/48824659/9093051.

Below is my stripped-down version:

SELECT
  rc.constraint_schema,
  rc.constraint_name,
  kcu.table_name,
  kcu.column_name,
  rcu.table_name AS referenced_table,
  rcu.column_name AS referenced_column
FROM information_schema.referential_constraints rc
LEFT JOIN information_schema.key_column_usage kcu
  ON rc.constraint_catalog = kcu.constraint_catalog
  AND rc.constraint_schema = kcu.constraint_schema
  AND rc.constraint_name = kcu.constraint_name
LEFT JOIN information_schema.key_column_usage rcu -- referenced columns
  ON rc.unique_constraint_catalog = rcu.constraint_catalog
  AND rc.unique_constraint_schema = rcu.constraint_schema
  AND rc.unique_constraint_name = rcu.constraint_name
  AND rcu.ordinal_position = kcu.position_in_unique_constraint;

Edit fixed the ordinal_position join condition

easd
  • 91
  • 1
  • 5