PostgreSQL maakt extra catalogusregels voor foreign keys aan, omdat een verwijzing naar een gepartitioneerde tabel ook over de fysieke partities heen moet werken. Bepaal of deze regels één relatie of afzonderlijke declaraties vertegenwoordigen door hun bovenliggende koppelingen en gerefereerde tabellen te inspecteren.
Deze walkthrough is bedoeld voor een ontwikkelaar die een Mac gebruikt om een onverwachte lijst met beperkingen in een databasebrowser of Node.js-applicatie te onderzoeken. De taak is om het gedrag met synthetische artikelrijen te reproduceren, de constraintfamilie te traceren en te bepalen wat in een logisch schemarapport thuishoort.
Waarom levert één foreign key meerdere constraintregels op?
Een gepartitioneerde tabel biedt een logische tabel waarvan de rijen in de partities worden opgeslagen. De bovenliggende tabel heeft zelf geen opslag. Dat onderscheid is van belang wanneer een andere tabel ernaar verwijst: PostgreSQL moet de relatie onderhouden wanneer rijen in de fysieke partities veranderen. Zie de officiële documentatie over tabelpartitionering.
De implementatie van PostgreSQL 18 maakt het mechanisme expliciet. De routine addFkRecurseReferenced maakt triggers voor foreign-key-acties aan en maakt recursief een extra regel pg_constraint aan voor elke gerefereerde partitie. De validatiecode legt uit dat deze regels aan de gerefereerde kant action triggers ondersteunen, en geen afzonderlijke validatiescans vertegenwoordigen. Zie PostgreSQL’s implementatie voor het aanmaken en valideren van foreign keys.
Deze regels betekenen dus niet dat elke verwijzende waarde in elke partitie moet bestaan. De gedeclareerde relatie verwijst nog steeds naar de gepartitioneerde bovenliggende tabel. PostgreSQL moet ook wijzigingen aan de gerefereerde kant afhandelen, waaronder het ingestelde gedrag wanneer een gerefereerde rij wordt verwijderd of de sleutel ervan verandert. Dat gedrag wordt beschreven in Foreign Keys.
Welke PostgreSQL-versies ondersteunen dit?
Foreign keys die naar een gepartitioneerde tabel verwijzen, zijn geïntroduceerd in PostgreSQL 12. Eerdere versies kunnen dezelfde declaratie met verwijzing naar de bovenliggende tabel niet gebruiken. PostgreSQL 12 wordt nu als niet-ondersteund vermeld; het markeert de historische introductie van deze functie en is geen aanbevolen installatiedoel. Dit artikel gebruikt de actuele documentatie van PostgreSQL 18 en de onderhouden broncodebranch ervan. Zie de release notes van PostgreSQL 12.
Reproduceer de relatie met synthetische artikelrijen
Gebruik een bestaande lokale PostgreSQL-ontwikkeldatabase en een werkende installatie van psql. Open op je Mac Terminal, maak verbinding met psql -d postgres en controleer de server met SHOW server_version;. Vervang indien nodig de naam van je ontwikkeldatabase. De officiële psql-documentatie beschrijft hoe je een database selecteert en interactieve SQL uitvoert.
Voer deze volledige setup één keer uit. Deze maakt een speciaal schema aan zonder bestaande objecten te verwijderen. Als dat schema al bestaat, stop dan en gebruik een nieuwe ontwikkeldatabase of hernoem het schema consequent in alle voorbeelden.
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;
De samengestelde primaire sleutel is bewust gekozen. PostgreSQL vereist dat een primaire of unieke constraint van een gepartitioneerde tabel elke kolom van de partitiesleutel bevat; voor expressies in partitiesleutels gelden aanvullende beperkingen. Omdat deze tabel is gepartitioneerd op publication_year, bevat de primaire sleutel die kolom en verwijst de foreign key naar het volledige paar. Zie Declaratieve partitionering: beperkingen.
Het voorbeeld identificeert een artikel daarom met (article_id, publication_year). Het stelt niet vast dat alleen article_id wereldwijd uniek is. Als je applicatie verwijzingen nodig heeft die uitsluitend article_id gebruiken, heroverweeg dan het ontwerp van de partitionering en sleutels voordat je dit voorbeeld overneemt.
Traceer de constraintfamilie in plaats van namen te vergelijken
De relevante catalogusvelden zijn conrelid, de verwijzende tabel; confrelid, de gerefereerde tabel; en conparentid, de OID van de bovenliggende constraint. Constraintnamen zijn niet noodzakelijk uniek. Alleen op basis van namen kun je dus niet vaststellen of twee regels onafhankelijk zijn. Deze velden worden beschreven in pg_constraint.
Voer deze query uit nadat je de setup hebt uitgevoerd. De query begint bij de foreign keys op het hoofdniveau die op article_notes zijn gedeclareerd en volgt hun child constraints recursief.
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;
Voor dit voorbeeld met één partitioneringsniveau impliceert de implementatie drie foreign-keyregels: één die naar articles verwijst en één naar elk van de twee partities. Dit is een verwacht resultaat op basis van de broncode, geen vastgelegde uitvoer. Gegenereerde namen en OID-waarden moeten niet hardgecodeerd worden. Dezelfde broncode maakt ook constraints aan de gerefereerde kant aan wanneer later een partitie wordt gemaakt of gekoppeld. Zie tablecmds.c.
| Regel in dit voorbeeld | Verwijzende tabel | Gerefereerde tabel | Bovenliggende koppeling |
|---|---|---|---|
| Hoofdniveaurelatie | article_notes |
articles |
conparentid = 0 |
| Child van gerefereerde partitie | article_notes |
articles_2025 |
OID van de rootconstraint |
| Child van gerefereerde partitie | article_notes |
articles_2026 |
OID van de rootconstraint |
Het detail dat gemakkelijk over het hoofd wordt gezien, is dat de verwijzende tabel in alle drie de regels article_notes blijft. Inspecteer beide tabelkolommen en de bovenliggende koppeling; als je alleen kijkt naar de tabel die bij de constraint hoort, kan de structuur van de gerefereerde partitie verborgen blijven.
De query toont ook gereconstrueerde definities via pg_get_constraintdef(). PostgreSQL beschrijft deze functie als een reconstructie en niet als de oorspronkelijk ingediende SQL. Gebruik de functie om de relatie te begrijpen, niet om de exacte migratietekst terug te halen. Zie Systeeminformatiefuncties en -operatoren.
Inspecteer dezelfde regels met node-postgres op je Mac
node-postgres retourneert de queryresultaten van de server; de regels die met partitionering samenhangen, komen voort uit het constraintmechanisme van PostgreSQL. De gedocumenteerde query-API accepteert SQL-tekst en een afzonderlijke array met parameterwaarden. Hier wordt de relatienaam als tekstwaarde doorgegeven, die PostgreSQL omzet naar regclass. Zie Queries.
Installeer een actuele, ondersteunde versie van Node.js, maak een werkmap aan en installeer pg. De installatieopdracht en informatie over compatibiliteit met de runtime staan in de introductie van node-postgres.
mkdir pg-fk-inspection
cd pg-fk-inspection
npm init -y
npm install pg
Sla het volgende volledige programma op als inspect-fks.mjs. Het vereist de hierboven vastgelegde SQL-setup. Anders dan het recursieve SQL-rapport toont dit programma regels die direct zijn gekoppeld aan de opgegeven verwijzende tabel; dat volstaat voor de niet-gepartitioneerde tabel article_notes in dit voorbeeld.
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();
}
Voer het uit met dezelfde database die voor de SQL-setup is gebruikt. node-postgres gebruikt PostgreSQL-omgevingsvariabelen voor verbindingen, waaronder PGDATABASE; de standaardwaarden worden beschreven in Connecting.
PGDATABASE=postgres node inspect-fks.mjs
Als de relatie niet wordt gevonden, controleer dan of beide sessies dezelfde database gebruiken en of de setup succesvol is vastgelegd. Een lege uitvoer betekent dat deze query geen foreign keys heeft gevonden die rechtstreeks aan die relatie zijn gekoppeld; verwijzingen vanuit andere tabellen worden er niet mee gerapporteerd. De JavaScript-code is tijdens de voorbereiding van dit artikel op syntaxis gecontroleerd. Database-uitvoering was niet beschikbaar, dus zijn de SQL-voorbeelden en de verwachte catalogusstructuur gecontroleerd aan de hand van de opgehaalde documentatie en broncode.
Bepaal wat je wilt tonen of onderzoeken
Gebruik de volgende stappen wanneer een lijst met constraints onverwacht lang lijkt. Zo kun je een rapport van logische relaties onderscheiden van een inventarisatie van de implementatie, zonder bruikbare informatie weg te laten.
- Controleer de relatie en serverversie. Gebruik de schemagekwalificeerde relatienaam en noteer
SHOW server_version;voordat je het gedrag met de documentatie vergelijkt. - Volg de bovenliggende koppelingen. In dit voorbeeld behoren childregels met dezelfde root-OID tot de constraintfamilie van de gedeclareerde relatie.
- Vergelijk beide kanten. Een veranderende
confrelidmet dezelfde verwijzende tabel verklaart de hierboven getoonde regels voor gerefereerde partities. - Kies het doel van het rapport. Gebruik voor relaties op hoofdniveau die op de geselecteerde tabel zijn gedeclareerd het filter
contype = 'f' AND conparentid = 0. Behoud het recursieve rapport voor de volledige familie. - Onderzoek afzonderlijke roots apart. Vergelijk kolomparen, gerefereerde tabellen, acties, validatiestatus en uitstelbaarheid voordat je besluit dat twee declaraties overbodig zijn.
Deze aanpak voor rapportage volgt de identificatoren en relatievelden die in pg_constraint worden beschreven. Het filter voor alleen de root is bewust beperkt: een partitie kan via overgeërfde child constraints meedoen, dus een filter op roots voor een leaf-tabel geeft geen volledig overzicht van alle relaties die erop van invloed zijn.
Als je bewijs nodig hebt van de objecten die de relatie afdwingen, koppelt pg_trigger.tgconstraint een trigger aan een constraint, terwijl tgrelid de tabel aanwijst waaraan de trigger is gekoppeld. De catalogus geeft ook aan welke triggers intern zijn gegenereerd. Deze velden bieden een extra mogelijkheid tot inspectie in pg_trigger.
Behoud de gegenereerde regels zolang de gedeclareerde relatie nodig is. Voor het gereproduceerde geval is de praktische oplossing om de relatie op hoofdniveau in een logische schemaview te tonen en de childregels in de diagnostische view te behouden. Zodra de bovenliggende koppelingen en beide tabeluiteinden zichtbaar zijn, laten de extra regels zien hoe PostgreSQL de verwijzing over partities heen implementeert.
Bronnen
Tabelpartitionering — PostgreSQL Global Development Group.
pg_constraint — PostgreSQL Global Development Group.
Release 12 — PostgreSQL Global Development Group.
tablecmds.c, REL_18_STABLE — PostgreSQL Global Development Group.
Constraints — PostgreSQL Global Development Group.
psql — PostgreSQL Global Development Group.
Systeeminformatiefuncties en -operatoren — PostgreSQL Global Development Group.
pg_trigger — PostgreSQL Global Development Group.
Introductie van node-postgres — node-postgres.
Queries — node-postgres.
Connecting — node-postgres.