O PostgreSQL cria linhas extras no catálogo de chaves estrangeiras porque uma referência a uma tabela particionada também precisa funcionar em suas partições físicas. Para determinar se essas linhas representam um único relacionamento ou declarações separadas, examine os vínculos com as linhas pai e as tabelas referenciadas.

Este passo a passo é para um desenvolvedor que usa um Mac para investigar uma lista inesperada de restrições em um navegador de banco de dados ou em um aplicativo Node.js. A tarefa é reproduzir o comportamento com linhas sintéticas de artigos, rastrear a família de restrições e decidir o que deve constar em um relatório de esquema lógico.

Por que uma chave estrangeira gera várias linhas de restrição?

Uma tabela particionada fornece uma tabela lógica cujas linhas são armazenadas em suas partições. A tabela pai não tem armazenamento próprio. Essa distinção importa quando outra tabela faz referência a ela: o PostgreSQL precisa manter o relacionamento quando as linhas das partições físicas são alteradas. Consulte a documentação oficial de Particionamento de tabelas.

A implementação do PostgreSQL 18 torna explícito esse mecanismo. Sua rotina addFkRecurseReferenced cria gatilhos de ação para chaves estrangeiras e cria recursivamente uma linha adicional de pg_constraint para cada partição referenciada. O código de validação explica que essas linhas do lado referenciado dão suporte aos gatilhos de ação, em vez de constituírem verificações de validação separadas. Consulte a implementação de criação e validação de chaves estrangeiras do PostgreSQL.

Portanto, essas linhas não significam que cada valor referenciador precisa existir em todas as partições. O relacionamento declarado continua tendo como alvo a tabela pai particionada. O PostgreSQL também precisa tratar alterações no lado referenciado, inclusive o comportamento configurado quando uma linha referenciada é excluída ou sua chave é alterada. Esses comportamentos estão descritos em Chaves estrangeiras.

Quais versões do PostgreSQL oferecem suporte a isso?

O suporte a chaves estrangeiras que referenciam uma tabela particionada foi introduzido no PostgreSQL 12. Versões anteriores não podem usar a mesma declaração com a tabela pai como alvo. O PostgreSQL 12 agora consta como sem suporte; ele marca o limite histórico da introdução do recurso, não uma versão recomendada para instalação. Este artigo usa a documentação atual do PostgreSQL 18 e sua ramificação de código-fonte mantida. Consulte as notas de versão do PostgreSQL 12.

Reproduza o relacionamento com linhas sintéticas de artigos

Use um banco de dados de desenvolvimento PostgreSQL local existente e uma instalação funcional de psql. No Mac, abra o Terminal, conecte-se com psql -d postgres e verifique o servidor com SHOW server_version;. Se necessário, substitua pelo nome do seu banco de dados de desenvolvimento. A documentação oficial do psql descreve a seleção do banco de dados e a execução interativa de SQL.

Execute esta configuração completa uma vez. Ela cria um esquema dedicado sem remover objetos existentes. Se esse esquema já existir, pare e use um banco de dados de desenvolvimento novo ou renomeie o esquema de forma consistente em todos os exemplos.

BEGIN;

CREATE SCHEMA fk_partition_demo;

CREATE TABLE fk_partition_demo.articles (
    article_id integer NOT NULL,
    publication_year integer NOT NULL,
    title text NOT NULL,
    PRIMARY KEY (article_id, publication_year)
) PARTITION BY RANGE (publication_year);

CREATE TABLE fk_partition_demo.articles_2025
    PARTITION OF fk_partition_demo.articles
    FOR VALUES FROM (2025) TO (2026);

CREATE TABLE fk_partition_demo.articles_2026
    PARTITION OF fk_partition_demo.articles
    FOR VALUES FROM (2026) TO (2027);

CREATE TABLE fk_partition_demo.article_notes (
    note_id integer PRIMARY KEY,
    article_id integer NOT NULL,
    publication_year integer NOT NULL,
    note_text text NOT NULL,
    CONSTRAINT article_notes_article_fk
        FOREIGN KEY (article_id, publication_year)
        REFERENCES fk_partition_demo.articles
            (article_id, publication_year)
);

INSERT INTO fk_partition_demo.articles
    (article_id, publication_year, title)
VALUES
    (101, 2025, 'A guide to paper models'),
    (102, 2026, 'Choosing a notebook layout');

