SQL जॉइन्स

Join दो tables की rows को एक result में जोड़ता है: वह उन rows की जोड़ी बनाता है जो join condition पूरी करती हैं, आम तौर पर एक foreign key जो उसी primary key के बराबर हो जिसे वह refer करती है। orders JOIN customers ON orders.customer_id = customers.customer_id हर order को उसे देने वाले customer के बगल में रखता है, ताकि एक ही query दिखा दे कि order 1042 सही व्यक्ति का है या नहीं।

Join type तय करता है कि बिना साथी वाली rows का क्या हो: INNER JOIN उन्हें हटा देता है, जबकि LEFT, RIGHT और FULL joins उन्हें रखते हैं और गायब पक्ष में NULL भर देते हैं। इसी फ़र्क से tester orphan rows ढूँढता है: ऐसी child rows जिनका parent मौजूद नहीं।

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

कई data defects tables के बीच रहते हैं: deleted customer की ओर इशारा करता order, बिना order का payment, या कोई import जिसने दूसरे system के ids लिख दिए। हर table अकेले में सही दिखती है; सिर्फ़ join टूटे references दिखाता है। पकड़ी न जाएँ तो ऐसी rows screens पर असल से कम items दिखाती हैं और reports को आपस में असहमत करती हैं।

Joins tester के लिए एक जाल भी हैं। Inner join बिना match वाली rows को बिना किसी error के छिपा देता है, और गलत join condition ठीक दिखने वाली पर गलत rows लौटाती है। सही join type और पहले से अनुमानित row count ही query को false pass की बजाय evidence बनाते हैं।

Join rows का मिलान कैसे करता है

आप FROM … JOIN में दोनों tables लिखते हैं और ON के बाद join condition, जो आम तौर पर वैसा relationship होती है जैसा keys and relationships वाले lesson में था: foreign key orders.customer_id उस primary key के बराबर होती है जिसे वह refer करती है, यहाँ customers.customer_id। Result में rows की हर वह जोड़ी होती है जिसके लिए condition true हो।

नीचे की tables में order 104 customer 9 को refer करता है, जो मौजूद नहीं: वैसा ही टूटा reference जिसे tester खोजता है। c और o aliases हैं, यानी tables के छोटे नाम।

-- customers                    orders
-- customer_id  full_name       order_id  customer_id  total
-- 1            Layla           101       1            250.00
-- 2            Omar            102       1             40.00
-- 3            Priya           103       2            120.00
--                              104       9             75.00

SELECT c.customer_id, c.full_name, o.order_id, o.total
FROM customers c
INNER JOIN orders o ON o.customer_id = c.customer_id
ORDER BY o.order_id;
-- 3 rows: customer 1 with orders 101 and 102, customer 2 with order 103

INNER, LEFT, RIGHT और FULL joins

चारों types matched जोड़ियाँ लौटाते हैं और सिर्फ़ बिना match वाली rows में अलग हैं। पहले लिखी गई customers left table है; orders right table है।

  • INNER JOIN, या सिर्फ़ JOIN: केवल matched जोड़ियाँ, 3 rows। Customer 3, जिसके कोई orders नहीं, और order 104, जिसका customer गायब है, बिना किसी warning के गायब हो जाते हैं।
  • LEFT JOIN (LEFT OUTER JOIN): left table की हर row, 4 rows। Customer 3 बना रहता है, order columns में NULL के साथ।
  • RIGHT JOIN (RIGHT OUTER JOIN): right table की हर row, 4 rows। Order 104 बना रहता है, customer columns में NULL के साथ।
  • FULL JOIN (FULL OUTER JOIN): दोनों tables की हर row, 5 rows। PostgreSQL, SQL Server और Oracle Database इसे support करते हैं; MySQL नहीं करता, और SQLite RIGHT व FULL joins version 3.39.0 से ही support करता है।
Customers 1, 2 और 3 को orders 101 से 104 के साथ join किया गया, जहाँ order 104 गायब customer 9 की ओर इशारा करता है: INNER JOIN 3 rows लौटाता है, LEFT और RIGHT JOIN 4-4, FULL JOIN 5।
RowINNER JOINसिर्फ़ matchesLEFT JOIN+ हर customerRIGHT JOIN+ हर orderFULL JOIN+ दोनों पक्ष
Customer 1 + order 101result मेंresult मेंresult मेंresult में
Customer 1 + order 102result मेंresult मेंresult मेंresult में
Customer 2 + order 103result मेंresult मेंresult मेंresult में
Customer 3कोई order नहींresult में नहींresult मेंorder कॉलम NULLresult में नहींresult मेंorder कॉलम NULL
Order 104customer 9 मौजूद नहींresult में नहींresult में नहींresult मेंcustomer कॉलम NULLresult मेंcustomer कॉलम NULL
लौटी rows3445
  • Matched जोड़ी: हर join type इसे लौटाता है
  • बिना match वाली row: सिर्फ़ उस outer join में रहती है जो उसकी table रखता है, गायब पक्ष में NULL के साथ
  • Orphan row: order ऐसे customer को refer करता है जो मौजूद नहीं

