PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Comment puis-je trouver des tables qui référencent une ligne particulière via une clé étrangère ?

Valeurs NULL dans les colonnes de référence

Cette requête produit l'instruction DML pour rechercher toutes les lignes dans toutes les tables, où une colonne a une contrainte de clé étrangère référençant une autre table mais maintenez un NULL valeur dans cette colonne :

WITH x AS (
 SELECT c.conrelid::regclass    AS tbl
      , c.confrelid::regclass   AS ftbl
      , quote_ident(k.attname)  AS fk
      , quote_ident(pf.attname) AS pk
 FROM   pg_constraint c
 JOIN   pg_attribute  k ON (k.attrelid, k.attnum) = (c.conrelid, c.conkey[1])
 JOIN   pg_attribute  f ON (f.attrelid, f.attnum) = (c.confrelid, c.confkey[1])
 LEFT   JOIN pg_constraint p  ON p.conrelid = c.conrelid AND p.contype = 'p'
 LEFT   JOIN pg_attribute  pf ON (pf.attrelid, pf.attnum)
                               = (p.conrelid, p.conkey[1])
 WHERE  c.contype   = 'f'
 AND    c.confrelid = 'fk_tbl'::regclass  -- references to this tbl
 AND    f.attname   = 'fk_tbl_id'         -- and only to this column
)
SELECT string_agg(format(
'SELECT %L AS tbl
     , %L AS pk
     , %s::text AS pk_val
     , %L AS fk
     , %L AS ftbl
FROM   %1$s WHERE %4$s IS NULL'
                  , tbl
                  , COALESCE(pk 'NONE')
                  , COALESCE(pk 'NULL')
                  , fk
                  , ftbl), '
UNION ALL
') || ';'
FROM   x;

Produit une requête comme celle-ci :

SELECT 'some_tbl' AS tbl
     , 'some_tbl_id' AS pk
     , some_tbl_id::text AS pk_val
     , 'fk_tbl_id' AS fk
     , 'fk_tbl' AS ftbl
FROM   some_tbl WHERE fk_tbl_id IS NULL
UNION ALL
SELECT 'other_tbl' AS tbl
     , 'other_tbl_id' AS pk
     , other_tbl_id::text AS pk_val
     , 'some_name_id' AS fk
     , 'fk_tbl' AS ftbl
FROM   other_tbl WHERE some_name_id IS NULL;

Produit une sortie comme celle-ci :

    tbl    |     pk       | pk_val |    fk        |  ftbl
-----------+--------------+--------+--------------+--------
 some_tbl  | some_tbl_id  | 49     | fk_tbl_id    | fk_tbl
 some_tbl  | some_tbl_id  | 58     | fk_tbl_id    | fk_tbl
 other_tbl | other_tbl_id | 66     | some_name_id | fk_tbl
 other_tbl | other_tbl_id | 67     | some_name_id | fk_tbl
  • Ne couvre pas de manière fiable les clés étrangères ou primaires multicolonnes . Vous devez rendre la requête plus complexe pour cela.

  • Je convertis toutes les valeurs de clé primaire en text pour couvrir tous les types.

  • Adaptez ou supprimez ces lignes pour trouver une clé étrangère pointant vers une autre ou any colonne / tableau :

    AND    c.confrelid = 'fk_tbl'::regclass
    AND    f.attname = 'fk_tbl_id' -- and only this column
    
  • Testé avec PostgreSQL 9.1.4. J'utilise le pg_catalog les tables. De manière réaliste, rien de ce que j'utilise ici ne changera, mais cela n'est pas garanti dans les versions majeures. Réécrivez-le avec les tables de information_schema si vous en avez besoin pour fonctionner de manière fiable entre les mises à jour. C'est plus lent, mais sûr.

  • Je n'ai pas nettoyé les noms de table dans le script DML généré, car quote_ident() échouerait avec des noms qualifiés de schéma. Il est de votre responsabilité d'éviter les noms de table nuisibles comme "users; DELETE * FROM users;" . Avec un peu plus d'effort, vous pouvez récupérer le nom du schéma et le nom de la table séparément et utiliser quote_ident() .

Valeurs NULL dans les colonnes référencées

Ma première solution fait quelque chose de subtilement différent de ce que vous demandez, car ce que vous décrivez (tel que je le comprends) est inexistant. La valeur NULL est "inconnu" et ne peut pas être référencé. Si vous voulez réellement trouver des lignes avec un NULL valeur dans une colonne qui a des contraintes FK pointant vers il (pas à la ligne particulière avec le NULL valeur, bien sûr), alors la requête peut être grandement simplifiée :

WITH x AS (
 SELECT c.confrelid::regclass   AS ftbl
       ,quote_ident(f.attname)  AS fk
       ,quote_ident(pf.attname) AS pk
       ,string_agg(c.conrelid::regclass::text, ', ') AS referencing_tbls
 FROM   pg_constraint c
 JOIN   pg_attribute  f ON (f.attrelid, f.attnum) = (c.confrelid, c.confkey[1])
 LEFT   JOIN pg_constraint p  ON p.conrelid = c.confrelid AND p.contype = 'p'
 LEFT   JOIN pg_attribute  pf ON (pf.attrelid, pf.attnum)
                               = (p.conrelid, p.conkey[1])
 WHERE  c.contype = 'f'
 -- AND    c.confrelid = 'fk_tbl'::regclass  -- only referring this tbl
 GROUP  BY 1, 2, 3
)
SELECT string_agg(format(
'SELECT %L AS ftbl
     , %L AS pk
     , %s::text AS pk_val
     , %L AS fk
     , %L AS referencing_tbls
FROM   %1$s WHERE %4$s IS NULL'
                  , ftbl
                  , COALESCE(pk, 'NONE')
                  , COALESCE(pk, 'NULL')
                  , fk
                  , referencing_tbls), '
UNION ALL
') || ';'
FROM   x;

Recherche toutes ces lignes dans l'ensemble de la base de données (a commenté la restriction à une table). Testé avec Postgres 9.1.4 et fonctionne pour moi.

Je regroupe plusieurs tables faisant référence à la même colonne étrangère dans une requête et j'ajoute une liste de tables de référence pour donner un aperçu.