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

Relational algebra — select, project আর set অপারেশন

Relational Algebra — Selection σ, Projection π, Rename ρ, Set Operators, Cartesian Product, Expression Trees and Equivalence Rules

Taught in Bangla, this lesson introduces relational algebra as a formal query language: selection σ, projection π and why duplicates disappear, union, difference and intersection, Cartesian product and rename ρ, the six basic operators and derived ones mapped to SQL, expression trees, equivalence rules such as pushing selections down to cut cost, and exam-style translation of English questions into algebra, checked in live SQL.

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

  • কেন SQL-এর পাশাপাশি একটি আনুষ্ঠানিক (formal) কোয়েরি ভাষা দরকার, আর Codd কীভাবে এটি দাঁড় করালেন
  • σ, π, ρ, ∪, −, ∩ আর × — সংজ্ঞা, SQL রূপ, আর পাতাতেই চালানো ফল
  • σ-এর cascade আর ∩ = R − (R − S)-এর ছোট প্রমাণ
  • ইংরেজি প্রশ্নকে expression, expression tree আর ← দিয়ে ধাপে ধাপে লেখা
  • Equivalence rule, selection নিচে নামানোর লাভ, আর পরীক্ষার ধাঁচের প্রশ্নের algebra

১ · কেন একটি আনুষ্ঠানিক কোয়েরি ভাষা

মডিউল ৩ আর ৪-এ আপনি অনেক SQL লিখেছেন। SQL একটি declarative language: আপনি বলেন কী চাই, DBMS ঠিক করে কীভাবে আনবে। কিন্তু এর জন্য একটি শর্ত আছে। “কী চাই” কথাটির অর্থ একদম নিখুঁত হতে হবে। নইলে দুটি DBMS একই কোয়েরির দুই রকম উত্তর দেবে, আর optimizer কোয়েরিটি নিরাপদে বদলাতে পারবে না।

রান্নার রেসিপির কথা ভাবুন। “লবণ পরিমাণমতো” লেখা থাকলে দশজন রাঁধুনির রান্না দশ রকম হয়। ওষুধের কারখানায় এটি চলে না। সেখানে প্রতিটি উপাদান গ্রামে মাপা, প্রতিটি ধাপ নির্দিষ্ট। Relational algebra হলো ডেটাবেসের সেই মাপা রেসিপি। প্রতিটি অপারেটরের অর্থ গণিতের সংজ্ঞায় বাঁধা। তাই “এই দুটি রেসিপি কি একই খাবার বানায়?” — এ প্রশ্নের উত্তর প্রমাণ করে দেওয়া যায়।

এই নিখুঁততা তিনটি কাজে লাগে:

  • অর্থ ঠিক করা। SQL standard-এর অনেক নিয়ম আসলে algebra-র ভাষায় ভাবা: WHERE মানে selection, FROM-এর কমা মানে product, UNION মানে union.
  • Optimization-এর ভিত্তি। দুটি expression সবসময় একই ফল দেয় কি না, তা প্রমাণ করা যায়। তখন query optimizer নিশ্চিন্তে ধীর রূপটিকে দ্রুত রূপে বদলাতে পারে।
  • ভাষার ক্ষমতা মাপা। কোন প্রশ্ন একটি ভাষায় লেখা যায় আর কোনটি যায় না, তা স্পষ্ট বলা যায় (ভাবনার প্রশ্ন ৫)।

বাস্তবে আপনার লেখা SQL প্রথমে একটি parse tree হয়। তারপর DBMS তাকে relational algebra-র মতো অপারেটরের একটি গাছে বদলায়, যাকে বলে logical plan. সেই গাছটি নিয়মমাফিক ঘুরিয়ে-বদলে সবচেয়ে সস্তা query plan বেছে নেওয়া হয়। পর্ব ৪২-এ দেখবেন প্রতিটি অপারেটর কোন অ্যালগরিদমে চলে, আর পর্ব ৪৩-এ দেখবেন optimizer খরচ হিসাব করে কীভাবে বাছে। এই পর্ব তার ভাষাটি শেখায়।

ইতিহাস: ১৯৭০ সালে E. F. Codd তাঁর প্রবন্ধ “A Relational Model of Data for Large Shared Data Banks”-এ relational model প্রস্তাব করেন (পর্ব ৫)। ১৯৭২ সালের প্রবন্ধ “Relational Completeness of Data Base Sublanguages”-এ তিনি দুটি আনুষ্ঠানিক ভাষা পাশাপাশি রাখেন: একটি algebra (অপারেটর দিয়ে ধাপে ধাপে) আর একটি calculus (শর্ত দিয়ে বর্ণনা, পর্ব ৩৪)। তিনি দেখান, calculus-এ লেখা যায় এমন প্রতিটি প্রশ্ন algebra-তেও লেখা যায়। যে ভাষা অন্তত এতটা প্রকাশ করতে পারে, তাকে তিনি বললেন relationally complete. পরে SQL-সহ প্রায় সব relational ভাষাকে এই মাপকাঠিতে মাপা হয়েছে। Codd selection-কে বলতেন “restriction”; σ, π, ρ চিহ্নগুলো পরে পাঠ্যবইয়ে প্রচলিত হয়।

Relation মানে set — আর closure: ফলও একটি relation

পর্ব ৫-এ দেখেছেন, একটি relation হলো একই schema-র tuple-এর একটি set. এখান থেকে দুটি কথা আসে। এক, একই tuple দুবার থাকতে পারে না। দুই, tuple-এর কোনো ক্রম নেই; “প্রথম সারি” বলে কিছু নেই। SQL-এর টেবিল এই দুটি নিয়মই ঢিলে করেছে (duplicate চলে, ORDER BY দিয়ে ক্রম আসে)। Relational algebra কিন্তু খাঁটি set ধরে চলে। পার্থক্যটি অংশ ৩-এ বারবার দেখবেন।

মূল ধারণা

Closure property: relational algebra-র প্রতিটি অপারেটর এক বা দুটি relation নেয় আর একটি relation ফেরত দেয়। ফল নিজেও relation বলে, সেটি আবার আরেকটি অপারেটরের ইনপুট হতে পারে। যেমন পূর্ণসংখ্যার যোগফলও পূর্ণসংখ্যা, তাই (2 + 3) × 4 লেখা যায়। ঠিক তেমনি πname(σcgpa > 3.5(students)) লেখা যায়। এই গুণকে বলে closure.

Closure-এর কারণেই SQL-এ subquery, view আর CTE সম্ভব: একটি কোয়েরির ফল আরেকটির FROM-এ টেবিলের মতো বসে। অপারেটরগুলো দুই রকম। Unary অপারেটর একটি relation নেয়: σ, π, ρ. Binary অপারেটর দুটি নেয়: ∪, −, ∩, ×, আর পরের পর্বের ⋈ ও ÷.

এই পর্বের উদাহরণ চলবে db-uni বিশ্ববিদ্যালয় ডেটাবেসে। মূলত তিনটি relation: students (student_id, name, dept_id, batch, cgpa — ৮ জন; Fahim-এর cgpa আর Habib-এর dept_id NULL), departments (dept_id, name, building — CSE, EEE, BBA, English) আর enrollments (student_id, course_id, term, grade — ১৩টি ভর্তি)। মাঝে মাঝে instructors (instructor_id, name, dept_id, salary, mentor_id) আর courses (course_id, title, dept_id, credits, instructor_id) লাগবে। প্রতিটি লাইভ এডিটরের নিচে “নমুনা ডেটা” বাক্সে পুরো ডেটা দেখা যায়।

২ · Selection σ — কোন সারি থাকবে

Selection একটি relation থেকে সেই tuple-গুলো রাখে, যেগুলো একটি শর্ত পূরণ করে। শর্তটিকে বলে predicate. লেখা হয় গ্রিক অক্ষর σ (সিগমা) দিয়ে, শর্তটি নিচে ছোট করে:

σp(R) = { t | t ∈ R এবং p(t) সত্য }

Predicate-এ থাকে attribute, ধ্রুব মান, তুলনা (=, ≠, <, ≤, >, ≥) আর যুক্তির সংযোগ ∧ (AND), ∨ (OR), ¬ (NOT). যেমন σdept_id = 1 ∧ cgpa > 3.5(students) মানে “CSE-র যেসব শিক্ষার্থীর cgpa 3.5-এর বেশি”. ফলের schema হুবহু ইনপুটের মতো, শুধু সারি কম বা সমান: |σp(R)| ≤ |R|. SQL-এ এটিই WHERE.

students-এর ৮টি tupleলাল = এই ধাপে যাচাই

            
হিসাব
যাচাই হলো
ফলে এ পর্যন্ত
  1. ইনপুট relation. students-এ ৮টি tuple. Predicate দুই অংশের: dept_id = 1 আর cgpa > 3.5, মাঝে ∧. প্রতিটি tuple আলাদাভাবে, অন্যগুলোর দিকে না তাকিয়ে যাচাই হবে।
  2. প্রথম তিনটি। 2101-এর দুটি অংশই সত্য, তাই থাকে। 2102 CSE-র, কিন্তু cgpa 3.40 — একটি অংশ মিথ্যা হলেই ∧ মিথ্যা। 2103 দুই দিকেই মিথ্যা।
  3. পরের তিনটি। 2201 থাকে। 2202 বাদ। 2203-এর cgpa NULL, তাই দ্বিতীয় অংশ UNKNOWN (পর্ব ১৬); কিন্তু প্রথম অংশ FALSE, আর FALSE ∧ যেকোনো কিছু = FALSE.
  4. শেষ দুটি। 2301-এর cgpa 3.55, তাই থাকে। 2302-এর dept_id NULL, তুলনা UNKNOWN; দ্বিতীয় অংশ FALSE বলে পুরোটা FALSE. UNKNOWN হলেও সারিটি বাদ যেত — σ শুধু TRUE রাখে।
  5. ফল। ৩টি tuple, schema অপরিবর্তিত। লক্ষ করুন, প্রতিটি সারির সিদ্ধান্ত শুধু সেই সারির ওপর নির্ভর করে। তাই σ এক পাসেই চলে, আর বড় টেবিলে index থাকলে সব সারি পড়ারও দরকার পড়ে না (পর্ব ৪০)।