हर column में वही tables और वही condition o.customer_id = c.customer_id; सिर्फ़ join type बदलता है। खाली cell का मतलब है कि वह row उस result में नहीं है।

SELECT c.customer_id, o.order_id, o.total
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
ORDER BY c.customer_id, o.order_id;

-- customer_id  order_id  total
-- 1            101       250.00
-- 1            102        40.00
-- 2            103       120.00
-- 3            NULL      NULL     <- kept by LEFT JOIN, dropped by INNER JOIN

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

Join condition लिखना

ज़्यादातर गलत join results ON clause से आते हैं, और गलत condition भी ठीक-ठाक दिखने वाली rows लौटाती है, इसलिए हर row count को अपेक्षित संख्या से मिलाएँ।

Outer join में extra filter की जगह मायने रखती है। ON में वह सिर्फ़ तय करता है कि match क्या गिना जाए, इसलिए left table की बिना match वाली rows बनी रहती हैं। WHERE में वह join के बाद चलता है, NULL वाली rows हटा देता है और left join को चुपचाप inner join बना देता है, जैसा नीचे की दो queries दिखाती हैं। IS NULL की जाँच इसका अपवाद है: वह ठीक NULL वाली rows ही रखती है, और अगले section का orphan check इसी पर टिका है।

  • उन्हीं columns पर join करें जिन्हें relationship इस्तेमाल करता है: o.customer_id = c.customer_id। Condition o.order_id = c.customer_id भी चल जाती है, पर जहाँ भी दोनों numbers संयोग से बराबर हों, वहाँ असंबंधित rows जोड़ देती है।
  • Composite key के हर column का इस्तेमाल करें: अगर customer ids सिर्फ़ किसी tenant (shared, multi-tenant system में एक client organisation) के अंदर unique हैं, तो tenant_id और customer_id दोनों पर join करें; वरना order दूसरे tenants के उसी number वाले customers से भी जुड़ जाएगा।
  • One-to-many joins में दोहराव की उम्मीद रखें: customer 1 दो बार दिखता है क्योंकि उसके दो orders हैं, इसलिए rows गिनना orders गिनना है, customers नहीं।
  • Condition छोड़ देने पर हर row हर row से जुड़ती है, यानी Cartesian product, जिसे CROSS JOIN जानबूझकर लौटाता है: 3 customers और 4 orders से 12 rows बनती हैं। यह FROM customers, orders (comma से अलग tables) में होता है जब कोई WHERE उन्हें नहीं जोड़ता, और MySQL में बिना ON वाले JOIN में भी; PostgreSQL ऐसा JOIN reject करता है।
  • NULL किसी के बराबर नहीं होता, दूसरे NULL के भी नहीं, इसलिए जिस row के join column में NULL हो वह कभी match नहीं होती: inner join उसे हटा देता है, left join उसे रखता है।
-- Every customer, with their orders over 100.00 if any: 3 rows
SELECT c.customer_id, o.order_id, o.total
FROM customers c
LEFT JOIN orders o
  ON o.customer_id = c.customer_id AND o.total > 100.00;
-- 1 | 101 | 250.00     2 | 103 | 120.00     3 | NULL | NULL

-- The same filter in WHERE also removes customer 3: 2 rows
SELECT c.customer_id, o.order_id, o.total
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
WHERE o.total > 100.00;
-- 1 | 101 | 250.00     2 | 103 | 120.00

LEFT JOIN … IS NULL से orphan rows ढूँढना

Orphan row वह child row है जिसकी foreign key ऐसे parent की ओर इशारा करती है जो मौजूद नहीं, जैसे order 104। Child table से शुरू करें, parent को left join करें, और सिर्फ़ वे rows रखें जिनसे कोई parent match नहीं हुआ। सही database शून्य rows लौटाता है; लौटी हर row bug report का evidence है। उलटी दिशा में यही ढाँचा ऐसे parents ढूँढता है जिनके ज़रूरी children नहीं हैं, जैसे बिना items वाला order।

IS NULL parent की primary key पर जाँचें, जो match होने पर कभी NULL नहीं होती; optional phone number जैसा nullable column उन customers के orders भी दिखा देगा जिनका phone नहीं है। जिन children की foreign key ख़ुद NULL है, वे भी आती हैं: column required हो तो यह defect है, वरना उन्हें AND o.customer_id IS NOT NULL से बाहर रखें।

