সরাসরি পর্বে যান
পর্ব ৫৩ · ৮৩-এর মধ্যে · মডিউল ৯

n-ary relation আর relational database-এর গণিত

n-ary Relations and the Mathematics of Relational Databases — Keys, Relational Algebra, Selection Pushdown, Functional Dependencies, Armstrong's Axioms and Attribute Closure

Taught in Bangla, this lesson treats a database table as an n-ary relation and counts how many relations are possible. It defines superkeys, candidate keys and primary keys with a pattern for proving minimality, builds queries with selection, projection, natural join, rename and set operations, and runs a small relational algebra engine in in-page Python. It proves selection pushdown, shows equations that look right but fail, proves Armstrong's axioms sound and finds candidate keys with attribute closure.

এই পর্বে যা শিখবেন

  • n-ary relation, domain, degree আর relation schema-র নির্ভুল সংজ্ঞা, আর কতগুলো relation সম্ভব তার গণনা
  • Superkey, candidate key আর primary key-র সংজ্ঞা, আর একটি key যে সত্যিই minimal তা প্রমাণ করার ধাঁচ
  • Selection, projection, natural join, rename আর সেটের অপারেশন দিয়ে query লেখা, আর একটি ছোট relational algebra engine
  • Selection pushdown-এর প্রমাণ, আর কোন “স্বাভাবিক দেখতে” সমীকরণ আসলে মিথ্যা
  • Functional dependency, Armstrong-এর axiom-এর soundness প্রমাণ, আর attribute closure দিয়ে candidate key খোঁজা

১ · টেবিল মানেই relation — n-ary relation-এর সংজ্ঞা

পর্ব ৫২ “Relation আর তার ধর্ম — reflexive, symmetric, transitive”-এ relation ছিল দুটি বস্তুর সম্পর্ক: “গ্রাহক a পণ্য b কিনেছেন”। কিন্তু বাস্তবের তথ্য প্রায়ই দুইয়ের বেশি জিনিস একসঙ্গে বাঁধে। বাজারঘরের একটি অর্ডার বলে: কোন অর্ডার নম্বর, কোন গ্রাহক, কোন পণ্য, কত পিস। এখানে চারটি জিনিস একসঙ্গে সত্য, আর এটিকে চারটি আলাদা জোড়ায় ভাঙলে তথ্য হারায়।

পরীক্ষার ফলাফলের খাতাটি ভাবুন: প্রতিটি লাইনে রোল নম্বর, নাম, বিষয় আর নম্বর। লাইনগুলো কোন ক্রমে লেখা, তাতে ফলাফল বদলায় না; আর একই লাইন দুবার লিখলে কোনো নতুন তথ্য আসে না। খাতাটি আসলে “সত্য বাক্যগুলোর একটি সেট”, যার প্রতিটি বাক্য চারটি ঘরের একটি সারি। গণিতে এরই নাম n-ary relation.

সংজ্ঞা ১.১ (n-ary relation)

A1, A2, …, An সেট হলে এদের ওপর একটি n-ary relation হলো Cartesian product A1 × A2 × ⋯ × An-এর যেকোনো subset R. প্রতিটি Ai-কে বলে i-তম domain, আর n হলো relation-এর degree. R-এর প্রতিটি উপাদান একটি n-tuple (a1, …, an), যেখানে ai ∈ Ai.

n = 2 হলে এটি পর্ব ৫২-এর binary relation; n = 1 হলে শুধু একটি subset; আর n = 3 হলে ternary relation. পর্ব ১৪ “সেট — সংজ্ঞা, subset, power set আর Cartesian product”-এ Cartesian product আর ordered pair এসেছে, এখানে আমরা শুধু জোড়ার জায়গায় n-tuple নিচ্ছি। পর্ব ০৪-এর predicate-ও একই জিনিস: দুই argument-এর predicate Ord(c, o) আসলে যে জোড়াগুলোতে সত্য, তাদের সেট। একটি n-ary predicate আর একটি n-ary relation একই তথ্যের দুই রূপ।

বাজারঘরের তিনটি টেবিল এই পর্বজুড়ে চলবে। Customers-এর domain তিনটি: গ্রাহক নম্বর, নাম আর জেলা। Orders-এর চারটি: অর্ডার নম্বর, গ্রাহক নম্বর, পণ্য নম্বর, পরিমাণ।

Customers (cid, name, district)Products (pid, pname, category)Orders (oid, cid, pid, qty)
(C1, Rahim, Dhaka)(P1, Tea, grocery)(O1, C1, P1, 2)
(C2, Karim, Khulna)(P2, Rice, grocery)(O2, C1, P3, 1)
(C3, Sumi, Dhaka)(P3, Fan, electronics)(O3, C2, P2, 5)
(C4, Nila, Sylhet)(P4, Lamp, electronics)(O4, C3, P1, 1)
(O5, C3, P4, 2)
(O6, C1, P2, 3)

Orders একটি 4-ary relation: Orders ⊆ OID × CID × PID × ℕ. প্রথম গণনার প্রশ্ন: একটি নির্দিষ্ট domain-এর ওপর কতগুলো আলাদা relation সম্ভব?

উপপাদ্য ১.২ (tuple আর relation গণনা)

A1, …, An সসীম হলে |A1 × ⋯ × An| = |A1| · |A2| ⋯ |An|, আর এদের ওপর n-ary relation-এর সংখ্যা

2|A1| · |A2| ⋯ |An|

  1. একটি n-tuple বানাতে প্রথম ঘরের জন্য |A1|-টি উপায়, দ্বিতীয়টির জন্য |A2|-টি, এভাবে n-টি স্বাধীন বাছাই। — গুণের নিয়ম, পর্ব ৩১
  2. তাই মোট tuple N = ∏|Ai|-টি। — ধাপ ১
  3. Relation মানে এই N-উপাদানের সেটের একটি subset, অর্থাৎ power set-এর একটি উপাদান। — সংজ্ঞা ১.১
  4. N-উপাদানের সেটের subset 2N-টি, কারণ প্রতিটি tuple “আছে বা নেই” এই দুই উপায়ে ঠিক হয়। ∎ — power set-এর আকার, পর্ব ১৪

উদাহরণ: |A1| = 3, |A2| = 4, |A3| = 2 হলে tuple 24-টি আর ternary relation 224 = 16,777,216-টি। মাত্র তিনটি ছোট domain থেকেই প্রায় এক কোটি সাতষট্টি লাখ সম্ভাব্য টেবিল; এজন্যই একটি database-এর “সব সম্ভাব্য অবস্থা” পরীক্ষা করে কোনো নিয়ম যাচাই করা যায় না, প্রমাণ লাগে।

২ · Relational model — schema, attribute আর কেন ক্রম নেই

১৯৭০ সালে IBM-এর গবেষক Edgar F. Codd “A Relational Model of Data for Large Shared Data Banks” নামে একটি প্রবন্ধ প্রকাশ করেন (Communications of the ACM-এ)। তাঁর মূল প্রস্তাব ছিল: database-এর সব তথ্য গাণিতিক relation হিসেবে রাখা হোক, আর প্রশ্ন করা হোক সেটের অপারেশন দিয়ে, ডেটা ডিস্কে কীভাবে সাজানো আছে তা না জেনেই। এই ধারণাটিকেই relational model বলা হয়। আজকের SQL database-গুলো এই মডেলের ওপর দাঁড়িয়ে, যদিও পুরোপুরি মানে না (§৯-এ দেখবেন)।

গণিতের সংজ্ঞায় tuple-এর ঘরগুলো অবস্থান দিয়ে চেনা হয়: প্রথম, দ্বিতীয়, তৃতীয়। Database-এ প্রতিটি অবস্থানের একটি নাম থাকে, যাকে বলে attribute: cid, name, district. Attribute-এর নামসহ relation-এর কাঠামোকে বলে relation schema, যেমন Customers(cid, name, district). Schema হলো টেবিলের “মাথার সারি”, আর কোনো এক মুহূর্তের সারিগুলোর সেট হলো instance; schema অনেকদিন একই থাকে, instance প্রতিদিন বদলায়।

সংজ্ঞা ২.১ (named perspective)

Attribute-এর নামসহ দেখলে একটি tuple হলো একটি ফাংশন t: {A1, …, An} → মানের সেট, যেখানে প্রতিটি attribute A-র মান t(A) তার domain-এ থাকে। সংক্ষেপে t[A] লেখা হয়, আর attribute-সেট X-এর জন্য t[X] মানে X-এর ঘরগুলোর মান।

এই দৃষ্টিতে কলামের ক্রমও উঠে যায়: (cid = C1, name = Rahim, district = Dhaka) আর (district = Dhaka, cid = C1, name = Rahim) একই ফাংশন (পর্ব ১৬-এ দুটি ফাংশন সমান যদি প্রতিটি ইনপুটে একই মান দেয়)। আর relation একটি সেট বলে দুটি জিনিস স্বয়ংক্রিয়ভাবে আসে:

  • সারির ক্রম নেই। {a, b} = {b, a}; তাই “প্রথম সারি” কথাটির কোনো গাণিতিক অর্থ নেই। SQL-এ ক্রম চাইলে ORDER BY আলাদা করে লিখতে হয়।
  • একই সারি দুবার নেই। {a, a} = {a}. এটি পরের অংশে key-র অস্তিত্ব প্রমাণে কাজে লাগবে।

এই পর্বের কাজ গণিত: সংজ্ঞা, উপপাদ্য আর প্রমাণ। টেবিল বানানো, SQL লেখা আর বাস্তব database চালানো শিখতে ডেটাবেস (DBMS) ও SQL কোর্সের পর্ব ৫ “Relational model — relation, tuple, attribute আর domain” দেখুন; এখানে সেই কাজের পেছনের যুক্তিটুকু।

৩ · Key — superkey, candidate key, primary key

বাজারঘরে দুজন গ্রাহকের নাম “Rahim” হতে পারে, কিন্তু গ্রাহক নম্বর কখনো এক হবে না। যে কলাম (বা কলামের দল) দিয়ে প্রতিটি সারিকে আলাদা করে চেনা যায়, সেটিই key. এখানে একটি সূক্ষ্ম বিষয় আছে: key একটি নিয়ম, যা schema-র সব বৈধ instance-এ খাটার কথা। একটি নির্দিষ্ট দিনের টেবিলে নামগুলো আলাদা হলেও name key নয়, কারণ কাল আরেকজন Rahim আসতে পারেন। নিচের সংজ্ঞাগুলো একটি instance r-এর জন্য লেখা; schema-র key মানে এমন সেট, যা সব বৈধ instance-এ শর্ত মানে।

সংজ্ঞা ৩.১ (superkey, candidate key, primary key)

U হলো relation-এর সব attribute-এর সেট, আর K ⊆ U.

  • K একটি superkey, যদি r-এর যেকোনো দুটি আলাদা tuple t ≠ u-এর জন্য t[K] ≠ u[K]; অর্থাৎ ফাংশন t ↦ t[K] one-to-one.
  • K একটি candidate key, যদি K superkey আর K-এর কোনো proper subset superkey নয় (minimal).
  • Primary key হলো candidate key-গুলোর মধ্যে ডিজাইনার যেটিকে প্রধান হিসেবে বেছে নেন; গাণিতিকভাবে এটি আলাদা কিছু নয়, একটি বাছাই।