INSERT INTO fk_partition_demo.article_notes
    (note_id, article_id, publication_year, note_text)
VALUES
    (1, 101, 2025, 'Add a materials list'),
    (2, 102, 2026, 'Include a grid example');

COMMIT;

A chave primária composta é intencional. O PostgreSQL exige que a restrição de chave primária ou exclusiva de uma tabela particionada inclua todas as colunas da chave de particionamento, além de impor restrições adicionais a expressões nas chaves de particionamento. Como esta tabela é particionada por publication_year, a chave primária inclui essa coluna, e a chave estrangeira referencia o par completo. Consulte Particionamento declarativo: limitações.

Consequentemente, o exemplo identifica um artigo por (article_id, publication_year). Ele não estabelece que article_id seja globalmente exclusivo por si só. Se o seu aplicativo exigir referências usando apenas article_id, reavalie o particionamento e o projeto das chaves antes de adotar este exemplo.

Rastreie a família de restrições em vez de comparar nomes

Os campos úteis do catálogo são conrelid, a tabela que faz a referência; confrelid, a tabela referenciada; e conparentid, o OID da restrição pai. Os nomes das restrições não são necessariamente exclusivos; portanto, só os nomes não permitem determinar se duas linhas são independentes. Esses campos estão documentados em pg_constraint.

Depois de executar a configuração, execute esta consulta. Ela começa pelas chaves estrangeiras raiz declaradas em article_notes e segue recursivamente as restrições filhas.

WITH RECURSIVE fk_family AS (
    SELECT c.oid, c.conparentid, c.oid AS root_oid, 0 AS depth
    FROM pg_catalog.pg_constraint AS c
    WHERE c.contype = 'f'
      AND c.conrelid =
          'fk_partition_demo.article_notes'::regclass
      AND c.conparentid = 0

    UNION ALL

    SELECT child.oid, child.conparentid,
           parent.root_oid, parent.depth + 1
    FROM pg_catalog.pg_constraint AS child
    JOIN fk_family AS parent
      ON child.conparentid = parent.oid
    WHERE child.contype = 'f'
)
SELECT
    family.root_oid,
    family.depth,
    c.oid AS constraint_oid,
    c.conname,
    c.conparentid,
    c.conrelid::regclass AS referencing_table,
    c.confrelid::regclass AS referenced_table,
    pg_get_constraintdef(c.oid) AS definition
FROM fk_family AS family
JOIN pg_catalog.pg_constraint AS c ON c.oid = family.oid
ORDER BY family.root_oid, family.depth, c.oid;

Para este exemplo de um único nível, a implementação implica três linhas de chave estrangeira: uma que tem articles como alvo e uma para cada uma de suas duas partições. Esse é um resultado esperado, deduzido do código-fonte, e não uma saída de execução capturada. Os nomes gerados e os valores de OID não devem ser codificados fixamente. O mesmo código-fonte também cria restrições do lado referenciado quando uma partição é criada ou anexada posteriormente. Consulte tablecmds.c.

Linha neste exemplo Tabela que faz a referência Tabela referenciada Vínculo com a linha pai
Relacionamento raiz article_notes articles conparentid = 0
Restrição filha da partição referenciada article_notes articles_2025 OID da restrição raiz
Restrição filha da partição referenciada article_notes articles_2026 OID da restrição raiz

O detalhe que passa facilmente despercebido é que a tabela que faz a referência continua sendo article_notes nas três linhas. Examine as duas colunas de tabela e o vínculo com a linha pai; olhar apenas para a tabela associada à restrição pode ocultar a estrutura das partições referenciadas.

A consulta também exibe definições reconstruídas por meio de pg_get_constraintdef(). O PostgreSQL documenta essa função como uma reconstrução, não como o SQL enviado originalmente. Use-a para entender o relacionamento, não para recuperar o texto exato da migração. Consulte Funções e operadores de informação do sistema.

Examine as mesmas linhas com node-postgres no Mac

node-postgres retorna os resultados da consulta do servidor; as linhas relacionadas às partições têm origem no mecanismo de restrições do PostgreSQL. A API de consulta documentada aceita texto SQL e um array separado de valores de parâmetros. Aqui, o nome da relação é passado como um valor de texto convertido pelo PostgreSQL para regclass. Consulte Consultas.

Com uma versão atual compatível do Node.js instalada, crie um diretório de trabalho e instale pg. O comando de instalação e as orientações de compatibilidade do ambiente de execução estão na introdução ao node-postgres.

