कंस्ट्रेंट्स और डेटा इंटीग्रिटी

Constraint database schema में declare किया गया एक rule है। Database हर insert, update और delete पर इसे जाँचता है और rule तोड़ने वाले हर statement को reject कर देता है, चाहे वह किसी भी application से आया हो। SQL standard और बड़े relational engines एक core set साझा करते हैं: NOT NULL, UNIQUE, PRIMARY KEY, FOREIGN KEY और CHECK, और DEFAULT को आम तौर पर इनके साथ पढ़ाया जाता है।

उदाहरण: order lines पर CHECK (quantity BETWEEN 1 AND 50) हो, तो quantity 0 वाली line को database खुद reject करता है, चाहे वह web form से आए, CSV import से या किसी developer की script से। Data integrity लक्ष्य है — ऐसा data जो पूरा, valid और consistent हो — और constraints उसका वह हिस्सा हैं जिसकी guarantee database दे सकता है।

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

Application validation code के एक रास्ते में रहता है; data कई रास्तों से आता है: imports, batch jobs, admin screens, दूसरी services और एक ही पल में दौड़ती दो requests। इनमें से कोई भी web form वाली checks छोड़ सकता है। Constraint न हो तो गलत row चुपचाप store हो जाती है और हफ़्तों बाद duplicate customer या ऐसे order की ओर इशारा करती order line बनकर सामने आती है जो मौजूद ही नहीं।

गलत data किसी के ध्यान देने से पहले reports, invoices और दूसरे systems में फैल जाता है, और उसे ठीक करने के लिए सिर्फ़ code fix नहीं, data fix चाहिए। Tester के लिए हर constraint एक सटीक requirement है: एक statement reject होना चाहिए, एक accept, और missing constraint को एक ही statement से दिखाया जा सकता है।

छह constraints और हर एक क्या reject करता है

Primary key और foreign key से आप keys and relationships वाले lesson में मिल चुके हैं; यहाँ वे छह में से दो constraints हैं। इस section के आख़िर में दी गई order_lines table छहों declare करती है, और बाकी lesson उसी को टेस्ट करता है।

  • NOT NULL: column में value होनी चाहिए। खाली string '' भी value है, इसलिए अकेला NOT NULL खाली SKU स्वीकार कर लेता है (Oracle जाना-माना अपवाद है: वह '' को NULL के रूप में store करता है)।
  • UNIQUE: दो rows एक ही value या एक ही combination नहीं रख सकतीं: UNIQUE (order_id, sku) हर SKU को हर order में सिर्फ़ एक बार आने देता है। NULL, NULL के बराबर नहीं होता, इसलिए PostgreSQL, MySQL और SQLite एक UNIQUE column में कई NULL स्वीकार करते हैं; SQL Server सिर्फ़ एक।
  • PRIMARY KEY: UNIQUE और NOT NULL एक साथ, हर table में अधिकतम एक।
  • FOREIGN KEY: value referenced key में मौजूद होनी चाहिए, या NULL हो। ON DELETE clause न हो तो ऐसे parent को delete करना reject होता है जिसके children अभी मौजूद हैं; ON DELETE CASCADE या SET NULL यह व्यवहार बदलते हैं, और सही कौन-सा है यह requirement तय करती है।
  • CHECK: एक condition जो हर row को पूरी करनी है। यह तभी fail होता है जब condition false हो; NULL से comparison का नतीजा unknown होता है, इसलिए CHECK (quantity > 0) NULL को जाने देता है, जब तक column NOT NULL भी न हो।
  • DEFAULT: वह value जो insert में column छोड़ने पर store होती है। यह कुछ reject नहीं करता, और जो insert खुद NULL भेजता है उसे default नहीं मिलता।
-- orders(id) already exists
CREATE TABLE order_lines (
  id        INTEGER      PRIMARY KEY,
  order_id  INTEGER      NOT NULL REFERENCES orders (id),
  sku       VARCHAR(20)  NOT NULL,
  quantity  INTEGER      NOT NULL CHECK (quantity BETWEEN 1 AND 50),
  status    VARCHAR(10)  NOT NULL DEFAULT 'open'
            CHECK (status IN ('open', 'shipped', 'returned')),
  UNIQUE (order_id, sku)
);

Data integrity के चार प्रकार