খাঁটি relational algebra-য় NULL নেই। Codd পরে NULL আর তিন-মানের যুক্তি যোগ করেন, আর SQL সেটি নিয়েছে। SQL-এর σ তাই “predicate TRUE হলে রাখো” — FALSE আর UNKNOWN দুটিই বাদ। এখন দুটি গুরুত্বপূর্ণ নিয়ম।

নিয়ম ও প্রমাণ

Cascade: σp ∧ q(R) = σp(σq(R)). Commutativity: σp(σq(R)) = σq(σp(R)).

প্রমাণ। যেকোনো tuple t নিন। t ∈ σp(σq(R)) ⇔ t ∈ σq(R) এবং p(t) ⇔ (t ∈ R এবং q(t)) এবং p(t) ⇔ t ∈ R এবং (p(t) ∧ q(t)) ⇔ t ∈ σp ∧ q(R). মাঝের ধাপে শুধু “এবং”-এর associativity আর commutativity লেগেছে। দুই পাশে ঠিক একই tuple আছে, তাই relation দুটি সমান। Commutativity আসে সরাসরি: σp(σq(R)) = σp ∧ q(R) = σq ∧ p(R) = σq(σp(R)). ∎

তিন-মানের যুক্তিতেও এটি টেকে, কারণ p ∧ q TRUE হয় ঠিক তখনই, যখন p আর q দুটিই TRUE.

এই নিয়মের ব্যবহারিক মানে: একটি বড় শর্তকে টুকরো করা যায়, আর টুকরোগুলো যেকোনো ক্রমে, এমনকি গাছের ভিন্ন ভিন্ন জায়গায় বসানো যায়। অংশ ৮-এ ঠিক এটিই কাজে লাগবে।

∨-এর জন্যও একটি সম্পর্ক আছে: σp ∨ q(R) = σp(R) ∪ σq(R). প্রমাণ একই ধাঁচের: t বাঁ পাশে ⇔ t ∈ R এবং (p(t) অথবা q(t)) ⇔ (t ∈ R এবং p(t)) অথবা (t ∈ R এবং q(t)) ⇔ t ডান পাশে। ∎ একইভাবে σ¬p(R) = R − σp(R), তবে শুধু খাঁটি দুই-মানের যুক্তিতে। SQL-এ p UNKNOWN হলে সারিটি দুই দিকেই বাদ পড়ে, তাই ডান পাশে NULL-ওয়ালা সারি বাড়তি থেকে যায় — নিচের তৃতীয় কোয়েরি ঠিক এটিই দেখায়। নিচের এডিটরে প্রথম দুটি কোয়েরি এই দুই রূপ, তৃতীয়টি ∨ আর ¬-এর উদাহরণ।

-- σ dept_id = 1 ∧ cgpa > 3.5 (students)
SELECT * FROM students WHERE dept_id = 1 AND cgpa > 3.5;

-- cascade: σ dept_id = 1 ( σ cgpa > 3.5 (students) )
SELECT * FROM (SELECT * FROM students WHERE cgpa > 3.5)
WHERE dept_id = 1;

-- σ ¬(dept_id = 1 ∨ dept_id = 2) (students)
SELECT student_id, name, dept_id FROM students
WHERE NOT (dept_id = 1 OR dept_id = 2);

প্রথম দুটি ফল হুবহু এক: 2101 | Arif Hossain | 1 | 2021 | 3.75, 2201 | Dilruba Khatun | 1 | 2022 | 3.9, 2301 | Gulshan Ara | 1 | 2023 | 3.55 (রানার 3.90-কে 3.9 দেখায়)। Cascade-এর প্রমাণ এখানে চোখের সামনে। তৃতীয় ফলে একটিমাত্র সারি: 2202 | Emon Mia | 3. Habib (dept_id NULL) কেন নেই? NULL = 1 আর NULL = 2 দুটিই UNKNOWN, তাদের OR-ও UNKNOWN, আর NOT UNKNOWN = UNKNOWN. সারিটি TRUE হয়নি, তাই বাদ। খাঁটি algebra-য় ¬(p ∨ q) সব “বাকি” সারি দিত; SQL-এ NULL থাকলে “বাকি” কথাটির অর্থ সাবধানে ভাবতে হয়।

৩ · Projection π — কোন কলাম থাকবে (আর duplicate কেন যায়)

Projection সারি নয়, কলাম বাছে। π (পাই)-এর নিচে attribute-এর একটি তালিকা L লেখা হয়: πname, cgpa(students) মানে প্রতিটি tuple থেকে শুধু name আর cgpa রাখা।

πL(R) = { t[L] | t ∈ R }

এখানে t[L] মানে tuple t-এর শুধু L-এর অংশ। সংজ্ঞাটি একটি set, আর set-এ একই জিনিস দুবার থাকে না। তাই কলাম কেটে ফেলার পর দুটি tuple একই হয়ে গেলে ফলে সেটি একবারই আসে। πbatch(students)-এ ৮ জন শিক্ষার্থী আছেন, কিন্তু batch মাত্র তিনটি: 2021, 2022, 2023. ফল তাই ৩টি tuple. সাধারণভাবে |πL(R)| ≤ |R|, আর সমান হওয়া নিশ্চিত যদি L-এর মধ্যে একটি superkey থাকে — তখন দুটি tuple কখনো L-এ মিলতে পারে না।

SQL এখানে algebra থেকে সরে গেছে। SQL-এর টেবিল একটি multiset (bag), আর সাধারণ SELECT duplicate রাখে। এই আচরণকে বলে bag semantics. Algebra-র π-এর হুবহু SQL রূপ তাই SELECT DISTINCT, শুধু SELECT নয়।

-- SQL-এর bag: duplicate থাকে
SELECT batch FROM students;

-- algebra-র π batch (students): set, duplicate নেই
SELECT DISTINCT batch FROM students;

-- π dept_id, batch (students)
SELECT DISTINCT dept_id, batch FROM students;

প্রথম ফলে ৮টি সারি: 2021 তিনবার, 2022 তিনবার, 2023 দুবার। দ্বিতীয় ফলে ৩টি: 2021, 2022, 2023 — এটিই πbatch(students). তৃতীয় ফলে ৭টি সারি: 1 | 2021, 2 | 2021, 1 | 2022, 3 | 2022, 2 | 2022, 1 | 2023, NULL | 2023. ৮ থেকে একটি কমেছে, কারণ Arif আর Bristy দুজনেই (1, 2021). Habib-এর NULL একটি আলাদা মান হিসেবে টিকে আছে; DISTINCT দুটি NULL-কে এক ধরে (পর্ব ২২)।

কেন SQL duplicate রাখে? প্রথম কারণ খরচ: duplicate মুছতে sort বা hash লাগে। দ্বিতীয় কারণ অর্থ: গড় cgpa বের করতে একই cgpa-র দুজনকেই লাগে। তবে মনে রাখুন, set আর bag-এ কিছু নিয়ম আলাদা। যেমন set-এ R ∪ R = R, কিন্তু bag-এ UNION ALL দিলে প্রতিটি সারি দ্বিগুণ হয়। Optimizer তাই প্রতিটি rule bag-এ খাটে কি না দেখে নেয়।

Projection-এর দুটি নিয়ম। Cascade: L1 ⊆ L2 হলে πL1(πL2(R)) = πL1(R) — শুধু শেষ projection-টিই গুরুত্বপূর্ণ। σ-এর সাথে অদলবদল: p-তে ব্যবহৃত সব attribute L-এ থাকলে πL(σp(R)) = σp(πL(R)). শর্তটি জরুরি: πname(σcgpa > 3.5(students)) ঠিক আছে, কিন্তু σcgpa > 3.5(πname(students)) অর্থহীন, কারণ π-এর পর cgpa কলামটিই নেই।

৪ · Set অপারেটর — ∪, − আর ∩

Relation যেহেতু set, তাই set-এর তিনটি পুরোনো অপারেশন সরাসরি চলে। পর্ব ২২-এ এদের SQL রূপ UNION, EXCEPT আর INTERSECT দেখেছেন। এখানে সংজ্ঞা:

  • R ∪ S = { t | t ∈ R অথবা t ∈ S } — union
  • R − S = { t | t ∈ R এবং t ∉ S } — set difference
  • R ∩ S = { t | t ∈ R এবং t ∈ S } — intersection

একটি শর্ত ছাড়া এগুলো অর্থহীন: R আর S union-compatible হতে হবে। অর্থাৎ দুটির attribute-এর সংখ্যা (degree) সমান, আর i-তম attribute-এর domain দুই পাশে এক। πstudent_id(students) ∪ πcourse_id(courses) union-compatible নয়: দুই পাশে একটি করে কলাম, কিন্তু একটি শিক্ষার্থীর id, আরেকটি কোর্সের কোড। ফলের attribute-এর নাম প্রথা অনুযায়ী বাঁ পাশ থেকে আসে।

দুই পাশের attribute-এর অর্থ এক কিন্তু নাম আলাদা হলে ρ দিয়ে নাম মিলিয়ে নেওয়াই পরিষ্কার রীতি। ধরুন বিশ্ববিদ্যালয়ের সমাবর্তনের আমন্ত্রণপত্রের জন্য সব শিক্ষার্থী আর শিক্ষকের id একটি তালিকায় চাই:

ρP(person_id)(πstudent_id(students)) ∪ ρP(person_id)(πinstructor_id(instructors))

দুই পাশেই এখন একটি attribute, নাম person_id, domain পূর্ণসংখ্যার id. তবে লক্ষ করুন, union-compatibility শুধু গঠনের নিয়ম; একটি শিক্ষার্থীর id আর একজন শিক্ষকের id একই সংখ্যা হলে ∪ তাদের এক করে ফেলবে। অর্থ ঠিক আছে কি না, তা নিয়ম দেখে না — লেখককে দেখতে হয়।

-- π student_id (σ course_id='CSE301' (enrollments))
--   ∪ π student_id (σ course_id='CSE101' (enrollments))
SELECT student_id FROM enrollments WHERE course_id = 'CSE301'
UNION
SELECT student_id FROM enrollments WHERE course_id = 'CSE101';

-- bag রূপ: duplicate থাকে
SELECT student_id FROM enrollments WHERE course_id = 'CSE301'
UNION ALL
SELECT student_id FROM enrollments WHERE course_id = 'CSE101';

