PostgreSQL अतिरिक्त विदेशी-कुंजी कैटलॉग पंक्तियाँ बनाता है, क्योंकि पार्टिशन की गई तालिका के संदर्भ को उसके भौतिक पार्टिशनों में भी काम करना चाहिए। यह जानने के लिए कि ये पंक्तियाँ एक ही संबंध को दर्शाती हैं या अलग-अलग घोषणाओं को, उनके पैरेंट लिंक और संदर्भित तालिकाएँ देखें।

यह वॉकथ्रू उस डेवलपर के लिए है जो Mac का उपयोग करके डेटाबेस ब्राउज़र या Node.js एप्लिकेशन में अप्रत्याशित प्रतिबंध सूची की जाँच कर रहा है। इसका उद्देश्य कृत्रिम लेख पंक्तियों के साथ इस व्यवहार को दोहराना, प्रतिबंधों के समूह का पता लगाना और यह तय करना है कि तार्किक स्कीमा रिपोर्ट में क्या शामिल होना चाहिए।

एक विदेशी कुंजी से कई प्रतिबंध पंक्तियाँ क्यों बनती हैं?

पार्टिशन की गई तालिका एक तार्किक तालिका उपलब्ध कराती है, जिसकी पंक्तियाँ उसके पार्टिशनों में संग्रहीत होती हैं। पैरेंट के पास अपना कोई स्टोरेज नहीं होता। जब कोई दूसरी तालिका उसे संदर्भित करती है, तब यह अंतर मायने रखता है: PostgreSQL को भौतिक पार्टिशनों की पंक्तियों में बदलाव होने पर भी संबंध बनाए रखना होता है। आधिकारिक Table Partitioning दस्तावेज़ देखें।

PostgreSQL 18 का कार्यान्वयन इस तंत्र को स्पष्ट करता है। इसका addFkRecurseReferenced रूटीन विदेशी-कुंजी कार्रवाई ट्रिगर बनाता है और संदर्भित हर पार्टिशन के लिए एक अतिरिक्त pg_constraint पंक्ति पुनरावर्ती रूप से बनाता है। इसका सत्यापन कोड बताता है कि संदर्भित पक्ष की ये पंक्तियाँ अलग-अलग सत्यापन स्कैन के बजाय कार्रवाई ट्रिगरों को सहारा देती हैं। PostgreSQL का विदेशी-कुंजी निर्माण और सत्यापन कार्यान्वयन देखें।

इसलिए इन पंक्तियों का अर्थ यह नहीं है कि हर संदर्भित मान हर पार्टिशन में मौजूद होना चाहिए। घोषित संबंध अब भी पार्टिशन की गई पैरेंट तालिका को लक्षित करता है। PostgreSQL को संदर्भित पक्ष में होने वाले बदलाव भी संभालने होते हैं, जिनमें संदर्भित पंक्ति हटाए जाने या उसकी कुंजी बदलने पर कॉन्फ़िगर किया गया व्यवहार शामिल है। ये व्यवहार Foreign Keys में बताए गए हैं।

PostgreSQL के किन संस्करणों में इसका समर्थन है?

पार्टिशन की गई तालिका को संदर्भित करने वाली विदेशी कुंजियाँ PostgreSQL 12 में शुरू की गई थीं। पुराने संस्करणों में पैरेंट को लक्षित करने वाली ऐसी घोषणा का उपयोग नहीं किया जा सकता। PostgreSQL 12 अब असमर्थित संस्करणों में सूचीबद्ध है; यह सुविधा की ऐतिहासिक सीमा है, सुझाया गया इंस्टॉलेशन लक्ष्य नहीं। यह लेख PostgreSQL 18 के मौजूदा दस्तावेज़ और उसकी अनुरक्षित स्रोत शाखा का उपयोग करता है। PostgreSQL 12 रिलीज़ नोट्स देखें।

कृत्रिम लेख पंक्तियों के साथ संबंध को दोहराएँ

किसी मौजूदा स्थानीय PostgreSQL डेवलपमेंट डेटाबेस और काम कर रहे psql इंस्टॉलेशन का उपयोग करें। अपने Mac पर Terminal खोलें, psql -d postgres से कनेक्ट करें और SHOW server_version; चलाकर सर्वर जाँचें। ज़रूरत पड़ने पर अपने डेवलपमेंट डेटाबेस का नाम रखें। आधिकारिक psql दस्तावेज़ डेटाबेस चुनने और इंटरैक्टिव SQL चलाने का तरीका बताते हैं।

यह पूरा सेटअप एक बार चलाएँ। यह मौजूदा ऑब्जेक्ट हटाए बिना एक समर्पित स्कीमा बनाता है। अगर वह स्कीमा पहले से मौजूद है, तो रुकें और नया डेवलपमेंट डेटाबेस उपयोग करें या सभी उदाहरणों में स्कीमा का नाम एकसमान रूप से बदलें।

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;

