(RDB) Join को समझने के 13 तरीके
(justinjaffray.com)- relational database में inner join सिर्फ SQL syntax नहीं है; उसी structure को lookup, nested loop, logical model, type checking और algebraic perspective से अलग-अलग तरह से समझा जा सकता है
- normalized tables में join, references को follow करके बिना duplication के store की गई जानकारी को फिर से जोड़ने का सबसे practical tool बन जाता है
- implementation perspective से इसे row pairs पर iterate करके सिर्फ condition satisfy करने वाले combinations रखने, या column domains में दोनों relations में मौजूद value combinations चुनने के तरीके के रूप में देखा जा सकता है
- programming model में join को
flatMap, SQLLATERAL, ORM की N+1 problem का समाधान, Rust trait-based type checking, और Set monad केandThenसे समझाया जा सकता है - mathematical रूप से graph paths, minimal model, largest permissible relation, partial order का least upper bound, और relational expression का ring product—ये सभी join की समान properties दिखाते हैं
Normalized data में join lookup बन जाता है
- Join को सबसे practical रूप से किसी value को lookup करने या existing data में redundant information जोड़ने के काम के तौर पर देखा जा सकता है
- उदाहरण
user,country,country_codeको एक ही table में store करने के तरीके से शुरू होता है- समान
countryvalue के लिएcountry_codeदोहराया जाता है, जिससे duplication होता है - अगर data ऐसा है जो अक्सर बदलता है, तो हर जगह को साथ में update करना पड़ता है, जिससे errors और inefficiency बढ़ती है
- समान
- Normalized form में
countryऔरcountry_codeके relation को अलग table में अलग कर दिया जाता है, और user table सिर्फcountry_idको reference करता है usersऔरcountriesकोcountry_idपरINNER JOINकरने से originaluser,country,country_codeform फिर से मिल जाता है- आगे की explanation यह मानती है कि same-name columns के आधार पर implicitly join किया जा रहा है, लेकिन detailed SQL syntax से सख्ती से बंधी नहीं है
Implementation perspective: rows और columns पर iterate करने वाला join
- जब दो sets
R,Sऔर predicatepहों, join सभीr ∈ R,s ∈ Sपर iterate करता है और केवल वे cases output करता है जिनमेंp(r, s)true हो- अगर दो collections का Cartesian product सभी possible row connections है, तो join उसका वह subset है जो condition satisfy करता है
- Column-centric view में हर column के domain को possible values का set माना जाता है और column value combinations पर iterate किया जाता है
- अगर
R(a, b)औरS(b, c)हों, तोa,b,cके domains पर iterate किया जाता है - केवल जब
(a, b)Rमें हो और(b, c)Sमें हो, तब[a, b, c]output किया जाता है
- अगर
Compatible alternative realities के रूप में join
- John और Sally का example ऐसी situation में join को समझाता है जहाँ दोनों के पास कुछ-कुछ information है और सिर्फ compatible realities बचाई जाती हैं
- John अपने pet और stray animal के possible combinations जानता है, और Sally भी अपने pet और stray animal के possible combinations रखती है
- जब John के पास dog हो और stray dog हो, और Sally के पास cat हो और stray mouse हो—ये दोनों एक साथ true नहीं हो सकते
- क्योंकि दोनों को वही stray observe करना चाहिए
- दोनों tables को
strayके आधार पर join करने से John के pet·stray·Sally के pet combinations में से केवल वे cases बचते हैं जिनमें आपसी contradiction नहीं है
Programming model में join
flatMapवह function है जो original array के हर element के लिए नया array बनाता है और results को concatenate करता है; इसे join implement करने में use किया जा सकता हैSELECT * FROM r INNER JOIN s ON pकोr.flatMap(x => s.filter(y => p(x, y)))के रूप में express किया जाता है- SQL के कुछ variants में
LATERALsyntax join कोflatMapform में बदल देता है
- अगर
LATERALके right side में left columns का reference नहीं है, तो यह Cartesian product के बराबर है- Query decorrelation right side के column references को successive rewrites के जरिए हटाने के तरीके पर depend करता है
- ORM में common N+1 problem को भी join से explain किया जा सकता है
- अगर result set की हर row के लिए extra query execute की जाए, तो Postgres जैसे connection use करने वाले databases में individual queries की fixed cost बड़ी होती है
- Database से “ये सभी lookups कर दो” कहकर request करने का result
users INNER JOIN countriesजैसा join है - Sqlite जैसे in-process databases में यह problem कम होती है
Graph paths और logical model
- Relation दो sets को “relate” करता है, इसलिए इसे graph के रूप में देखा जा सकता है
userstable user names के set औरcountry_idset को connect करता हैcountry_idऔर two-letter country codes को connect करने वाला relation भी अलग graph के रूप में दिखाया जा सकता है
- अगर पहले graph का right set और दूसरे graph का left set same vertex set share करते हैं, तो उन्हें merge करके देखा जा सकता है
- Left set से शुरू होकर middle vertex के जरिए right set तक जाने वाले सभी paths list करें, तो वह दो relations का join बन जाता है
- Formal logic में relation को predicate माना जाता है, और sentences के set को true बनाने वाले facts के set को model माना जाता है
- अगर
users(A, B)औरcountries(B, C, D)true हैं, तोQ(A, B, C, D)true है—ऐसा implication रखा जाता है - इस condition को satisfy करने वाले कई models हो सकते हैं
- Standard result पाने के लिए condition satisfy करने वाले models में से smallest model चुना जाता है
- यह smallest model
usersऔरcountryके join result जैसा ही है
- अगर
Type checking के रूप में join
- ML-style type system Prolog और Datalog से काफी मिलता-जुलता है, इसलिए इसे join की तरह express किया जा सकता है
- Rust example में relations को traits के रूप में define किया जाता है
UsersऔरCountryCoderelations की भूमिका निभाते हैंSmudge,Sissel,Petee,Canada,UnitedStates,CA,USconcrete types के रूप में define किए जाते हैं
(Smudge, Canada): Users,(Canada, CA): CountryCodeजैसे trait implementations relation की rows के बराबर हैं(A, B, C)को join में शामिल होने के लिए(A, B): Usersऔर(B, C): CountryCodeहोना चाहिएtest::<(Smudge, _, CA)>()type checking में succeed करता है, लेकिनtest::<(Smudge, _, US)>()fail करता है क्योंकि(Canada, US): CountryCodeimplement नहीं किया गया है
Set monad operations के रूप में join
- JavaScript के
SomeऔरNoneexample optional records को combine करने के तरीके से शुरू होता है- अगर दो records में same
countryहो, तो उन्हें merge करकेSomereturn करता है - अगर वे compatible नहीं हैं या value नहीं है, तो
Nonereturn करता है
- अगर दो records में same
andThenoptional value के अंदर की value निकालकर merge function apply करता है- उसी
combinefunction को बनाए रखते हुए container कोRelमें बदल दें, तो relation sets process किए जा सकते हैंRel.mapसभी rows पर function apply करता हैRel.andThenहर row से निकले relation कोflatMapसे concatenate करता है
usersrelation औरcountriesrelation पर वहीcombineचलाने सेSmudge,Sissel,Peteeके साथ country code जुड़ा हुआ join result मिलता है
Largest permissible relation और partial order का join
- अगर दो relations
R,Sके सभी columns वाला तीसरा relationTनई information invent नहीं करता, तो उसे permissible define किया जाता हैTकी किसी row कोRके columns तक restrict करने पर वह rowRमें होनी चाहिए- इसी तरह
Sके columns तक restrict करने पर भी वह rowSमें होनी चाहिए
- उदाहरण के लिए
Smudge, Canada, USpermissible नहीं है- सिर्फ
country,country_codeदेखें तोCanada, USबनता है, लेकिन यहSमें मौजूद row नहीं है
- सिर्फ
- Empty relation भी permissible है, लेकिन largest permissible relation में
Smudge-Canada-CA,Sissel-Canada-CA,Petee-United States-USशामिल हैं - यह largest permissible relation ही दो relations का join है
- Partial order view में
R ≤ Qको इस तरह define किया जाता हैQमेंRके सभी columns शामिल होंQकी हर row कोRके columns तक restrict करने पर वहRकी row बने
- इस partial order में दो relations
R,Sका least upper boundR ∨ Sमौजूद होता है, और यही relational join के समान अर्थ वाला join है
Ring product के रूप में join
- Relation को algebraically भी express किया जा सकता है
- एक row को column-value pairs के product के रूप में दिखाया जाता है
- एक relation को कई rows के sum के रूप में दिखाया जाता है
- उदाहरण के लिए
user = Smudgeऔरcountry_id = 1को multiply करने वाला term एक row बन जाता है - Expression simplify करने के लिए rules जोड़े जाते हैं
- Idempotence:
[x = y][x = y] = [x = y] - Contradiction:
[x = y][x = z] = 0ify ≠ z
- Idempotence:
- User relation
Rऔर country lookup relationSको multiply करके, distributive और commutative laws से expand करने पर contradictory terms गायब हो जाते हैं और सिर्फ compatible terms बचते हैं - बची हुई expression
Smudge-1-Canada-CA,Sissel-1-Canada-CA,Petee-2-United States-USहै, और यही ठीक-ठीक दो relations का join है - इस तरीके को tensor contraction के रूप में भी देखा जा सकता है
1 टिप्पणियां
Hacker News की राय
Join को spatial dimension के रूप में सोचना शुरू किया तो समझना बहुत आसान हो गया
Dim_X,Dim_Y,Dim_Zकी तरह हर dimension को अलग table में रखकर उन्हें उसीEntityIdसे जोड़ें, तो इसे किसी entity की 3D position बनाने जैसा देखा जा सकता है3 dimensions बनाने के लिए कम-से-कम 2 inner joins चाहिए, और time जैसी non-spatial dimension को भी इसी तरह extend किया जा सकता है
अगर किसी specific time को restrict न करें, तो यह एक ऐसी report बन जाती है जिसमें किसी entity की time के साथ रही सभी positions होती हैं
दूसरे join types भी, अगर schema को दिमाग में “rotate” कर सकने लायक concept पकड़ लें, तो इसी variation के रूप में समझना आसान हो जाता है
https://dbdb.io/db/hyperdex
EntityPosition(EntityId, X, Y, Z)जैसी table रखी जाएगीहालांकि कई dimensions के pieces को मिलाने और aggregation संभालने के मामले में data warehouse वाली तरफ ध्यान जाता है
JOIN,INNER JOINजैसी syntax क्यों इस्तेमाल होती है, यह हमेशा सवाल रहा है।FROMमें tables list करकेWHEREclause में join condition को equation की तरह लिखना कहीं ज्यादा clear लगता हैcomplex
FROMclause में कईJOINमिले हों तो पढ़ना मुश्किल हो जाता है, और equivalent conditional expressions कोWHEREमें पढ़ना ज्यादा intuitive हैशून्यवां नजरिया यह है कि “join relational algebra का operator” है
https://en.m.wikipedia.org/wiki/Relational_algebra
natural join
R ⋈ Sउन tuple combinations का set है जिनमें common attribute names समान होते हैं, और यह logicalANDसे मेल खाने वाला relational operation है⋈को Cartesian product की तरह देखा जा सकता है, जो predicate से उन rows को filter करता है जिन्हें result में नहीं आना चाहिए, और SQL का बड़ा हिस्सा इस दृष्टिकोण से अच्छी तरह समझ आता हैकई दिनों से query execution/planning implementation पर material ढूंढ रहा हूं, लेकिन predicates, existing indexes, joins जैसी implementation-side चीजों पर material आसानी से नहीं मिल रहा
Google search results usage material से भरे पड़े हैं
अब तक मुझे सिर्फ CMU Database Group का material मिला है, और वह शानदार है
https://pi3.informatik.uni-mannheim.de/~moer/querycompiler.p...
TUM का “Database Systems on Modern CPU Architectures” course भी मददगार हो सकता है, और 2020 material में पूरी video lectures हैं
https://db.in.tum.de/teaching/ss20/moderndbs/?lang=en
https://www.sqlite.org/optoverview.html
https://www.sqlite.org/queryplanner.html
https://www.postgresql.org/docs/current/planner-optimizer.ht...
query execution और query planning असल में लगभग अलग-अलग चीजें हैं
join optimization paper के लिए मूल Selinger paper अब भी सबसे अच्छा लगता है
https://courses.cs.duke.edu/compsci516/cps216/spring03/paper...
यह outer joins support नहीं करता और ज्यादा efficient techniques भी आ चुकी हैं, लेकिन System R family optimizers देखने वालों के लिए इसे पढ़ना अब भी familiar लगेगा
Postgres का
src/backend/optimizer/READMEभी ऐसे कई contents देता है जो दूसरी जगह मुश्किल से मिलते हैंCMU के Andy Pavlo lectures online इस content को explain करने वाले लगभग इकलौते resources में से हैं, और “Building Query Compilers” PDF अधूरा है, लेकिन इसमें Moerkotte वगैरह के key papers शामिल हैं, इसलिए modern implementation करनी हो तो देखने लायक है
applicable indexes ढूंढना आम तौर पर यह देखने भर से हो जाता है कि sargable predicate है या नहीं, इसलिए मुश्किल नहीं है, लेकिन selectivity estimation कठिन है, और join के बाद selectivity estimation optimizer की सबसे कठिन problems में से एक है
जैसे अगर
A=x AND B=y AND C=zहो और(A,B),(B,C)indexes की selectivity/cardinality information ही हो, तो तीनों conditions की overall selectivity कैसे estimate करें—यह भी आसान problem नहीं हैइसे solve करने के लिए “second-order cone programming” solver मांगने वाले papers भी हैं
query planningsearch किया, तो मोटे तौर पर देखने पर भी implementation-side results ज्यादा मिले14वां तरीका multi-join है, और इसे “worst-case optimal join” भी कहा जाता है, लेकिन यह नाम खास अच्छा नहीं है
दो-दो tables को join करते हुए लगातार intermediate results बनाने के बजाय, इसका मतलब है 3 या उससे अधिक tables को बिना intermediate result के साथ में join करना
संबंधित blog post और छोटा video https://relational.ai/blog/dovetail-join पर हैं, और मूल paper https://dl.acm.org/doi/pdf/10.1145/3180143 पर है
मैं RelationalAI में काम करता/करती हूं, और academia में करीब 10 साल तक research हुए इस नए join algorithm को हम और कुछ अन्य नई database companies market में ला रहे हैं
https://justinjaffray.com/a-gentle-ish-introduction-to-worst...
ANDNORबन जाता है, और Tetris इसी बात का फायदा उठाता हैworst-case bound stateless/streaming WCOJ से ज्यादा tight नहीं होता, लेकिन real data के पास अक्सर काफी छोटे box certificates होते हैं
मैंने नहीं देखा कि Dovetail join recursive queries, यानी arbitrary datalog जहां सिर्फ output relation specify किया जाता है और intermediate relations engine खुद संभालता है, को support करता है या नहीं
जिज्ञासा है कि क्या यह ऐसी queries support करता है
relational model की बारीकियां मुख्य रूप से application-level developers को दिखाने वाले ऐसे और articles होने चाहिए
functional programming के नजरिए से explanation और exploration भी concise और convincing है
लगता है N+1 problem सिखाने का एक और मौका चूक गया
non-clustered index पर join करना भी अब भी N+1 ही है; बस यह network और disk के बीच आने-जाने वाला N+1 नहीं, बल्कि disk पर N+1 है
inner join एक condition वाला Cartesian product है
equality join condition वाला inner join condition को सीधे बनाता है, और non-equality join condition में actual evaluation की जरूरत होती है
अच्छी explanation है। “सही तरीका tables को normalize करना है” वाली बात transactional database में सही है, लेकिन data warehouse में कुछ हद तक denormalization व्यापक रूप से स्वीकार की जाती है
normalization example देखकर वह समय याद आ गया जब मैं सोचता/सोचती था कि numeric primary keys strings से तेज होती हैं और उसी हिसाब से tables design करता/करती था
तब बेअर्थ
idबन जाते थे, और असल में चाहिए होने वाली unique value पाने के लिए join की जरूरत पड़ती थीएक दिन समझ आया कि दो tables में वही unique key इस्तेमाल करने से joins घट सकते हैं, और यह simple लेकिन बहुत effective था
idfield रखना पसंद है। यह logging में मदद करता है और कई fields वाली “असली” key की चिंता नहीं करनी पड़तीइसके बजाय string value पर unique index रखता/रखती हूं, और उससे भी अहम, integrity constraint उसी पर लगाता/लगाती हूं
meaningful strings से भरी table, numeric
idया UUID से भरी table की तुलना में कहीं ज्यादा readable होती है