PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Como posso encontrar tabelas que fazem referência a uma linha específica por meio de uma chave estrangeira?

Valores NULL nas colunas de referência


Essa consulta produz a instrução DML para localizar todas as linhas em todas as tabelas, onde uma coluna tem uma restrição de chave estrangeira referenciando outra tabela mas mantenha um NULL valor nessa coluna:
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;

Produz uma consulta como esta:
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;

Produz saída como esta:
    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

  • Não cobre chaves estrangeiras ou primárias de várias colunas de forma confiável . Você tem que tornar a consulta mais complexa para isso.

  • Eu converto todos os valores de chave primária para text para cobrir todos os tipos.

  • Adapte ou remova essas linhas para encontrar uma chave estrangeira apontando para outra ou qualquer coluna/tabela:
    AND    c.confrelid = 'fk_tbl'::regclass
    AND    f.attname = 'fk_tbl_id' -- and only this column
    

  • Testado com PostgreSQL 9.1.4. Eu uso o pg_catalog mesas. Realisticamente, nada do que eu uso aqui vai mudar, mas isso não é garantido nos principais lançamentos. Reescreva-o com tabelas de information_schema se você precisar que ele funcione de maneira confiável nas atualizações. Isso é mais lento, mas com certeza.

  • Não limpei os nomes das tabelas no script DML gerado porque quote_ident() falharia com nomes qualificados pelo esquema. É sua responsabilidade evitar nomes de tabelas prejudiciais como "users; DELETE * FROM users;" . Com um pouco mais de esforço, você pode recuperar o nome do esquema e o nome da tabela separadamente e usar quote_ident() .

Valores NULL nas colunas referenciadas


Minha primeira solução faz algo sutilmente diferente do que você pergunta, porque o que você descreve (como eu o entendo) é inexistente. O valor NULL é "desconhecido" e não pode ser referenciado. Se você realmente deseja encontrar linhas com um NULL valor em uma coluna que tem restrições FK apontando para (não para a linha específica com o NULL valor, é claro), então a consulta pode ser muito simplificada:
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;

Localiza todas essas linhas em todo o banco de dados (comentou a restrição a uma tabela). Testado com Postgres 9.1.4 e funciona para mim.

Agrupo várias tabelas que fazem referência à mesma coluna estrangeira em uma consulta e adiciono uma lista de tabelas de referência para fornecer uma visão geral.