कंपोज़िट प्राइमरी की जानबूझकर रखी गई है। PostgreSQL के अनुसार पार्टिशन की गई तालिका के प्राइमरी या यूनिक प्रतिबंध में हर पार्टिशन-की कॉलम शामिल होना चाहिए; पार्टिशन की में एक्सप्रेशन पर अतिरिक्त पाबंदियाँ भी हैं। चूँकि यह तालिका publication_year के आधार पर पार्टिशन की गई है, इसलिए प्राइमरी की में वह कॉलम शामिल है और विदेशी कुंजी पूरी जोड़ी को संदर्भित करती है। Declarative Partitioning: Limitations देखें।

इसलिए नमूने में किसी लेख की पहचान (article_id, publication_year) से होती है। इससे यह साबित नहीं होता कि article_id अकेला वैश्विक रूप से यूनिक है। अगर आपके एप्लिकेशन को केवल article_id का उपयोग करके संदर्भ चाहिए, तो इस उदाहरण को अपनाने से पहले पार्टिशनिंग और कुंजी डिज़ाइन पर दोबारा विचार करें।

नामों की तुलना करने के बजाय प्रतिबंधों के समूह का पता लगाएँ

कैटलॉग के उपयोगी फ़ील्ड हैं: conrelid, संदर्भ देने वाली तालिका; confrelid, संदर्भित तालिका; और conparentid, पैरेंट प्रतिबंध का OID। प्रतिबंध के नाम अनिवार्य रूप से यूनिक नहीं होते, इसलिए केवल नाम देखकर यह तय नहीं किया जा सकता कि दो पंक्तियाँ स्वतंत्र हैं या नहीं। इन फ़ील्ड का विवरण pg_constraint में है।

सेटअप चलाने के बाद यह क्वेरी निष्पादित करें। यह article_notes पर घोषित रूट विदेशी कुंजियों से शुरू होकर उनके चाइल्ड प्रतिबंधों को पुनरावर्ती रूप से खोजती है।

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;

इस एक-स्तरीय उदाहरण में, कार्यान्वयन के अनुसार तीन विदेशी-कुंजी पंक्तियाँ बनती हैं: एक articles को लक्षित करती है और एक उसके दोनों पार्टिशनों में से प्रत्येक को। यह स्रोत से निकला अपेक्षित परिणाम है, कैप्चर किया गया निष्पादन आउटपुट नहीं। बनाए गए नाम और OID मान हार्ड-कोड नहीं करने चाहिए। यही स्रोत बाद में कोई पार्टिशन बनाए जाने या जोड़े जाने पर संदर्भित-पक्ष के प्रतिबंध भी बनाता है। tablecmds.c देखें।

इस उदाहरण की पंक्ति संदर्भ देने वाली तालिका संदर्भित तालिका पैरेंट लिंक
रूट संबंध article_notes articles conparentid = 0
संदर्भित-पार्टिशन चाइल्ड article_notes articles_2025 रूट प्रतिबंध OID
संदर्भित-पार्टिशन चाइल्ड article_notes articles_2026 रूट प्रतिबंध OID

आसानी से छूट जाने वाली बात यह है कि तीनों पंक्तियों में संदर्भ देने वाली तालिका article_notes ही रहती है। दोनों तालिका कॉलम और पैरेंट लिंक देखें; केवल प्रतिबंध से जुड़ी तालिका देखने पर संदर्भित-पार्टिशन की संरचना छिप सकती है।

क्वेरी pg_get_constraintdef() के ज़रिए पुनर्निर्मित परिभाषाएँ भी दिखाती है। PostgreSQL इस फ़ंक्शन को मूल रूप से दिए गए SQL के बजाय पुनर्निर्माण के रूप में दस्तावेज़ित करता है। इसका उपयोग संबंध समझने के लिए करें, माइग्रेशन का हूबहू मूल पाठ वापस पाने के लिए नहीं। System Information Functions and Operators देखें।

अपने Mac पर node-postgres से उन्हीं पंक्तियों का निरीक्षण करें

node-postgres सर्वर के क्वेरी परिणाम लौटाता है; पार्टिशन से जुड़ी पंक्तियाँ PostgreSQL के प्रतिबंध तंत्र से आती हैं। इसका दस्तावेज़ित क्वेरी API SQL टेक्स्ट और पैरामीटर मानों की अलग ऐरे स्वीकार करता है। यहाँ संबंध का नाम टेक्स्ट मान के रूप में दिया जाता है, जिसे PostgreSQL regclass में बदलता है। Queries देखें।

वर्तमान समर्थित Node.js संस्करण इंस्टॉल होने पर एक कार्यशील डायरेक्टरी बनाएँ और pg इंस्टॉल करें। इंस्टॉलेशन कमांड और रनटाइम संगतता संबंधी निर्देश node-postgres परिचय में हैं।

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