Textbooks logical data integrity, यानी stored values के सही होने, को चार प्रकारों में बाँटती हैं; physical integrity, यानी crash और disk faults के बाद भी data बचा रहना, backup और recovery testing का विषय है। चारों को checklist की तरह इस्तेमाल करें: हर प्रकार के लिए खोजें कि उसकी रक्षा कौन करता है।

  • Entity integrity: हर row पहचानी जा सकती है। रक्षा करते हैं PRIMARY KEY, और email address जैसी natural keys पर UNIQUE।
  • Referential integrity: हर reference किसी मौजूद row की ओर इशारा करता है। रक्षा करता है FOREIGN KEY; इसके उल्लंघन orphan rows हैं।
  • Domain integrity: हर value अपने column के लिए valid है। रक्षा करते हैं data type और length, NOT NULL, CHECK और DEFAULT।
  • User-defined (business) integrity: इस business के अपने rules, जैसे "doctor के दो appointments overlap नहीं कर सकते"। एक row वाला rule CHECK हो सकता है; कई rows या tables में फैले rule के लिए आम तौर पर trigger या application code चाहिए, जो stored procedures and triggers वाले lesson का विषय है।

Constraint को टेस्ट कैसे करें

दो layers पर टेस्ट करें। Database पर साबित करें कि rule मौजूद है और सच में लागू होता है। Product के ज़रिए साबित करें कि rejection साफ़ message बनता है: declare किया गया rule जिसका error user तक 500 page बनकर पहुँचे, फिर भी defect है।

Design document से नहीं, चल रहे schema से शुरू करें: migration files पढ़ें, या इस section के आख़िर की information_schema queries से database से पूछें; information_schema वे standard views हैं जिनमें database अपनी tables, columns और constraints का ब्योरा देता है (PostgreSQL, MySQL और SQL Server इसे देते हैं)। फिर हर rule के लिए:

  • एक rule, दो tests, boundary value analysis की तरह: वह value जो reject होनी चाहिए और सबसे नज़दीकी value जो accept होनी चाहिए। BETWEEN 1 AND 50 में 0 और 51 reject, 1 और 50 accept; VARCHAR(20) में 21 characters reject और 20 accept।
  • एक बार में एक column तोड़ें और पक्का करें कि कौन-सा rule चला। दो rules तोड़ने वाली row को वह constraint reject करता है जिसे engine पहले जाँचता है। PostgreSQL SQLSTATE, यानी पाँच characters वाला standard error code, लौटाता है: NOT NULL के लिए 23502, UNIQUE और PRIMARY KEY के लिए 23505, FOREIGN KEY के लिए 23503 और CHECK के लिए 23514, सब class 23, integrity constraint violation में।
  • हर violation को UI और API से दोहराएँ: साफ़ message या API contract में तय 4xx status, जैसे 409 या 422, की उम्मीद करें; table या constraint के नाम दिखाने वाला 500 कभी नहीं।
  • वे रास्ते टेस्ट करें जो application validation छोड़ देते हैं: imports, batch jobs, admin tools और एक ही पल में भेजी गई दो एक जैसी requests। जो code insert से पहले पूछता है "क्या यह email मौजूद है?", वह दौड़ती दोनों requests को जाने दे सकता है; दूसरी को सिर्फ़ UNIQUE constraint रोकता है।
  • अपना engine जानें: MySQL CHECK को version 8.0.16 से ही लागू करता है, और 9.0 से पहले column definition के अंदर लिखे REFERENCES को parse करके अनदेखा कर देता है, जैसा order_lines में है, इसलिए वहाँ key अलग से declare करनी होगी: FOREIGN KEY (order_id) REFERENCES orders (id)। SQLite foreign keys तभी लागू करता है जब PRAGMA foreign_keys = ON चले, और VARCHAR(20) की length को अनदेखा करता है।
  • जहाँ किसी rule का constraint नहीं है, वहाँ SQL for Testers की duplicate और orphan queries से stored data का audit करें।
SELECT constraint_name, constraint_type
FROM information_schema.table_constraints
WHERE table_name = 'order_lines';

SELECT column_name, is_nullable, column_default
FROM information_schema.columns
WHERE table_name = 'order_lines';

आज़माएँ: बाउंड्री वैल्यू जनरेटर आज़माएँ: स्ट्रिंग लेंथ काउंटर

Worked example: constraint test script