UNION-এর ফল ৪টি tuple: 2101, 2102, 2201, 2301. CSE301 নিয়েছেন 2101, 2102, 2201; CSE101 নিয়েছেন 2201 আর 2301. Dilruba (2201) দুই পাশেই আছেন, কিন্তু set-এ একবার। UNION ALL দিল ৫টি সারি, 2201 দুবার — এটি bag-এর union, algebra-র ∪ নয়।

∪ আর ∩ commutative আর associative: R ∪ S = S ∪ R, (R ∪ S) ∪ T = R ∪ (S ∪ T). কিন্তু − দুটির কোনোটিই নয়। R − S আর S − R সাধারণত আলাদা, আর (R − S) − T ≠ R − (S − T). “কোন শিক্ষার্থী কোনো কোর্সেই ভর্তি হননি?” — এর উত্তর difference দিয়ে:

πstudent_id(students) − πstudent_id(enrollments)

∩ আসলে − দিয়েই লেখা যায়

উপপাদ্য

R ∩ S = R − (R − S), যেকোনো union-compatible R আর S-এর জন্য।

প্রমাণ। যেকোনো t নিন। t ∈ R − (R − S) ⇔ t ∈ R এবং ¬(t ∈ R − S) ⇔ t ∈ R এবং ¬(t ∈ R এবং t ∉ S) ⇔ t ∈ R এবং (t ∉ R অথবা t ∈ S) [De Morgan]. এখন “t ∈ R এবং t ∉ R” কখনো সত্য নয়, তাই বিতরণ (distribution) করলে থাকে: t ∈ R এবং t ∈ S ⇔ t ∈ R ∩ S. ∎

R = CSE101-এর student_id, S = CSE201-এর student_idলাল = এই ধাপের ফল

            
অবস্থা
এই ধাপে
ফল
  1. দুটি relation. R-এ CSE101-এর দুজন (2201, 2301), S-এ CSE201-এর তিনজন (2101, 2102, 2201). দুটিই এক কলামের, একই domain — union-compatible.
  2. R − S. R-এর যে tuple S-এ নেই: শুধু 2301. অর্থাৎ যিনি CSE101 নিয়েছেন কিন্তু CSE201 নেননি।
  3. R − (R − S). R থেকে সেই “শুধু R-এ” অংশটি বাদ দিলে থাকে 2201. যুক্তিটি সহজ: R-এর প্রতিটি tuple হয় S-এ আছে, নয়তো নেই; যেগুলো নেই সেগুলো সরালে বাকিগুলো S-এ আছে।
  4. মিলিয়ে দেখা। সরাসরি ∩-ও দিল {2201}. তাই ∩ একটি derived অপারেটর: মূল সেটে না রাখলেও কোনো ক্ষমতা হারায় না। নিচের এডিটরে একই হিসাব SQL-এ।
-- π student_id (students) − π student_id (enrollments)
SELECT student_id FROM students
EXCEPT
SELECT student_id FROM enrollments;

-- R ∩ S সরাসরি
SELECT student_id FROM enrollments WHERE course_id = 'CSE101'
INTERSECT
SELECT student_id FROM enrollments WHERE course_id = 'CSE201';

-- R − (R − S): ভেতরের অংশ subquery-তে, কারণ SQLite বাঁ থেকে ডানে চলে
SELECT student_id FROM enrollments WHERE course_id = 'CSE101'
EXCEPT
SELECT * FROM (SELECT student_id FROM enrollments WHERE course_id = 'CSE101'
               EXCEPT
               SELECT student_id FROM enrollments WHERE course_id = 'CSE201');

প্রথম ফল একটি tuple: 2302. Habib Ullah কোনো কোর্সে ভর্তি হননি। দ্বিতীয় আর তৃতীয় দুটির ফলই 2201 — উপপাদ্যটি ডেটায় মিলল। তৃতীয় কোয়েরিতে বন্ধনী ছাড়া লিখলে SQLite (R − R) − S হিসাব করত, যা সবসময় খালি; − associative নয়, তাই বন্ধনীর জায়গা অর্থ বদলায়। (Bag-এও মজার ব্যাপার: m − max(m − n, 0) = min(m, n), তাই EXCEPT ALL দিয়ে INTERSECT ALL-ও একইভাবে লেখা যায়।)

৫ · Cartesian product × আর rename ρ

এ পর্যন্ত প্রতিটি অপারেটর একটি relation-এর ভেতরে কাজ করেছে, অথবা একই আকারের দুটি relation মিলিয়েছে। কিন্তু “Arif কোন বিভাগের, সেই বিভাগ কোন ভবনে?” — এমন প্রশ্নে দুটি ভিন্ন relation-এর তথ্য একই tuple-এ আনতে হয়। এর মৌলিক অপারেটর Cartesian product:

R × S = { r ⧺ s | r ∈ R, s ∈ S }

অর্থাৎ R-এর প্রতিটি tuple S-এর প্রতিটি tuple-এর সাথে জোড়া লাগে (r ⧺ s মানে দুটি tuple পাশাপাশি জুড়ে একটি লম্বা tuple). R-এর degree m আর S-এর n হলে ফলের degree m + n. সারির সংখ্যা ঠিক |R| × |S|. students (৮) × departments (৪) = ৩২টি tuple, যার বেশিরভাগই অর্থহীন জোড়া (Arif-এর পাশে EEE, BBA, English…)। তাই × প্রায় সবসময় একটি σ-এর সাথে আসে, যা ঠিক জোড়াগুলো রাখে: σstudents.dept_id = departments.dept_id(students × departments). এই জোড়াটিকেই পরের পর্বে join ⋈ নাম দেওয়া হবে। SQL-এ × হলো CROSS JOIN, বা FROM-এ কমা দিয়ে দুটি টেবিল।

-- π name (σ batch=2023 (students)) × π name (σ dept_id=1 (instructors))
SELECT s.name AS student, i.name AS instructor
FROM (SELECT name FROM students WHERE batch = 2023) AS s
CROSS JOIN (SELECT name FROM instructors WHERE dept_id = 1) AS i;

-- |students × departments| = 8 × 4
SELECT COUNT(*) AS rows_in_product FROM students CROSS JOIN departments;

প্রথম ফলে ২ × ৩ = ৬টি সারি: Gulshan Ara আর Habib Ullah (2023 ব্যাচ) প্রত্যেকে Dr. Anisur Rahman, Shahana Parvin আর Tasnim Chowdhury (CSE-র শিক্ষক)-এর সাথে জোড়া — যেমন পরীক্ষার viva বোর্ডের সব সম্ভাব্য (শিক্ষার্থী, পরীক্ষক) জোড়ার তালিকা। দ্বিতীয় ফল 32. টেবিল দুটি ১০ হাজার আর ১০ হাজার সারির হলে product হতো ১০ কোটি সারি। × তাই সবচেয়ে বিপজ্জনক অপারেটর; অংশ ৮-এ এর খরচ কমানোর কৌশল দেখবেন।

Rename ρ — একই relation দুবার লাগলে

উপরের এডিটরে দুই পাশেই name নামে কলাম ছিল, তাই AS দিয়ে আলাদা নাম দিতে হয়েছে। Algebra-তে একই সমস্যা আছে: × করলে দুই পাশের একই নামের attribute আলাদা করতে হয়। কখনো আবার একই relation-কে নিজের সাথেই জোড়া লাগাতে হয়। এর জন্য rename অপারেটর ρ (রো):

  • ρS(R) — relation R-কে S নামে ডাকা। তার attribute হয় S.A, S.B, …
  • ρS(B1, …, Bn)(R) — relation-এর সাথে attribute-এর নামও বদলানো।

ρ ফলের tuple বদলায় না, শুধু নাম বদলায়; তবু এটি ছাড়া কিছু প্রশ্ন লেখাই যায় না। একটি ক্লাসিক উদাহরণ: aggregate ফাংশন ছাড়া সর্বোচ্চ cgpa বের করা। কৌশল: “যার চেয়ে বড় কোনো cgpa আছে” এমন সব cgpa বের করুন, তারপর সব cgpa থেকে সেগুলো বাদ দিন। যা থাকে, তার চেয়ে বড় কেউ নেই।

πcgpa(students) − πs.cgpa(σs.cgpa < t.cgpa(ρs(students) × ρt(students)))
-- সর্বোচ্চ cgpa, MAX ছাড়া: ρ দিয়ে students-কে দুবার
SELECT cgpa FROM students WHERE cgpa IS NOT NULL
EXCEPT
SELECT s.cgpa FROM students AS s CROSS JOIN students AS t
WHERE s.cgpa < t.cgpa;

-- NULL-এর শর্তটি বাদ দিলে কী হয়?
SELECT cgpa FROM students
EXCEPT
SELECT s.cgpa FROM students AS s CROSS JOIN students AS t
WHERE s.cgpa < t.cgpa;

প্রথম ফল একটি tuple: 3.9, Dilruba-র cgpa. ডান পাশে আছে সেই সব cgpa, যার চেয়ে বড় অন্তত একটি আছে — 3.90 ছাড়া বাকি সব। দ্বিতীয় ফলে দুটি সারি: NULL আর 3.9. Fahim-এর NULL cgpa-র জন্য s.cgpa < t.cgpa কখনো TRUE নয়, তাই ডান পাশে NULL আসে না, আর EXCEPT তাকে রেখে দেয়। খাঁটি algebra-য় NULL নেই বলে সেখানে এ সমস্যা ওঠে না; SQL-এ অনুবাদ করার সময় NULL-এর জন্য আলাদা শর্ত ভাবতে হয়। SQL-এর AS s আর AS t ঠিক ρs আর ρt.

৬ · মূল ছয়টি অপারেটর, derived অপারেটর আর SQL-এর সাথে মিল

পাঠ্যবইয়ে relational algebra-র মূল (primitive) অপারেটর ধরা হয় ছয়টি: {σ, π, ∪, −, ×, ρ}. “মূল” মানে দুটি কথা। এক, বাকি সব অপারেটর এদের দিয়ে লেখা যায়। দুই, এদের কোনোটিকে বাকি পাঁচটি দিয়ে লেখা যায় না। যেমন − কেন অপরিহার্য? বাকি পাঁচটি monotone: ইনপুটে tuple বাড়ালে ফলের tuple কখনো কমে না। কিন্তু R − S-এ S বড় করলে ফল ছোট হয়। monotone অপারেটরের যেকোনো সমন্বয়ও monotone (আরোহ যুক্তিতে), তাই তারা কখনো − তৈরি করতে পারবে না। একইভাবে × ছাড়া ফলের degree কখনো বাড়ানো যায় না, তাই × অপরিহার্য।