খেয়াল করুন, “minimal” আর “সবচেয়ে ছোট” এক কথা নয়। একটি relation-এ এক attribute-এর candidate key আর দুই attribute-এর candidate key একসঙ্গে থাকতে পারে; দুটিই minimal, কারণ কোনোটির ভেতর থেকে কিছু বাদ দেওয়া যায় না। §৮-এ এমন উদাহরণ দেখবেন।

উপপাদ্য ৩.২ (superkey-র superset-ও superkey; key সবসময় থাকে)

(ক) K superkey আর K ⊆ L ⊆ U হলে L-ও superkey. (খ) যেকোনো relation instance-এ অন্তত একটি candidate key আছে।

  1. (ক) t ≠ u নিন। K superkey বলে কোনো A ∈ K-তে t[A] ≠ u[A]. — সংজ্ঞা ৩.১
  2. A ∈ K ⊆ L, তাই t আর u L-এর একটি attribute-এ আলাদা, অর্থাৎ t[L] ≠ u[L]. তাই L superkey. — subset-এর সংজ্ঞা
  3. (খ) U নিজেই superkey: t ≠ u মানে দুটি আলাদা ফাংশন, তাই কোনো attribute-এ মান আলাদা, অর্থাৎ t[U] ≠ u[U]. এখানেই “relation একটি সেট, একই সারি দুবার নেই” কাজে লাগল। — §২
  4. U সসীম, তাই এর superkey-subset-গুলোর মধ্যে সবচেয়ে কম উপাদানের একটি K আছে। K-এর কোনো proper subset-এর উপাদান আরও কম, তাই সেটি superkey নয়; অর্থাৎ K candidate key. ∎ — well-ordering, পর্ব ১১

ধাপ ৩ বলছে, SQL-এর টেবিলে (যেখানে একই সারি দুবার থাকতে পারে) উপপাদ্য (খ) ভেঙে যায়: দুটি হুবহু এক সারিকে কোনো কলাম দিয়ে আলাদা করা যায় না। গণিতের relation আর SQL-এর টেবিলের এই পার্থক্য §৯-এ আবার আসবে।

(ক) অংশটির একটি কাজের ফল আছে, যা পরীক্ষায় “K যে candidate key তা দেখান” প্রশ্নের উত্তরের ধাঁচ ঠিক করে দেয়:

Corollary ৩.৩ (minimality যাচাই)

K একটি superkey হলে K candidate key হবে যদি এবং কেবল যদি প্রতিটি A ∈ K-এর জন্য K − {A} superkey নয়।

  1. (⇒) K candidate key হলে K − {A} একটি proper subset, তাই superkey নয়। — সংজ্ঞা
  2. (⇐) ধরুন প্রতিটি K − {A} superkey নয়, কিন্তু কোনো proper subset M ⊊ K superkey. K-এর এমন একটি A নিন যা M-এ নেই; তাহলে M ⊆ K − {A}. — proper subset-এর সংজ্ঞা
  3. উপপাদ্য ৩.২(ক) অনুযায়ী K − {A}-ও superkey, যা অনুমানের বিরোধী। ∎ — proof by contradiction, পর্ব ০৮

অর্থাৎ k-টি attribute-এর একটি key-র minimality দেখাতে 2k − 2-টি proper non-empty subset পরীক্ষা লাগে না, k-টি “একটি বাদ” পরীক্ষাই যথেষ্ট। আর যদি একটি candidate key-তে k-টি attribute আর পুরো schema-য় n-টি, তাহলে ওই key-কে ধারণ করে এমন superkey ঠিক 2n−k-টি: key-র বাইরের n − k-টি attribute প্রতিটি “নেওয়া বা না নেওয়া”। Key-গুলো ডিজাইনে কীভাবে বাছা হয়, আর foreign key ও integrity constraint-এর ব্যবহার, ডেটাবেস (DBMS) ও SQL কোর্সের পর্ব ৬ “Key আর integrity constraint”-এ আছে।

৪ · Relational algebra — selection, projection, rename আর সেটের অপারেশন

Codd-এর দ্বিতীয় বড় ধারণা: প্রশ্নগুলোও গণিত। কয়েকটি অপারেশন আছে, প্রতিটি এক বা দুটি relation নিয়ে একটি নতুন relation দেয়। ফল আবার relation বলে অপারেশনগুলো একটির ওপর আরেকটি বসিয়ে যেকোনো জটিল প্রশ্ন লেখা যায়, ঠিক যেমন +, × দিয়ে সংখ্যার রাশি লেখা যায়। এই ব্যবস্থার নাম relational algebra.

সংজ্ঞা ৪.১ (মৌলিক অপারেশন)

R-এর schema U, আর c একটি শর্ত যা একটি tuple-এর কিছু attribute-এর মান দেখে সত্য বা মিথ্যা বলে।

  • Selection: σc(R) = {t ∈ R : c(t) সত্য}. সারি ছাঁকা, schema একই থাকে।
  • Projection: X ⊆ U হলে πX(R) = {t[X] : t ∈ R}. কিছু কলাম রাখা; ফল সেট বলে একই মান একবারই থাকে।
  • Rename: ρ attribute-এর নাম বদলায়, যেমন ρbuyer←cid(Orders). Tuple বদলায় না, শুধু মাথার নাম।
  • Union R ∪ S, difference R − S, intersection R ∩ S: দুটি relation-এর schema একই হলে (union-compatible) সাধারণ সেটের অপারেশন।
  • Cartesian product R × S: schema আলাদা হলে প্রতিটি t ∈ R আর u ∈ S জুড়ে একটি লম্বা tuple; |R × S| = |R| · |S|.

শেষ দুটি লাইন সরাসরি পর্ব ১৫ “সেটের অপারেশন — union, intersection, Venn diagram আর identity” আর পর্ব ১৪-এর Cartesian product. তাই সেখানকার সব identity এখানেও চলে: R ∪ S = S ∪ R, R − (S ∪ T) = (R − S) ∩ (R − T), ইত্যাদি। Selection-এর জন্য দুটি সহজ নিয়ম সংজ্ঞা থেকেই আসে, কারণ “c1 আর c2 দুটিই সত্য” মানে প্রথমে একটি দিয়ে ছেঁকে তারপর অন্যটি দিয়ে ছাঁকা:

σc1 ∧ c2(R) = σc1(σc2(R)) = σc2(σc1(R))

Projection-এ পুনরাবৃত্তি বাদ পড়াটা গণিতের দিক থেকে স্বাভাবিক, কিন্তু প্রথমবার দেখলে অবাক লাগে। Orders থেকে শুধু গ্রাহক নম্বর নিলে ৬টি অর্ডারের জায়গায় মাত্র ৩টি মান থাকে:

1(O1, C1, P1, 2)2(O2, C1, P3, 1)3(O3, C2, P2, 5)4(O4, C3, P1, 1)5(O5, C3, P4, 2)6(O6, C1, P2, 3)7ফল: π_cid(Orders)
π_cid(Orders)
t[cid]
নতুন?
সেট
bag-এ সারি
  1. O1. প্রথম সারির cid = C1. ফলাফলের সেট খালি ছিল, এখন {C1}.
  2. O2. cid আবার C1. সেটে C1 আগেই আছে, তাই সেট বদলায় না: {C1}. SQL-এর সাধারণ SELECT cid কিন্তু এখানে দ্বিতীয় C1 সারি রাখত।
  3. O3. cid = C2, নতুন মান: {C1, C2}.
  4. O4. cid = C3, নতুন মান: {C1, C2, C3}.
  5. O5. cid = C3, আগেই আছে। সেট একই থাকে।
  6. O6. cid = C1, আগেই আছে। ৬টি সারি পড়া শেষ, সেট এখনো {C1, C2, C3}.
  7. ফল। πcid(Orders) = {C1, C2, C3}, ৩টি tuple. SQL-এর SELECT cid ৬টি সারি দেয় (bag), আর SELECT DISTINCT cid দেয় এই ৩টি। cid এখানে Orders-এর superkey নয়, তাই সারি কমেছে।

শেষ ধাপের পর্যবেক্ষণটি একটি ছোট উপপাদ্য:

উপপাদ্য ৪.২ (projection-এ সারি কখন কমে)

যেকোনো X ⊆ U-এর জন্য |πX(R)| ≤ |R|, আর সমতা হয় যদি এবং কেবল যদি X এই instance-এ একটি superkey.

  1. f(t) = t[X] একটি ফাংশন R → πX(R), আর π-এর সংজ্ঞা অনুযায়ী এটি onto. — সংজ্ঞা ৪.১
  2. Onto ফাংশনে codomain-এর আকার domain-এর চেয়ে বেশি হতে পারে না, তাই |πX(R)| ≤ |R|. — পর্ব ১৬
  3. সসীম সেটে একটি onto f-এর ক্ষেত্রে |πX(R)| = |R| হয় ঠিক তখনই যখন f one-to-one (pigeonhole, পর্ব ৩২)। আর f one-to-one মানেই t ≠ u ⇒ t[X] ≠ u[X], অর্থাৎ X superkey. ∎ — সংজ্ঞা ৩.১

৫ · Natural join — মিলে যাওয়া সারি জোড়া দেওয়া

Orders-এ শুধু গ্রাহক নম্বর আছে, জেলা নেই; জেলা আছে Customers-এ। “কোন অর্ডার কোন জেলায় যাবে” জানতে দুটি টেবিলকে cid দিয়ে জুড়তে হবে। Cartesian product সব জোড়া দেয় (4 × 6 = 24), যার বেশিরভাগই অর্থহীন (C2-এর অর্ডারের সঙ্গে Rahim-এর ঠিকানা)। দরকার শুধু সেই জোড়া, যেখানে দুই দিকের cid এক।

সংজ্ঞা ৫.১ (natural join)

R-এর schema X, S-এর schema Y. তাদের natural join R ⋈ S-এর schema X ∪ Y, আর

R ⋈ S = {t : t[X] ∈ R এবং t[Y] ∈ S}

অর্থাৎ X ∪ Y-এর ওপর এমন সব tuple t, যার X-অংশ R-এ আর Y-অংশ S-এ আছে। দুটি অংশ X ∩ Y-এর ঘরে একই মান নেয়, তাই এটিই “মিলে যাওয়া সারি জোড়া দেওয়া”।

সংজ্ঞাটি এভাবে লেখার সুবিধা হলো প্রমাণে সরাসরি ব্যবহার করা যায়। হিসাবের নিয়মটি সহজ: R-এর প্রতিটি সারি নিন, S-এর প্রতিটি সারির সঙ্গে মেলান, সাধারণ কলামে মিললে জুড়ে দিন। নিচে একটি ছোট উদাহরণ:

