PostgreSQL: SQL 脚本,用于获取将特定列作为外键的所有表的列表

我正在使用 PostgreSQL,并试图列出所有具有来自某个表的特定列的表作为外键/引用。这能做到吗?我确信这些信息存储在 information_schema的某个地方,但我不知道如何开始查询它。

61999 次浏览
SELECT
r.table_name
FROM information_schema.constraint_column_usage       u
INNER JOIN information_schema.referential_constraints fk
ON u.constraint_catalog = fk.unique_constraint_catalog
AND u.constraint_schema = fk.unique_constraint_schema
AND u.constraint_name = fk.unique_constraint_name
INNER JOIN information_schema.key_column_usage        r
ON r.constraint_catalog = fk.constraint_catalog
AND r.constraint_schema = fk.constraint_schema
AND r.constraint_name = fk.constraint_name
WHERE
u.column_name = 'id' AND
u.table_catalog = 'db_name' AND
u.table_schema = 'public' AND
u.table_name = 'table_a'

它使用完整的目录/模式/名称三元组来标识来自所有3个 information _ schema 视图的 db 表。您可以根据需要放弃一个或两个。

该查询列出了所有对表“ d”中的“ a”列具有外键约束的表

就个人而言,我更喜欢基于引用的唯一约束而不是列进行查询。看起来像这样:

SELECT rc.constraint_catalog,
rc.constraint_schema||'.'||tc.table_name AS table_name,
kcu.column_name,
match_option,
update_rule,
delete_rule
FROM information_schema.referential_constraints AS rc
JOIN information_schema.table_constraints AS tc USING(constraint_catalog,constraint_schema,constraint_name)
JOIN information_schema.key_column_usage AS kcu USING(constraint_catalog,constraint_schema,constraint_name)
WHERE unique_constraint_catalog='catalog'
AND unique_constraint_schema='schema'
AND unique_constraint_name='constraint name';

下面是一个允许按列名查询的版本:

SELECT rc.constraint_catalog,
rc.constraint_schema||'.'||tc.table_name AS table_name,
kcu.column_name,
match_option,
update_rule,
delete_rule
FROM information_schema.referential_constraints AS rc
JOIN information_schema.table_constraints AS tc USING(constraint_catalog,constraint_schema,constraint_name)
JOIN information_schema.key_column_usage AS kcu USING(constraint_catalog,constraint_schema,constraint_name)
JOIN information_schema.key_column_usage AS ccu ON(ccu.constraint_catalog=rc.unique_constraint_catalog AND ccu.constraint_schema=rc.unique_constraint_schema AND ccu.constraint_name=rc.unique_constraint_name)
WHERE ccu.table_catalog='catalog'
AND ccu.table_schema='schema'
AND ccu.table_name='name'
AND ccu.column_name='column';

其他解决方案不能保证在 postgreql 中工作,因为约束 _ name 不能保证是唯一的; 因此会得到假阳性。PostgreSQL 过去常常把约束命名为“ $1”之类的愚蠢的东西,如果你有一个通过升级来维护的旧数据库,你可能仍然有一些这样的约束。

因为这个问题是针对 AT PostgreSQL 的,而这正是您正在使用的,所以您可以查询内部 postgres 表 pg _ class 和 pg _ tribute 以获得更准确的结果。

注意: FK 可以位于多个列上,因此引用列(pg _ tribute 的 attnum)是一个 ARRAY,这就是在答案中使用 ARRAY _ agg 的原因。

您唯一需要插入的是 TARGET _ TABLE _ NAME:

select
(select r.relname from pg_class r where r.oid = c.conrelid) as table,
(select array_agg(attname) from pg_attribute
where attrelid = c.conrelid and ARRAY[attnum] <@ c.conkey) as col,
(select r.relname from pg_class r where r.oid = c.confrelid) as ftable
from pg_constraint c
where c.confrelid = (select oid from pg_class where relname = 'TARGET_TABLE_NAME');

如果你想走另一条路(列出一个特定表格所引用的所有东西) ,那么只需将最后一行改为:

where c.conrelid = (select oid from pg_class where relname = 'TARGET_TABLE_NAME');

哦,因为实际的问题是针对特定的列,所以您可以用这个列指定列名:

select (select r.relname from pg_class r where r.oid = c.conrelid) as table,
(select array_agg(attname) from pg_attribute
where attrelid = c.conrelid and ARRAY[attnum] <@ c.conkey) as col,
(select r.relname from pg_class r where r.oid = c.confrelid) as ftable
from pg_constraint c
where c.confrelid = (select oid from pg_class where relname = 'TARGET_TABLE_NAME') and
c.confkey @> (select array_agg(attnum) from pg_attribute
where attname = 'TARGET_COLUMN_NAME' and attrelid = c.confrelid);

一个恢复外键名称和表名称的简单请求:

SELECT CONSTRAINT_NAME, table_name
FROM
information_schema.table_constraints
WHERE table_schema='public' and constraint_type='FOREIGN KEY'

此查询需要 只有引用的 表名列名,并生成包含外键两侧的结果集。

select confrelid::regclass, af.attname as fcol,
conrelid::regclass, a.attname as col
from pg_attribute af, pg_attribute a,
(select conrelid,confrelid,conkey[i] as conkey, confkey[i] as confkey
from (select conrelid,confrelid,conkey,confkey,
generate_series(1,array_upper(conkey,1)) as i
from pg_constraint where contype = 'f') ss) ss2
where af.attnum = confkey and af.attrelid = confrelid and
a.attnum = conkey and a.attrelid = conrelid
AND confrelid::regclass = 'my_table'::regclass AND af.attname = 'my_referenced_column';

示例结果集:

confrelid |         fcol         |   conrelid    |     col
----------+----------------------+---------------+-------------
my_table | my_referenced_column | some_relation | source_type
my_table | my_referenced_column | some_feature  | source_type

都归功于 Lane 和 Krogh 在 PostgreSQL 论坛

SELECT
main_table.table_name            AS main_table_table_name,
main_table.column_name           AS main_table_column_name,
main_table.constraint_name       AS main_table_constraint_name,
info_other_table.table_name      AS info_other_table_table_name,
info_other_table.constraint_name AS info_other_table_constraint_name,
info_other_table.column_name     AS info_other_table_column_name
FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE main_table
INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS other_table
ON other_table.unique_constraint_name = main_table.constraint_name
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE info_other_table
ON info_other_table.constraint_name = other_table.constraint_name
WHERE main_table.table_name = 'MAIN_TABLE_NAME';

如果使用 psql客户机,只需发出 \d table_name命令,查看哪些表引用给定的表。链接文档页面:

\d[S+] [ pattern ]

对于与 pattern匹配的每个关系(表、视图、物化视图、索引、序列或外部表)或复合类型, 显示所有列及其类型、表空间(如果不是默认值)和 任何特殊属性,如 NOT NULL或默认值 还显示了索引、约束、规则和触发器 表中,还会显示相关联的外部服务器。

表约束可以包括多个列 。正确处理这个问题的诀窍是通过 约束序列位置约束序列位置连接每个列。如果您没有正确地连接,那么每当一个表在一个唯一约束中有多个列时,您的脚本就会与 重复的行一起崩溃。

质疑

列出所有外键列及其引用。

select
-- unique reference info
ref.table_catalog    as ref_database,
ref.table_schema     as ref_schema,
ref.table_name       as ref_table,
ref.column_name      as ref_column,
refd.constraint_type as ref_type, -- e.g. UNIQUE or PRIMARY KEY


-- foreign key info
fk.table_catalog as fk_database,
fk.table_schema  as fk_schema,
fk.table_name    as fk_table,
fk.column_name   as fk_column,
map.update_rule  as fk_on_update,
map.delete_rule  as fk_on_delete


-- lists fk constraints and maps them to pk constraints
from information_schema.referential_constraints as map


-- join unique constraints (e.g. PKs constraints) to ref columns info
inner join information_schema.key_column_usage as ref
on  ref.constraint_catalog = map.unique_constraint_catalog
and ref.constraint_schema = map.unique_constraint_schema
and ref.constraint_name = map.unique_constraint_name


-- optional: to include reference constraint type
left join information_schema.table_constraints as refd
on  refd.constraint_catalog = ref.constraint_catalog
and refd.constraint_schema = ref.constraint_schema
and refd.constraint_name = ref.constraint_name


-- join fk columns to the correct ref columns using ordinal positions
inner join information_schema.key_column_usage as fk
on  fk.constraint_catalog = map.constraint_catalog
and fk.constraint_schema = map.constraint_schema
and fk.constraint_name = map.constraint_name
and fk.position_in_unique_constraint = ref.ordinal_position --IMPORTANT!