বাকিগুলো derived অপারেটর: এরা নতুন ক্ষমতা আনে না, শুধু লেখা ছোট করে, আর DBMS এদের জন্য দ্রুত অ্যালগরিদম রাখে।

Derived অপারেটরমূল অপারেটর দিয়েকোথায়
R ∩ SR − (R − S)এই পর্ব
R ⋈θ S (theta join)σθ(R × S)পর্ব ৩৩
R ⋈ S (natural join)π(σসমান নামের attribute সমান(R × S))পর্ব ৩৩
R ÷ S (division)−, π আর × দিয়েপর্ব ৩৩

ফলের আকার — degree আর cardinality

পরীক্ষায় প্রায়ই জিজ্ঞেস করা হয়: “R-এর degree m আর |R| = r, S-এর degree n আর |S| = s হলে ফলের degree আর সারির সংখ্যা কত?” প্রতিটি অপারেটরের সংজ্ঞা থেকেই উত্তর আসে। সারির সংখ্যার ক্ষেত্রে বেশিরভাগ অপারেটরের শুধু একটি সীমা বলা যায়, নির্দিষ্ট সংখ্যা নয় — নির্দিষ্ট সংখ্যা ডেটার ওপর নির্ভর করে।

Expressionফলের degreeফলের সারি (cardinality)
σp(R)m০ থেকে r
πL(R)|L|১ থেকে r (R খালি না হলে); L-এ superkey থাকলে ঠিক r
ρ(R)mঠিক r
R ∪ Sm (= n)max(r, s) থেকে r + s
R − Sm০ থেকে r
R ∩ Sm০ থেকে min(r, s)
R × Sm + nঠিক r · s

যেমন ∪-এর সর্বনিম্ন max(r, s), কারণ বড়টির সব tuple ফলে থাকবেই; সর্বোচ্চ r + s, যখন দুই পাশে কোনো সাধারণ tuple নেই। এই সীমাগুলো শুধু পরীক্ষার জন্য নয় — optimizer মাঝের relation-এর আকার অনুমান করতে ঠিক এই যুক্তি দিয়ে শুরু করে।

Relational algebra ↔ SQL

AlgebraSQLখেয়াল রাখুন
σp(R)SELECT * FROM R WHERE pSQL-এ UNKNOWN সারিও বাদ
πL(R)SELECT DISTINCT L FROM RDISTINCT ছাড়া bag
ρS(R), ρS(B…)(R)FROM R AS S, col AS Bself join-এ অপরিহার্য
R ∪ SUNIONUNION ALL = bag union
R − SEXCEPT (Oracle: MINUS)NOT IN-এ NULL-এর ফাঁদ
R ∩ SINTERSECTderived
R × SCROSS JOIN বা FROM R, S|R| × |S| সারি
σθ(R × S)R JOIN S ON θপর্ব ৩৩
T ← EWITH T AS (E) বা viewঅংশ ৭

সবচেয়ে কাজের সূত্রটি হলো সাধারণ SELECT-এর অনুবাদ। SELECT DISTINCT A1, …, Ak FROM R1, …, Rm WHERE p মানে:

πA1, …, Ak(σp(R1 × … × Rm))

এটি পর্ব ১৫-এর logical processing order-এর গণিত রূপ: আগে FROM (×), তারপর WHERE (σ), শেষে SELECT (π)। ORDER BY, LIMIT, GROUP BY আর aggregate ফাংশন মূল algebra-য় নেই। ক্রম relation-এর ধারণার বাইরে, আর গোনা-যোগের জন্য পাঠ্যবইয়ে একটি বাড়তি অপারেটর γ (grouping) নিয়ে extended relational algebra বানানো হয়।

৭ · Expression tree — প্রশ্ন থেকে গাছ, গাছ থেকে উত্তর

Closure-এর কারণে প্রতিটি algebra expression একটি গাছ: পাতায় (leaf) মূল relation, প্রতিটি ভেতরের node-এ একটি অপারেটর, আর প্রতিটি node-এর ফল তার ওপরের node-এর ইনপুট। একে বলে expression tree (বা query tree). গাছটি নিচ থেকে ওপরে হিসাব হয়। প্রতিটি node-এর ফলকে বলে intermediate result. প্রশ্নটি নিন: “CSE বিভাগের যেসব শিক্ষার্থীর cgpa 3.5-এর বেশি, তাঁদের নাম।”

Expression tree আর মাঝের relationলাল = এই ধাপের node

            
মাঝের relation
এই ধাপের node
সারি
কলাম
  1. প্রশ্ন ভাঙা। তিনটি প্রশ্ন করুন: কী দেখাব (π-এর তালিকা), কোন শর্ত (σ), তথ্য কোন কোন relation-এ (পাতা)। বিভাগের নাম 'CSE' লেখা আছে departments-এ, cgpa আছে students-এ — তাই দুটি relation লাগবে।
  2. গাছ বানানো। পাতায় ρs(students) আর ρd(departments); ρ ছোট নাম দেয়, আর দুই পাশের name/dept_id আলাদা করে। ওপরে ×, তার ওপরে জোড়ার শর্ত, তারপর প্রশ্নের শর্ত, একেবারে ওপরে π. হিসাব শুরু হয় পাতা থেকে: ৮ আর ৪টি tuple.
  3. × node. প্রতিটি শিক্ষার্থী প্রতিটি বিভাগের সাথে: ৮ × ৪ = ৩২টি tuple, ৫ + ৩ = ৮টি কলাম। বেশিরভাগ জোড়া অর্থহীন, যেমন Arif-এর পাশে EEE.
  4. জোড়ার σ. s.dept_id = d.dept_id শুধু ঠিক জোড়া রাখে: ৭টি tuple. Habib-এর dept_id NULL, তাই কোনো বিভাগের সাথে তুলনা TRUE হয়নি — তিনি এখানেই বাদ পড়লেন।
  5. প্রশ্নের σ. বিভাগ CSE আর cgpa > 3.5: ৭ থেকে ৩টি — Arif, Dilruba, Gulshan. Bristy CSE-র, কিন্তু cgpa 3.40.
  6. π আর এক লাইনের রূপ। শুধু s.name রাখলে ৩টি নাম। কোনো duplicate হয়নি, তবে হলে π তা মুছত। গাছটি বন্ধনী দিয়ে এক লাইনে লেখা যায় — একই জিনিসের দুটি চেহারা।
-- গাছটি হুবহু: π( σ( σ( ρs(students) × ρd(departments) ) ) )
SELECT DISTINCT s.name
FROM students AS s CROSS JOIN departments AS d
WHERE s.dept_id = d.dept_id
  AND d.name = 'CSE'
  AND s.cgpa > 3.5;

ফল ৩টি সারি: Arif Hossain, Dilruba Khatun, Gulshan Ara — ঠিক গাছের শেষ ধাপের মতো। WHERE-এর তিনটি শর্ত AND দিয়ে জোড়া, যা cascade নিয়মে গাছের দুটি σ node-এর সমান। FROM-এ CROSS JOIN = ×, AS = ρ, DISTINCT = π-এর duplicate মোছা। (বাস্তবে আপনি JOIN … ON লিখবেন; সেটি পর্ব ৩৩-এর ⋈, অর্থ একই।)

গাছ নিচ থেকে ওপরে হিসাব করার ক্রমটি আসলে tree-র post-order traversal: আগে দুই সন্তান, তারপর নিজে। তবে একটি কথা মনে রাখুন। ডায়াগ্রামে প্রতিটি মাঝের relation পুরোটা লিখে দেখানো হয়েছে, কিন্তু বাস্তব DBMS সাধারণত তা করে না। × বা join থেকে একটি tuple বেরোলেই সেটি সাথে সাথে ওপরের σ-এ যায়, তারপর π-এ, আর ফলের প্রথম সারি তৈরি হয়ে যায় — পুরো ৩২টি সারি কোথাও জমা থাকে না। একে বলে pipelining; পর্ব ৪২-এ দেখবেন কোন অপারেটর এভাবে চলতে পারে (σ, π) আর কোনটিকে আগে সব ইনপুট পড়তে হয় (sort, duplicate মোছা)। Algebra বলে কী হিসাব হবে; কীভাবে, তা ইঞ্জিনের সিদ্ধান্ত।

গাছ আঁকার তিনটি প্রচলিত ভুল। এক, π খুব নিচে বসানো: πname(students) × departments লিখলে পরে dept_id আর cgpa-র শর্ত লেখার উপায় থাকে না, কারণ কলাম দুটি আগেই কাটা পড়েছে। দুই, × দিয়ে জোড়ার পর জোড়ার শর্ত ভুলে যাওয়া — তখন প্রতিটি শিক্ষার্থী প্রতিটি বিভাগের সাথে থেকে যায়, আর CSE-র শর্তটি সবাইকে “CSE-র” বানিয়ে দেয়। তিন, একই নামের attribute (দুটি name, দুটি dept_id) কোন relation-এর, তা না লেখা। প্রতিটি attribute-এর আগে relation বা ρ-এর নাম লিখুন।

Assignment ← — লম্বা expression-কে ধাপে ভাঙা

এক লাইনের expression বড় হলে পড়া কঠিন। তাই algebra-তে একটি assignment চিহ্ন আছে: T ← E মানে “E-এর ফলকে T নামে একটি অস্থায়ী relation হিসেবে রাখো”। এটি নতুন কোনো ক্ষমতা আনে না, শুধু নাম দিয়ে ধাপ সাজায় — ঠিক প্রোগ্রামিংয়ের অস্থায়ী variable-এর মতো। একই প্রশ্ন এবার ধাপে ধাপে, selection-গুলো আগে বসিয়ে:

CSE    ← σname = 'CSE'(departments)
Good   ← σcgpa > 3.5(students)
Pairs  ← ρP(sname, sdept, ddept)(πGood.name, Good.dept_id, CSE.dept_id(Good × CSE))
Result ← πsname(σsdept = ddept(Pairs))

SQL-এ এর সবচেয়ে কাছের রূপ CTE (পর্ব ২৫): প্রতিটি WITH name AS (…) একটি ←.