1S = (C1,Dhaka) (C2,Khulna) (C4,Sylhet)2R: (O1, C1)3R: (O3, C2)4R: (O4, C1)5R: (O7, C5)6ফল: R ⋈ S
সাধারণ attribute: cid
R-এর সারি
S-এ মিল
ফলে যোগ
মোট তুলনা
  1. প্রস্তুতি। R-এর schema (oid, cid), S-এর (cid, district). সাধারণ attribute শুধু cid, তাই ফলের schema (oid, cid, district). R-এ ৪টি, S-এ ৩টি সারি।
  2. (O1, C1). S-এর তিনটি সারির সঙ্গে cid মেলানো হলো (৩টি তুলনা)। শুধু (C1, Dhaka) মেলে, তাই ফলে (O1, C1, Dhaka).
  3. (O3, C2). আবার ৩টি তুলনা, মোট ৬। (C2, Khulna) মেলে: ফলে (O3, C2, Khulna).
  4. (O4, C1). একই গ্রাহক C1 দ্বিতীয়বার; S-এর (C1, Dhaka) আবার ব্যবহৃত হয়: (O4, C1, Dhaka). মোট তুলনা ৯।
  5. (O7, C5). S-এ C5 নেই, তাই এই সারি ফলে আসে না। Natural join মিল-না-পাওয়া সারি ফেলে দেয়। মোট তুলনা 4 × 3 = 12.
  6. ফল। R ⋈ S-এ ৩টি সারি। S-এর (C4, Sylhet)-ও ফলে নেই, কারণ R-এ C4-এর কোনো অর্ডার নেই। এভাবে সব জোড়া মেলানোর পদ্ধতিকে nested-loop join বলে; বড় টেবিলে database অন্য কৌশল (sort বা hash) নেয়।
উপপাদ্য ৫.২ (join-এর দুই প্রান্ত আর আকার)

(ক) X ∩ Y = ∅ হলে R ⋈ S হলো R × S (কলামের ক্রম বাদে)। (খ) X = Y হলে R ⋈ S = R ∩ S. (গ) সবসময় 0 ≤ |R ⋈ S| ≤ |R| · |S|.

  1. (ক) কোনো সাধারণ attribute নেই, তাই যেকোনো t ∈ R আর u ∈ S জুড়ে X ∪ Y-এর একটি tuple হয়, যার X-অংশ t আর Y-অংশ u. এটিই Cartesian product. — সংজ্ঞা ৫.১
  2. (খ) X = Y = X ∪ Y হলে শর্ত হয় “t ∈ R এবং t ∈ S”, অর্থাৎ t ∈ R ∩ S. — intersection-এর সংজ্ঞা
  3. (গ) ফলের প্রতিটি t একটি জোড়া (t[X], t[Y]) ∈ R × S থেকে আসে, আর আলাদা t আলাদা জোড়া দেয় (X ∪ Y-এর প্রতিটি ঘর এই দুই অংশের কোনো একটিতে আছে)। তাই ফলের আকার |R × S|-এর বেশি নয়। ∎ — one-to-one ফাংশন

এবার একটি ছোট relational algebra engine. Relation রাখা হয়েছে (schema, tuple-এর সেট) জোড়া হিসেবে; select, project আর join মাত্র কয়েক লাইন, আর প্রতিটি লাইন ওপরের সংজ্ঞার সরাসরি অনুবাদ। প্রশ্ন: “ঢাকার গ্রাহকেরা কোন কোন category-র পণ্য কিনেছেন?” relational algebra-য়: πname, category(σdistrict = Dhaka(Customers ⋈ Orders ⋈ Products)). প্রথমবার চালালে ব্রাউজারে Python (Pyodide) নামতে কয়েক সেকেন্ড লাগে।

def select(r, pred):                    # sigma: keep rows where pred holds
    A, rows = r
    return A, {t for t in rows if pred(dict(zip(A, t)))}

def project(r, keep):                   # pi: keep columns, a set drops repeats
    A, rows = r
    return tuple(keep), {tuple(t[A.index(a)] for a in keep) for t in rows}

def join(r, s):                         # natural join on shared attributes
    (A, R), (B, S) = r, s
    common = [a for a in A if a in B]
    extra = [b for b in B if b not in A]
    out = {t + tuple(u[B.index(b)] for b in extra)
           for t in R for u in S
           if all(t[A.index(c)] == u[B.index(c)] for c in common)}
    return A + tuple(extra), out

cust = (("cid", "name", "district"), {("C1", "Rahim", "Dhaka"),
        ("C2", "Karim", "Khulna"), ("C3", "Sumi", "Dhaka"), ("C4", "Nila", "Sylhet")})
prod = (("pid", "pname", "category"), {("P1", "Tea", "grocery"),
        ("P2", "Rice", "grocery"), ("P3", "Fan", "electronics"), ("P4", "Lamp", "electronics")})
orders = (("oid", "cid", "pid", "qty"), {("O1", "C1", "P1", 2), ("O2", "C1", "P3", 1),
          ("O3", "C2", "P2", 5), ("O4", "C3", "P1", 1), ("O5", "C3", "P4", 2), ("O6", "C1", "P2", 3)})

big = join(join(cust, orders), prod)
print("schema:", big[0])
print("|cust|, |orders|, |big| =", len(cust[1]), len(orders[1]), len(big[1]))
dhaka = select(big, lambda t: t["district"] == "Dhaka")
ans = project(dhaka, ["name", "category"])
print(ans[0])
for row in sorted(ans[1]):
    print("  ", row)
print("pi_cid(orders):", sorted(project(orders, ["cid"])[1]))
print("never ordered:", sorted(project(cust, ["cid"])[1] - project(orders, ["cid"])[1]))

আসল আউটপুট (ব্রাউজারের Python):

schema: ('cid', 'name', 'district', 'oid', 'pid', 'qty', 'pname', 'category')
|cust|, |orders|, |big| = 4 6 6
('name', 'category')
   ('Rahim', 'electronics')
   ('Rahim', 'grocery')
   ('Sumi', 'electronics')
   ('Sumi', 'grocery')
pi_cid(orders): [('C1',), ('C2',), ('C3',)]
never ordered: [('C4',)]

তিন টেবিলের join-এ ঠিক ৬টি সারি, প্রতিটি অর্ডারের জন্য একটি, কারণ প্রতিটি অর্ডারের cid আর pid-এর একটিই মিল আছে। ঢাকার দুই গ্রাহক Rahim (৩টি অর্ডার) আর Sumi (২টি) মিলে ৫টি সারি, কিন্তু (name, category) জোড়া মাত্র ৪টি: Rahim-এর Tea আর Rice দুটিই grocery, projection দুটিকে একটি করে দিয়েছে। শেষ লাইনটি difference দিয়ে লেখা আরেকটি প্রশ্ন: πcid(Customers) − πcid(Orders), অর্থাৎ যিনি কখনো অর্ডার দেননি, সেই Nila (C4) গ্রাহক। “সব category-তেই কিনেছেন এমন গ্রাহক” ধরনের প্রশ্নে লাগে ∀, যা পর্ব ০৫-এর relational division-এর বিষয়।

৬ · বীজগণিতের সমীকরণ আর query optimisation

একই প্রশ্ন বিভিন্ন রাশিতে লেখা যায়, যেমন 3 × (4 + 5) আর 3 × 4 + 3 × 5. ফল একই, কিন্তু খরচ আলাদা হতে পারে। Database-এর query optimizer ঠিক এই কাজ করে: আপনার লেখা query-কে relational algebra-য় অনুবাদ করে, তারপর প্রমাণিত সমীকরণ (equivalence rule) দিয়ে এমন একটি সমতুল্য রাশিতে বদলায়, যা কম খরচে চলে। “সমতুল্য” মানে: যেকোনো instance-এ দুই রাশির ফল একই relation. সবচেয়ে গুরুত্বপূর্ণ নিয়মটি হলো selection pushdown: আগে ছাঁকুন, পরে জোড়া দিন।

Selection pushdown: একই প্রশ্নের দুটি query tree

c মানে district = 'Dhaka'. বাঁয়ে আগে join, পরে ছাঁকা; ডানে আগে ছাঁকা, পরে join. সংখ্যাগুলো §১-এর টেবিলে nested-loop join-এর জোড়া-তুলনা।

Selection pushdown: sigma_c(Customers join Orders) equals sigma_c(Customers) join Orders; 24 pair comparisons before, 12 after σ c ⋈ ⋈ σ c Customers Orders Customers Orders = 4 × 6 = 24 তুলনা 2 × 6 = 12 তুলনা একই ফল, অর্ধেক কাজ
উপপাদ্য ৬.১ (selection pushdown)

R-এর schema X, S-এর schema Y, আর শর্ত c শুধু X-এর attribute ব্যবহার করে। তাহলে

σc(R ⋈ S) = σc(R) ⋈ S

দুটি সেট সমান দেখাতে দুই দিকের subset দেখাতে হয়, যাকে পর্ব ১৪-এ double inclusion বলা হয়েছে। মূল পর্যবেক্ষণ: c শুধু X-এর ঘর দেখে, তাই X ∪ Y-এর একটি tuple t-এর জন্য c(t) আর c(t[X]) একই কথা।

  1. (⊆) t ∈ σc(R ⋈ S) নিন। তাহলে t ∈ R ⋈ S আর c(t) সত্য। — σ-এর সংজ্ঞা
  2. t ∈ R ⋈ S থেকে t[X] ∈ R আর t[Y] ∈ S. — সংজ্ঞা ৫.১
  3. c শুধু X-এর attribute দেখে, তাই c(t[X]) = c(t) সত্য; সুতরাং t[X] ∈ σc(R). — অনুমান আর σ-এর সংজ্ঞা
  4. t[X] ∈ σc(R) আর t[Y] ∈ S, তাই t ∈ σc(R) ⋈ S. — সংজ্ঞা ৫.১, σc(R)-এর schema-ও X
  5. (⊇) t ∈ σc(R) ⋈ S নিন। তাহলে t[X] ∈ σc(R), অর্থাৎ t[X] ∈ R আর c(t[X]) সত্য; আর t[Y] ∈ S. — দুই সংজ্ঞা
  6. t[X] ∈ R আর t[Y] ∈ S বলে t ∈ R ⋈ S; আর c(t) = c(t[X]) সত্য, তাই t ∈ σc(R ⋈ S). ∎ — ধাপ ৩-এর একই পর্যবেক্ষণ

অনুমানটি কোথায় লেগেছে দেখুন: ধাপ ৩ আর ৬. শর্ত যদি S-এর কোনো attribute দেখে (যেমন category = 'grocery' আর R = Customers), তাহলে σc(R) লেখাই যায় না, কারণ R-এ ওই কলাম নেই। তখন শর্তটি S-এর দিকে নামাতে হয়। আর যে শর্ত দুই দিকের attribute একসঙ্গে দেখে, তা নামানো যায় না।

কিছু সমীকরণ দেখতে স্বাভাবিক, কিন্তু মিথ্যা। নিচের প্রমাণটি পড়ে ভুল ধরার চেষ্টা করুন:

ভুল প্রমাণ (ভুলটি খুঁজুন): “দাবি: πA(R − T) = πA(R) − πA(T). প্রমাণ: a ∈ πA(R − T) ⇔ কোনো t ∈ R − T-তে t[A] = a ⇔ কোনো t ∈ R-এ t[A] = a এবং সেই t ∉ T ⇔ a ∈ πA(R) এবং a ∉ πA(T). ∎”

ভুল কোথায়: শেষ “⇔”. “সেই নির্দিষ্ট t টি T-তে নেই” আর “A-মান a আছে এমন কোনো tuple T-তে নেই” এক কথা নয়। R = {(0, 0)}, T = {(0, 2)} নিন: R − T = R, তাই বাঁ দিক {0}; কিন্তু πA(R) − πA(T) = {0} − {0} = ∅. শুধু ⊇ দিকটি সাধারণভাবে সত্য। Projection union-এর ওপর ছড়ায় (π(R ∪ T) = π(R) ∪ π(T)), কিন্তু difference-এর ওপর নয়; selection আবার difference-এর ওপরও ছড়ায়।