Order 7 orders में मौजूद है; order 9999 नहीं। T0 एक valid row insert करता है, और उसके बाद हर statement ठीक एक rule तोड़ता है। Comments expected result और PostgreSQL error code बताते हैं।

Statements एक-एक करके ऐसे test database में चलाएँ जिसे फेंका जा सके। PostgreSQL में fail हुआ statement अपना transaction abort कर देता है, इसलिए एक ही BEGIN block के अंदर T1 के बाद हर statement गलत वजह से fail होगा।

-- Disposable test database, autocommit: run one statement at a time.
-- T0 valid row -> accepted; status gets the DEFAULT 'open'
INSERT INTO order_lines (id, order_id, sku, quantity)
VALUES (1, 7, 'A-100', 2);
-- T1 PRIMARY KEY: id 1 again -> rejected, 23505
INSERT INTO order_lines (id, order_id, sku, quantity)
VALUES (1, 7, 'B-200', 2);
-- T2 UNIQUE (order_id, sku): A-100 on order 7 again -> rejected, 23505
INSERT INTO order_lines (id, order_id, sku, quantity)
VALUES (2, 7, 'A-100', 1);
-- T3 FOREIGN KEY: order 9999 does not exist -> rejected, 23503
INSERT INTO order_lines (id, order_id, sku, quantity)
VALUES (3, 9999, 'C-300', 1);
-- T4 NOT NULL: no SKU -> rejected, 23502
INSERT INTO order_lines (id, order_id, sku, quantity)
VALUES (4, 7, NULL, 1);
-- T5 CHECK quantity: 0 -> rejected, 23514 (also 51; 1 and 50 are accepted)
INSERT INTO order_lines (id, order_id, sku, quantity)
VALUES (5, 7, 'D-400', 0);
-- T6 CHECK status: 'lost' is not in the list -> rejected, 23514
INSERT INTO order_lines (id, order_id, sku, quantity, status)
VALUES (6, 7, 'E-500', 1, 'lost');
-- T7 explicit NULL skips the DEFAULT -> rejected by NOT NULL, 23502
INSERT INTO order_lines (id, order_id, sku, quantity, status)
VALUES (7, 7, 'F-600', 1, NULL);
order_lines के लिए constraint test matrix: छहों rules में से हर एक के लिए वह बदलाव जिसे database को reject करना है, सबसे नज़दीकी value जिसे accept करना है, और आगे जाँचने वाला gap।
ConstraintReject होना चाहिएसबसे नज़दीकी value जो accept होफिर जाँचें
NOT NULLskuNULLT4 · error 23502'A-100'खाली string और spaces NOT NULL से निकल जाते हैं: क्या निकलने चाहिए?
UNIQUE(order_id, sku)Order 7 पर A-100 दो बारT2 · error 23505दूसरे order पर A-100API से वही line दो बार, एक ही पल में भी: 409 या 422, और एक ही row
PRIMARY KEYidid 1 दूसरी बारT1 · error 23505नया idid को NULL भेजना भी reject होता है: PRIMARY KEY में NOT NULL शामिल है
FOREIGN KEYorder_idOrder 9999, जो मौजूद नहींT3 · error 23503Order 7Lines रहते order 7 delete करना: reject, जब तक ON DELETE CASCADE न हो
CHECKquantity0 / 51T5 · error 235141 / 50CHECK NULL को जाने देता है; यहाँ NOT NULL उसे रोकता है
DEFAULTstatus 'open'status को NULL भेजनाT7 · NOT NULL reject करता हैstatus छोड़ा → openAPI field छोड़ती है या null भेजती है?
  • सख़्त अर्थ में constraint नहीं: DEFAULT value भरता है, कुछ reject नहीं करता

Expected results और error codes वैसे जैसे PostgreSQL देता है। UNIQUE column में NULLs और letter case की तुलना जैसी बातों में engines अलग होते हैं।

आज़माएँ: SQL INSERT जनरेटर आज़माएँ: SQL क्वेरी वैलिडेटर आज़माएँ: SQL डेटासेट जनरेटर

नतीजे पढ़ना: pass होते tests के पीछे के gaps