-- প্রতিটি WITH = একটি ←
WITH cse   AS (SELECT * FROM departments WHERE name = 'CSE'),
     good  AS (SELECT * FROM students WHERE cgpa > 3.5),
     pairs AS (SELECT good.name AS sname, good.dept_id AS sdept,
                      cse.dept_id AS ddept
               FROM good CROSS JOIN cse)
SELECT DISTINCT sname FROM pairs WHERE sdept = ddept;

ফল আবারও Arif Hossain, Dilruba Khatun, Gulshan Ara. কিন্তু এবার × হয়েছে ৩টি “ভালো” শিক্ষার্থী আর ১টি বিভাগের মধ্যে — মাত্র ৩টি জোড়া, আগের ৩২টির বদলে। AS sname, AS sdept হলো attribute-এর ρ. একই উত্তর, অনেক ছোট মাঝের relation: পরের অংশ ঠিক এটিই ব্যাখ্যা করে।

৮ · Equivalence rule — selection নিচে নামালে খরচ কমে

দুটি expression equivalent (E1 ≡ E2) যদি ডেটাবেসের যেকোনো অবস্থায় দুটি একই relation দেয় — শুধু আজকের ডেটায় নয়। Optimizer এমন নিয়মের একটি তালিকা রাখে আর গাছ বদলাতে সেগুলো প্রয়োগ করে। প্রধান কয়েকটি:

#নিয়মশর্ত
১σp ∧ q(E) ≡ σp(σq(E))সবসময় (অংশ ২)
২σp(σq(E)) ≡ σq(σp(E))সবসময়
৩πL1(πL2(E)) ≡ πL1(E)L1 ⊆ L2
৪σp(E1 × E2) ≡ σp(E1) × E2p-র সব attribute E1-এর
৫E1 × E2 ≡ E2 × E1; (E1 × E2) × E3 ≡ E1 × (E2 × E3)কলামের ক্রম বাদে
৬σp(E1 ∪ E2) ≡ σp(E1) ∪ σp(E2); একই − আর ∩-এসবসময়
৭πL(E1 ∪ E2) ≡ πL(E1) ∪ πL(E2)∪-এ খাটে, − -এ নয়

নিয়ম ৪-এর প্রমাণ। E1 × E2-এর প্রতিটি tuple হলো r ⧺ s, যেখানে r ∈ E1, s ∈ E2. p শুধু E1-এর attribute দেখে, তাই p(r ⧺ s) = p(r). তাহলে r ⧺ s ∈ σp(E1 × E2) ⇔ r ∈ E1, s ∈ E2, p(r) ⇔ r ∈ σp(E1), s ∈ E2 ⇔ r ⧺ s ∈ σp(E1) × E2. ∎ নিয়ম ৭ − -এ কেন খাটে না: R(A, B) = {(1, 2)}, S(A, B) = {(1, 3)} নিন। πA(R − S) = πA({(1, 2)}) = {1}, কিন্তু πA(R) − πA(S) = {1} − {1} = ∅. একটি counterexample-ই যথেষ্ট।

নিয়ম ১ আর ৪ মিলিয়ে optimizer-এর সবচেয়ে পুরোনো কৌশল: selection-কে যত নিচে সম্ভব নামানো (predicate pushdown). কারণ × আর join-এর খরচ ইনপুটের আকারের গুণফলের ওপর নির্ভর করে। ইনপুট ছোট করলে পুরো গাছ সস্তা হয়।

প্রতিটি node-এর পাশে [সারির সংখ্যা]লাল = এই ধাপে যা বদলাল

            
× এর আকার
বাঁ ইনপুট
ডান ইনপুট
× এর ফল
  1. সরল গাছ। অংশ ৭-এর গাছে সব σ × এর ওপরে। তাই × পুরো ৮ × ৪ = ৩২টি tuple বানায়, যার ২৫টি পরে ফেলে দেওয়া হয়। মাঝের relation-গুলোতে মোট ৪২টি সারি তৈরি হলো।
  2. শর্ত টুকরো করা। নিয়ম ১ দিয়ে বড় শর্তটি তিনটি আলাদা σ. দেখুন প্রতিটি শর্ত কোন relation-এর attribute ব্যবহার করে: cgpa শুধু students-এর, name='CSE' শুধু departments-এর, আর dept_id-এর সমতা দুটিরই।
  3. প্রথম σ নামানো। নিয়ম ৪: যে শর্ত শুধু students দেখে, তা × এর আগেই students-এ বসানো যায়। students ৮ থেকে ৩-এ নামল; এখনই × করলে ৩ × ৪ = ১২.
  4. দ্বিতীয় σ নামানো। একই নিয়ম departments-এর দিকে (× commutative, তাই ডান পাশেও খাটে)। departments ৪ থেকে ১-এ নামল; × এখন ৩ × ১ = ৩.
  5. নতুন গাছ। দুই পাশের শর্ত নিচে, শুধু জোড়ার শর্তটি × এর ওপরে, কারণ সেটি দুই পাশের attribute চায়। মাঝের মোট সারি ১০, আগে ছিল ৪২. উত্তর একই, কারণ প্রতিটি ধাপ একটি প্রমাণিত equivalence.
  6. বড় ডেটায়। ২০,০০০ শিক্ষার্থী আর ৪০টি বিভাগের একটি কাল্পনিক হিসাব (cgpa > 3.5 ধরা হয়েছে ৫,০০০ জনের): সরল × বানায় ৮ লাখ সারি, নামানো × মাত্র ৫,০০০. পার্থক্য ঠিক ২০,০০০/৫,০০০ × ৪০/১ = ১৬০ গুণ।
-- সরল গাছের ×
SELECT COUNT(*) AS naive_rows FROM students CROSS JOIN departments;

-- selection নামানোর পর ×
SELECT COUNT(*) AS pushed_rows
FROM (SELECT * FROM students WHERE cgpa > 3.5)
CROSS JOIN (SELECT * FROM departments WHERE name = 'CSE');

প্রথম ফল 32, দ্বিতীয় 3 — ডায়াগ্রামের দুটি × node-এর আকার। একটি সতর্কতা: আপনি SQL যে রূপেই লিখুন, SQLite বা PostgreSQL-এর optimizer নিজেই WHERE-এর শর্তগুলো টুকরো করে নিচে নামায়। তাই অংশ ৭-এর দুটি SQL কোয়েরি বাস্তবে প্রায় একই plan পায়। এখানে subquery দিয়ে নামানো হয়েছে শুধু আকারটি গুনে দেখানোর জন্য। প্রশ্নটি তাই “আমি কীভাবে লিখব” নয়, “optimizer কোন নিয়মে বদলাতে পারে” — আর সেই নিয়মগুলোই এই সারণিতে।

একটি হিসাব মনে রাখুন। × এর আউটপুট ঠিক |R| · |S|. σp(R)-এর আকার |R|-এর একটি ভগ্নাংশ, যাকে বলে selectivity: p যত কম সারি রাখে, selectivity তত ছোট, আর নামানোর লাভ তত বড়। একই যুক্তিতে projection-ও নিচে নামানো যায় (দরকারি কলাম আগেই রেখে), যাতে প্রতিটি মাঝের tuple ছোট হয়। তবে যেকোনো জায়গায় নয় — ওপরের কোনো শর্ত বা জোড়ায় যে কলাম লাগবে, তা রাখতেই হবে।

আরও গভীরে: Selection নামানো সাধারণত লাভজনক, কিন্তু সবসময় নয়। শর্তটি যদি খুব দামি হয় (যেমন প্রতিটি সারিতে একটি ধীর user-defined function) আর জোড়ার পরে অনেক কম সারি থাকে, তাহলে শর্তটি ওপরে রাখাই সস্তা; ১৯৯০-এর দশকে “expensive predicate” নিয়ে গবেষণায় এটি দেখানো হয়েছে। তাই আধুনিক optimizer — ১৯৭৯ সালে System R-এর Selinger ও সহকর্মীদের cost-based পদ্ধতি থেকে শুরু করে — নিয়মগুলো দিয়ে অনেক সমান গাছ বানায়, প্রতিটির খরচ statistics দিয়ে অনুমান করে, আর সবচেয়ে সস্তাটি বাছে (পর্ব ৪৩)। SQL-এর bag semantics-এর জন্য optimizer আসলে bag algebra-র নিয়ম ব্যবহার করে। অধিকাংশ নিয়ম দুই জগতে এক, কিন্তু যেমন R ∪ R ≡ R শুধু set-এ খাটে।

৯ · পরীক্ষার ধাঁচে — ইংরেজি প্রশ্ন থেকে algebra

বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর লিখিত পরীক্ষায় প্রচলিত ধরন হলো: একটি schema দেওয়া থাকে, আর কয়েকটি ইংরেজি প্রশ্নের relational algebra লিখতে হয়। একটি নিয়মিত পদ্ধতি মেনে চলুন: (১) কোন attribute দেখাতে হবে → শেষে π; (২) তথ্য কোন relation-এ → পাতা; (৩) একাধিক relation হলে × আর জোড়ার σ; (৪) প্রশ্নের শর্ত → σ; (৫) “কখনো না”, “কেউ না” → −; “দুটিতেই” → ∩; “অথবা” দুই উৎস থেকে → ∪. Schema: students(student_id, name, dept_id, batch, cgpa), enrollments(student_id, course_id, term, grade), instructors(instructor_id, name, dept_id, salary, mentor_id), courses(course_id, title, dept_id, credits, instructor_id).

  1. Find the ids of students who enrolled in both CSE201 and CSE301.
    πstudent_id(σcourse_id='CSE201'(enrollments)) ∩ πstudent_id(σcourse_id='CSE301'(enrollments))
    সাধারণ ভুল: σcourse_id='CSE201' ∧ course_id='CSE301'(enrollments). একটি tuple-এর course_id একসাথে দুটি হতে পারে না, তাই ফল সবসময় খালি।
  2. Find the names of students who got grade A in some course.
    πname(σstudents.student_id = enrollments.student_id ∧ grade='A'(students × enrollments))
    “some course” মানে অন্তত একটি; π-এর duplicate মোছা নিশ্চিত করে যে দুবার A পাওয়া শিক্ষার্থীর নাম একবারই আসে।
  3. Find the ids and names of instructors who teach no course.
    πinstructor_id, name(instructors) − πinstructor_id, name(σinstructors.instructor_id = courses.instructor_id(instructors × courses))
    সাধারণ ভুল: πname(instructors) − πinstructor_id(courses). দুই পাশ union-compatible নয় — একটি নাম, আরেকটি সংখ্যা। − এর দুই পাশে একই attribute রাখুন, তারপর দরকার হলে ওপরে π.
  4. Find the highest cgpa. অংশ ৫-এর ρ-ওয়ালা expression. পরীক্ষায় aggregate নিষেধ থাকলে এটিই প্রত্যাশিত উত্তর।
  5. Find all pairs of student ids (a, b) where a and b are in the same department.
    πs1.student_id, s2.student_id(σs1.dept_id = s2.dept_id ∧ s1.student_id < s2.student_id(ρs1(students) × ρs2(students)))
    এখানে ρ ছাড়া উপায় নেই, কারণ একই relation-এর দুটি কপি লাগছে। s1.student_id < s2.student_id শর্তটি দুটি কাজ করে: নিজের সাথে নিজের জোড়া (2101, 2101) বাদ দেয়, আর (2101, 2102) ও (2102, 2101)-এর একটিই রাখে। db-uni-তে ফল ৭টি জোড়া: CSE-র চারজনের ৬টি, EEE-র দুজনের ১টি। BBA-তে একজন, তাই জোড়া নেই; Habib-এর dept_id NULL, তাই তিনি কারও সাথে মেলেন না।
  6. Find the ids of students who are in the EEE department (dept_id = 2) or enrolled in BBA101.
    πstudent_id(σdept_id = 2(students)) ∪ πstudent_id(σcourse_id = 'BBA101'(enrollments))
    দুটি আলাদা relation থেকে আসা “অথবা”, তাই ∪; দুই পাশ πstudent_id করে union-compatible বানানো হয়েছে। উত্তর {2103, 2203, 2202}. একটি σ-এর ভেতরে ∨ দিয়ে লেখা যেত না, কারণ dept_id আর course_id একই relation-এ নেই।