नीचे के block की दूसरी query यही check NOT EXISTS से लिखती है। Brackets के अंदर की query, यानी subquery, हर order के लिए जाँची जाती है; NOT EXISTS तब true होता है जब उसे कोई row नहीं मिलती। SELECT 1 सिर्फ़ यह बताता है कि subquery के columns मायने नहीं रखते, मायने सिर्फ़ इसका है कि कोई row है या नहीं।

लागू foreign key constraint orphans को रोकता है, फिर भी practice में वे दिखते हैं:

  • Constraint कभी घोषित ही नहीं हुआ, जो legacy schemas में आम है।
  • Bulk import या migration के लिए constraints बंद किए गए।
  • SQLite foreign keys तभी लागू करता है जब connection के लिए PRAGMA foreign_keys = ON set हो।
  • Parent और child दो अलग services के databases में हैं।
-- Orders whose customer does not exist (expected: 0 rows)
SELECT o.order_id, o.customer_id
FROM orders o
LEFT JOIN customers c ON c.customer_id = o.customer_id
WHERE c.customer_id IS NULL;
-- returns order 104 with customer_id 9: an orphan

-- The same check written with NOT EXISTS
SELECT o.order_id, o.customer_id
FROM orders o
WHERE NOT EXISTS (
  SELECT 1 FROM customers c WHERE c.customer_id = o.customer_id
);

आज़माएँ: SQL INSERT जनरेटर आज़माएँ: SQL डेटासेट जनरेटर

Worked example: order history से गायब orders

पुराने shop system से migration के बाद एक support ticket आता है कि कुछ customers को "My orders" page पर अपने सारे orders नहीं दिखते; यह page orders को customers से inner join करता है। Tester को शक है कि कुछ orders ऐसे customers को refer करते हैं जो मौजूद नहीं।

  • Expected: हर migrated order किसी मौजूदा customer का है, इसलिए orphan check 0 rows लौटाता है।
  • Actual: 13 rows, सभी में customer_id 900000 से ऊपर, जो पुराने system की numbering है। Migration ने पुराने customer numbers को नए ids में map करने की बजाय सीधे copy कर दिया, इसलिए ये orders किसी मौजूदा customer के नहीं रहे: page का inner join इन्हें logged-in customer से कभी नहीं जोड़ पाता, और कहीं कोई error नहीं आता।
  • Report: query, 13 order ids, expected 0 rows, संभावित कारण, और यह सवाल कि क्या foreign key constraint जोड़ा जाए।
  • Fix के बाद confirmation testing: वही query 0 rows लौटाती है, और प्रभावित customer को page पर गायब orders दिखने लगते हैं।
-- Orphan check after the migration (expected: 0 rows)
SELECT o.order_id, o.customer_id, o.created_at
FROM orders o
LEFT JOIN customers c ON c.customer_id = o.customer_id
WHERE c.customer_id IS NULL
ORDER BY o.order_id;

आम गलतियाँ

  • गायब data ढूँढने के लिए inner join इस्तेमाल करना: वह ठीक वही बिना match वाली rows हटा देता है जिन्हें आप ढूँढ रहे हैं।
  • Left join के बाद right-hand table को WHERE में filter करना, जिससे वह inner join बन जाता है। ऐसी conditions ON में रखें; WHERE में सिर्फ़ orphan check की IS NULL जाँच रहती है।
  • गलत column पर, या composite key के सिर्फ़ एक हिस्से पर किए join के ठीक दिखने वाले output पर row count जाँचे बिना भरोसा करना।
  • One-to-many join में दोहराई गई parent rows को duplicate data समझ लेना।
  • Parent की primary key की बजाय उसके किसी nullable column पर IS NULL जाँचना।
  • यह मान लेना कि orphans हो ही नहीं सकते क्योंकि data model में relationship दिखता है; जाँचें कि foreign key घोषित और लागू है।

QA का नज़रिया

Databases के साथ काम करने वाले testers नियमित रूप से joins इस्तेमाल करते हैं ताकि कई tables में फैले एक बदलाव को verify करें, जैसे API से बना order अपने customer और item lines के साथ। Orphan checks हर उस काम के बाद चलाएँ जो bulk में लिखता है या application को bypass करता है: migrations, imports, batch jobs, manual data fixes, parent records का delete। Developers से पूछें कि किन relationships में foreign key constraint नहीं है, और उन्हें पहले जाँचें।

