【问题标题】:PostgreSQL: SQL script to get a list of all tables that have a particular column as foreign keyPostgreSQL:用于获取具有特定列作为外键的所有表的列表的 SQL 脚本
【发布时间】:2011-07-17 20:26:03
【问题描述】:

我正在使用 PostgreSQL,我正在尝试将表中具有特定列的所有表列为外键/引用。这可以做到吗?我确定这些信息存储在 information_schema 的某个位置,但我不知道如何开始查询它。

【问题讨论】:

标签: sql database postgresql foreign-keys


【解决方案1】:
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 个信息模式视图中识别数据库表。您可以根据需要删除一两个。

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

【讨论】:

  • 我一定是做错了什么,当我尝试您的查询时,我只是一遍又一遍地重复表 'd' 的名称。
  • @Anomie 您在使用更新后的查询吗?我改了 - 是的,我弄错了
  • @cyberkiwi:是的,两个查询的结果相同。
  • 我看到了问题。 R 应该是 INFORMATION_SCHEMA.KEY_COLUMN_USAGE,而不是 INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE
  • 较旧的模式不适用于此,因为 PostgreSQL 过去不以独特的方式命名约束。请参阅我的 PostgreSQL 特定答案。
【解决方案2】:

不保证其他解决方案在 postgresql 中有效,因为不保证约束名称是唯一的;因此你会得到误报。 PostgreSQL 过去常常将约束命名为“$1”之类的愚蠢事物,如果您有一个通过升级维护的旧数据库,那么您可能还有一些类似的东西。

由于这个问题是针对 AT PostgreSQL 的,而这正是您所使用的,那么您可以查询内部 postgres 表 pg_class 和 pg_attribute 以获得更准确的结果。

注意:FK 可以在多个列上,因此引用列(pg_attribute 的 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);

【讨论】:

  • 我认为这是正确的答案 - 这是一个比所选答案更准确的答案。
  • 有什么方法可以包含源列?我现在正在修改查询,如果我找到解决方案会发布。
  • 我提交了包含新选择的编辑 - 出于某种原因,S.E.不喜欢我在评论中提交 SQL。
  • @Tony K. 有没有办法知道(通过查询目录表)是否在其他表中引用了特定行,而不仅仅是整个列?谢谢。
  • 如果您有同名但在不同架构中的表,您需要在 where 条件下进行过滤:where c.confrelid = ( select oid from pg_class where relname = 'TARGET_TABLE_NAME' AND relnamespace = (select oid from pg_namespace where nspname = 'SCHEMA_NAME'))
【解决方案3】:

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

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';

【讨论】:

    【解决方案4】:

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

    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 and Krogh at the PostgreSQL forum

    【讨论】:

      【解决方案5】:
      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';
      

      【讨论】:

        【解决方案6】:

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

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

        【讨论】:

          【解决方案7】:

          表约束可以包含多个列。做到这一点的诀窍是通过它们的约束序数位置连接每一列。如果您没有正确连接,您的脚本将在重复行 ? 每当一个表在一个唯一约束中有多个列时。

          查询

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

          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                             |
          

          现在剩下的就是将它们连接在一起。上面的主要查询是您可以这样做的一种方式。

          【讨论】:

            【解决方案8】:

            如果您使用psql 客户端,您可以简单地发出\d table_name 命令来查看哪些表引用了给定的表。从链接的文档页面:

            \d[S+] [ pattern ]

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

            【讨论】:

              【解决方案9】:

              我把 @Tony Kanswer 变成了一个可重用的函数,它接受一个模式/表/列元组并返回所有具有外键关系的表: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
              ;
              

              【讨论】:

                猜你喜欢
                • 2022-06-20
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-12-29
                • 2018-12-21
                • 2020-03-14
                • 2020-03-17
                • 2016-07-10
                相关资源
                最近更新 更多