-- প্রশ্ন ১: ∩
SELECT student_id FROM enrollments WHERE course_id = 'CSE201'
INTERSECT
SELECT student_id FROM enrollments WHERE course_id = 'CSE301';

-- প্রশ্ন ২: π(σ(×))
SELECT DISTINCT s.name FROM students s CROSS JOIN enrollments e
WHERE s.student_id = e.student_id AND e.grade = 'A';

-- প্রশ্ন ৩: −
SELECT instructor_id, name FROM instructors
EXCEPT
SELECT i.instructor_id, i.name FROM instructors AS i CROSS JOIN courses AS c
WHERE i.instructor_id = c.instructor_id;

প্রশ্ন ১-এর ফল ৩টি: 2101, 2102, 2201. প্রশ্ন ২-এর ফল ২টি নাম: Arif Hossain আর Dilruba Khatun — দুজনেই দুটি করে A পেয়েছেন, কিন্তু DISTINCT (π) নাম একবার রেখেছে; DISTINCT মুছলে ৪টি সারি আসত। প্রশ্ন ৩-এর ফল একটি: 4 | Rumana Islam. বাকি পাঁচজন অন্তত একটি কোর্স পড়ান। খাতায় algebra লেখার পর এভাবে SQL-এ অনুবাদ করে ছোট ডেটায় চালিয়ে দেখা নিজের উত্তর যাচাইয়ের সবচেয়ে নির্ভরযোগ্য উপায়।

১০ · AI-এর যুগে relational algebra

AI সহকারী SQL লেখে দ্রুত, আর relational algebra-র প্রশ্নের উত্তরও দেয়। কিন্তু দুই জায়গাতেই এর পরিচিত ভুল আছে। Algebra জানলে আপনি সেগুলো ধরতে পারবেন, কারণ প্রতিটি SQL-কে আপনি π(σ(×)) আকারে পড়তে পারেন:

AI-এর ভুলকেন ভুলকীভাবে ধরবেন
π-এর SQL রূপ হিসেবে DISTINCT ছাড়া SELECTSQL-এ bag, algebra-য় set — সারির সংখ্যা মেলে নাফলের COUNT(*) আর COUNT(DISTINCT …) তুলনা
“দুটিতেই” প্রশ্নে একই কলামে দুই মানের ANDএকটি tuple-এ দুটি মান হয় না, ফল খালিখালি ফল এলে সন্দেহ করুন; ∩ বা দুবার ρ লাগে
− এর দুই পাশে ভিন্ন attributeunion-compatible নয়দুই পাশের কলামের অর্থ মিলিয়ে দেখুন
σ-কে − বা নিচে ভুল জায়গায় নামানো, π-কে − এর ভেতরে বিতরণπ − -এর ওপর বিতরণ হয় নাছোট counterexample দিয়ে যাচাই
বানানো কলাম বা টেবিলের নামschema না দেখে লেখাআসল schema দিন, নামগুলো মিলিয়ে নিন
NULL-এর কথা না ভেবে algebra থেকে SQL অনুবাদখাঁটি algebra-য় NULL নেইNULL-ওয়ালা একটি সারি দিয়ে চালিয়ে দেখুন

AI-কে algebra-র প্রশ্ন দিলে শুধু উত্তর না চেয়ে গাছ, SQL আর যাচাইয়ের ডেটাও চেয়ে নিন:

আমার schema: students(student_id, name, dept_id, batch, cgpa), enrollments(student_id, course_id, term, grade). cgpa আর grade-এ NULL থাকতে পারে। প্রশ্ন: "Find the names of students who never got an F." ১. শুধু মূল অপারেটর {σ, π, ∪, −, ×, ρ} দিয়ে relational algebra expression দাও। ২. Expression tree-টি নিচ থেকে ওপরে লেখো, প্রতিটি node-এ কী আছে বলো। ৩. সমান SQL দাও (SQLite), আর বলো কোথায় DISTINCT বা EXCEPT লাগছে এবং কেন। ৪. কোনো কোর্সে ভর্তি না হওয়া শিক্ষার্থী আর NULL grade — এরা ফলে আসবে কি না, ব্যাখ্যা করো। ৫. ৫–৬টি সারির নমুনা ডেটা আর প্রত্যাশিত ফল দাও, যাতে আমি নিজে চালিয়ে মেলাতে পারি।

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

পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:

  • σ আর π-এর পার্থক্য? — σ সারি (tuple) বাছে, schema অপরিবর্তিত; π কলাম (attribute) বাছে আর duplicate মোছে।
  • মূল অপারেটর কোনগুলো? — σ, π, ∪, −, ×, ρ; ∩, join, division derived.
  • R ∩ S-কে − দিয়ে লিখুন। — R − (R − S).
  • |R| = 5, |S| = 4 হলে |R × S|? degree 3 ও 2 হলে ফলের degree? — ২০টি tuple, degree ৫.
  • Selection আগে করা কেন ভালো? — নিয়ম σp(R × S) ≡ σp(R) × S অনুযায়ী উত্তর একই, কিন্তু × বা join-এর ইনপুট ছোট হয়।

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

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

প্র ০১Codd কেন algebra আর calculus — দুটি আনুষ্ঠানিক ভাষা দিলেন, আর “relational completeness” কেন একটি গুরুত্বপূর্ণ মাপকাঠি হয়ে উঠল?

১৯৭০-এর আগে ডেটাবেসে প্রশ্ন করা মানে ছিল প্রোগ্রাম লেখা: কোন রেকর্ড থেকে শুরু করব, কোন pointer ধরে এগোব (পর্ব ৪-এর navigational access). প্রশ্নের অর্থ আর তা উত্তর করার পথ একসাথে জড়ানো ছিল। Codd-এর মূল দাবি ছিল দুটিকে আলাদা করা। কিন্তু আলাদা করতে হলে “প্রশ্নের অর্থ” কথাটিকে প্রোগ্রাম ছাড়াই নিখুঁতভাবে বলার একটি উপায় লাগে।

১৯৭২ সালের প্রবন্ধে তিনি দুটি উপায় দিলেন। Relational calculus বলে “কোন tuple-গুলো চাই” — একটি শর্ত দিয়ে, গণিতের logic-এর ভাষায়, কোনো ধাপ ছাড়া। Relational algebra বলে “কোন অপারেটর কোন ক্রমে প্রয়োগ করলে উত্তর পাওয়া যায়”। প্রথমটি ব্যবহারকারীর ভাষার কাছাকাছি, দ্বিতীয়টি বাস্তবায়নের কাছাকাছি। তিনি একটি অ্যালগরিদম (reduction) দেখান, যা যেকোনো calculus expression-কে সমান algebra expression-এ বদলায়। এর মানে, ব্যবহারকারী বর্ণনামূলকভাবে লিখলেও সিস্টেম সবসময় একটি নির্দিষ্ট অপারেটর-ক্রমে তা হিসাব করতে পারে। আজকের DBMS-এর মূল কাঠামো — declarative ভাষা, ভেতরে অপারেটরের গাছ — এই ফলেরই প্রয়োগ।

“Relationally complete” তখন একটি মাপকাঠি হলো: কোনো নতুন ভাষা অন্তত algebra বা calculus-এর সব প্রশ্ন প্রকাশ করতে পারে কি? এটি ভাষা-নকশাকারীদের একটি ন্যূনতম লক্ষ্য দিল। SQL এই মাপে উত্তীর্ণ, যদিও ঠিকভাবে বললে SQL-এর তুলনা bag algebra আর NULL-সহ একটি বর্ধিত রূপের সাথে। সাথে SQL অনেক কিছু বাড়তি দেয়: aggregate, ক্রম, recursion.

একটি সীমাবদ্ধতাও মনে রাখার মতো। Completeness মানে “যথেষ্ট শক্তিশালী”, “সবকিছু পারে” নয়। পরে দেখা গেছে, কিছু খুব স্বাভাবিক প্রশ্ন — যেমন “এই শিক্ষকের mentor-এর mentor-এর … সবাই কে” — মূল algebra-য় লেখাই যায় না (প্রশ্ন ৫)।

মূল উপলব্ধি: দুটি সমান শক্তির ভাষা — একটি বর্ণনার, একটি ধাপের — এবং তাদের মধ্যে প্রমাণিত অনুবাদ, এটাই declarative ডেটাবেসকে সম্ভব করেছে।

প্র ০২Algebra set ধরে, SQL bag ধরে। এই পার্থক্য কি শুধু DISTINCT লেখা-না-লেখার ব্যাপার, নাকি এর গভীর প্রভাব আছে?