यह पूरा प्रोग्राम inspect-fks.mjs नाम से सेव करें। इसके लिए ऊपर दिया गया SQL सेटअप कमिट होना ज़रूरी है। पुनरावर्ती SQL रिपोर्ट से अलग, यह प्रोग्राम दी गई संदर्भ देने वाली तालिका से सीधे जुड़ी पंक्तियाँ सूचीबद्ध करता है; इस उदाहरण की गैर-पार्टिशन की गई article_notes तालिका के लिए इतना पर्याप्त है।

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();
}

इसे उसी डेटाबेस के विरुद्ध चलाएँ जिसका उपयोग SQL सेटअप में किया था। node-postgres PostgreSQL के कनेक्शन पर्यावरण चर का उपयोग करता है, जिनमें PGDATABASE शामिल है, और उनके डिफ़ॉल्ट मान Connecting में बताए गए हैं।

PGDATABASE=postgres node inspect-fks.mjs

अगर संबंध नहीं मिलता, तो पुष्टि करें कि दोनों सत्र एक ही डेटाबेस का उपयोग कर रहे हैं और सेटअप सफलतापूर्वक कमिट हुआ है। खाली परिणाम का अर्थ है कि इस क्वेरी को उस संबंध से सीधे जुड़ी कोई विदेशी कुंजी नहीं मिली; यह दूसरी तालिकाओं से आने वाले संदर्भों की रिपोर्ट नहीं करता। इस लेख की तैयारी के दौरान JavaScript की सिंटैक्स जाँच की गई थी। डेटाबेस निष्पादन उपलब्ध नहीं था, इसलिए SQL उदाहरणों और अपेक्षित कैटलॉग संरचना का निरीक्षण प्राप्त दस्तावेज़ों और स्रोत के आधार पर किया गया।

तय करें कि क्या दिखाना है या जाँचना है

जब प्रतिबंध सूची अप्रत्याशित रूप से लंबी दिखाई दे, तो इस क्रम का उपयोग करें। यह उपयोगी प्रमाण हटाए बिना तार्किक संबंध रिपोर्ट और कार्यान्वयन सूची को अलग रखता है।

  1. संबंध और सर्वर संस्करण की पुष्टि करें। स्कीमा सहित संबंध का नाम उपयोग करें और दस्तावेज़ों से व्यवहार की तुलना करने से पहले SHOW server_version; का परिणाम दर्ज करें।
  2. पैरेंट लिंक देखें। इस उदाहरण में, समान रूट OID वाली चाइल्ड पंक्तियाँ घोषित संबंध के प्रतिबंध-समूह का हिस्सा हैं।
  3. दोनों सिरों की तुलना करें। संदर्भ देने वाली वही तालिका रहते हुए confrelid का बदलना ऊपर दिखाई गई संदर्भित-पार्टिशन पंक्तियों की वजह स्पष्ट करता है।
  4. रिपोर्ट का उद्देश्य चुनें। चुनी गई तालिका पर घोषित रूट संबंधों के लिए contype = 'f' AND conparentid = 0 से फ़िल्टर करें। पूरे समूह के लिए पुनरावर्ती रिपोर्ट रखें।
  5. अलग-अलग रूट की अलग जाँच करें। यह तय करने से पहले कि दो घोषणाएँ अनावश्यक हैं, कॉलम जोड़ियों, संदर्भित तालिकाओं, कार्रवाइयों, सत्यापन स्थिति और डिफरेबिलिटी की तुलना करें।

रिपोर्टिंग का यह तरीका pg_constraint में दस्तावेज़ित पहचानकर्ताओं और संबंध फ़ील्ड पर आधारित है। केवल रूट रखने वाला फ़िल्टर जानबूझकर सीमित दायरे का है: कोई पार्टिशन विरासत में मिले चाइल्ड प्रतिबंधों के ज़रिए संबंध में शामिल हो सकता है, इसलिए किसी लीफ़ तालिका को केवल रूट तक फ़िल्टर करने से उस पर असर डालने वाले सभी संबंधों की पूरी रिपोर्ट नहीं मिलती।

अगर आपको प्रवर्तन ऑब्जेक्ट के प्रमाण चाहिए, तो pg_trigger.tgconstraint किसी ट्रिगर को उसके प्रतिबंध से जोड़ता है, जबकि tgrelid उस तालिका की पहचान करता है जिस पर ट्रिगर है। कैटलॉग आंतरिक रूप से बनाए गए ट्रिगरों की भी पहचान करता है। ये फ़ील्ड pg_trigger में जाँच का एक और रास्ता देते हैं।

घोषित संबंध की आवश्यकता अब भी हो तो बनाई गई पंक्तियाँ बनाए रखें। दोहराए गए उदाहरण के लिए व्यावहारिक समाधान यह है कि तार्किक स्कीमा दृश्य में रूट संबंध दिखाएँ और नैदानिक दृश्य में उसके चाइल्ड रखें। पैरेंट लिंक और दोनों तालिका-सिरे दिखाई देने पर अतिरिक्त पंक्तियाँ समझाती हैं कि PostgreSQL पार्टिशनों में संदर्भ को कैसे लागू करता है।

स्रोत