कीज़ और रिलेशनशिप्स

Key एक column या columns का set है, जिससे relational database rows को पहचानता और आपस में जोड़ता है। Primary key किसी table की हर row को unique तरीके से पहचानती है: customers में id 42 एक ही customer है, कभी दो नहीं। Foreign key किसी दूसरी table का column है जो वही value store करके उस row की ओर इशारा करता है: जिस order में customer_id 42 है, वह customer 42 का है।

इन्हीं दो keys से relationships बनती हैं, और referential integrity वह नियम है जो उन्हें जोड़े रखता है: हर foreign key value किसी मौजूद row की ओर इशारा करती है।

यह क्यों ज़रूरी है

Keys से ही application अपना data दोबारा ढूँढता है। एक ही identifier वाले दो customers का मतलब है कि support agent गलत account खोलेगा; deleted customer की ओर इशारा करने वाला order, order history तोड़ देता है; किसी के न माँगे delete rule से invoices अपने customer के साथ मिट जाती हैं। Screen सही दिख सकती है जबकि stored data पहले से गलत हो, और failure बाद में किसी report, export या दूसरी service में सामने आता है।

Tester के लिए हर foreign key एक नियम है जिसका एक positive test होता है (valid reference accept हो) और negative tests होते हैं (गैर-मौजूद parent का reference reject हो; parent delete करने पर वही हो जो requirement कहती है)।

Primary key: एक row, एक पहचान

Primary key (PK) एक column या columns का combination है जिसकी value हर row में unique होती है और कभी NULL नहीं होती। एक table में ज़्यादा से ज़्यादा एक primary key होती है, और database ऐसी row reject करता है जिसकी key NULL हो या किसी दूसरी row में पहले से इस्तेमाल हो। कई columns से बनी primary key को composite key कहते हैं।

Natural key का असली दुनिया में पहले से मतलब होता है, जैसे product का SKU। Surrogate key का कोई business मतलब नहीं होता: auto-increment integer, या UUID, यानी 128-bit identifier जिसे application database से अगला number माँगे बिना generate कर सकता है। Teams अक्सर surrogate primary key रखती हैं और natural identifier पर UNIQUE constraint लगाती हैं, जो ऐसी value reject करता है जो किसी दूसरी row में पहले से हो, क्योंकि email जैसे असली identifiers बदलते हैं।

  • Uniqueness: पहले से मौजूद email वाला दूसरा customer साफ़ message के साथ reject हो, server error के साथ नहीं।
  • Stability: email बदलने वाले customer का id वही रहता है, इसलिए उसके orders अब भी उसी की ओर इशारा करते हैं।
  • Generation: records parallel में बनें, या ऐसे import के बाद जिसने अपनी ids खुद लिखीं, keys आपस में टकराती नहीं। ऐसा import database के counter को पीछे छोड़ सकता है, जिससे अगले नए record को पहले से इस्तेमाल हुई id मिल जाती है।
  • Exposure: sequential ids आसानी से guess हो जाती हैं, इसलिए URL में /orders/1043 बदलकर किसी दूसरे customer का order खोलना access-control test है। UUID को guess करना मुश्किल है, पर वह इस check की जगह नहीं लेता।

आज़माएँ: UUID जनरेटर

Foreign key और referential integrity

Foreign key (FK) किसी child table का column, या columns का set, है जिसकी values किसी parent table की primary key, या किसी दूसरी unique key, से मेल खानी चाहिए। orders.customer_id REFERENCES customers (id) में orders child है। Referential integrity तब होती है जब foreign key की हर non-null value किसी मौजूद parent row से मेल खाए; जो child row इसे तोड़ती है वह orphan row है।