Bug report में exact query, अपेक्षित row count (integrity check के लिए शून्य) और लौटी rows रखें। उपयोगी checks को नामित, read-only queries के रूप में shared set में रखें, ताकि वे fix के बाद और बाद के migrations के बाद फिर चलें।

अभ्यास

Orders और payments को join करें, फिर orphans ढूँढें

आपका काम

एक shop orders और payments को नीचे दी गई दो tables में रखती है। काग़ज़ पर, या इन rows से भरे local SQLite (FULL JOIN के लिए version 3.39.0 या नया) या PostgreSQL database में, पता करें कि हर join क्या लौटाता है, फिर दो integrity checks लिखें।

सामग्री
orders                     payments
order_id  status           payment_id  order_id  amount
201       PAID             1           201        90.00
202       PAID             2           207        60.00
203       PENDING          3           201        10.00
चरण
  1. order_id पर orders INNER JOIN payments का result लिखें और rows गिनें।
  2. Orders से LEFT JOIN और FULL JOIN के लिए दोहराएँ; हर NULL चिह्नित करें।
  3. Orphan payments का check और सही database पर उसका expected result लिखें।
  4. बिना payment वाले PAID orders का check लिखें, और समझाएँ कि status filter ON में नहीं, WHERE में क्यों होना चाहिए।
मॉडल उत्तर दिखाएँ
मॉडल उत्तर

INNER JOIN: 2 rows, order 201 payment 1 के साथ और payment 3 के साथ। Orders 202 और 203 और payment 2 हट जाते हैं।

Orders से LEFT JOIN: 4 rows, जिनमें orders 202 और 203 जुड़ते हैं, payment columns NULL के साथ। FULL JOIN: 5 rows, जिनमें payment 2 भी जुड़ता है, order columns NULL के साथ, क्योंकि order 207 मौजूद नहीं।

Orphan payments: SELECT p.payment_id, p.order_id FROM payments p LEFT JOIN orders o ON o.order_id = p.order_id WHERE o.order_id IS NULL; सही database पर expected: 0 rows। यहाँ यह payment 2 लौटाती है, जिसे आप query और row के साथ report करते हैं।

बिना payment वाले PAID orders: SELECT o.order_id FROM orders o LEFT JOIN payments p ON p.order_id = o.order_id WHERE o.status = 'PAID' AND p.payment_id IS NULL; यह order 202 लौटाती है। Status filter left table को जाँचता है, इसलिए WHERE में वह PAID के अलावा वाले orders, जैसे 203, हटाता है और किसी row को सिर्फ़ इसलिए नहीं हटाता कि उसके payment columns NULL हैं। ON में ले जाने पर वह सिर्फ़ order 203 को किसी payment से match होने से रोकेगा: left join फिर भी 203 को NULL payment columns के साथ रखेगा, और check उसे गलती से report करेगा। p.payment_id IS NULL की जाँच WHERE में रहती है क्योंकि उसे join के बाद चलना है।

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

JOIN और INNER JOIN में क्या फ़र्क है?

कोई फ़र्क नहीं। अकेले JOIN का मतलब INNER JOIN है, और LEFT JOIN, LEFT OUTER JOIN का छोटा रूप है; OUTER शब्द optional है। INNER या LEFT साफ़ लिखने से query पढ़ने वाला अगला व्यक्ति इरादा समझ जाता है।

Orphans को NOT IN से क्यों न ढूँढें?

क्योंकि अगर subquery एक भी NULL लौटाए तो NOT IN (subquery) कोई row नहीं लौटाता, और check pass हो जाता है जबकि orphans मौजूद हैं। LEFT JOIN … IS NULL और NOT EXISTS NULLs होने पर भी सही जवाब देते हैं।

क्या RIGHT JOIN की कभी ज़रूरत पड़ती है?

ज़रूरी नहीं: हर RIGHT JOIN को tables की जगह बदलकर LEFT JOIN के रूप में लिखा जा सकता है, और ज़्यादातर teams LEFT JOIN पसंद करती हैं क्योंकि जिस table से आप शुरू करते हैं वह पहले पढ़ी जाती है। फिर भी दूसरों की queries पढ़ते समय आपको इसे समझना होगा।

क्या दो से ज़्यादा tables join हो सकती हैं?

हाँ। हर अतिरिक्त JOIN … ON अब तक बने result में एक table जोड़ता है, जैसे orders को customers से और फिर order_items से। हर नया join जोड़ने के बाद row count जाँचें, क्योंकि one-to-many joins rows को कई गुना कर देते हैं।

संदर्भ

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

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

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

डेटाबेस

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

डेटाबेस

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

डेटाबेस

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

टेस्ट डेटा

और गहराई से

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

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