নিচের ব্লক দুটি দাবিই এলোমেলো ছোট টেবিলে পরীক্ষা করে: pushdown প্রতিবার সত্য কি না, কাজ কতটা কমে, আর projection-difference সমীকরণ কতবার ভাঙে।

import random
random.seed(53)

def table(n):                           # n random pairs over {0,1,2,3}
    return {(random.randrange(4), random.randrange(4)) for _ in range(n)}

def join_ab_bc(R, S):                   # R(a,b) natural join S(b,c)
    return {(a, b, c) for (a, b) in R for (b2, c) in S if b == b2}

ok = bad = work_before = work_after = 0
first_bad = None
for trial in range(500):
    R, S = table(random.randint(0, 8)), table(random.randint(0, 8))
    cond = lambda a: a >= 2             # condition uses only R's attribute a
    left = {t for t in join_ab_bc(R, S) if cond(t[0])}
    sR = {(a, b) for (a, b) in R if cond(a)}
    right = join_ab_bc(sR, S)
    ok += left == right
    work_before += len(R) * len(S)      # pairs compared before pushdown
    work_after += len(sR) * len(S)      # pairs compared after pushdown
    T = table(random.randint(0, 8))     # projection vs difference
    lhs = {a for (a, b) in R - T}
    rhs = {a for (a, b) in R} - {a for (a, b) in T}
    if lhs != rhs:
        bad += 1
        if first_bad is None:
            first_bad = (sorted(R), sorted(T), lhs, rhs)
print("pushdown equal in", ok, "of 500 trials")
print("pairs compared: before", work_before, " after", work_after)
print("pi(R - T) != pi(R) - pi(T) in", bad, "of 500 trials")
R, T, lhs, rhs = first_bad
print("example R =", R)
print("        T =", T)
print("pi_a(R - T) =", sorted(lhs), " pi_a(R) - pi_a(T) =", sorted(rhs))

আসল আউটপুট (ব্রাউজারের Python):

pushdown equal in 500 of 500 trials
pairs compared: before 6148  after 3139
pi(R - T) != pi(R) - pi(T) in 302 of 500 trials
example R = [(0, 0), (2, 3), (3, 3)]
        T = [(0, 2), (1, 3)]
pi_a(R - T) = [0, 2, 3]  pi_a(R) - pi_a(T) = [2, 3]

৫০০টি এলোমেলো টেবিল-জোড়ায় pushdown প্রতিবার একই ফল দিয়েছে, যা উপপাদ্য ৬.১-এর সঙ্গে মেলে (তবে মনে রাখুন, ৫০০টি পরীক্ষা প্রমাণ নয়; প্রমাণটি ওপরে)। শর্ত a ≥ 2 মোটামুটি অর্ধেক সারি রাখে, তাই join-এর জোড়া-তুলনাও প্রায় অর্ধেক: 6148 থেকে 3139. বাস্তব database-এ শর্ত প্রায়ই অনেক বেশি ছাঁকে (যেমন একটি গ্রাহক নম্বর), তখন লাভ হাজার গুণও হতে পারে। অন্যদিকে projection-difference সমীকরণ ৫০০-র মধ্যে ৩০২ বার ভেঙেছে। প্রথম counterexample-এ R − T = R (কোনো সারি সাধারণ নেই), তাই বাঁ দিকে 0 আছে; কিন্তু T-তেও a = 0-এর একটি সারি (0, 2) আছে বলে ডান দিক থেকে 0 বাদ পড়েছে, ঠিক ভুল প্রমাণের শেষ ধাপের ফাঁক।

Optimizer-এর কাজের বাকি অংশ, যেমন খরচ অনুমান, statistics আর join-এর ক্রম বাছাই, ডেটাবেস (DBMS) ও SQL কোর্সের পর্ব ৪৩ “Query optimization — cost, statistics আর plan”-এ; অপারেশনগুলোর আরও উদাহরণ পর্ব ৩২ আর পর্ব ৩৩-এ।

৭ · Functional dependency আর Armstrong-এর axiom

বাজারঘরের নিয়ম: একটি অর্ডার একজন গ্রাহকেরই; একজন গ্রাহকের একটিই জেলা; একটি পণ্যের একটিই category. এগুলো টেবিলের “বাক্য” নয়, টেবিলের ওপর শর্ত। এমন শর্তের গাণিতিক রূপের নাম functional dependency.

সংজ্ঞা ৭.১ (functional dependency)

X, Y ⊆ U. Relation instance r-এ functional dependency X → Y খাটে, যদি r-এর যেকোনো দুটি tuple t, u-এর জন্য

t[X] = u[X] ⇒ t[Y] = u[Y]

অর্থাৎ X-এর মান জানলে Y-এর মান একটিই। ভাষান্তরে: πX ∪ Y(r)-এ X থেকে Y-তে একটি ফাংশন আছে, আর এজন্যই নামে “functional” শব্দটি।

উদাহরণ: OrderLine(oid, cid, district, pid, category, qty) টেবিলে oid → cid, cid → district, pid → category আর {oid, pid} → qty. Superkey-কেও এই ভাষায় লেখা যায়: K superkey ⇔ K → U খাটে (দুটি tuple K-তে মিললে পুরো সারিতে মেলে, অর্থাৎ একই সারি)। একটি FD যদি সব instance-এ নিজে থেকেই খাটে, যেমন {oid, pid} → oid, তাকে বলে trivial FD; এটি ঠিক তখনই হয় যখন Y ⊆ X.

প্রশ্ন হলো: কয়েকটি FD জানা থাকলে আর কোন কোন FD নিশ্চিতভাবে খাটে? যেমন oid → cid আর cid → district থেকে oid → district আসা উচিত। William W. Armstrong ১৯৭৪ সালের একটি প্রবন্ধে তিনটি নিয়ম দেন বলে জানা যায়, যা আজ Armstrong-এর axiom নামে পরিচিত:

উপপাদ্য ৭.২ (Armstrong-এর axiom sound)

যেকোনো instance r আর X, Y, Z ⊆ U-এর জন্য:

  • Reflexivity: Y ⊆ X হলে X → Y খাটে।
  • Augmentation: X → Y খাটলে XZ → YZ খাটে (XZ মানে X ∪ Z).
  • Transitivity: X → Y আর Y → Z খাটলে X → Z খাটে।

“Sound” মানে নিয়মগুলো কখনো মিথ্যা FD দেয় না (পর্ব ০৬-এর soundness-এর একই অর্থ)। তিনটির প্রমাণ, প্রতিটিতে দুটি tuple t, u নিয়ে:

  1. Reflexivity: t[X] = u[X] ধরুন। Y ⊆ X, তাই Y-এর প্রতিটি ঘর X-এরও ঘর, আর সেখানে মান সমান; অর্থাৎ t[Y] = u[Y]. — subset-এর সংজ্ঞা
  2. Augmentation: t[XZ] = u[XZ] ধরুন। তাহলে t[X] = u[X] আর t[Z] = u[Z]. X → Y থেকে t[Y] = u[Y]. দুটি মিলে t[YZ] = u[YZ]. — X ⊆ XZ, Z ⊆ XZ
  3. Transitivity: t[X] = u[X] ধরুন। X → Y থেকে t[Y] = u[Y]; তারপর Y → Z থেকে t[Z] = u[Z]. তাই X → Z. ∎ — দুবার সংজ্ঞা ৭.১

এই তিনটি থেকে আরও নিয়ম আসে: union (X → Y আর X → Z হলে X → YZ), decomposition (X → YZ হলে X → Y), pseudo-transitivity (X → Y আর WY → Z হলে WX → Z), সবই এই তিনটির ফল। উল্টো দিকের গভীর ফলটি হলো completeness: F থেকে যে FD সত্যিই আসে (F মানে এমন প্রতিটি instance-এ খাটে), তা এই তিন নিয়মে বের করা যায়। এর প্রমাণের মূল ধারণা §৮-এর শেষে।

সাধারণ ভুল: বাঁ দিক ভাঙা যায় না। XY → Z থেকে X → Z আসে না: (x, y1, z1) আর (x, y2, z2) দুটি সারিতে XY → Z খাটে (XY-এর মান আলাদা), কিন্তু X = x একই অথচ Z আলাদা। {oid, pid} → qty খাটে, কিন্তু oid → qty খাটে না, কারণ একটি অর্ডারে একাধিক পণ্য থাকতে পারে। ডান দিক ভাঙা (decomposition) কিন্তু বৈধ।

৮ · Attribute closure — কোন attribute-গুলো নির্ধারিত, আর key খোঁজা

Armstrong-এর নিয়ম পরপর খাটিয়ে সব FD তালিকা করা যায়, কিন্তু U-তে n-টি attribute থাকলে সম্ভাব্য FD 2n · 2n-টি। কাজের প্রশ্ন সাধারণত ছোট: “X জানলে কোন কোন attribute জানা যায়?” এর উত্তরই attribute closure X+: F-এর অধীনে X → A আসে এমন সব attribute A-র সেট।

Algorithm ৮.১ (X+ হিসাব)
  1. M ← X (M মানে এ পর্যন্ত নির্ধারিত attribute-এর সেট).
  2. F-এর প্রতিটি V → W দেখুন: V ⊆ M হলে M ← M ∪ W.
  3. একটি পুরো পালায় M না বদলালে থামুন; X+ = M. নইলে ধাপ ২ আবার।

Algorithm থামবেই: প্রতিটি পালায় M অন্তত একটি attribute বাড়ে, নইলে থামে, আর M ⊆ U; তাই বড়জোর |U| + 1 পালা। নিচে R(A, B, C, D, E), F = {A → B, BC → D, B → C, D → E}-এ {A}+:

1M ← {A}2পালা ১: A → B3পালা ১: BC → D4পালা ১: B → C5পালা ১: D → E6পালা ২: BC → D7পালা ২: D → E8পালা ৩: কিছু বদলায় না
M = এ পর্যন্ত নির্ধারিত
FD
বাঁ দিক ⊆ M?
M
  1. শুরু। Reflexivity অনুযায়ী A → A, তাই M = {A}.
  2. A → B. বাঁ দিক {A} ⊆ M, তাই B যোগ: M = {A, B}. (A → B সরাসরি F-এ আছে।)
  3. BC → D. বাঁ দিকের attribute C এখনো M-এ নেই, তাই এই FD এই মুহূর্তে কিছু দেয় না। পরে আবার দেখতে হবে, এজন্যই algorithm-এ একাধিক পালা।
  4. B → C. B ∈ M, তাই attribute C যোগ: {A, B, C}. যুক্তি: A → B আর B → C, transitivity-তে A → C.
  5. D → E. D এখনো নেই। পালা ১ শেষ; M বদলেছে, তাই আরেক পালা।
  6. পালা ২, BC → D. এবার {B, C} ⊆ {A, B, C}, তাই D যোগ: {A, B, C, D}. (A → B এর আগে মেলেছে, আবার দেখলে নতুন কিছু দেয় না।)
  7. D → E. D এখন আছে, তাই E যোগ: {A, B, C, D, E}. পালা ২-এ M বদলেছে, তাই পালা ৩.
  8. পালা ৩. চারটি FD-র কোনোটি নতুন কিছু দেয় না, algorithm থামে। {A}+ = {A, B, C, D, E} = U, তাই A → U, অর্থাৎ {A} একটি superkey; এক attribute বলে candidate key-ও।