Foreign key declare होने पर database गैर-मौजूद customer का order reject करता है। जिस parent के children अभी मौजूद हैं, उसे delete करने पर schema में लिखा referential action चलता है। इसे चुनना business का फ़ैसला है, इसलिए product owner से पक्का करें, फिर टेस्ट करें:

  • NO ACTION (default) या RESTRICT: delete reject होता है; दोनों में फ़र्क सिर्फ़ इतना है कि check कब चलता है।
  • CASCADE: children भी parent के साथ delete होते हैं।
  • SET NULL: children बचे रहते हैं और उनकी foreign key NULL हो जाती है, इसलिए column को NULL स्वीकार करना चाहिए।
  • NULL foreign key check पास कर लेती है, जब तक column NOT NULL न हो: optional relationship के लिए यह सही है, mandatory relationship के लिए defect।
  • Declare होने का मतलब हमेशा enforce होना नहीं: SQLite default रूप से foreign keys तभी जाँचता है जब हर connection पर PRAGMA foreign_keys = ON चलाया जाए, और कोई foreign key दूसरी service तक नहीं पहुँचती। वहाँ integrity सिर्फ़ application बचाता है।

One-to-one, one-to-many और many-to-many

Cardinality बताती है कि एक तरफ़ की कितनी rows दूसरी तरफ़ की कितनी rows से जुड़ सकती हैं। तीनों तरह की relationships इन्हीं दो keys से बनती हैं; बदलता सिर्फ़ यह है कि foreign key कहाँ है और उस पर कौन-सा constraint है।

  • One-to-many (1:N): एक customer के कई orders। Foreign key many side पर है, orders.customer_id, और किसी parent का एक भी child न होना भी संभव है।
  • One-to-one (1:1): एक customer का ज़्यादा से ज़्यादा एक loyalty card। यहाँ foreign key UNIQUE भी है, इसलिए उसी customer का दूसरा card reject होता है।
  • Many-to-many (N:M): एक order में कई products और एक product कई orders में। order_items जैसी junction table हर जोड़ी के लिए एक row रखती है, जिसमें दोनों तरफ़ की foreign key होती है; जोड़ी पर composite primary key duplicates रोकती है।
  • कोई foreign key parent को children रखने पर मजबूर नहीं कर सकती: "हर order में कम से कम एक line" application की ज़िम्मेदारी है, और खाली order एक classic defect है जिसे ढूँढना चाहिए।
Foreign key कहाँ है और कौन-सा constraint उसे सीमित करता है, इसी से relationship तय होती है: 1:N में many side पर, 1:1 में UNIQUE के साथ, और N:M में junction table के अंदर दो बार, composite key के साथ।

One-to-many (1:N)

एक customer, कई orders

customer_id INTEGER NOT NULL REFERENCES customers (id)

Foreign key:
Many side पर: orders.customer_id
सीमा तय करने वाला constraint:
कोई नहीं: हर customer के शून्य या ज़्यादा orders
Negative test:
मौजूद न होने वाले customer 999 का order: reject

One-to-one (1:1)

एक customer, ज़्यादा से ज़्यादा एक loyalty card

customer_id INTEGER NOT NULL UNIQUE REFERENCES customers (id)

Foreign key:
loyalty_cards.customer_id, साथ में UNIQUE
सीमा तय करने वाला constraint:
Foreign key पर UNIQUE: हर customer का ज़्यादा से ज़्यादा एक card
Negative test:
Customer 42 का दूसरा card: reject

Many-to-many (N:M)

कई orders, कई products

PRIMARY KEY (order_id, product_id)

Foreign key:
दो, junction table order_items में
सीमा तय करने वाला constraint:
Composite primary key: हर order और product की जोड़ी ज़्यादा से ज़्यादा एक बार
Negative test:
एक order में वही product दो बार: reject या merge

Worked example: एक छोटे store की keys टेस्ट करना

