87 votes

PostgreSQL : SQL script pour obtenir une liste de toutes les tables qui ont une colonne particulière comme clé étrangère.

J'utilise PostgreSQL et j'essaie de lister toutes les tables qui ont une colonne particulière d'une table comme clé étrangère/référence. Est-ce possible ? Je suis sûr que cette information est stockée quelque part dans le fichier information_schema mais je n'ai aucune idée de la façon dont je dois m'y prendre.

4voto

Lucas Points 106
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';

4voto

eugene y Points 37378

Si vous utilisez le psql vous pouvez simplement émettre la commande \d table_name pour voir quelles tables font référence à la table donnée. Depuis la page de documentation liée :

\d[S+] [ pattern ]

Pour chaque relation (table, vue, vue matérialisée, index, séquence ou table étrangère) ou type composite correspondant à l'attribut pattern , affiche toutes les colonnes, leurs types, le tablespace (s'il n'est pas celui par défaut) et tout attribut spécial tel que NOT NULL ou les valeurs par défaut. Le site associé Les index, contraintes, règles et déclencheurs associés sont également affichés. Pour les tables étrangères, le serveur étranger associé est également affiché.

3voto

Jakobii Points 400

Les contraintes de table peuvent inclure plusieurs colonnes . L'astuce pour y parvenir est de joindre chaque colonne par leur positions ordinales contraintes . Si vous ne joignez pas correctement votre script explosera avec rangées dupliquées lorsqu'une table comporte plusieurs colonnes dans une contrainte unique.

Requête

Liste toutes les colonnes de clés étrangères et leurs références.

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!

Liens utiles

Explication

considérer la relation entre ces deux tableaux.

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.
);

obtenir les noms des contraintes de la table

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     |

références aux contraintes

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               |

contrainte ordinal_position de la colonne.

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                             |

Il ne reste plus qu'à les joindre. La requête principale ci-dessus est une façon de le faire.

1voto

colophonemes Points 18

Je me suis retourné @Tony K 's réponse en une fonction réutilisable qui prend un tuple schéma/table/colonne et renvoie toutes les tables qui ont une relation de clé étrangère : https://gist.github.com/colophonemes/53b08d26bdd219e6fc11677709e8fc6c

J'avais besoin de quelque chose comme ça afin de mettre en œuvre un script qui fusionnait deux enregistrements en un seul.

Fonction :

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;

Exemple d'utilisation :

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

Notez l'utilisation de SET CONSTRAINTS ALL DEFERRED; dans la fonction, ce qui garantit que les relations de clé étrangère sont vérifiées à la fin de la fusion. Vous devrez peut-être mettre à jour vos contraintes pour qu'elles soient 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
;

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X