आठों expected results साबित करते हैं कि declared rules काम करते हैं। उठाने लायक findings आम तौर पर उससे आती हैं जो rules से छूट जाता है:

  • VALUES (8, 7, '', 1) accept हो जाता है, क्योंकि खाली SKU NULL नहीं है। पूछें कि schema को CHECK (sku <> '') चाहिए या API को इसे reject करना चाहिए।
  • T7 में API का एक trap छिपा है: जो service गायब JSON field को explicit NULL बना देती है, वह status के बिना हर request fail कर देती है। Request एक बार field छोड़कर और एक बार "status": null के साथ भेजें।
  • Lines मौजूद रहते DELETE FROM orders WHERE id = 7 reject होना चाहिए, सिवाय तब जब requirement कहे कि lines अपने order के साथ जाएँ (ON DELETE CASCADE)।
  • T2 वाली line को API से दो बार post करें: ऐसे message के साथ 409 या 422 की उम्मीद करें जिसे user समझे, और table में अब भी एक ही row।
  • Capital O वाला 'Open' status का CHECK पार करेगा या नहीं, यह column के collation पर निर्भर है: PostgreSQL इसे reject करता है, जबकि MySQL का default collation इसे accept करके store कर लेता है। इसलिए इसे उसी engine पर टेस्ट करें जिस पर आप ship करते हैं।

आम गलतियाँ

  • Rules को सिर्फ़ user interface से टेस्ट करना, जिसका validation छिपा देता है कि database कुछ लागू करता भी है या नहीं।
  • NOT NULL को "required" समझना: यह NULL reject करता है पर खाली string accept कर लेता है।
  • हर error को pass मान लेना। अगर row दो rules तोड़ती है या statement में syntax error है, तो rejection टेस्ट हो रहे rule के बारे में कुछ साबित नहीं करता।
  • भूल जाना कि CHECK और nullable foreign key column, दोनों NULL को जाने देते हैं।
  • चल रहे database की जगह design document पर भरोसा करना, जहाँ constraint गायब, disabled या engine version में unsupported हो सकता है।
  • 500 response या raw database error को इसलिए स्वीकार कर लेना कि "गलत data save नहीं हुआ"। Data सुरक्षित है; error handling defect है।

QA का नज़रिया

Constraint testing refinement और code review में शुरू होती है। Story के हर rule के लिए, जैसे "email unique होना चाहिए", पूछें कि वह कहाँ लागू होता है — UI, API, database या तीनों में — और fail होने पर user को क्या दिखना चाहिए। Pull requests में migrations पढ़ें: गायब NOT NULL या foreign key को data लिखे जाने से पहले ठीक करना सबसे सस्ता है। जो table पहले से किसी UNIQUE या CHECK constraint को तोड़ती है, उसमें वह constraint जोड़ना आम तौर पर fail होता है, इसलिए पूछें कि मौजूदा rows कैसे साफ़ होंगी।

Violation tests सिर्फ़ ऐसे test database में चलाएँ जिसे बदलने की आपको अनुमति हो, production में कभी नहीं। इन्हें एक छोटी data-driven table के रूप में रखें — constraint, statement, expected error, expected API response — और हर schema change के बाद दोबारा चलाएँ। Defect report में exact statement या request, expected और actual result, और engine व उसका version लिखें।

अभ्यास

Clinic की appointments table के constraint tests बनाएँ

आपका काम

एक clinic अपने appointments नीचे की table में store करता है। हर constraint के लिए valid row में एक ऐसा बदलाव लिखें जिसे database को reject करना चाहिए, और सबसे नज़दीकी बदलाव जिसे accept करना चाहिए, दोनों के expected result के साथ।

फिर वे rules बताएँ जिनकी receptionist उम्मीद करेगा पर कोई constraint लागू नहीं करता, और बताएँ कि आप उन्हें कैसे टेस्ट करेंगे।

सामग्री
-- patients 12 and 13 and doctors 3 and 4 exist
CREATE TABLE appointments (
  id          INTEGER      PRIMARY KEY,
  patient_id  INTEGER      NOT NULL REFERENCES patients (id),
  doctor_id   INTEGER      NOT NULL REFERENCES doctors (id),
  starts_at   TIMESTAMP    NOT NULL,
  minutes     INTEGER      NOT NULL DEFAULT 15 CHECK (minutes BETWEEN 10 AND 60),
  status      VARCHAR(12)  NOT NULL DEFAULT 'booked'
              CHECK (status IN ('booked', 'attended', 'no_show', 'cancelled')),
  UNIQUE (doctor_id, starts_at)
);
चरण
  1. Constraints को column दर column लिखें, दो columns वाले UNIQUE और दोनों defaults समेत।
  2. एक valid baseline row लिखें, फिर एक बार में एक constraint तोड़ें।
  3. हर constraint के लिए सबसे नज़दीकी accepted value जोड़ें, और minutes छोड़ने व minutes को NULL भेजने के नतीजे लिखें।
  4. वे business rules नोट करें जिन्हें schema बिना सुरक्षा के छोड़ देता है।