यह schema एक online store के test basis का हिस्सा है: हर REFERENCES clause एक relationship है, और हर ON DELETE clause, या उसका न होना, एक नियम है। Product owner पक्का करता है कि orders वाला customer delete नहीं हो सकता, order delete करने पर उसकी lines भी हटती हैं, और जिस product का कभी order हुआ हो वह delete नहीं हो सकता। हर action UI या API से करें, फिर stored rows जाँचें।

  • मौजूदा customer 42 का order: accept, और customer_id 42 के साथ stored।
  • गैर-मौजूद customer 999 का order, API से भेजा गया: validation message के साथ reject, server error नहीं, और कुछ भी stored नहीं।
  • Orders वाले customer 42 को delete करना: reject (RESTRICT)।
  • Customer 42 का दूसरा loyalty card: UNIQUE foreign key से reject।
  • एक order में वही product दो बार: composite key से reject, या requirement कहे तो एक line में merge।
  • Order delete करना: order_items में उसकी rows भी साथ में हटती हैं (CASCADE)।
  • जिस product का order हुआ हो उसे delete करना: reject (NO ACTION)।
  • ऐसे customer को delete करना जिसका loyalty card है पर कोई order नहीं: यह भी reject, क्योंकि loyalty_cards पर कोई delete rule नहीं है (NO ACTION)। यह नियम किसी ने नहीं बताया, इसलिए product owner से पूछें कि क्या यह जानबूझकर है।
CREATE TABLE customers (
  id          INTEGER PRIMARY KEY,
  email       VARCHAR(254) NOT NULL UNIQUE,
  full_name   VARCHAR(100) NOT NULL
);

CREATE TABLE loyalty_cards (                 -- 1:1 with customers
  card_no     CHAR(10) PRIMARY KEY,
  customer_id INTEGER NOT NULL UNIQUE REFERENCES customers (id)
);

CREATE TABLE orders (                        -- 1:N: one customer, many orders
  id          INTEGER PRIMARY KEY,
  customer_id INTEGER NOT NULL REFERENCES customers (id) ON DELETE RESTRICT,
  created_at  TIMESTAMP NOT NULL
);

CREATE TABLE products (
  id          INTEGER PRIMARY KEY,
  sku         VARCHAR(20) NOT NULL UNIQUE,
  name        VARCHAR(100) NOT NULL
);

CREATE TABLE order_items (                   -- N:M junction: orders x products
  order_id    INTEGER NOT NULL REFERENCES orders (id) ON DELETE CASCADE,
  product_id  INTEGER NOT NULL REFERENCES products (id),
  quantity    INTEGER NOT NULL,
  PRIMARY KEY (order_id, product_id)
);

आज़माएँ: SQL फ़ॉर्मेटर आज़माएँ: SQL INSERT जनरेटर

आम गलतियाँ

  • Relationships सिर्फ़ screens से टेस्ट करना: मौजूदा customers वाला drop-down उन गलत references को छिपा देता है जो API, imports और batch jobs अब भी भेज सकते हैं।
  • सिर्फ़ इसलिए foreign key मान लेना कि column का नाम customer_id है। Naming convention कुछ भी enforce नहीं करता।
  • CASCADE को हमेशा सही या हमेशा गलत मानना: invoices को आम तौर पर customer delete होने के बाद भी बचे रहना चाहिए, जबकि deleted order की lines को नहीं।
  • Junction table को छोड़ देना: duplicate जोड़ियाँ, और एक तरफ़ delete होने पर जोड़ियों का क्या होता है।
  • जहाँ enforcement बंद है वहाँ integrity tests चलाना, जैसे PRAGMA foreign_keys = ON के बिना SQLite, और उन्हें pass बताना।

QA का नज़रिया

शुरुआत refinement में करें: data model या migration (schema बदलने वाली script) की हर नई foreign key के लिए पूछें कि क्या वह खाली हो सकती है, parent delete होने पर children का क्या होगा, और यह किसने तय किया। जवाब test conditions बन जाते हैं, और जिस सवाल का कोई जवाब न दे सके वह code लिखे जाने से पहले मिला requirement defect है।