mkdir pg-fk-inspection
cd pg-fk-inspection
npm init -y
npm install pg

Salve o programa completo a seguir como inspect-fks.mjs. Ele requer a configuração SQL confirmada acima. Ao contrário do relatório SQL recursivo, este programa lista as linhas vinculadas diretamente à tabela fornecida que faz a referência; isso é suficiente para a tabela não particionada article_notes deste exemplo.

import pg from 'pg';

const { Client } = pg;
const client = new Client({ connectionTimeoutMillis: 5000 });
const relation = process.argv[2] ??
  'fk_partition_demo.article_notes';

try {
  await client.connect();

  const version = await client.query('SHOW server_version');
  console.log('PostgreSQL:', version.rows[0].server_version);

  const result = await client.query(`
    SELECT oid, conname,
           conrelid::regclass::text AS referencing_table,
           confrelid::regclass::text AS referenced_table,
           conparentid,
           conparentid = 0 AS is_root,
           pg_get_constraintdef(oid) AS definition
    FROM pg_catalog.pg_constraint
    WHERE contype = 'f' AND conrelid = $1::regclass
    ORDER BY conparentid, oid
  `, [relation]);

  console.table(result.rows);
} catch (error) {
  console.error(error.message);
  process.exitCode = 1;
} finally {
  await client.end();
}

Execute-o no mesmo banco de dados usado para a configuração SQL. node-postgres usa variáveis de ambiente de conexão do PostgreSQL, incluindo PGDATABASE, e documenta os valores padrão em Conexão.

PGDATABASE=postgres node inspect-fks.mjs

Se a relação não for encontrada, confirme que ambas as sessões usam o mesmo banco de dados e que a configuração foi confirmada com sucesso. Um resultado vazio significa que esta consulta não encontrou chaves estrangeiras vinculadas diretamente a essa relação; ela não informa referências recebidas de outras tabelas. O JavaScript passou por uma verificação de sintaxe durante a preparação deste artigo. Não foi possível executar as consultas no banco de dados, então os exemplos de SQL e a estrutura esperada do catálogo foram examinados com base na documentação e no código-fonte consultados.

Decida o que exibir ou investigar

Use a sequência a seguir quando uma lista de restrições parecer inesperadamente longa. Ela diferencia um relatório de relacionamentos lógicos de um inventário da implementação, sem descartar evidências úteis.

  1. Confirme a relação e a versão do servidor. Use o nome da relação qualificado pelo esquema e registre SHOW server_version; antes de comparar o comportamento com a documentação.
  2. Siga os vínculos com as linhas pai. Neste exemplo, as linhas filhas que compartilham o OID raiz pertencem à família de restrições do relacionamento declarado.
  3. Compare os dois lados. Uma alteração em confrelid com a mesma tabela que faz a referência explica as linhas das partições referenciadas mostradas acima.
  4. Defina o objetivo do relatório. Para relacionamentos raiz declarados na tabela selecionada, filtre com contype = 'f' AND conparentid = 0. Para a família completa, mantenha o relatório recursivo.
  5. Investigue as raízes separadamente. Compare os pares de colunas, as tabelas referenciadas, as ações, o estado de validação e a possibilidade de adiamento antes de decidir que duas declarações são redundantes.

Essa abordagem de geração de relatórios segue os identificadores e campos de relacionamento documentados em pg_constraint. O filtro que mostra apenas as raízes tem escopo intencionalmente limitado: uma partição pode participar por meio de restrições filhas herdadas; portanto, filtrar uma tabela folha para mostrar apenas raízes não produz um relatório completo de todos os relacionamentos que a afetam.

Se você precisar de evidências dos objetos de imposição, pg_trigger.tgconstraint vincula um gatilho a uma restrição, enquanto tgrelid identifica a tabela que contém o gatilho. O catálogo também identifica gatilhos gerados internamente. Esses campos oferecem um caminho adicional de inspeção em pg_trigger.

Mantenha as linhas geradas quando o relacionamento declarado continuar sendo necessário. No caso reproduzido, a solução prática é exibir o relacionamento raiz em uma visualização do esquema lógico e manter as linhas filhas na visualização de diagnóstico. Quando os vínculos com a linha pai e os dois pontos finais da tabela ficam visíveis, as linhas extras explicam como o PostgreSQL implementa a referência entre partições.

Fontes