উপপাদ্য ৮.২ (closure algorithm সঠিক)

Algorithm ৮.১ যে M দেয়, তার জন্য: Armstrong-এর নিয়মে F থেকে X → Y বের হয় ⇔ Y ⊆ M. বিশেষ করে, X superkey ⇔ X+ = U.

  1. Sound দিক (M-এ যা ঢোকে, তা নির্ধারিত): দাবি করি প্রতিটি মুহূর্তে X → M বের করা যায়। শুরুতে M = X, reflexivity. — induction-এর ভিত্তি, পর্ব ১০
  2. ধরুন X → M বের হয়, আর V → W খাটিয়ে M বাড়ল, যেখানে V ⊆ M ছিল। Reflexivity-তে M → V, transitivity-তে X → V, তারপর X → W. Union নিয়মে X → M ∪ W. — induction-এর ধাপ
  3. তাই Y ⊆ M হলে X → M আর decomposition-এ X → Y. — ধাপ ২
  4. Complete দিক (মূল ধারণা): ধরুন A ∉ M. দুই সারির একটি instance বানান: দুটি সারি M-এর প্রতিটি attribute-এ 0, আর বাকি সব attribute-এ একটিতে 0, অন্যটিতে 1. — counterexample গঠন
  5. এই instance F-এর প্রতিটি V → W মানে: V ⊆ M না হলে দুই সারি V-তে আলাদা, শর্ত ফাঁকা অর্থে সত্য; V ⊆ M হলে algorithm থেমেছে বলে W ⊆ M, তাই W-তেও দুই সারি এক। — M আর কোনো FD-তে বাড়ে না
  6. কিন্তু দুই সারি X ⊆ M-এ এক আর A-তে আলাদা, তাই এখানে X → A খাটে না। Sound নিয়ম কখনো এমন FD দিতে পারে না যা F মানে এমন একটি instance-এ মিথ্যা; তাই X → A বের হয় না। ∎ — উপপাদ্য ৭.২

ধাপ ৪–৬-এর একই দুই-সারির কৌশল থেকে Armstrong-এর নিয়মের completeness-ও আসে। আর শেষ লাইনটি key খোঁজার যন্ত্র দেয়: সব subset X ছোট থেকে বড় ক্রমে নিন, X+ = U হলে X superkey, আর আগে পাওয়া কোনো key-র superset না হলে X candidate key (corollary ৩.৩-এর ভাবনা)। নিচের ব্লক এটিই করে দুটি schema-তে: বাজারঘরের OrderLine আর পরীক্ষার ধাঁচের R(A, B, C, D) with F = {AB → C, C → D, D → A}.

from itertools import combinations

def closure(X, F):                      # X+ under the FDs in F
    C = set(X)
    changed = True
    while changed:
        changed = False
        for lhs, rhs in F:
            if lhs <= C and not rhs <= C:
                C |= rhs
                changed = True
    return C

def candidate_keys(U, F):               # smallest first, skip supersets
    keys, supers = [], 0
    for k in range(1, len(U) + 1):
        for X in map(set, combinations(sorted(U), k)):
            if closure(X, F) == U:
                supers += 1
                if not any(K <= X for K in keys):
                    keys.append(X)
    return [sorted(K) for K in keys], supers

fd = lambda l, r: (set(l.split()), set(r.split()))
U1 = {"oid", "cid", "district", "pid", "category", "qty"}
F1 = [fd("oid", "cid"), fd("cid", "district"),
      fd("pid", "category"), fd("oid pid", "qty")]
print("{oid}+ =", sorted(closure({"oid"}, F1)))
print("{oid,pid}+ == U:", closure({"oid", "pid"}, F1) == U1)
print("keys, superkeys:", candidate_keys(U1, F1))
U2 = set("ABCD")
F2 = [fd("A B", "C"), fd("C", "D"), fd("D", "A")]
for X in ("A", "C", "AB", "BC", "BD"):
    print(X + "+ =", "".join(sorted(closure(set(X), F2))))
print("keys, superkeys:", candidate_keys(U2, F2))

আসল আউটপুট (ব্রাউজারের Python):

{oid}+ = ['cid', 'district', 'oid']
{oid,pid}+ == U: True
keys, superkeys: ([['oid', 'pid']], 16)
A+ = A
C+ = ACD
AB+ = ABCD
BC+ = ABCD
BD+ = ABCD
keys, superkeys: ([['A', 'B'], ['B', 'C'], ['B', 'D']], 7)

OrderLine-এ {oid}+ = {oid, cid, district}: অর্ডার নম্বর জানলে গ্রাহক আর জেলা জানা যায়, কিন্তু পণ্য বা পরিমাণ নয়। একমাত্র candidate key {oid, pid}, আর superkey ১৬টি, ঠিক §৩-এর সূত্র 26−2 = 16. দ্বিতীয় schema-য় তিনটি candidate key: AB, BC, BD. B কোনো FD-র ডান দিকে নেই, তাই B প্রতিটি key-তে থাকতেই হবে; বাকি অংশ A, C বা D যেকোনো একটি, কারণ C → D → A একটি চক্র। Superkey ৭টি: AB, BC বা BD-কে ধারণ করে এমন সব subset. সব subset পরীক্ষা করায় এই পদ্ধতি 2n-এ বাড়ে; n = 30-এ প্রায় ১০৭ কোটি subset. Functional dependency থেকে normalisation (2NF, 3NF, BCNF) ডেটাবেস (DBMS) ও SQL কোর্সের পর্ব ৩৫ আর পর্ব ৩৬-এ।

৯ · গণিত বনাম SQL, আর পরীক্ষার অঙ্ক

এই পর্বের সব উপপাদ্য গাণিতিক relation-এর জন্য। SQL-এর টেবিল দুই জায়গায় আলাদা, আর সেটি সৎভাবে জানা দরকার।

  • Bag, সেট নয়। SQL-এ একই সারি একাধিকবার থাকতে পারে, আর SELECT পুনরাবৃত্তি রাখে; DISTINCT লিখলে তবেই সেট। এই নিয়মের নাম bag semantics: টেবিল একটি multiset, যার ধারণা পর্ব ৩৫ “পুনরাবৃত্তিসহ বাছাই”-এর পুনরাবৃত্তিসহ সংগ্রহের মতো। Bag-এ কিছু সেট-identity ভাঙে: সেটে R ∪ R = R, কিন্তু SQL-এর UNION ALL-এ সারি দ্বিগুণ হয়।
  • NULL. অজানা মানের জন্য SQL একটি বিশেষ চিহ্ন রাখে, যা কোনো domain-এর উপাদান নয়। NULL-এর সঙ্গে তুলনার ফল TRUE বা FALSE নয়, UNKNOWN; এটিই three-valued logic. ফলে “district = 'Dhaka' অথবা district ≠ 'Dhaka'” সব সারি দেয় না, যা পর্ব ০২-এর দুই-মানের যুক্তিতে একটি tautology হতো।
from collections import Counter

rows = [("C1", "Dhaka"), ("C2", "Khulna"), ("C3", "Dhaka"),
        ("C4", None), ("C5", None)]                # None plays SQL's NULL
as_set = {d for _, d in rows}                      # algebra: projection is a set
as_bag = Counter(d for _, d in rows)               # SQL SELECT district: a bag
print("set :", len(as_set), "rows", sorted(as_set, key=str))
print("bag :", sum(as_bag.values()), "rows", dict(as_bag))

def eq3(x, y):                                     # SQL's three-valued "="
    if x is None or y is None:
        return "UNKNOWN"
    return "TRUE" if x == y else "FALSE"

print("Python None == None :", None == None)
print("SQL    NULL = NULL  :", eq3(None, None))
where_true = [c for c, d in rows if eq3(d, "Dhaka") == "TRUE"]
where_not = [c for c, d in rows if eq3(d, "Dhaka") == "FALSE"]
print("district = 'Dhaka'  :", where_true)
print("district <> 'Dhaka' :", where_not)
print("missing from both   :", sorted({c for c, _ in rows} - set(where_true) - set(where_not)))

আসল আউটপুট (ব্রাউজারের Python):

set : 3 rows ['Dhaka', 'Khulna', None]
bag : 5 rows {'Dhaka': 2, 'Khulna': 1, None: 2}
Python None == None : True
SQL    NULL = NULL  : UNKNOWN
district = 'Dhaka'  : ['C1', 'C3']
district <> 'Dhaka' : ['C2']
missing from both   : ['C4', 'C5']

প্রথম দুই লাইন projection-এর দুই অর্থ: গণিতে ৩টি সারি, SQL-এর সাধারণ SELECT-এ ৫টি। তারপর একটি সূক্ষ্ম পার্থক্য: Python-এ None == None সত্য, কিন্তু SQL-এ NULL = NULL-এর ফল UNKNOWN (এখানে eq3 ফাংশন দিয়ে অনুকরণ)। তাই আমাদের §৫-এর engine NULL-যুক্ত সারি join-এ মেলাত, SQL মেলায় না। শেষ তিন লাইনে WHERE শুধু TRUE সারি রাখে; জেলা অজানা এমন দুই গ্রাহক C4, C5 “ঢাকা” বা “ঢাকা নয়” কোনো ফলেই আসেননি। NULL-এর পুরো নিয়ম ডেটাবেস (DBMS) ও SQL কোর্সের পর্ব ১৬-এ।

পরীক্ষার ধাঁচের অঙ্ক

অঙ্ক ১. R(A, B) ৩টি সারি, S(C, D) ৪টি সারি। R ⋈ S-এ কয়টি সারি? সমাধান: কোনো সাধারণ attribute নেই, তাই উপপাদ্য ৫.২(ক) অনুযায়ী R ⋈ S = R × S, আর সারি 3 · 4 = 12. যদি সাধারণ attribute থাকত, উত্তর 0 থেকে 12-এর মধ্যে যেকোনো কিছু হতে পারত।

অঙ্ক ২. R(A, B, C, D, E), F = {A → B, B → C, C → A, D → E}. সব candidate key আর superkey-র সংখ্যা বের করুন। সমাধান: D কোনো FD-র ডান দিকে নেই, তাই প্রতিটি key-তে D থাকবে; D+ = {D, E}, তাই একা D যথেষ্ট নয়। {A, D}+: A থেকে B, B থেকে C, D থেকে E, মোট U. একইভাবে {B, D}+ = {C, D}+ = U, কারণ A, B, C একটি চক্রে একে অন্যকে নির্ধারণ করে। প্রতিটি দুই attribute-এর, আর একটি করে বাদ দিলে (A বা D একা) superkey নয়, তাই corollary ৩.৩ অনুযায়ী candidate key: AD, BD, CD. Superkey মানে D আছে, {A, B, C}-এর অন্তত একটি আছে, আর E ইচ্ছেমতো: (23 − 1) · 2 = 14টি (scratch-এ brute force দিয়ে মেলানো)।