有用的链接

解释

考虑它们与表之间的关系。

create table foo (
a int,
b int,
primary key (a,b)
);


create table bar (
c int,
d int,
foreign key (c,d) references foo (b,a) -- i flipped a,b to make a point later.
);

获取表约束名称

select * from information_schema.table_constraints where table_name in ('foo','bar');
| constraint_name | table_name | constraint_type |
| --------------- | ---------- | --------------- |
| foo_pkey        | foo        | PRIMARY KEY     |
| bar_c_d_fkey    | bar        | FOREIGN KEY     |

约束引用

select * from information_schema.referential_constraints where constraint_name in ('bar_c_d_fkey');
| constraint_name | unique_constraint_name |
| --------------- | ---------------------- |
| bar_c_d_fkey    | foo_pkey               |

列的约束 ordinal_position

select * from information_schema.key_column_usage where table_name in ('foo','bar');
| constraint_name | table_name | column_name | ordinal_position | position_in_unique_constraint |
| --------------- | ---------- | ----------- | ---------------- | ----------------------------- |
| foo_pkey        | foo        | a           | 1                | null                          |
| foo_pkey        | foo        | b           | 2                | null                          |
| bar_c_d_fkey    | bar        | c           | 1                | 2                             |
| bar_c_d_fkey    | bar        | d           | 2                | 1                             |

现在剩下的就是将它们连接在一起。

我将 @ Tony K回答转换为一个可重用的函数,该函数接受模式/表/列元组,并返回所有具有外键关系的表: https://gist.github.com/colophonemes/53b08d26bdd219e6fc11677709e8fc6c

为了实现将两条记录合并为一条记录的脚本,我需要类似的东西。

功能:

CREATE SCHEMA utils;


-- Return type for the utils.get_referenced_tables function
CREATE TYPE utils.referenced_table_t AS (
constraint_name name,
schema_name name,
table_name name,
column_name name[],
foreign_schema_name name,
foreign_table_name name
);
/*
A function to get all downstream tables that are referenced to a table via a foreign key relationship
The function looks at all constraints that contain a reference to the provided schema-qualified table column
It then generates a list of the schema/table/column tuples that are the target of these references
Idea based on https://stackoverflow.com/a/21125640/7114675
Postgres built-in reference:
- pg_namespace  => schemas
- pg_class      => tables
- pg_attribute  => table columns
- pg_constraint => constraints
*/
CREATE FUNCTION utils.get_referenced_tables (schema_name name, table_name name, column_name name)
RETURNS SETOF utils.referenced_table_t AS $$
-- Wrap the internal query in a select so that we can order it more easily
SELECT * FROM (
-- Get human-readable names for table properties by mapping the OID's stored on the pg_constraint
-- table to the underlying value on their relevant table.
SELECT
-- constraint name - we get this directly from the constraints table
pg_constraint.conname AS constraint_name,
-- schema_name
(
SELECT pg_namespace.nspname FROM pg_namespace
WHERE pg_namespace.oid = pg_constraint.connamespace
) as schema_name,
-- table_name
(
SELECT pg_class.relname FROM pg_class
WHERE pg_class.oid = pg_constraint.conrelid
) as table_name,
-- column_name
(
SELECT array_agg(attname) FROM pg_attribute
WHERE attrelid = pg_constraint.conrelid
AND ARRAY[attnum] <@ pg_constraint.conkey
) AS column_name,
-- foreign_schema_name
(
SELECT pg_namespace.nspname FROM pg_namespace
WHERE pg_namespace.oid = (
SELECT pg_class.relnamespace FROM pg_class
WHERE pg_class.oid = pg_constraint.confrelid
)
) AS foreign_schema_name,
-- foreign_table_name
(
SELECT pg_class.relname FROM pg_class
WHERE pg_class.oid = pg_constraint.confrelid
) AS foreign_table_name
FROM pg_constraint
-- confrelid = constraint foreign relation id = target schema + table
WHERE confrelid IN (
SELECT oid FROM pg_class
-- relname = target table name
WHERE relname = get_referenced_tables.table_name
-- relnamespace = target schema
AND relnamespace = (
SELECT oid FROM pg_namespace
WHERE nspname = get_referenced_tables.schema_name
)
)
-- confkey = constraint foreign key = the column on the foreign table linked to the target column
AND confkey @> (
SELECT array_agg(attnum) FROM pg_attribute
WHERE attname = get_referenced_tables.column_name
AND attrelid = pg_constraint.confrelid
)
) a
ORDER BY
schema_name,
table_name,
column_name,
foreign_table_name,
foreign_schema_name
;
$$ LANGUAGE SQL STABLE;