Testing के दौरान product से action करें और database में verify करें, उन paths समेत जिन्हें UI छिपाता है। Migrations, imports और fail हुए batch jobs के बाद उन relationships में orphans जाँचें जिन्हें database enforce नहीं करता। Defect report में tables, key values, कारण और टूटा हुआ नियम लिखें, जैसे: order 1043 customer 77 को reference करता है, जो मौजूद नहीं; यह row nightly import से आई।

अभ्यास

Course enrolment schema की कमियाँ खोजें

आपका काम

एक training platform की requirements: student कई courses में enrol कर सकता है और एक course में कई students होते हैं, लेकिन कोई एक ही course में दो बार enrol नहीं होता; हर student का ज़्यादा से ज़्यादा एक card होता है; enrolments वाला course delete करना reject होता है, जबकि student को delete करने पर उसके enrolments हट जाते हैं।

पता करें कि नीचे का schema कहाँ ये नियम enforce नहीं करता, और expected results के साथ test conditions लिखें।

सामग्री
CREATE TABLE students (
  id          INTEGER PRIMARY KEY,
  email       VARCHAR(254) NOT NULL UNIQUE
);

CREATE TABLE courses (
  id          INTEGER PRIMARY KEY,
  title       VARCHAR(120) NOT NULL
);

CREATE TABLE student_cards (
  card_no     CHAR(8) PRIMARY KEY,
  student_id  INTEGER NOT NULL REFERENCES students (id)
);

CREATE TABLE enrolments (
  id          INTEGER PRIMARY KEY,
  student_id  INTEGER NOT NULL REFERENCES students (id) ON DELETE CASCADE,
  course_id   INTEGER NOT NULL REFERENCES courses (id) ON DELETE CASCADE
);
चरण
  1. हर foreign key को उसकी child table, parent table और delete rule के साथ लिखें।
  2. हर requirement कौन-सी relationship बताती है और schema उसे कैसे enforce करता है, यह लिखें।
  3. हर नियम और हर कमी के लिए expected result के साथ एक test condition लिखें।
मॉडल उत्तर दिखाएँ
मॉडल उत्तर

Foreign keys: student_cards.student_id students को reference करता है, कोई delete rule नहीं (NO ACTION); enrolments.student_id students को, CASCADE के साथ; enrolments.course_id courses को, CASCADE के साथ।

Relationships: students और courses के बीच N:M, junction table enrolments के ज़रिए; students और student cards के बीच 1:1। Student delete करने का नियम schema सही enforce करता है: enrolments.student_id पर ON DELETE CASCADE।

कमी 1: enrolments में (student_id, course_id) पर UNIQUE constraint नहीं है। Test: उसी student को उसी course में दो बार enrol करें, दो parallel API calls से भी। Expected: सिर्फ़ एक row, और दूसरी कोशिश पर साफ़ message। Fix: UNIQUE (student_id, course_id), या इस जोड़ी को primary key बनाएँ।

कमी 2: student_cards.student_id UNIQUE नहीं है, इसलिए schema 1:N की अनुमति देता है। Test: उसी student को दूसरा card जारी करें। Expected: reject। Fix: student_id को UNIQUE बनाएँ।

कमी 3: enrolments.course_id पर ON DELETE CASCADE है, इसलिए course delete करने पर उसके enrolments चुपचाप delete हो जाते हैं। Test: एक enrolment वाला course delete करें। Expected: reject, और दोनों rows बची रहें। Fix: ON DELETE RESTRICT, या clause हटा दें ताकि default NO ACTION लागू हो।

यह भी टेस्ट करें: गैर-मौजूद course 999 में enrol करना reject होता है; जिस student के enrolments हैं पर card नहीं, उसे delete करने पर student और उसके enrolments हटते हैं और courses बचे रहते हैं। Card वाले student के बारे में product owner से पूछें: student_cards पर कोई delete rule नहीं है, इसलिए वह delete reject होता है।

अक्सर पूछे जाने वाले सवाल