প্রভাব গভীর, কারণ equivalence rule — যার ওপর optimizer দাঁড়িয়ে — set আর bag-এ সবসময় এক নয়। Set-এ R ∪ R = R; bag-এর UNION ALL-এ প্রতিটি সারি দ্বিগুণ হয়। Set-এ πA(R) ∪ πA(R) আর πA(R) একই, bag-এ নয়। Set-এ “R-এর প্রতিটি tuple S-এ আছে কি না” একটি হ্যাঁ/না প্রশ্ন; bag-এ “কতবার আছে” হিসাব করতে হয়, তাই INTERSECT ALL-এ min(m, n), EXCEPT ALL-এ max(m − n, 0) আসে (পর্ব ২২)।

মজার ব্যাপার, কিছু নিয়ম bag-এও অক্ষত থাকে। R ∩ S = R − (R − S) bag-এও খাটে: একটি tuple R-এ m বার, S-এ n বার থাকলে R − S-এ max(m − n, 0) বার, আর R − (R − S)-এ m − max(m − n, 0) = min(m, n) বার — ঠিক INTERSECT ALL. σ-এর cascade আর commutativity-ও bag-এ খাটে, কারণ σ প্রতিটি সারি আলাদাভাবে বিচার করে। তাই optimizer-এর নির্মাতাদের প্রতিটি rule আলাদা করে যাচাই করতে হয়: “এটি কি bag-এও সত্য?”

দৈনন্দিন কাজে প্রভাবটি আসে গোনায়। π-কে DISTINCT ছাড়া অনুবাদ করলে একটি join-এর পর একই শিক্ষার্থীর নাম কয়েকবার আসে। তার ওপর COUNT(*) করলে সংখ্যা বাড়ে। উল্টোভাবে, যেখানে bag দরকার (মোট বিক্রি), সেখানে DISTINCT দিলে সমান অঙ্কের দুটি বিক্রি এক হয়ে যায়। আবার DISTINCT একটি sort বা hash ডেকে আনে, যা বড় টেবিলে দামি।

C. J. Date ও Hugh Darwen-এর মতো তাত্ত্বিকেরা দীর্ঘদিন যুক্তি দিয়েছেন যে duplicate-এর কোনো যৌক্তিক অর্থ নেই, আর SQL-এর এই পছন্দ ভুলের উৎস। বাস্তব সিস্টেম অবশ্য খরচ আর গোনার সুবিধার জন্য bag ধরে রেখেছে।

মূল উপলব্ধি: Algebra-য় যুক্তি করুন, কিন্তু SQL-এ অনুবাদের সময় প্রতিবার জিজ্ঞেস করুন — এখানে set চাই, না bag? উত্তর অনুযায়ী DISTINCT, UNION না UNION ALL বেছে নিন।

প্র ০৩Selection নিচে নামানো যদি প্রায় সবসময় ভালো হয়, তবে optimizer কেন শুধু এই নিয়ম দিয়ে চলে না? কখন এটি যথেষ্ট নয়, বা ক্ষতিকর?

প্রথম দিকের অনেক optimizer আসলে heuristic ছিল: কয়েকটি স্থির নিয়ম — selection আগে, projection আগে, সবচেয়ে ছোট relation দিয়ে join শুরু। সরল প্রশ্নে এটি ভালো কাজ করে, আর আজও প্রায় সব optimizer প্রথম ধাপে predicate pushdown করে। কিন্তু তিনটি কারণে এটুকু যথেষ্ট নয়।

এক, join-এর ক্রম। পাঁচটি টেবিল join করতে হলে কোনটির পর কোনটি — এই সিদ্ধান্তে খরচ অনেক গুণ আলাদা হতে পারে, আর selection নামানোর নিয়ম এ বিষয়ে কিছু বলে না। সম্ভাব্য ক্রমের সংখ্যা টেবিলের সংখ্যার সাথে factorial হারে বাড়ে। ১৯৭৯ সালে System R-এর Selinger ও সহকর্মীরা statistics দিয়ে প্রতিটি plan-এর খরচ অনুমান আর dynamic programming দিয়ে সেরা join ক্রম খোঁজার যে পদ্ধতি দেন, তা আজও বেশিরভাগ বাণিজ্যিক optimizer-এর ভিত্তি।

দুই, দামি predicate. শর্তটি যদি একটি ধীর ফাংশন হয় (ধরুন প্রতিটি সারির ছবি বিশ্লেষণ), আর join-এর পর মাত্র কয়েকটি সারি থাকে, তাহলে শর্তটি ওপরে রেখে কম সারিতে চালানোই সস্তা। ১৯৯০-এর দশকে Hellerstein-সহ গবেষকেরা এ নিয়ে কাজ করে দেখান, predicate-এর জায়গা তার খরচ আর selectivity দুটি মিলিয়ে ঠিক করতে হয়।

তিন, index আর ভৌত গঠন। একটি selection নামানো হলো, কিন্তু সেই কলামে index না থাকলে পুরো টেবিল পড়তেই হবে; আবার কখনো join-এর আগে না নামিয়ে index-ওয়ালা join (পর্ব ৪২) বেশি লাভজনক। Algebra-র নিয়ম শুধু বলে কোন রূপগুলো সমান; কোনটি সস্তা, তা ঠিক করে খরচের মডেল।

আরেকটি সীমা: outer join-এর ক্ষেত্রে selection সবদিকে নামানো যায় না — LEFT JOIN-এর ডান পাশের শর্ত নিচে নামালে ফল বদলে যেতে পারে (পর্ব ১৯-এর ON বনাম WHERE).

মূল উপলব্ধি: Equivalence rule দেয় বিকল্পের তালিকা; cost model দেয় বিচার। “Selection আগে” একটি ভালো অভ্যাস, আইন নয়।

প্র ০৪মূল অপারেটরের সেট ছোট রাখার কী লাভ? ∩ বা join-কে মূল ধরলে কী ক্ষতি হতো, আর ρ-কে কেন আলাদা অপারেটর বলা হয়?

গণিতে ছোট ভিত্তির সুবিধা একটাই, কিন্তু খুব বড়: প্রমাণ সহজ হয়। “Relational algebra-র প্রতিটি expression-এর একটি নির্দিষ্ট ধর্ম আছে” — এমন দাবি প্রমাণ করতে আরোহ (induction) চালাতে হয় প্রতিটি অপারেটরের ওপর। ছয়টি হলে ছয়টি ক্ষেত্র; derived অপারেটর আপনা থেকেই আসে, কারণ তারা মূলগুলো দিয়ে তৈরি। অংশ ৬-এর “বাকি পাঁচটি monotone” যুক্তিটি এমনই একটি প্রমাণ। ∩-কে মূল ধরলেও কোনো ভুল হতো না, শুধু সেটটি আর minimal থাকত না — ∩ ছাড়াও একই ক্ষমতা পাওয়া যায়।

ভিত্তি কোনটি, তা অবশ্য পছন্দের ব্যাপার। Codd-এর ১৯৭২ সালের তালিকায় join আর division-ও ছিল। কিছু পাঠ্যবই × এর বদলে natural join-কে মূল ধরে। কিন্তু যে ভিত্তিই নিন, সেটির ক্ষমতা একই — এটিই গুরুত্বপূর্ণ।

ρ-এর ব্যাপারটি সূক্ষ্ম। ρ কোনো tuple-এর মান বদলায় না, তাই মনে হতে পারে এটি “আসল” অপারেটর নয়। কিন্তু attribute-এর নাম algebra-র অর্থের অংশ। ∪-এর ফলের attribute-এর নাম কী, × এর পর দুটি name কলাম কীভাবে আলাদা করা যাবে, আর একই relation দুবার লাগলে (অংশ ৫-এর সর্বোচ্চ cgpa) দুটি কপি কীভাবে চেনা যাবে — নাম বদলানোর একটি আনুষ্ঠানিক উপায় ছাড়া এগুলো বলা যায় না। কিছু পাঠ্যবই attribute-কে নামের বদলে অবস্থান দিয়ে চেনে (১ম, ২য় কলাম); সেখানে ρ লাগে না, কিন্তু expression পড়া কঠিন হয়।

বাস্তব DBMS-এর জন্য উল্টো দিকটিও সত্য: ছোট ভিত্তি তত্ত্বে ভালো, কিন্তু ইঞ্জিনে ∩, join, semi join-এর আলাদা দ্রুত অ্যালগরিদম লাগে। তাই ভেতরের logical plan-এ অপারেটরের তালিকা অনেক বড়।

মূল উপলব্ধি: মূল সেট ছোট রাখা হয় যুক্তি আর প্রমাণের জন্য; derived অপারেটর রাখা হয় মানুষ আর ইঞ্জিনের সুবিধার জন্য। দুটি একে অপরের পরিপূরক।

প্র ০৫Relational algebra কোন প্রশ্নের উত্তর দিতে পারে না? আর SQL সেই ফাঁক কীভাবে পূরণ করেছে?

সবচেয়ে বিখ্যাত উদাহরণ transitive closure. db-uni-তে instructors-এর mentor_id আছে: Tasnim-এর mentor Shahana, Shahana-র mentor Anisur. প্রশ্ন: “Tasnim-এর উপরের সব mentor কে?” এক ধাপ (mentor) বের করতে একটি ρ আর ×; দুই ধাপের জন্য আরও একটি; k ধাপের জন্য k-টি। কিন্তু ডেটায় শৃঙ্খল কত লম্বা, তা আগে জানা নেই, আর একটি algebra expression-এর আকার স্থির। ১৯৭৯ সালে Aho আর Ullman প্রমাণ করেন, কোনো একক relational algebra expression যেকোনো দৈর্ঘ্যের শৃঙ্খলের transitive closure হিসাব করতে পারে না। ঠিক এই কারণে পর্ব ২৫-এর recursive CTE দরকার হয়েছিল, যা SQL:1999-এ standard-এ আসে।