मॉडल उत्तर दिखाएँ
मॉडल उत्तर

Baseline, accept: patient 12, doctor 3, 2026-11-02 10:00, 30 minutes; status छोड़ा गया और booked store हुआ।

PRIMARY KEY: वही id दोबारा → reject; नया id → accept। FOREIGN KEY: patient 999 या doctor 999 → reject; doctor 4 के साथ patient 13 → accept। NOT NULL: starts_at NULL → reject।

CHECK: minutes 9 और 61 → reject, 10 और 60 → accept; status done → reject, no_show → accept। DEFAULT: minutes छोड़ा → 15 store; minutes या status को NULL भेजा → NOT NULL reject करता है।

UNIQUE (doctor_id, starts_at): doctor 3 फिर से 10:00 पर → reject; patient 13 के साथ doctor 4 10:00 पर, या doctor 3 10:30 पर → accept।

जिन्हें कोई लागू नहीं करता: overlapping appointments (doctor 3 10:00 पर 30 minutes के लिए और फिर 10:15 पर, दोनों accept होते हैं, क्योंकि UNIQUE सिर्फ़ एक जैसे start times की तुलना करता है), और एक ही patient की एक ही समय पर दो doctors के पास booking। इन्हें booking screen और API से टेस्ट करें, और self-join से stored rows का audit करें: ऐसी query जो appointments को खुद से join करके एक ही doctor के overlap होने वाले appointments के जोड़े ढूँढे। फिर पूछें कि rule application code में, trigger में या PostgreSQL के exclusion constraint के रूप में रहना चाहिए, जो उसी doctor की किसी दूसरी row से overlap होने वाली time range वाली row को reject कर सकता है।

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

अगर application पहले से input validate करता है, तो database constraints क्यों टेस्ट करें?

क्योंकि application अकेला लिखने वाला नहीं है। Imports, scheduled jobs, admin tools, दूसरी services और concurrent requests उसी validation के बिना tables तक पहुँचते हैं। Application checks users को अच्छे messages देते हैं; constraints data की guarantee देते हैं। Tester दोनों जाँचता है।

क्या DEFAULT सच में constraint है?

सख़्त अर्थ में नहीं। SQL standard और PostgreSQL documentation में default column definition का हिस्सा है, constraints का नहीं, क्योंकि यह कभी कुछ reject नहीं करता; हालाँकि SQL Server इसे DEFAULT constraint कहता है। इसे constraints के साथ इसलिए पढ़ाया जाता है कि यह stored data को आकार देता है, और इसके अपने दो tests चाहिए: column छोड़ना, और column को NULL भेजना।

क्या मैं database access के बिना constraints टेस्ट कर सकता हूँ?

आंशिक रूप से। API से आप duplicates, गायब fields, range से बाहर values और न मौजूद references भेजकर responses जाँच सकते हैं, पर यह नहीं जान सकते कि data किस layer ने reject किया, न पहले से stored गलत rows ढूँढ सकते हैं। Schema और test database का read access माँगें, या database-level tests के लिए किसी developer के साथ pair करें।

क्या हर business rule को database constraint बनना चाहिए?

नहीं। जो rules दूसरी services, मौजूदा समय या अक्सर बदलने वाली policy पर निर्भर हैं, वे आम तौर पर application code में रहते हैं। Uniqueness, valid references और non-negative amounts जैसे core invariants को constraint से फ़ायदा होता है, क्योंकि data चाहे कोई भी लिखे, उन्हें सही रहना है। जानें कि हर rule कौन-सी layer लागू करती है, उस layer को टेस्ट करें, और उसके आसपास के रास्तों को भी।

संदर्भ

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

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

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

डेटाबेस

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

डेटाबेस

किसी भी dialect के लिए वास्तविक जैसी टेस्ट पंक्तियों के साथ SQL seed स्क्रिप्ट (CREATE TABLE + INSERT) जनरेट करें।

टेस्ट डेटा

और गहराई से

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

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