क्या कोई table बिना primary key के हो सकती है?

ज़्यादातर databases इसकी अनुमति देते हैं, लेकिन तब कोई गारंटी नहीं कि हर row पहचानी जा सके: duplicate rows में फ़र्क नहीं किया जा सकता, update या delete कई rows पर लग सकता है, और दूसरी tables उसकी rows को भरोसे से reference नहीं कर सकतीं। बिना primary key वाली table को team के लिए सवाल मानें, और उसमें duplicates जाँचें।

क्या UNIQUE column और primary key एक ही हैं?

दोनों duplicate values reject करते हैं, लेकिन table में primary key सिर्फ़ एक होती है और standard SQL में उसके columns कभी NULL नहीं हो सकते। SQLite इसका जाना-माना अपवाद है: backward compatibility के लिए वह NOT NULL न लिखे गए ज़्यादातर primary key columns में NULL स्वीकार कर लेता है। Table में कई UNIQUE constraints हो सकते हैं, और UNIQUE column आम तौर पर NULL स्वीकार करता है; कितने NULL स्वीकार होंगे, यह database के हिसाब से बदलता है। Foreign key इन दोनों में से किसी को भी reference कर सकती है।

अगर database foreign keys enforce करता है, तो क्या फिर भी referential integrity टेस्ट करूँ?

हाँ, लेकिन आप database engine को टेस्ट नहीं कर रहे। आप जाँचते हैं कि सही delete rules वाले सही constraints मौजूद हैं, कि वे हर environment में चालू हैं, और कि application reject होने पर server error की जगह साफ़ message देता है। जहाँ foreign key हो ही नहीं सकती, जैसे दो services के बीच, वहाँ application के अपने checks ही अकेली सुरक्षा हैं।

Orphan rows कैसे ढूँढें?

Child table को उसकी parent table से join करें और वे child rows रखें जिनका कोई match नहीं, जैसे SELECT o.id FROM orders o LEFT JOIN customers c ON c.id = o.customer_id WHERE c.id IS NULL। SQL JOINs वाला lesson यह pattern step by step समझाता है।

संदर्भ

  • Constraints — PostgreSQL DocumentationPostgreSQL Global Development Groupcheck, not-null, unique, primary key, foreign key और exclusion constraints — वे नियम जो डेटाबेस लागू करता है और टेस्टर जाँचता है।
  • ISO/IEC 9075-1:2023 — Information technology — Database languages SQL — Part 1: Framework (SQL/Framework)ISO/IEC · 2023अंतरराष्ट्रीय SQL मानक का framework भाग, जिसे PostgreSQL, MySQL, SQLite आदि की बोलियाँ अलग-अलग हद तक लागू करती हैं।
  • SQL As Understood By SQLiteSQLiteSQLite द्वारा स्वीकृत SQL व्याकरण, स्टेटमेंट दर स्टेटमेंट, सिंटैक्स डायग्राम के साथ।

Mutqan tools से अभ्यास करें

SQL क्वेरी को एकसमान कीवर्ड केसिंग, इंडेंटेशन और लाइन ब्रेक के साथ फ़ॉर्मेट करें (MySQL, PostgreSQL, SQL Server, Oracle, SQLite)।

डेटाबेस

SQL स्टेटमेंट में सिंटैक्स की समस्याएँ, असंतुलित कोष्ठक, बिना बंद की स्ट्रिंग और जोखिम भरे पैटर्न जाँचें।

डेटाबेस

टेबल परिभाषा, नमूना पंक्तियों, CSV या JSON से INSERT स्टेटमेंट बनाएँ।

डेटाबेस

UUID v4 (रैंडम) और v7 (समय-क्रमित) पहचानकर्ता थोक में बनाएँ और मौजूदा UUID वैलिडेट करें।

यूटिलिटीज़

और गहराई से

इस topic में आपकी प्रगति

QA रोडमैप का हिस्सा