PostgreSQL: SQL script to get a list of all tables that has a particular column as foreign key

前端 未结 9 939
独厮守ぢ
独厮守ぢ 2021-01-29 23:31

I\'m using PostgreSQL and I\'m trying to list all the tables that have a particular column from a table as a foreign-key/reference. Can this be done? I\'m sure this information

相关标签:
9条回答
  • 2021-01-29 23:55
    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 = 'a'
      AND U.TABLE_CATALOG = 'b'
      AND U.TABLE_SCHEMA = 'c'
      AND U.TABLE_NAME = 'd'
    

    This uses the full catalog/schema/name triplet to identify a db table from all 3 information_schema views. You can drop one or two as required.

    The query lists all tables that have a foreign key constraint against the column 'a' in table 'd'

    0 讨论(0)
  • 2021-01-29 23:56

    A simple request for recovered the names of foreign key as well as the names of the tables:

    SELECT CONSTRAINT_NAME, table_name
    FROM
       information_schema.table_constraints 
    WHERE table_schema='public' and constraint_type='FOREIGN KEY'
    
    0 讨论(0)
  • 2021-01-29 23:58

    I turned @Tony K's answer into a reusable function that takes in a schema/table/column tuple and returns all tables that have a foreign key relationship: https://gist.github.com/colophonemes/53b08d26bdd219e6fc11677709e8fc6c

    I needed something like this in order to implement a script that merged two records into a single record.

    Function:

    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;
    
    

    Example usage:

    /*
      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;
    

    Note the use of SET CONSTRAINTS ALL DEFERRED; in the function, which ensures that foreign key relationships are checked at the end of the merge. You may need to update your constraints to be 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
    ;
    
    0 讨论(0)
  • 2021-01-30 00:06

    The other solutions are not guaranteed to work in postgresql, as the constraint_name is not guaranteed to be unique; thus you will get false positives. PostgreSQL used to name constraints silly things like '$1', and if you've got an old database you've been maintaining through upgrades, you likely still have some of those around.

    Since this question was targeted AT PostgreSQL and that is what you are using, then you can query the internal postgres tables pg_class and pg_attribute to get a more accurate result.

    NOTE: FKs can be on multiple columns, thus the referencing column (attnum of pg_attribute) is an ARRAY, which is the reason for using array_agg in the answer.

    The only thing you need plug in is the 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');
    

    If you want to go the other way (list all of the things a specific table refers to), then just change the last line to:

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

    Oh, and since the actual question was to target a specific column, you can specify the column name with this one:

    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);
    
    0 讨论(0)
  • 2021-01-30 00:06

    This query requires only the referenced table name and column name, and produces a result set containing both sides of the 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';
    

    Example result set:

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

    All credit to Lane and Krogh at the PostgreSQL forum.

    0 讨论(0)
  • 2021-01-30 00:09

    If you use the psql client, you can simply issue the \d table_name command to see which tables reference the given table. From the linked documentation page:

    \d[S+] [ pattern ]

    For each relation (table, view, materialized view, index, sequence, or foreign table) or composite type matching the pattern, show all columns, their types, the tablespace (if not the default) and any special attributes such as NOT NULL or defaults. Associated indexes, constraints, rules, and triggers are also shown. For foreign tables, the associated foreign server is shown as well.

    0 讨论(0)
提交回复
热议问题