আরও কয়েকটি ফাঁক:

  • গোনা ও যোগ: “প্রতিটি বিভাগে কতজন” — মূল algebra-য় সংখ্যা তৈরির কোনো অপারেটর নেই। Extended algebra-র γ আর SQL-এর GROUP BY এটি পূরণ করে।
  • ক্রম: relation একটি set, তার কোনো “প্রথম” বা “শেষ” নেই। ORDER BY, LIMIT আর window function (পর্ব ২৬) algebra-র বাইরের জিনিস।
  • অজানা মান: NULL আর তিন-মানের যুক্তি মূল মডেলে নেই; SQL-এ এগুলো যোগ হওয়ায় অনেক সুন্দর নিয়ম (যেমন ¬(p ∨ q) সব বাকি সারি দেয়) আর সরাসরি খাটে না।
  • নতুন মান: algebra শুধু ডেটাবেসে থাকা মানগুলো নাড়াচাড়া করে; cgpa × 25 বা নাম জোড়ার মতো হিসাবের জন্য extended projection লাগে।

এই সীমা আসলে একটি সুবিধার উল্টো পিঠ। মূল algebra-র প্রতিটি expression সবসময় শেষ হয়, আর ডেটার আকারের polynomial সময়ে হিসাব করা যায়। Recursion যোগ করলে ক্ষমতা বাড়ে, কিন্তু optimizer-এর কাজ জটিল হয়, আর সাবধান না হলে অসীম loop-ও সম্ভব (পর্ব ২৫-এর cycle detection).

মূল উপলব্ধি: Relational algebra ইচ্ছে করে সীমিত — তাই এর প্রতিটি প্রশ্ন নিরাপদ আর optimize করা যায়। SQL বাস্তবের চাহিদায় এর বাইরে গেছে, আর প্রতিটি বাড়তি ক্ষমতার সাথে নতুন সতর্কতাও এনেছে।

অনুশীলন

খাতা-কলম নিন। একটি algebra লেখা, একটি হাতে হিসাব, একটি প্রমাণ আর একটি গাছ বদলানো।

  1. Algebra লিখুন: db-uni-র schema-য় “Find the names of students enrolled in CSE301.” শুধু মূল অপারেটর দিয়ে expression লিখুন, তারপর SQL-এ অনুবাদ করে উপরের যেকোনো db-uni এডিটরে চালিয়ে মিলিয়ে নিন।
    πname(σstudents.student_id = enrollments.student_id ∧ course_id='CSE301'(students × enrollments))

    ভালো রূপ (selection নামানো): πname(σstudents.student_id = enrollments.student_id(students × σcourse_id='CSE301'(enrollments))). SQL: SELECT DISTINCT s.name FROM students s CROSS JOIN enrollments e WHERE s.student_id = e.student_id AND e.course_id = 'CSE301'; ফল ৩টি নাম: Arif Hossain, Bristy Saha, Dilruba Khatun. Dilruba-র grade এখনো NULL, কিন্তু প্রশ্নটি ভর্তি নিয়ে, grade নিয়ে নয় — তাই তিনি আছেন।

  2. হাতে হিসাব করুন: R(A, B) = {(1, x), (2, y), (3, x)} আর S(A, B) = {(2, y), (4, z)}. বের করুন: πB(R), R ∪ S, R − S, S − R, R ∩ S (সূত্র R − (R − S) দিয়ে), আর R × S-এর degree ও tuple-এর সংখ্যা।
    • πB(R) = {x, y} — x দুবার ছিল, set-এ একবার। (SQL-এর DISTINCT ছাড়া SELECT দিত ৩টি সারি।)
    • R ∪ S = {(1, x), (2, y), (3, x), (4, z)} — ৪টি, (2, y) একবার।
    • R − S = {(1, x), (3, x)}; S − R = {(4, z)} — দুটি আলাদা।
    • R ∩ S = R − {(1, x), (3, x)} = {(2, y)}.
    • R × S: degree ২ + ২ = ৪, tuple ৩ × ২ = ৬টি। ফলের attribute-এর নাম R.A, R.B, S.A, S.B — একই নাম আলাদা করতে prefix (বা ρ) লাগে।
  3. প্রমাণ করুন বা খণ্ডন করুন: (ক) σp(R − S) = σp(R) − S. (খ) πA(R ∩ S) = πA(R) ∩ πA(S).

    (ক) সত্য। t ∈ σp(R − S) ⇔ t ∈ R, t ∉ S, p(t) ⇔ (t ∈ R এবং p(t)) এবং t ∉ S ⇔ t ∈ σp(R) − S. ∎ অর্থাৎ − এর ক্ষেত্রে শুধু বাঁ পাশে selection দিলেই চলে; σp(R) − σp(S)-ও সমান।

    (খ) মিথ্যা। Counterexample: R(A, B) = {(1, 2)}, S(A, B) = {(1, 3)}. R ∩ S = ∅, তাই বাঁ পাশ ∅. কিন্তু πA(R) ∩ πA(S) = {1} ∩ {1} = {1}. কলাম কেটে ফেলার পর আলাদা tuple “একই” হয়ে যায় — π ∪-এর ওপর বিতরণ হয়, ∩ বা − এর ওপর নয়।

  4. গাছ বদলান আর আকার গুনুন: প্রশ্ন “titles of courses taught by instructors whose salary > 100000.” সরল expression πtitle(σinstructors.instructor_id = courses.instructor_id ∧ salary > 100000(instructors × courses)). db-uni-তে instructors-এ ৬টি আর courses-এ ৭টি tuple. সরল গাছে আর selection নামানো গাছে × কত সারি বানায়? শেষ উত্তর কী?

    সরল গাছে × = ৬ × ৭ = ৪২টি সারি। নিয়ম ১ দিয়ে শর্ত ভেঙে salary > 100000 নামান instructors-এর ওপর: πtitle(σinstructors.instructor_id = courses.instructor_id(σsalary > 100000(instructors) × courses)). শিক্ষক দুজন: Dr. Anisur Rahman (120000) আর Dr. Mahbub Alam (110000). তাই × = ২ × ৭ = ১৪টি সারি — তিন ভাগের এক ভাগ। জোড়ার শর্তের পর থাকে CSE201 (শিক্ষক 1) আর EEE101 (শিক্ষক 3). উত্তর: Data Structures, Electrical Circuits. যাচাই: SELECT DISTINCT c.title FROM (SELECT * FROM instructors WHERE salary > 100000) i CROSS JOIN courses c WHERE i.instructor_id = c.instructor_id;

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

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

ক্রম“CSE-র যেসব শিক্ষার্থীর cgpa 3.5-এর বেশি, তাঁদের নাম” — expression tree-র হিসাব নিচ থেকে ওপরে যে ক্রমে হয়, সেভাবে সাজান (▲▼ চেপে সরান)

  1. পাতা: students আর departments relation পড়া
  2. দুটির Cartesian product × নেওয়া
  3. σ দিয়ে শুধু dept_id মেলা জোড়া রাখা
  4. σ দিয়ে বিভাগ CSE আর cgpa > 3.5 রাখা
  5. π দিয়ে শুধু name রাখা আর duplicate মোছা

Relational algebra-য় πbatch(students)-এর ফলে কয়টি tuple থাকবে, যদি students-এ ৮টি tuple থাকে আর batch কলামে মান 2021 তিনবার, 2022 তিনবার, 2023 দুবার থাকে?

  • ৮টি
  • ১টি
  • ৩টি
  • ২টি

Relational algebra-র মূল (primitive) অপারেটর সেট {σ, π, ∪, −, ×, ρ} দিয়ে R ∩ S কীভাবে লেখা যায়?

  • (R ∪ S) − S
  • R − (R − S)
  • (R − S) ∪ (S − R)
  • R × S

R-এর degree 3 আর ৫টি tuple, S-এর degree 2 আর ৪টি tuple. R × S-এর degree আর tuple-এর সংখ্যা কত?

  • degree 6, ৯টি tuple
  • degree 5, ৯টি tuple
  • degree 6, ২০টি tuple
  • degree 5, ২০টি tuple

নিচের কোন equivalence সবসময় সত্য নয়?

  • πA(R − S) ≡ πA(R) − πA(S)
  • σp ∧ q(R) ≡ σp(σq(R))
  • σp(R ∪ S) ≡ σp(R) ∪ σp(S)
  • R ∪ S ≡ S ∪ R

SQL কোয়েরি SELECT DISTINCT A FROM R, S WHERE p-এর সঠিক relational algebra রূপ কোনটি?

  • σp(πA(R × S))
  • πA(σp(R ∪ S))
  • πA(σp(R × S))
  • πA(σp(ρR(S)))

students(ID, name, cgpa) relation থেকে সর্বোচ্চ cgpa বের করতে aggregate ফাংশন ছাড়া কোন অপারেটরটি অপরিহার্য, কারণ একই relation-কে নিজের সাথে তুলনা করতে হয়?

  • ∩ (intersection)
  • ρ (rename)
  • ∪ (union)
  • π (projection)

students-এ ২০,০০০ আর departments-এ ৪০টি tuple. শর্ত cgpa > 3.5 পূরণ করে ৫,০০০ শিক্ষার্থী, আর name = 'CSE' পূরণ করে ১টি বিভাগ। দুটি selection Cartesian product-এর নিচে নামালে × কয়টি tuple বানায়?

  • ৮,০০,০০০
  • ২,০০,০০০
  • ২০,০০০
  • ৫,০০০

শব্দকোষ

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

মনে রাখুন

  1. Relational algebra relation-এর ওপর অপারেটরের নিখুঁত ভাষা; closure-এর কারণে প্রতিটি ফল আবার ইনপুট হতে পারে, আর SQL ভেতরে এমন অপারেটরের গাছে বদলে যায়।
  2. σ সারি বাছে (WHERE), π কলাম বাছে আর duplicate মোছে (SELECT DISTINCT), ρ নাম বদলায় (AS). σ-এর cascade: σp ∧ q = σpσq.
  3. ∪, −, ∩-এর দুই পাশ union-compatible; − commutative নয়; R ∩ S = R − (R − S).
  4. × দেয় |R| · |S| সারি আর degree যোগ; মূল সেট {σ, π, ∪, −, ×, ρ}, বাকিরা derived. SELECT DISTINCT A FROM R, S WHERE p = πA(σp(R × S)).
  5. Equivalence rule সমান গাছ দেয়; selection নিচে নামালে মাঝের relation ছোট হয় — কোনটি সবচেয়ে সস্তা, তা ঠিক করে খরচের মডেল।

পরের পর্বে: এই পর্বে বারবার σ(R × S) লিখেছেন। এর নিজের নাম আছে — join. পর্ব ৩৩-এ theta join, natural join, outer join আর “সব কোর্স নিয়েছেন এমন শিক্ষার্থী” ধরনের প্রশ্নের জন্য division ÷.

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

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