অঙ্ক ৩. F = {A → B, B → C, CD → E} হলে {A, D}+ কত, আর {A}+? সমাধান: {A, D} থেকে B, তারপর C, তারপর C আর D দুটিই এসে গেছে বলে CD → E থেকে E: {A, D}+ = {A, B, C, D, E}. কিন্তু {A}+ = {A, B, C}, কারণ D না থাকলে CD → E খাটে না।

আরও গভীরে: Relational algebra-র select–project–join অংশ আর first-order logic-এর ∃ ও ∧ দিয়ে লেখা সূত্র (“conjunctive query”) একই প্রশ্ন প্রকাশ করে, আর দুটি conjunctive query সমতুল্য কি না তা নির্ণয় করা যায়; এই ফল Chandra আর Merlin-এর ১৯৭৭ সালের কাজের সঙ্গে যুক্ত বলে উল্লেখ করা হয়, যদিও সমস্যাটি NP-complete. অন্যদিকে FD-র implication আসলে propositional Horn clause-এর implication-এর সমতুল্য (A ∧ B → C), আর attribute closure হলো পর্ব ০৭-এর unit propagation-এর মতো forward chaining. পুনরাবৃত্তিমূলক প্রশ্ন (যেমন “কোন গুদাম থেকে কোন জেলায় পৌঁছানো যায়”) relational algebra-য় লেখা যায় না; সেজন্য Datalog আর SQL-এর recursive query, যার গণিত পর্ব ৫৪ “Relation-এর closure আর Warshall-এর algorithm”-এর transitive closure.

পরীক্ষা ও ইন্টারভিউতে: বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় এই ধরনের প্রশ্ন প্রায়ই আসে:

  • |A1| = 3, |A2| = 4, |A3| = 2 হলে ternary relation কয়টি? 224টি।
  • Superkey আর candidate key-র পার্থক্য? Candidate key হলো minimal superkey; superkey-তে বাড়তি attribute থাকতে পারে।
  • F = {AB → C, C → D, D → A} হলে R(A, B, C, D)-এর candidate key? AB, BC, BD.
  • σc(R ⋈ S) = σc(R) ⋈ S কখন? যখন c শুধু R-এর attribute ব্যবহার করে।
  • Armstrong-এর তিন axiom? Reflexivity, augmentation, transitivity.

১০ · AI-এর যুগে — relational algebra আর key-র উত্তর যাচাই

Key আর FD-র অঙ্কে AI সহকারী প্রায়ই ধাপগুলো সুন্দর করে লেখে, কিন্তু কয়েকটি নির্দিষ্ট ভুল বারবার করে, আর আত্মবিশ্বাসী সুরে:

AI-এর ভুলকীভাবে ধরবেন
Candidate key-র তালিকায় একটি key বাদ পড়া, বা একটি superkey-কে key বলাপ্রতিটি key-র closure হিসাব করুন, আর একটি একটি attribute বাদ দিয়ে দেখুন (corollary ৩.৩)
XY → Z থেকে X → Z “decomposition” বলে চালিয়ে দেওয়াদুই সারির counterexample বানান; decomposition শুধু ডান দিকে চলে
Closure-এ এক পালাতেই থেমে যাওয়াশেষ পালায় কোনো FD নতুন কিছু দিচ্ছে না, তা নিজে দেখুন
σ এমন দিকে নামানো, যেখানে শর্তের কলাম নেই; বা π-কে difference-এর ওপর ছড়ানোশর্তের প্রতিটি attribute কোন টেবিলের, তালিকা করুন; ছোট counterexample চালান
SQL-এর আচরণ (duplicate, NULL) আর গণিতের relation মিশিয়ে ফেলাপ্রশ্নটি set না bag অর্থে, তা স্পষ্ট করুন
Codd বা Armstrong-এর প্রবন্ধের সাল আর শিরোনাম নিশ্চিত সুরে ভুল বলামূল প্রবন্ধ বা বিশ্বস্ত পাঠ্যবইয়ে মেলান

AI-এর কাছে key বা closure-এর সমাধান চাইলে এমন কাঠামো দিন, যাতে প্রতিটি ধাপ আলাদা করে যাচাই করা যায়:

নিচের relation-এর সব candidate key ধাপে ধাপে বের করো: R(A, B, C, D, E), F = {A → B, B → C, C → A, D → E} ১. কোন attribute কোনো FD-র ডান দিকে নেই, আগে তা লেখো; সেগুলো প্রতিটি key-তে থাকবে কেন, ব্যাখ্যা দাও। ২. প্রতিটি প্রার্থী X-এর জন্য X+ হিসাব পালা ধরে দেখাও: কোন FD কোন পালায় কী যোগ করল। ৩. প্রতিটি key-র minimality দেখাও: একটি একটি attribute বাদ দিয়ে closure আর U নয়। ৪. Superkey-র মোট সংখ্যা গুনে দেখাও। ৫. সব subset পরীক্ষা করে key খোঁজার একটি ছোট Python কোড দাও (শুধু itertools), যাতে আমি উত্তর মেলাতে পারি। ৬. Armstrong-এর কোন নিয়ম কোথায় খাটালে, নাম ধরে বলো; কোনো সাল বা উৎস নিয়ে নিশ্চিত না হলে তা স্পষ্ট বলো।

AI-এর উত্তর হাতে পেলে — যাচাইয়ের চেকলিস্ট

Relational algebra-র সুবিধা হলো এর প্রতিটি দাবি ছোট টেবিলে পরীক্ষা করা যায়: চার-পাঁচ সারির দুটি টেবিল, আর ত্রিশ লাইনের একটি engine. AI একটি সমীকরণ “সবসময় সত্য” বললে কয়েকশো এলোমেলো টেবিলে চালিয়ে দেখুন; একটিও counterexample পেলে দাবিটি মিথ্যা, আর কোনোটি না পেলে প্রমাণ খুঁজুন।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।

প্র ০১Codd-এর relational model-এর আগে database কেমন ছিল, আর একটি গাণিতিক ধারণা কেন শিল্পে এত বড় পরিবর্তন আনল?

১৯৬০-এর দশকের database-গুলো মূলত দুই ধরনের ছিল। Hierarchical মডেলে (IBM-এর IMS এর পরিচিত উদাহরণ) তথ্য গাছের মতো সাজানো: একটি গ্রাহকের নিচে তার অর্ডার, অর্ডারের নিচে পণ্য। Network মডেলে (CODASYL কমিটির প্রস্তাবিত মান) রেকর্ডগুলো pointer দিয়ে যুক্ত। দুটিতেই প্রোগ্রামারকে জানতে হতো ডেটা কোন পথে সাজানো, আর প্রশ্ন লিখতে হতো সেই পথ ধরে এক রেকর্ড থেকে আরেক রেকর্ডে “হেঁটে”। কাঠামো বদলালে (যেমন পণ্যের নিচে অর্ডার রাখা) পুরোনো প্রোগ্রাম ভেঙে যেত।

Codd ১৯৭০ সালের প্রবন্ধে প্রস্তাব করেন যে ব্যবহারকারী শুধু relation দেখবেন, আর প্রশ্ন করবেন কী চাই তা বলে, কীভাবে খুঁজতে হবে তা না বলে। এর নাম data independence: ভেতরের সাজানো বদলালেও প্রশ্ন একই থাকে। এটি সম্ভব হয়েছিল ঠিক এই পর্বের গণিতের কারণে। প্রশ্ন যদি relational algebra-র রাশি হয়, তাহলে database নিজেই প্রমাণিত সমীকরণ দিয়ে রাশিটিকে দ্রুত রূপে বদলাতে পারে; প্রোগ্রামারকে পথ বাছতে হয় না। শুরুর দিকে অনেকে সন্দেহ করেছিলেন এমন ব্যবস্থা যথেষ্ট দ্রুত হবে কি না।

সেই সন্দেহের উত্তর এসেছিল ১৯৭০-এর দশকের দুটি গবেষণা প্রকল্প থেকে: IBM-এর System R আর Berkeley-র Ingres. System R-এর দল একটি cost-based optimizer বানায়, যার ১৯৭৯ সালের বর্ণনা (Selinger ও সহকর্মীদের প্রবন্ধ) আজও এই বিষয়ের ভিত্তি হিসেবে পড়া হয় বলে জানা যায়। একই প্রকল্প থেকে SQL ভাষার পূর্বসূরি SEQUEL এসেছিল। এরপর বাণিজ্যিক relational database দ্রুত ছড়ায়, আর আজ প্রায় সব ব্যাংক, মোবাইল অপারেটর, সরকারি নিবন্ধন বা অনলাইন দোকানের তথ্যের পেছনে এই মডেলের কোনো রূপ আছে। Codd ১৯৮১ সালে Turing Award পান।

শিক্ষাটি হলো, একটি সঠিক বিমূর্ত ধারণা ব্যবহারিক দিক থেকেও শক্তিশালী হতে পারে। “টেবিল = সেট, প্রশ্ন = সেটের অপারেশন” এই সরলতা থেকেই key-র অস্তিত্ব (উপপাদ্য ৩.২), optimisation-এর সমীকরণ (উপপাদ্য ৬.১) আর ডিজাইনের নিয়ম (FD, normalisation) একটি একক তত্ত্ব থেকে আসে। তবে বাস্তব SQL পুরোপুরি এই আদর্শ মানে না (bag আর NULL), যা Codd নিজেও সমালোচনা করেছিলেন বলে জানা যায়।

মূল উপলব্ধি: Relational model “কী চাই” আর “কীভাবে খুঁজব” আলাদা করেছিল; সেই বিভাজন সম্ভব হয়েছিল কারণ প্রশ্নগুলো গাণিতিক রাশি, যাদের সমতুল্যতা প্রমাণ করা যায়।

প্র ০২গণিতে relation একটি সেট, অথচ SQL টেবিলকে bag ধরে। কেন এই সিদ্ধান্ত, আর এর ফলে কোন নিয়ম ভাঙে?

প্রথম কারণ খরচ। Projection-এর পর পুনরাবৃত্তি ফেলতে হলে সব ফল sort করতে হয় বা একটি hash table-এ রাখতে হয়; লাখো সারির ফলে এটি সময় আর মেমরি দুটিই খায়। অনেক প্রশ্নে পুনরাবৃত্তি থাকলেও ক্ষতি নেই, তাই SQL ডিফল্টে তা রাখে আর দরকার হলে DISTINCT লিখতে বলে। দ্বিতীয় কারণ গণনা। বাজারঘরের মোট বিক্রি জানতে SUM(qty) চাই। যদি আগে πqty নিয়ে পুনরাবৃত্তি ফেলা হতো, তাহলে দুটি অর্ডারে ২ পিস করে থাকলে একটি ২ হারিয়ে যেত, আর যোগফল ভুল হতো। COUNT, SUM, AVG-এর মতো aggregate-এর জন্য বহুলতা অপরিহার্য।

কিন্তু bag-এ সেটের কিছু চেনা identity ভাঙে। সেটে R ∪ R = R (idempotence), কিন্তু UNION ALL-এ R-এর প্রতিটি সারি দুবার আসে। সেটে R ∩ (S ∪ T) = (R ∩ S) ∪ (R ∩ T), কিন্তু bag-এ union-কে “বহুলতা যোগ” আর intersection-কে “ন্যূনতম বহুলতা” ধরলে এই distributive নিয়ম সাধারণভাবে খাটে না; যেমন R, S, T প্রতিটিতে x একবার থাকলে বাঁ দিকে x একবার, ডান দিকে দুবার। উপপাদ্য ৩.২(খ)-ও ভাঙে: দুটি হুবহু এক সারিকে কোনো কলাম দিয়ে আলাদা করা যায় না, তাই key ছাড়া একটি SQL টেবিলে সত্যিই কোনো superkey নেই। এজন্যই বাস্তবে প্রায় প্রতিটি টেবিলে primary key ঘোষণা করা হয়, যা টেবিলটিকে আবার সেটের মতো আচরণ করতে বাধ্য করে।