示例用法:

/*
Function to merge two people into a single person
The primary person (referenced by primary_person_id) will be retained, the secondary person
will have all their records re-referenced to the primary person, and then the secondary person
will be deleted
Note that this function may be destructive! For most tables, the records will simply be merged,
but in cases where merging would violate a UNIQUE or EXCLUSION constraint, the secondary person's
respective records will be dropped. For example, people cannot have overlapping pledges (on the
pledges.pledge table). If the secondary person has a pledge that overlaps with a pledge that is
on record for the primary person, the secondary person's pledge will just be deleted.
*/
CREATE FUNCTION utils.merge_person (primary_person_id BIGINT, secondary_person_id BIGINT)
RETURNS people.person AS $$
DECLARE
_referenced_table utils.referenced_table_t;
_col name;
_exec TEXT;
_primary_person people.person;
BEGIN
-- defer all deferrable constraints
SET CONSTRAINTS ALL DEFERRED;
-- This loop updates / deletes all referenced tables, setting the person_id (or equivalent)
-- From secondary_person_id => primary_person_id
FOR _referenced_table IN (SELECT * FROM utils.get_referenced_tables('people', 'person', 'id')) LOOP
-- the column_names are stored as an array, so we need to loop through these too
FOREACH _col IN ARRAY _referenced_table.column_name LOOP
RAISE NOTICE 'Merging %.%(%)', _referenced_table.schema_name, _referenced_table.table_name, _col;


-- FORMAT allows us to safely build a dynamic SQL string
_exec = FORMAT(
$sql$ UPDATE %s.%s SET %s = $1 WHERE %s = $2 $sql$,
_referenced_table.schema_name,
_referenced_table.table_name,
_col,
_col
);


RAISE NOTICE 'SQL:  %', _exec;


-- wrap the execution in a block so that we can handle uniqueness violations
BEGIN
EXECUTE _exec USING primary_person_id, secondary_person_id;
RAISE NOTICE 'Merged %.%(%) OK!', _referenced_table.schema_name, _referenced_table.table_name, _col;
EXCEPTION
-- Error codes are Postgres built-ins, see https://www.postgresql.org/docs/9.6/errcodes-appendix.html
WHEN unique_violation OR exclusion_violation THEN
RAISE NOTICE 'Cannot merge record with % = % on table %.%, falling back to deletion!', _col, secondary_person_id, _referenced_table.schema_name, _referenced_table.table_name;
_exec = FORMAT(
$sql$ DELETE FROM %s.%s WHERE %s = $1 $sql$,
_referenced_table.schema_name,
_referenced_table.table_name,
_col
);
RAISE NOTICE 'SQL:  %', _exec;
EXECUTE _exec USING secondary_person_id;
RAISE WARNING 'Deleted record with % = % on table %.%', _col, secondary_person_id, _referenced_table.schema_name, _referenced_table.table_name;
END;


END LOOP;
END LOOP;


-- Once we've updated all the tables, we can safely delete the secondary person
RAISE WARNING 'Deleted person with id = %', secondary_person_id;


-- Get our primary person so that we can return them
SELECT * FROM people.person WHERE id = primary_person_id INTO _primary_person;


RETURN _primary_person;


END
$$ LANGUAGE plpgsql VOLATILE;

注意函数中使用的 SET CONSTRAINTS ALL DEFERRED;,它确保在合并结束时检查外键关系。您可能需要更新您的约束为 DEFERRABLE INITIALLY DEFERRED:

ALTER TABLE settings.contact_preference
DROP CONSTRAINT contact_preference_person_id_fkey,
DROP CONSTRAINT person_id_current_address_id_fkey,
ADD CONSTRAINT contact_preference_person_id_fkey
FOREIGN KEY (person_id)
REFERENCES people.person(id)
ON UPDATE CASCADE ON DELETE CASCADE
DEFERRABLE INITIALLY IMMEDIATE,
ADD CONSTRAINT person_id_current_address_id_fkey
FOREIGN KEY (person_id, current_address_id)
REFERENCES people.address(person_id, id)
DEFERRABLE INITIALLY IMMEDIATE
;