তবে selection pushdown-এর মতো অনেক গুরুত্বপূর্ণ নিয়ম bag-এও সত্য, কারণ সেগুলো প্রতিটি সারির জন্য আলাদাভাবে প্রমাণ হয়; optimizer তাই নিশ্চিন্তে সেগুলো ব্যবহার করে। Bag-এর algebra নিয়ে ১৯৯০-এর দশকে আলাদা গবেষণা হয়েছে, আর দুটি bag-query সমতুল্য কি না নির্ণয় করা সেটের চেয়ে কঠিন হতে পারে বলে জানা যায়। পর্ব ৩৫-এ multiset-এর গণনা দেখেছেন; সেই গণিতই এখানে টেবিলের আচরণ ব্যাখ্যা করে।

ব্যবহারিক পরামর্শ: SQL লেখার সময় নিজেকে জিজ্ঞেস করুন “এই ফলে পুনরাবৃত্তি কি অর্থপূর্ণ?” গ্রাহকের তালিকায় একই নাম দুবার সাধারণত ভুল, অর্থাৎ DISTINCT লাগবে বা join ভুল হয়েছে। বিক্রির যোগফলে পুনরাবৃত্তি জরুরি। আর দুটি query-র ফল তুলনা করতে চাইলে মনে রাখুন, সেট হিসেবে সমান হলেও bag হিসেবে সমান নাও হতে পারে।

মূল উপলব্ধি: SQL bag বেছেছে খরচ বাঁচাতে আর aggregate ঠিক রাখতে; বিনিময়ে idempotence, কিছু distributive নিয়ম আর key-র নিশ্চিত অস্তিত্ব হারিয়েছে।

প্র ০৩NULL আর three-valued logic নিয়ে এত বিতর্ক কেন? NOT IN-এর ফাঁদটি কী?

বাস্তব তথ্য প্রায়ই অসম্পূর্ণ: নতুন গ্রাহক জেলা লেখেননি, পণ্যের ওজন এখনো মাপা হয়নি। গাণিতিক relation-এ প্রতিটি ঘরে domain-এর একটি মান থাকতেই হবে, তাই “অজানা” রাখার জায়গা নেই। SQL এর সমাধান হিসেবে NULL রাখে, আর তুলনার ফলের জন্য তৃতীয় মান UNKNOWN. AND, OR, NOT-এর truth table তিন মানে বাড়ানো হয় (যেমন FALSE AND UNKNOWN = FALSE, TRUE AND UNKNOWN = UNKNOWN), আর WHERE শুধু TRUE সারি রাখে।

সমস্যা হলো এতে দুই-মানের যুক্তির কিছু চেনা নিয়ম ভাঙে। p ∨ ¬p সবসময় TRUE নয়, p UNKNOWN হলে ফলও UNKNOWN; তাই ওপরের Python ব্লকে “ঢাকা” আর “ঢাকা নয়” মিলে সব গ্রাহক আসেননি। সবচেয়ে কুখ্যাত ফাঁদ NOT IN. ধরুন প্রশ্ন: “যে গ্রাহকের নম্বর ব্লক-তালিকায় নেই”, আর তালিকায় ভুলক্রমে একটি NULL ঢুকে গেছে। x NOT IN (C2, NULL) মানে x ≠ C2 AND x ≠ NULL; দ্বিতীয় অংশ সবসময় UNKNOWN, তাই পুরো শর্ত কখনো TRUE হয় না, আর প্রশ্নটি একটিও সারি দেয় না, কোনো error ছাড়াই। NOT EXISTS এই ফাঁদে পড়ে না, কারণ তা subquery-তে সারি আছে কি না দেখে (পর্ব ০৫-এর ∃-এর মতো)।

তাত্ত্বিক বিতর্কও পুরোনো। Codd নিজে পরে দুই রকম চিহ্ন প্রস্তাব করেছিলেন বলে জানা যায়: “মান আছে কিন্তু অজানা” আর “মান প্রযোজ্যই নয়” (যেমন অবিবাহিত মানুষের বিয়ের তারিখ)। অন্যদিকে Chris Date-এর মতো লেখকেরা NULL-কে পুরোপুরি বাদ দেওয়ার পক্ষে যুক্তি দিয়েছেন, যাতে relation আবার খাঁটি দুই-মানের যুক্তিতে ফিরে আসে; তাঁদের প্রস্তাব হলো অজানা তথ্য আলাদা টেবিলে রাখা। গবেষণায় “incomplete database” নামে একটি পুরো ক্ষেত্র আছে, যেখানে NULL-কে “সম্ভাব্য অনেক database-এর একটি সেট” ধরে প্রশ্নের “নিশ্চিত উত্তর” সংজ্ঞায়িত করা হয়; এই নিশ্চিত উত্তর হিসাব করা সাধারণভাবে কঠিন।

ব্যবহারিক পরামর্শ সহজ: যেখানে সম্ভব কলামে NOT NULL দিন, তুলনায় IS NULL আর IS NOT NULL আলাদা করে লিখুন, NOT IN-এর জায়গায় NOT EXISTS নিন, আর aggregate-এ NULL বাদ পড়ে (COUNT(column) NULL গোনে না) তা মনে রাখুন। গণিতের প্রমাণ হাতে রেখে SQL চালানোর আগে জিজ্ঞেস করুন: এই কলামে NULL থাকতে পারে কি?

মূল উপলব্ধি: NULL দুই-মানের যুক্তিকে তিন-মানে বদলায়, তাই p ∨ ¬p-এর মতো tautology আর NOT IN-এর স্বাভাবিক অর্থ ভেঙে যায়; সতর্ক না থাকলে প্রশ্ন নীরবে ভুল ফল দেয়।

প্র ০৪Optimizer join-এর ক্রম বাছে কীভাবে, আর কেন সব ক্রম পরীক্ষা করা অসম্ভব? এখানে Catalan সংখ্যা কোথায় আসে?

Natural join commutative (R ⋈ S = S ⋈ R, কলামের ক্রম বাদে) আর associative ((R ⋈ S) ⋈ T = R ⋈ (S ⋈ T)), দুটিই সংজ্ঞা ৫.১ থেকে সরাসরি প্রমাণ হয়, কারণ শর্ত শুধু “প্রতিটি অংশ নিজের টেবিলে আছে”। তাই n-টি টেবিলের join যেকোনো ক্রমে আর যেকোনো বন্ধনীতে করা যায়, ফল একই। কিন্তু খরচ বিশাল আলাদা হতে পারে: বাজারঘরের Customers ⋈ Orders ⋈ Products-এ যদি প্রথমে Customers আর Products জোড়া হয় (কোনো সাধারণ কলাম নেই), তাহলে মাঝের ফল একটি Cartesian product, 4 × 4 = 16 সারি, যার প্রায় সবই পরে ফেলে দিতে হয়। বড় টেবিলে এই মাঝের ফল কোটি সারি হতে পারে।

কয়টি ক্রম আছে? n-টি টেবিলকে একটি বাইনারি গাছের পাতায় বসাতে হবে। গাছের আকার (বন্ধনী বসানোর উপায়) Cn−1-টি, যেখানে Ck হলো Catalan সংখ্যা (পর্ব ৫০), আর পাতায় টেবিল বসানোর উপায় n!. মোট n! · Cn−1 = (2n − 2)!/(n − 1)!: n = 4-এ 120, n = 5-এ 1680 (scratch-এ হিসাব মেলানো), আর ১০টি টেবিলে প্রায় ১,৭৬৪ কোটি। শুধু “left-deep” গাছ (প্রতিবার একটি নতুন টেবিল যোগ) ধরলেও n!টি ক্রম, যা n = 10-এ ৩৬ লাখের বেশি।

তাই optimizer সব পরীক্ষা করে না। System R-এর ধ্রুপদি পদ্ধতি dynamic programming: প্রতিটি টেবিল-subset-এর জন্য সবচেয়ে সস্তা join পরিকল্পনা একবার হিসাব করে মনে রাখা, তারপর বড় subset-এ সেগুলো জোড়া দেওয়া। Subset 2n-টি, যা n! থেকে অনেক কম, কিন্তু তবুও সূচকীয়; তাই বহু টেবিলের query-তে database-গুলো heuristic বা এলোমেলো খোঁজে যায়। খরচ অনুমানের জন্য লাগে প্রতিটি মাঝের ফলের আকার, যা আসে statistics থেকে (কলামে কয়টি আলাদা মান, মানগুলো কীভাবে ছড়ানো); এই অনুমান ভুল হলে খারাপ পরিকল্পনা বাছা হয়, আর এটিই বাস্তবে ধীর query-র একটি বড় কারণ বলে গবেষণায় বারবার দেখা গেছে।

গণিতের ভূমিকা এখানে দুই স্তরে। প্রথমত, commutativity, associativity আর selection pushdown-এর প্রমাণ নিশ্চিত করে যে optimizer যা-ই বাছুক, উত্তর ঠিক থাকবে; শুধু গতি বদলাবে। দ্বিতীয়ত, গণনার তত্ত্ব (Catalan, factorial, subset) বলে দেয় খোঁজার জায়গা কত বড়, আর কোন কৌশল কতদূর চলে। বিস্তারিত ডেটাবেস (DBMS) ও SQL কোর্সের পর্ব ৪৩-এ।

মূল উপলব্ধি: Join-এর ক্রম ফল বদলায় না (প্রমাণিত), কিন্তু খরচ বদলায়; সম্ভাব্য ক্রম n! · Cn−1, তাই optimizer dynamic programming আর অনুমানে নির্ভর করে।

প্র ০৫একটি schema-র candidate key কয়টি হতে পারে, আর সবচেয়ে ছোট key খোঁজা কতটা কঠিন? FD-র সঙ্গে logic-এর সম্পর্ক কী?

Key-র সংখ্যা বিস্ময়করভাবে বড় হতে পারে। 2m-টি attribute A1, B1, …, Am, Bm নিন, আর FD দিন Ai → Bi ও Bi → Ai (প্রতিটি জোড়ার দুই সদস্য একে অন্যকে নির্ধারণ করে)। তাহলে একটি key-তে প্রতিটি জোড়া থেকে ঠিক একটি attribute থাকতে হবে, আর যেকোনো এমন বাছাই কাজ করে, তাই candidate key 2m-টি। m = 10 হলে মাত্র ২০টি কলামে ১০২৪টি key. অর্থাৎ “সব key তালিকা করো” প্রশ্নের উত্তর নিজেই সূচকীয় আকারের হতে পারে, কোনো algorithm তা দ্রুত লিখতে পারবে না।

আর “k-এর বেশি নয় এমন আকারের একটি key আছে কি?” প্রশ্নটি NP-complete বলে প্রমাণিত; এই ফল সাধারণত Lucchesi আর Osborn-এর ১৯৭৮ সালের কাজের সঙ্গে যুক্ত করা হয়। অন্যদিকে একটি নির্দিষ্ট সেট key কি না যাচাই করা সহজ: একবার closure, আর corollary ৩.৩ অনুযায়ী প্রতিটি attribute বাদ দিয়ে আরেকবার করে। Closure নিজে প্রায় linear সময়ে করা যায় (প্রতিটি FD-র বাঁ দিকের কয়টি attribute এখনো বাকি, তার গণনা রেখে), যা Beeri আর Bernstein-এর ১৯৭৯ সালের কাজে দেখানো হয়েছিল বলে জানা যায়। “যাচাই সহজ, খোঁজা কঠিন”, এই পরিচিত ছবিটি P আর NP-র (DSA কোর্সের পর্ব ৬৪)।

Logic-এর সংযোগটি সুন্দর। প্রতিটি attribute-কে একটি propositional variable ধরুন, আর V → W-কে Horn clause হিসেবে লিখুন: V-এর সব variable-এর AND থেকে W-এর প্রতিটি variable সত্য। Fagin ১৯৭৭ সালে দেখান বলে জানা যায় যে F থেকে X → A আসে ঠিক তখনই, যখন এই clause-গুলো আর X-এর প্রতিটি variable সত্য ধরে নিলে A-ও সত্য হতে বাধ্য। Attribute closure algorithm তখন Horn clause-এর forward chaining: যে clause-এর বাঁ দিকের সব সত্য, তার ডান দিক সত্য বলে চিহ্নিত করা, ঠিক পর্ব ০৭-এর SAT solver-এর unit propagation-এর মতো। উপপাদ্য ৮.২-এর দুই-সারির counterexample তাহলে একটি truth assignment: M-এর variable সত্য, বাকি মিথ্যা।

এই সংযোগ ব্যবহারিকও। Horn clause-এর satisfiability polynomial সময়ে হয়, অথচ সাধারণ SAT-এর জন্য কোনো polynomial algorithm জানা নেই (পর্ব ০৩-এর satisfiability); তাই FD-র implication দ্রুত, কিন্তু আরও জটিল dependency (যেমন multivalued বা join dependency) নিয়ে প্রশ্ন কঠিন হয়ে উঠতে পারে। Normalisation-এর তত্ত্ব (BCNF, 4NF) এই dependency-গুলোর ওপর দাঁড়িয়ে, যার বিস্তারিত ডেটাবেস (DBMS) ও SQL কোর্সের পর্ব ৩৭-এ।

মূল উপলব্ধি: একটি সেট key কি না যাচাই করা সহজ, কিন্তু key সূচকীয় সংখ্যক হতে পারে আর ছোট key খোঁজা NP-complete; FD আসলে Horn logic, আর closure হলো তার forward chaining.

অনুশীলন

আগে খাতায় করুন, তারপর উত্তর খুলে মেলান।

  1. প্রমাণ: Armstrong-এর তিনটি axiom ব্যবহার করে union নিয়মটি প্রমাণ করুন: X → Y আর X → Z হলে X → YZ.
    1. X → Y-তে X দিয়ে augmentation: XX → XY, অর্থাৎ X → XY, কারণ X ∪ X = X হয়। — augmentation
    2. X → Z-তে Y দিয়ে augmentation: XY → ZY, অর্থাৎ XY → YZ. — augmentation
    3. ধাপ ১ আর ২ থেকে X → XY আর XY → YZ, তাই X → YZ. ∎ — transitivity

    খেয়াল করুন, ধাপ ১-এ augmentation-এ Z-এর জায়গায় X নিজেই বসানো হয়েছে; axiom যেকোনো attribute-সেটের জন্য খাটে।

  2. হিসাব: (ক) Domain {0, 1}, {a, b, c} আর {x, y}-এর ওপর কয়টি ternary relation? তাদের কয়টিতে ঠিক ৩টি tuple? (খ) R(A, B) ৫টি সারি আর S(B, C) ৪টি সারি হলে |R ⋈ S| সর্বোচ্চ কত, আর কখন তা হয়?

    (ক) Tuple 2 · 3 · 2 = 12টি, তাই relation 212 = 4096টি (উপপাদ্য ১.২)। ঠিক ৩টি tuple-এর relation মানে ১২টি থেকে ৩টি বাছা: C(12, 3) = 220টি।

    (খ) উপপাদ্য ৫.২(গ) অনুযায়ী সর্বোচ্চ 5 · 4 = 20. এটি তখনই হয় যখন R-এর প্রতিটি সারি S-এর প্রতিটি সারির সঙ্গে মেলে, অর্থাৎ R আর S-এর সব সারিতে B-এর মান একই, যেমন সবখানে B = 1 হলে। তখন join কার্যত Cartesian product. ন্যূনতম 0, যখন দুই টেবিলের B-মানের কোনো মিল নেই।

  3. আউটপুট অনুমান: কোড না চালিয়ে বলুন কী ছাপা হবে, আর দুই সংখ্যার পার্থক্য relational algebra-র কোন ধারণা দেখায়। তারপর যেকোনো Python-এ চালিয়ে মেলান।
    orders = [("O1", "C1"), ("O2", "C1"), ("O3", "C2")]
    print(len([c for _, c in orders]), len({c for _, c in orders}))
    print(sorted({c for _, c in orders}))
    3 2
    ['C1', 'C2']

    প্রথম সংখ্যা list-এর দৈর্ঘ্য, যেখানে C1 দুবার আছে: এটি SQL-এর SELECT cid, অর্থাৎ bag-এর হিসাব। দ্বিতীয়টি set-এর আকার, যেখানে পুনরাবৃত্তি বাদ: এটি relational algebra-র πcid. দুটি আলাদা কারণ cid এই instance-এ superkey নয় (উপপাদ্য ৪.২)। আউটপুট CPython-এ চালিয়ে মেলানো।

  4. প্রমাণের ভুল ধরুন: একজন লিখলেন: “দাবি: XY → Z খাটলে X → Z খাটে। প্রমাণ: decomposition নিয়ম বলে একটি FD-কে ভাঙা যায়। XY → Z-কে ভেঙে X → Z আর Y → Z পাই। ∎ উদাহরণ: {oid, pid} → qty থেকে oid → qty.” ভুল কোথায়? একটি counterexample দিন।
    • ভুল ধাপ: decomposition শুধু ডান দিক ভাঙে: X → YZ থেকে X → Y. এখানে ভাঙা হয়েছে বাঁ দিক, যা কোনো sound নিয়ম দেয় না। বাঁ দিক বড় করলে শর্ত দুর্বল হয় (augmentation বা reflexivity আর transitivity দিয়ে XY → Z আসে X → Z থেকে), ছোট করলে নয়।
    • Counterexample: OrderLine-এর দুটি সারি (O1, P1, qty 2) আর (O1, P2, qty 5). {oid, pid}-এর মান আলাদা, তাই {oid, pid} → qty খাটে; কিন্তু oid = O1 দুই সারিতে এক অথচ qty আলাদা, তাই oid → qty খাটে না।
    • কেন গুরুত্বপূর্ণ: এই ভুল থেকে কেউ ভাবতে পারেন oid একাই key, আর সেই ভুল ডিজাইনে একটি অর্ডারে একটির বেশি পণ্য রাখা যাবে না।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমFD-সেট F দেওয়া থাকলে একটি attribute-সেট K যে candidate key, তা দেখানোর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. M ← K দিয়ে শুরু করা
  2. F-এর যে FD-র বাঁ দিক M-এ আছে, তার ডান দিক M-এ যোগ করা, যতক্ষণ M বাড়ে
  3. K+ = U কি না দেখে K-কে superkey বলা
  4. K থেকে একটি একটি attribute বাদ দিয়ে প্রতিটির closure হিসাব করা
  5. কোনোটির closure U না হলে K-কে candidate key বলা

তিনটি domain-এর আকার 3, 4 আর 2. এদের ওপর কয়টি আলাদা ternary relation (A1 × A2 × A3-এর subset) সম্ভব?

  • 24
  • 29
  • 224
  • 24!

R(A, B)-এ ৩টি সারি আর S(C, D)-তে ৪টি সারি; দুই schema-য় কোনো সাধারণ attribute নেই। Natural join R ⋈ S-এ কয়টি সারি?

  • 0
  • 12
  • 7
  • 4

R(A, B, C, D, E)-এ F = {A → B, B → C, CD → E}. Attribute closure {A, D}+ কত?

  • {A, D}
  • {A, B, C}
  • {A, B, C, D}
  • {A, B, C, D, E}

R(A, B, C, D)-এ F = {AB → C, C → D, D → A}. নিচের কোনটি candidate key-গুলোর সঠিক সম্পূর্ণ তালিকা?

  • AB, BC, BD
  • শুধু AB
  • ABC, ABD
  • C, D

R-এর schema X, S-এর schema Y. সমীকরণ σc(R ⋈ S) = σc(R) ⋈ S কোন শর্তে সবসময় সত্য?

  • যখন |R| < |S|
  • যখন c শুধু S-এর attribute ব্যবহার করে
  • যখন c শুধু R-এর attribute ব্যবহার করে
  • শুধু যখন X ∩ Y = ∅

Functional dependency নিয়ে নিচের কোন অনুমান-নিয়মটি sound নয় (অর্থাৎ কোনো কোনো relation-এ মিথ্যা FD দিতে পারে)?

  • X → Y আর Y → Z থেকে X → Z
  • XY → Z থেকে X → Z
  • X → YZ থেকে X → Y
  • X → Y থেকে XZ → YZ

Orders(oid, cid)-এ ৬টি সারি, আর cid কলামে মান C1, C1, C2, C3, C3, C1. Relational algebra-র πcid(Orders)-এ কয়টি tuple?

  • 6
  • 1
  • 2
  • 3

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. টেবিল = n-ary relation: A1 × ⋯ × An-এর subset; সারির ক্রম নেই, পুনরাবৃত্তি নেই, আর সম্ভাব্য relation 2∏|Ai|-টি।
  2. Key: superkey মানে t ↦ t[K] one-to-one; candidate key হলো minimal superkey; superkey-র superset-ও superkey, তাই একটি একটি attribute বাদ দিয়ে minimality যাচাই।
  3. Algebra: σ সারি ছাঁকে, π কলাম রাখে আর পুনরাবৃত্তি ফেলে, ⋈ মিলে যাওয়া সারি জোড়া দেয়; সাধারণ কলাম না থাকলে ⋈ = ×, সব কলাম সাধারণ হলে ⋈ = ∩.
  4. সমীকরণ: c শুধু R-এর কলাম দেখলে σc(R ⋈ S) = σc(R) ⋈ S; কিন্তু π(R − T) = π(R) − π(T) সাধারণভাবে মিথ্যা।
  5. FD আর closure: Armstrong-এর reflexivity, augmentation, transitivity sound; X+ = U ⇔ X superkey; বাঁ দিক কখনো ভাঙা যায় না।

পরের পর্বে: পর্ব ৫৪ “Relation-এর closure আর Warshall-এর algorithm”: একটি relation-কে সবচেয়ে কম যোগ করে reflexive, symmetric বা transitive বানানো, আর “কোন গুদাম থেকে কোন জেলায় পৌঁছানো যায়” ধরনের প্রশ্নের দ্রুত উত্তর খোঁজা।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান