এই পর্বে যা শিখবেন
- সাধারণ ER কোথায় আটকে যায়, আর superclass/subclass ও IS-A সম্পর্ক কীভাবে সমাধান দেয়
- Specialization আর generalization দিয়ে ধাপে ধাপে একটি hierarchy বানানো, আর EER ডায়াগ্রামের চিহ্ন পড়া
- Disjoint/overlapping, total/partial, attribute-defined/user-defined — কোন constraint কী বলে
- Shared subclass, category (union type) আর aggregation — কখন লাগে, কীভাবে আঁকে
- Inheritance-কে টেবিলে আনার তিন কৌশল, তাদের trade-off, আর SQL দিয়ে constraint রক্ষা — PostgreSQL INHERITS ও ORM-এর mapping সহ
১ · সাধারণ ER কোথায় আটকে যায়
পর্ব ৭-এ আপনি ER model দিয়ে বাস্তব জগৎ আঁকতে শিখেছেন: entity (জিনিস), attribute (গুণ) আর relationship (সম্পর্ক)। পর্ব ৮-এ যোগ হয়েছে cardinality, participation আর weak entity. এগুলো দিয়ে অনেক কিছু আঁকা যায়। কিন্তু একটি খুব সাধারণ পরিস্থিতিতে সাধারণ ER অস্বস্তিতে পড়ে: যখন কয়েক ধরনের জিনিস অনেকটা এক, কিন্তু পুরোপুরি এক নয়।
একটি বিশ্ববিদ্যালয়ের মানুষগুলোর কথা ভাবুন। শিক্ষার্থী, শিক্ষক, অফিসের কর্মচারী — সবার নাম, ফোন নম্বর, ঠিকানা, জন্মতারিখ আছে। সবাই লাইব্রেরি থেকে বই নেন, সবার একটি আইডি কার্ড আছে। কিন্তু শিক্ষার্থীর আছে batch আর CGPA, শিক্ষকের আছে পদবি আর বেতন, কর্মচারীর আছে শাখা আর শিফট। ব্যাংকেও একই ছবি: সঞ্চয়ী (savings) আর চলতি (current) — দুটিই হিসাব, দুটিরই হিসাব নম্বর, শাখা, ব্যালান্স, খোলার তারিখ আছে। কিন্তু সঞ্চয়ী হিসাবে সুদের হার থাকে, আর চলতি হিসাবে ওভারড্রাফটের সীমা।
বিশ্ববিদ্যালয়ের রেজিস্টার অফিসের তিনটি আলাদা খাতা ভাবুন: শিক্ষার্থীর খাতা, শিক্ষকের খাতা, কর্মচারীর খাতা। প্রতিটিতে নাম-ফোন-ঠিকানার কলাম আবার লেখা। লাইব্রেরিয়ান জানতে চাইলেন, “এই কার্ড নম্বরের মানুষটি কে?” — তিনটি খাতাই খুলতে হবে। আবার একজন শিক্ষার্থী পার্টটাইম কর্মচারী হলে তাঁর ফোন নম্বর দুই খাতায়, আর একদিন একটিতে বদলায়, অন্যটিতে না। EER-এর ধারণা হলো একটি “সবার খাতা” রাখা, যেখানে সবার সাধারণ তথ্য একবার, আর প্রতিটি ধরনের জন্য পাশে একটি ছোট “বিশেষ পাতা”।
সাধারণ ER-এ এই পরিস্থিতি আঁকার দুটি রাস্তা আছে, আর দুটিই খারাপ:
- একটি বড় PERSON entity, সব attribute সহ। তখন একজন শিক্ষকের batch আর CGPA-র ঘর চিরকাল খালি, শিক্ষার্থীর বেতনের ঘরও। আরও খারাপ, “শুধু শিক্ষকেরাই কোর্স পড়ান” — এই নিয়মটি ডায়াগ্রাম আর বলতে পারে না, কারণ TEACHES relationship-টি সব PERSON-এর সাথে জোড়া।
- STUDENT, TEACHER, STAFF — তিনটি আলাদা entity. তখন সাধারণ attribute তিনবার আঁকা, আর “যেকোনো মানুষ বই ধার নেন” relationship-টিও তিনবার: STUDENT–BORROWS, TEACHER–BORROWS, STAFF–BORROWS. আর তিনজনই যে আসলে “মানুষ”, সেই সত্যটি ডায়াগ্রামে কোথাও নেই।
এই সমস্যার সমাধান হিসেবে ১৯৭০-এর দশকের শেষ থেকে ER-কে বাড়ানো হয়, যাকে আজ বলা হয় EER (Enhanced বা Extended ER) model. এতে তিনটি নতুন ধারণা আসে: superclass/subclass (সাথে specialization ও generalization), category, আর aggregation. প্রথমে দেখুন, সমস্যাটি ডেটাতেই কেমন দেখায়। নিচের এডিটর চলে বিশ্ববিদ্যালয়ের নমুনা ডেটাবেসে (db-uni). এতে আছে departments (dept_id, name, building), instructors (instructor_id, name, dept_id, salary, mentor_id), students (student_id, name, dept_id, batch, cgpa), courses আর enrollments. খেয়াল করুন, students আর instructors দুটি আলাদা টেবিল, অথচ দুটিতেই name আর dept_id আছে।
-- CSE বিভাগের (dept_id = 1) সব মানুষ: দুই টেবিল জুড়ে খুঁজতে হয়
SELECT 'student' AS role, name, dept_id FROM students WHERE dept_id = 1
UNION ALL
SELECT 'instructor', name, dept_id FROM instructors WHERE dept_id = 1;ফলে ৭টি সারি: প্রথমে ৪ জন শিক্ষার্থী (Arif Hossain, Bristy Saha, Dilruba Khatun, Gulshan Ara), তারপর ৩ জন শিক্ষক (Dr. Anisur Rahman, Shahana Parvin, Tasnim Chowdhury), প্রত্যেকের পাশে role আর 1. “বিভাগের সব মানুষ” — এত সহজ প্রশ্নের জন্য দুটি টেবিল UNION ALL দিয়ে জুড়তে হলো (UNION বিস্তারিত পর্ব ২২-এ)। কাল অফিস কর্মচারীর টেবিল এলে তৃতীয় অংশ লাগবে। আর একজন শিক্ষক যদি একই সাথে পিএইচডি শিক্ষার্থী হন, তাঁর নাম দুই টেবিলে আলাদা id-তে থাকবে, আর কেউ বলতে পারবে না দুটি সারি একই মানুষের। ডেটাবেস জানেই না যে student আর instructor দুজনেই “মানুষ”। EER ঠিক এই না-বলা সত্যটি নকশায় তুলে আনে।
Peter Chen ER model প্রকাশ করেন ১৯৭৬ সালে। পরের বছরই, ১৯৭৭ সালে, John Miles Smith আর Diane C. P. Smith-এর প্রবন্ধ “Database Abstractions: Aggregation and Generalization” (ACM Transactions on Database Systems) দুটি বিমূর্তকরণের কথা বলে: কয়েকটি জিনিস মিলে একটি বড় জিনিস (aggregation), আর কয়েক ধরনের জিনিসকে একটি সাধারণ ধরনে তোলা (generalization). এই ধারণাগুলো এসেছিল আংশিকভাবে কৃত্রিম বুদ্ধিমত্তার জ্ঞান-উপস্থাপন (semantic network-এর IS-A সংযোগ) আর Simula 67-এর মতো প্রোগ্রামিং ভাষার class ও subclass থেকে। ১৯৮০-এর দশকে নানা গবেষক এগুলোকে ER-এ যোগ করেন; Ramez Elmasri ও সহকর্মীরা ১৯৮৫ সালে “category” ধারণাটি প্রস্তাব করেন। Elmasri আর Shamkant Navathe-এর পাঠ্যবই এই সম্প্রসারিত রূপকে “EER” নামে জনপ্রিয় করে, আর আজকের UML class diagram-এর inheritance-ও একই চিন্তার উত্তরসূরি।
২ · Superclass, subclass আর IS-A — attribute-এর উত্তরাধিকার
EER-এর মূল কথা একটি বাক্যে: কিছু entity type আরেকটি entity type-এর উপসেট। প্রতিটি STUDENT একজন PERSON; প্রতিটি TEACHER-ও একজন PERSON. এখানে PERSON হলো superclass (বড়, সাধারণ ধরন), আর STUDENT ও TEACHER হলো তার subclass (ছোট, বিশেষ ধরন)। দুজনের সম্পর্ককে বলে IS-A সম্পর্ক: “STUDENT IS-A PERSON” — একজন শিক্ষার্থী একজন মানুষ।
Subclass: S একটি entity type C-র subclass, যদি ডেটাবেসের যেকোনো বৈধ অবস্থায় S-এর entity-গুলোর সেট C-র entity-গুলোর সেটের উপসেট হয়: S ⊆ C. তখন C হলো S-এর superclass.
গুরুত্বপূর্ণ: subclass-এর একটি entity আর superclass-এর সংশ্লিষ্ট entity একই বাস্তব জিনিস — দুটি আলাদা মানুষ নন, একই মানুষকে দুটি ভূমিকায় দেখা। তাই তাদের key একই।
এই “একই জিনিস” থেকেই আসে EER-এর সবচেয়ে কাজের বৈশিষ্ট্য — attribute inheritance. Subclass-এর প্রতিটি entity superclass-এর সব attribute পায়, আর superclass যে সব relationship-এ অংশ নেয়, সেগুলোতেও অংশ নেয়। STUDENT-এর নিজের attribute মাত্র দুটি (batch, cgpa), কিন্তু প্রতিটি শিক্ষার্থীর person_id, name, phone-ও আছে, কারণ তিনি একজন PERSON. আর PERSON যেহেতু লাইব্রেরি থেকে বই ধার নেন (BORROWS), প্রতিটি STUDENT আর TEACHER-ও নেন — সম্পর্কটি একবারই আঁকা হয়।
যা উত্তরাধিকারে আসে
superclass-এর সব attribute, তার key, আর superclass যত relationship-এ আছে সেগুলো। গভীর hierarchy-তে পূর্বপুরুষদের সবারটাই আসে।
person_id, name, phone, BORROWSযা subclass-এর নিজের
specific (local) attribute আর specific relationship — যা শুধু ওই ধরনের entity-র ক্ষেত্রে অর্থপূর্ণ।
STUDENT: batch, cgpa · TEACHER: TEACHESযা সম্ভব নয়
superclass-এর অংশ নয় এমন subclass entity. প্রতিটি শিক্ষার্থীকে আগে মানুষ হতে হবে; উল্টোটি সত্য নয় — সব মানুষ শিক্ষার্থী নন।
STUDENT ⊆ PERSONSpecific relationship-এর উদাহরণটি আলাদা করে খেয়াল করুন। TEACHES (শিক্ষক কোর্স পড়ান) শুধু TEACHER-এর সাথে জোড়া, PERSON-এর সাথে নয়। এভাবে নকশা নিজেই বলে দেয় যে শিক্ষার্থী কোর্স পড়াতে পারেন না। এটিই প্রথম পথের (“সব attribute সহ একটি বড় PERSON”) সমস্যার সমাধান। আর BORROWS একবার PERSON-এ আঁকা — এটি দ্বিতীয় পথের (“তিনটি আলাদা entity”) সমস্যার সমাধান। EER দুই পথের ভালো দিক নেয়, খারাপ দিক ফেলে দেয়।
OOP-এর সাথে মিল আর অমিল: Java বা Python-এ class Student extends Person দেখে মনে হতে পারে একই জিনিস। মিলটি সত্যি — inheritance, “is-a” পরীক্ষা, সাধারণ অংশ একবার লেখা। কিন্তু অমিলও আছে। প্রোগ্রামে একটি object জন্ম থেকে মৃত্যু পর্যন্ত একটিই class-এর; ডেটাবেসে একজন মানুষ আজ শিক্ষার্থী, পরের বছর শিক্ষক, আর মাঝে দুটোই হতে পারেন — entity-র subclass-সদস্যপদ সময়ের সাথে বদলায়। আর EER-এ “method” বলে কিছু নেই; এখানে শুধু ডেটা আর নিয়ম।
৩ · Specialization আর generalization — উপর থেকে ভাগ, নিচ থেকে মিল
একটি superclass–subclass hierarchy-তে দুই দিক থেকে পৌঁছানো যায়। দুটি পথের ফল একই ছবি, কিন্তু চিন্তার দিক উল্টো।
- Specialization (top-down, উপর থেকে নিচে): একটি entity type দিয়ে শুরু করে তার ভেতরে বিশেষ উপদল খোঁজা। “PERSON-দের মধ্যে কারা কারা আলাদা বৈশিষ্ট্য রাখেন? শিক্ষার্থী আর শিক্ষক।” তারপর তাদের নিজস্ব attribute নিচে নামিয়ে দেওয়া।
- Generalization (bottom-up, নিচ থেকে উপরে): কয়েকটি আলাদা entity type দিয়ে শুরু করে তাদের মিল খোঁজা। “SAVINGS আর CURRENT দুটিরই হিসাব নম্বর, শাখা, ব্যালান্স আছে — তাহলে এরা দুজনেই ACCOUNT.” তারপর সাধারণ অংশ উপরে তুলে নতুন superclass বানানো।
প্রথমে specialization. নিচের ডায়াগ্রামে একটি PERSON entity থেকে ধাপে ধাপে hierarchy তৈরি হচ্ছে:
- শুরু: একটি PERSON, অনেক attribute. বিশ্ববিদ্যালয়ের সব মানুষ একটি entity type-এ: person_id (key, তাই নিচে দাগ), name, phone — আর batch, cgpa, salary, designation. শেষ চারটি দেখতে নিরীহ, কিন্তু প্রশ্ন করুন: প্রতিটি মানুষের কি এগুলো থাকে?
- লক্ষ করুন কার কোনটি লাগে। batch আর cgpa (নীল) শুধু শিক্ষার্থীর; salary আর designation (কমলা) শুধু শিক্ষকের। অর্থাৎ PERSON-এর ভেতরে দুটি উপদল লুকিয়ে আছে, যাদের নিজস্ব গুণ আছে। Specialization শুরু হয় ঠিক এই পর্যবেক্ষণ থেকে।
- উপদলগুলোকে subclass বানান। PERSON থেকে একটি দাগ নেমে একটি বৃত্তে যায়, বৃত্ত থেকে দুটি দাগ যায় STUDENT আর TEACHER-এ। এই বৃত্তটিই বলে “এটি একটি specialization”. STUDENT আর TEACHER এখন PERSON-এর subclass — প্রতিটি শিক্ষার্থী একজন PERSON, কিন্তু attribute-গুলো এখনো উপরে।
- Specific attribute নিচে নামান। batch আর cgpa এখন STUDENT-এর, salary আর designation TEACHER-এর। PERSON-এ থাকল শুধু সবার সাধারণ তিনটি: person_id, name, phone. একজন শিক্ষার্থীর এখনো person_id, name, phone আছে — উত্তরাধিকার সূত্রে, IS-A দিয়ে।
- Constraint লিখুন। একজন মানুষ একই সাথে শিক্ষার্থী আর শিক্ষক নন (এই নকশায়), তাই বৃত্তে d (disjoint). আবার অফিসের কর্মচারী জামাল সাহেব কোনোটিই নন, তিনি শুধু PERSON — তাই PERSON থেকে বৃত্তের দাগ একটি (partial). এই দুই সিদ্ধান্তের বিস্তারিত অংশ ৪-এ।
এবার উল্টো দিক — generalization. ধরুন একটি ব্যাংকের পুরোনো সিস্টেমে সঞ্চয়ী আর চলতি হিসাব দুটি আলাদা entity হিসেবে আঁকা, আর দুটিই CUSTOMER-এর সাথে HOLDS relationship-এ জোড়া। নিচে দেখুন কীভাবে মিল খুঁজে একটি superclass তৈরি হয়:
| entity type | |
| attribute যতবার আঁকা | |
| HOLDS relationship | |
| constraint |
- দুটি আলাদা entity type. SAVINGS_ACCOUNT-এর ৫টি আর CURRENT_ACCOUNT-এর ৫টি attribute — মোট ১০টি আঁকা। দুটিই CUSTOMER-এর সাথে HOLDS দিয়ে জোড়া, অর্থাৎ একই সম্পর্ক দুবার আঁকা।
- মিল খুঁজুন। acc_no, branch, balance, opened_on — চারটি attribute দুই জায়গাতেই, একই অর্থে। HOLDS-ও দুই জায়গায় একই অর্থে। শুধু interest_rate আর overdraft_limit আলাদা। যখন দেখবেন কয়েকটি entity type-এর key একই ধরনের আর অনেক attribute ও relationship মিলে যায়, সেটিই generalization-এর সংকেত।
- সাধারণ অংশ উপরে তুলুন। নতুন superclass ACCOUNT, তাতে চারটি সাধারণ attribute. SAVINGS আর CURRENT এখন তার subclass, প্রত্যেকে শুধু নিজের একটি attribute রাখে। আঁকা attribute ১০ থেকে ৬, আর HOLDS এখন একবারই — ACCOUNT-এর সাথে। “একজন গ্রাহকের সব হিসাব” এখন একটিই relationship.
- Constraint ঠিক করুন। ব্যাংকের নিয়মে প্রতিটি হিসাব ঠিক একটি ধরনের: কোনোটি একসাথে সঞ্চয়ী ও চলতি নয় (d, disjoint), আর ধরনহীন কোনো হিসাব নেই (দুই দাগ ║, total). ধরনটি একটি attribute থেকেই বোঝা যায়: acc_type. তাই এটি attribute-defined specialization, আর দাগের পাশে acc_type লেখা থাকে।
Specialization আর generalization-এর ফল একই ছবি, তাই অনেক বইয়ে দুটিকে একসাথে বলা হয় “specialization/generalization hierarchy”. পার্থক্য প্রক্রিয়ায়: নতুন নকশায় সাধারণত উপর থেকে ভাবা সহজ (specialization), আর কয়েকটি পুরোনো সিস্টেম বা বিভাগের খাতা মিলিয়ে একটি ডেটাবেস বানাতে গেলে নিচ থেকে (generalization). কিছু লেখক generalization-এর ক্ষেত্রে ধরে নেন যে সব entity কোনো না কোনো subclass-এ ছিল, তাই generalization সাধারণত total হয় — কারণ superclass-টি তো subclass-গুলো মিলিয়েই বানানো।
Hierarchy কত গভীর হতে পারে
একটি subclass নিজেও আবার specialize হতে পারে। STUDENT থেকে UNDERGRADUATE আর POSTGRADUATE; POSTGRADUATE থেকে MASTERS আর PHD. তখন একজন PHD শিক্ষার্থী উত্তরাধিকারে পান POSTGRADUATE-এর (thesis_title), STUDENT-এর (batch, cgpa) আর PERSON-এর (person_id, name, phone) সব attribute. যখন প্রতিটি subclass-এর ঠিক একটি superclass, তখন কাঠামোটি একটি গাছ — একে বলে specialization hierarchy. একটি subclass-এর একাধিক superclass থাকলে কী হয়, তা অংশ ৪-এর শেষে।
সব পার্থক্যই subclass নয়। ছেলে আর মেয়ে শিক্ষার্থীর জন্য দুটি subclass বানানোর দরকার নেই, যদি তাদের আলাদা কোনো attribute বা relationship না থাকে — একটি gender attribute যথেষ্ট। Subclass তখনই বানান, যখন উপদলটির নিজস্ব attribute, নিজস্ব relationship, বা আলাদা নিয়ম আছে। অকারণ subclass মানে অকারণ টেবিল আর JOIN.
৪ · Constraint — disjoint না overlapping, total না partial
Hierarchy আঁকা হলো। এবার দুটি প্রশ্নের উত্তর না দিলে নকশা অসম্পূর্ণ, কারণ ডেটাবেস কোন ডেটা মেনে নেবে আর কোনটি ফিরিয়ে দেবে, তা এই দুটি উত্তরই ঠিক করে।
Disjoint (d): একটি superclass entity সর্বোচ্চ একটি subclass-এ থাকতে পারে। subclass-গুলোর সেট পরস্পর-বিচ্ছিন্ন: STUDENT ∩ TEACHER = ∅.
Overlapping (o): একই entity একাধিক subclass-এ থাকতে পারে। একজন মানুষ একই সাথে STUDENT আর TA (teaching assistant) — দুটি ভূমিকা।
Total (দুই দাগ ║): superclass-এর প্রতিটি entity অন্তত একটি subclass-এ থাকতেই হবে: SAVINGS ∪ CURRENT = ACCOUNT.
Partial (এক দাগ │): কিছু entity কোনো subclass-এ না থেকে শুধু superclass-এ থাকতে পারে — অফিস কর্মচারী জামাল সাহেব শুধু PERSON.
দুটি প্রশ্ন একে অপরের থেকে স্বাধীন, তাই চারটি সমন্বয় সম্ভব। পর্ব ৮-এর participation constraint-এর সাথে total/partial-এর মিল লক্ষ করুন: সেখানে প্রশ্ন ছিল “প্রতিটি entity কি relationship-এ থাকতেই হবে?”, এখানে “প্রতিটি superclass entity কি কোনো subclass-এ থাকতেই হবে?” — চিহ্নও একই, দুই দাগ। নিচের ডায়াগ্রামে চারজন মানুষের সদস্যপদ দেখে দুটি প্রশ্নের উত্তর বের করুন:
| disjoint? | |
| total? | |
| ডায়াগ্রামে |
- ডেটা দেখুন। PERSON-এর তিনটি subclass: STUDENT, TEACHER আর TA (teaching assistant — যিনি ক্লাসে শিক্ষককে সাহায্য করেন, ল্যাব নেন)। চারজন মানুষের সদস্যপদ ✓ দিয়ে দেখানো। নিয়ম কী হওয়া উচিত, তা ঠিক হয় ব্যবসা থেকে; কিন্তু এমন নমুনা দিয়ে ভুল নিয়ম ধরা যায়।
- Disjoint পরীক্ষা: প্রতিটি সারিতে সর্বোচ্চ একটি ✓? Bristy-র সারিতে দুটি — তিনি শিক্ষার্থী, আবার একটি কোর্সের TA. বিশ্ববিদ্যালয়ে এটি স্বাভাবিক, নিয়মে বৈধ। তাই এই specialization disjoint হতে পারে না; বৃত্তে লিখতে হবে o (overlapping).
- Total পরীক্ষা: প্রতিটি সারিতে অন্তত একটি ✓? Jamal-এর সারিতে একটিও নেই — তিনি অফিস কর্মচারী, শুধু PERSON. বৈধ মানুষ, তাই specialization total নয়, partial: PERSON থেকে বৃত্তে এক দাগ। (চাইলে STAFF নামে আরেকটি subclass যোগ করে total বানানো যায় — সেটিও একটি নকশার সিদ্ধান্ত।)
- চারটি সমন্বয়। disjoint + total: প্রতিটি হিসাব ঠিক একটি ধরনের। disjoint + partial: শিক্ষার্থী বা শিক্ষক, কিংবা কোনোটিই না। overlapping + total: কারখানার প্রতিটি যন্ত্রাংশ নিজে বানানো বা কেনা, কিছু দুটোই (পাঠ্যবইয়ের পরিচিত PART উদাহরণ)। overlapping + partial: আমাদের PERSON → STUDENT / TEACHER / TA.
- Attribute-defined হলে disjoint বাধ্যতামূলক। যদি সদস্যপদ একটি attribute (যেমন ptype) দিয়ে ঠিক হয়, আর সেই attribute single-valued (প্রতি সারিতে একটি মান), তাহলে একজন একাধিক subclass-এ থাকতেই পারেন না। Bristy-কে এভাবে লেখা যায় না। Overlap দরকার হলে হয় user-defined সদস্যপদ, নয়তো প্রতিটি subclass-এর জন্য আলাদা হ্যাঁ/না flag (অংশ ৬)।
EER ডায়াগ্রামের চিহ্ন
পাঠ্যবই আর পরীক্ষায় সবচেয়ে প্রচলিত চিহ্নগুলো Elmasri–Navathe ধরনের। Superclass থেকে একটি দাগ একটি ছোট বৃত্তে যায়; বৃত্তের ভেতরে d বা o; বৃত্ত থেকে প্রতিটি subclass-এ একটি করে দাগ, যার মাঝে একটি ছোট ⊂ (subset) চিহ্ন — খোলা দিক superclass-এর দিকে, অর্থাৎ “এই subclass ওই superclass-এর উপসেট”। Superclass-বৃত্তের দাগ দুটি হলে total, একটি হলে partial. Attribute-defined হলে দাগের পাশে defining attribute-এর নাম।
একই ভাষার দুটি বাক্য
Disjoint + total (left) and overlapping + partial (right) in EER notation
acc_type. ডানে: একজন শিক্ষার্থী TA-ও হতে পারেন (o), আর এমন মানুষও আছেন যিনি কোনোটিই নন (এক দাগ = partial). প্রতিটি নিচের দাগে ছোট বাটির মতো চিহ্নটি ⊂ — খোলা দিক উপরের superclass-এর দিকে।বইভেদে চিহ্ন আলাদা। Silberschatz–Korth–Sudarshan-এর বইয়ে subclass থেকে superclass-এর দিকে ফাঁপা মাথার তীর আঁকা হয়, disjoint বোঝাতে তীরগুলো এক জায়গায় মিলিয়ে পাশে “disjoint” লেখা হয়, আর total বোঝাতে “total” শব্দ বসে। কোনো কোনো বই বৃত্তের বদলে উল্টো ত্রিভুজের ভেতরে “ISA” লেখে। UML class diagram-এ subclass থেকে superclass-এ ফাঁপা ত্রিভুজ-মাথার তীর, আর {disjoint, complete} বা {overlapping, incomplete} লেখা থাকে। পরীক্ষায় যে চিহ্নই ব্যবহার করুন, একটি ছোট legend দিয়ে দিন — অর্থ পরিষ্কার থাকলে নম্বর কাটে না।
Attribute-defined বনাম user-defined
একটি entity কোন subclass-এ যাবে, তা কে ঠিক করে? দুটি উপায়:
- Attribute-defined (বা predicate-defined): superclass-এর একটি attribute-এর মান দেখে সদস্যপদ ঠিক হয় —
acc_type = 'savings'হলে SAVINGS,'current'হলে CURRENT. একে বলে defining attribute; SQL-এ এটিই type বা discriminator কলাম হয়ে যায়। সুবিধা: DBMS নিজেই নিয়ম রক্ষা করতে পারে, কেউ ভুল subclass-এ ঢুকবে না। - User-defined: এমন কোনো attribute নেই; ব্যবহারকারী (বা অ্যাপ) প্রতিটি entity-কে হাতে হাতে subclass-এ রাখেন। “কে কোন কোর্সের TA হবেন” বিভাগীয় প্রধানের সিদ্ধান্ত, কোনো attribute-এর ফল নয়।
এই constraint-গুলো থেকে ডেটা বদলানোর কয়েকটি নিয়ম আপনাআপনি আসে: superclass থেকে একটি entity মুছলে সব subclass থেকেও মুছতে হয় (মানুষটিই না থাকলে শিক্ষার্থীও নেই)। Attribute-defined specialization-এ superclass-এ একটি entity ঢুকলে defining attribute-এর মান অনুযায়ী সে নিজে থেকেই সঠিক subclass-এর সদস্য। আর total specialization-এ superclass-এ ঢোকানো entity-কে অন্তত একটি subclass-এ ঢোকাতেই হবে — যা SQL-এ রক্ষা করা সবচেয়ে কঠিন, অংশ ৭-এ দেখবেন।
Shared subclass আর lattice — একাধিক superclass
এ পর্যন্ত প্রতিটি subclass-এর ঠিক একটি superclass. কিন্তু ধরুন বিশ্ববিদ্যালয়ের নকশায় PERSON-এর দুটি subclass: STUDENT আর EMPLOYEE (বেতনভোগী সবাই)। Teaching assistant একজন শিক্ষার্থী এবং একজন বেতনভোগী কর্মী — দুটিই একসাথে। তখন TEACHING_ASSISTANT-কে STUDENT আর EMPLOYEE দুটিরই subclass বানানো যায়। একে বলে shared subclass, আর কাঠামোটি আর গাছ থাকে না, হয়ে যায় একটি specialization lattice.
Shared subclass পায় multiple inheritance: STUDENT-এর batch, cgpa আর EMPLOYEE-এর salary — দুটিই। PERSON-এর attribute দুই পথে আসছে মনে হলেও আসে একবারই, কারণ দুই পথের মাথায় একই PERSON entity, একই মানুষ। প্রোগ্রামিং ভাষায় এই সমস্যার নাম “diamond problem”; EER-এ সমস্যা হয় না, কারণ entity একটিই, শুধু ভূমিকা একাধিক। আর মনে রাখুন শর্তটি: shared subclass-এর প্রতিটি সদস্য সব superclass-এর সদস্য — TA হতে হলে শিক্ষার্থী আর কর্মী দুটোই হতে হবে। অর্থাৎ shared subclass ⊆ STUDENT ∩ EMPLOYEE. পরের অংশের category-তে শর্তটি ঠিক উল্টো।
৫ · Category (union type) আর aggregation
Category — “এদের যেকোনো একজন”
বিআরটিএ-তে একটি গাড়ির রেজিস্ট্রেশনের কথা ভাবুন। গাড়ির মালিক হতে পারেন একজন ব্যক্তি (NID আছে), অথবা একটি কোম্পানি (ট্রেড লাইসেন্স আছে) — যেমন একটি বাস কোম্পানি। “মালিক” তাই একটি ভূমিকা, যা দুই সম্পূর্ণ আলাদা ধরনের entity নিতে পারে। PERSON আর COMPANY-কে কোনো সাধারণ superclass-এ তোলা অর্থহীন — তাদের key আলাদা, attribute আলাদা, জগৎ আলাদা। তবু OWNS relationship-এ আমরা বলতে চাই “একটি গাড়ির একজন OWNER”.
এর জন্য EER-এর হাতিয়ার category বা union type: একটি subclass, যার superclass একাধিক, আর যার প্রতিটি সদস্য সেই superclass-গুলোর অন্তত একটির সদস্য। OWNER ⊆ PERSON ∪ COMPANY. ডায়াগ্রামে superclass-গুলো থেকে দাগ একটি বৃত্তে যায়, বৃত্তে লেখা থাকে ∪ (union), আর বৃত্ত থেকে একটি দাগ category-তে।
OWNER — ব্যক্তি বা কোম্পানি
A category (union type): every OWNER is a PERSON or a COMPANY
| বিষয় | Shared subclass (lattice) | Category (union type) |
|---|---|---|
| সদস্যের শর্ত | সব superclass-এর সদস্য (∩) | অন্তত একটি superclass-এর সদস্য (∪) |
| উদাহরণ | TA ⊆ STUDENT ∩ EMPLOYEE | OWNER ⊆ PERSON ∪ COMPANY |
| উত্তরাধিকার | সব superclass-এর সব attribute | শুধু যে superclass-এর সদস্য, তার attribute |
| superclass-দের key | একই (সবাই একই PERSON) | সাধারণত আলাদা — তাই surrogate key লাগে |
| ডায়াগ্রামে বৃত্তে | প্রতিটি superclass-এর নিজস্ব specialization | ∪ |
শেষ সারির কথাটি টেবিলে আনার সময় গুরুত্বপূর্ণ: PERSON-এর key NID, COMPANY-র key ট্রেড লাইসেন্স নম্বর — VEHICLE-এর foreign key কোনটির দিকে আঙুল তুলবে? প্রচলিত সমাধান হলো OWNER-এর জন্য একটি নতুন surrogate key (owner_id), আর PERSON ও COMPANY দুটি টেবিলেই সেই owner_id foreign key হিসেবে রাখা। নিচের এডিটরে ঠিক এই নকশা: owner (owner_id, otype — 'P' ব্যক্তি, 'C' কোম্পানি), person_owner (owner_id, nid, name), company_owner (owner_id, trade_license, company_name) আর vehicle (reg_no, model, owner_id).
-- প্রতিটি গাড়ির মালিকের নাম: ব্যক্তি হলে name, কোম্পানি হলে company_name
SELECT v.reg_no, v.model, o.otype,
COALESCE(p.name, c.company_name) AS owner_name
FROM vehicle v
JOIN owner o ON o.owner_id = v.owner_id
LEFT JOIN person_owner p ON p.owner_id = o.owner_id
LEFT JOIN company_owner c ON c.owner_id = o.owner_id;ফল তিনটি সারি: DM-GA-11-2345 | Toyota Axio | P | Rahim Uddin, DM-BA-15-6789 | Hino Bus | C | Green Line Transport আর CM-LA-12-0042 | Honda CBR | P | Nusrat Jahan. প্রতিটি owner_id দুটি LEFT JOIN-এর ঠিক একটিতে মেলে, অন্যটিতে NULL আসে; COALESCE প্রথম non-NULL মানটি নেয় (পর্ব ১৬)। vehicle-এর foreign key একটিই — owner-এর দিকে — অথচ মালিক দুই রকমের হতে পারেন। এটিই category-র শক্তি। দুর্বলতাও আছে: owner 2-এর otype 'C', কিন্তু কেউ ভুল করে person_owner-এ owner_id 2 ঢোকালে এই নকশা আটকাবে না। সেটি ঠেকানোর কৌশল অংশ ৭-এ।
Aggregation — relationship-এর সাথে relationship
একটি সফটওয়্যার কোম্পানিতে কর্মীরা প্রজেক্টে কাজ করেন: EMPLOYEE —WORKS_ON— PROJECT, যেখানে WORKS_ON-এর নিজের attribute hours (সপ্তাহে কত ঘণ্টা)। এবার নতুন নিয়ম: প্রতিটি নিয়োগ — “রাকিব P1 প্রজেক্টে” — একজন ম্যানেজার তদারকি (monitor) করেন। তদারকিটি রাকিবের নয় (তিনি অন্য প্রজেক্টে অন্য ম্যানেজারের অধীনে থাকতে পারেন), P1-এরও নয় (P1-এর অন্য কর্মীদের অন্য ম্যানেজার থাকতে পারেন)। তদারকিটি রাকিব–P1 জোড়ার। সাধারণ ER-এ relationship শুধু entity-দের মধ্যে হয়; একটি relationship (WORKS_ON) আরেকটি relationship-এ (MONITORS) অংশ নিতে পারে না।
Aggregation এর সমাধান: WORKS_ON relationship-টিকে তার দুই entity সহ একটি বাক্সে ঘিরে একটি উচ্চতর স্তরের entity হিসেবে ধরা। তারপর সেই বাক্সটি MANAGER-এর সাথে MONITORS relationship-এ অংশ নেয়।
একটি নিয়োগকে তদারকি করা
Aggregation: the WORKS_ON relationship, boxed, takes part in MONITORS
কেন একটি ternary (তিন-পক্ষের) relationship MONITORS(EMPLOYEE, PROJECT, MANAGER) যথেষ্ট নয়? কারণ তাতে দুটি জিনিস হারায়। প্রথমত, এমন সারি ঢোকানো যাবে যেখানে কর্মী ওই প্রজেক্টে কাজই করেন না — তদারকি হবে এমন নিয়োগের, যার অস্তিত্ব নেই। দ্বিতীয়ত, WORKS_ON আর MONITORS-এর তথ্য একসাথে জড়িয়ে যায়: hours কোথায় থাকবে? Aggregation বলে, তদারকির শর্ত হলো নিয়োগটি আগে থাকা। টেবিলে এর মানে: monitors-এর foreign key একটি composite key, যা works_on-এর (emp_id, proj_id)-এর দিকে আঙুল তোলে। এই এডিটরে employee (emp_id, name), project (proj_id, title), works_on (emp_id, proj_id, hours) আর monitors (manager_id, emp_id, proj_id, since):
-- Mitu (3) P2-তে কাজ করেন: এই নিয়োগের তদারকি যোগ করা যায়
INSERT INTO monitors VALUES (1, 3, 'P2', '2025-10-01');
SELECT e.name, w.proj_id, w.hours, m.manager_id
FROM works_on w JOIN employee e ON e.emp_id = w.emp_id
LEFT JOIN monitors m ON m.emp_id = w.emp_id AND m.proj_id = w.proj_id;
-- Rakib (2) P2-তে কাজই করেন না: অস্তিত্বহীন নিয়োগের তদারকি
INSERT INTO monitors VALUES (1, 2, 'P2', '2025-10-05');প্রথম INSERT “✓ INSERT — ১টি সারি বদলেছে”। SELECT-এর ফলে তিনটি নিয়োগ: Rakib Hasan | P1 | 20 | 1, Mitu Rani | P1 | 10 | NULL আর Mitu Rani | P2 | 15 | 1 — Mitu-র P1 নিয়োগের এখনো কোনো তদারককারী নেই (NULL), কারণ তদারকি নিয়োগের, মানুষের নয়। শেষ INSERT-এ ত্রুটি: FOREIGN KEY constraint failed. (2, 'P2') জোড়াটি works_on-এ নেই, তাই তার তদারকিও হতে পারে না। monitors-এর primary key (emp_id, proj_id) হওয়ায় একটি নিয়োগের সর্বোচ্চ একজন তদারককারী — এটি MONITORS-এর cardinality (অনেক নিয়োগ : এক ম্যানেজার)। Aggregation টেবিলে আনার নিয়ম তাই এটুকুই: aggregated relationship-এর টেবিলের key (এখানে works_on-এর (emp_id, proj_id)) নতুন সম্পর্কের টেবিলে foreign key হয়ে যায়। ER থেকে টেবিলে যাওয়ার বাকি নিয়ম পর্ব ১০-এ।
৬ · EER থেকে টেবিল — inheritance আনার তিন কৌশল
Relational টেবিলে “subclass” বলে কিছু নেই — আছে শুধু টেবিল, কলাম, key আর constraint. তাই একটি superclass–subclass hierarchy-কে টেবিলে আনতে একটি সিদ্ধান্ত নিতে হয়। তিনটি প্রধান কৌশল আছে; Martin Fowler ২০০২ সালের বই “Patterns of Enterprise Application Architecture”-এ এদের যে নাম দেন, সেগুলোই আজ সবচেয়ে প্রচলিত:
১. প্রতি class-এ একটি টেবিল
Class table inheritance: person, student, teacher — তিনটি টেবিল। subclass টেবিলের primary key একই সাথে person-এর দিকে foreign key.
JOIN লাগে · NULL নেই২. প্রতি subclass-এ একটি পূর্ণ টেবিল
Concrete table inheritance: student আর teacher — প্রতিটিতে person-এর কলামগুলোও কপি। person নামে কোনো টেবিল নেই।
UNION লাগে · শুধু total-এ চলে৩. সবার জন্য একটি টেবিল
Single table inheritance: একটি person টেবিলে সব কলাম, সাথে একটি type কলাম (discriminator) যে বলে সারিটি কোন subclass-এর।
JOIN নেই · NULL অনেকএকই চারজন মানুষকে তিন কৌশলে রেখে তুলনা করুন — Arif আর Bristy শিক্ষার্থী, Dr. Anisur শিক্ষক, আর Jamal কোনো subclass-এ নেই (partial):
| টেবিল | |
| NULL ঘর | |
| পূর্ণ student পেতে JOIN | |
| Jamal-কে রাখা যায়? | |
| overlap সামলায়? |
- EER নকশা আর ডেটা। PERSON-এর দুটি subclass, disjoint আর partial. চারজন মানুষ: দুজন শিক্ষার্থী, একজন শিক্ষক, আর Jamal শুধু PERSON. এখন প্রতিটি কৌশলে এই চারজনকে রাখব আর গুনব — কয়টি টেবিল, কয়টি খালি ঘর, কয়টি JOIN.
- কৌশল ১ — প্রতি class-এ একটি টেবিল। person-এ চারজনই। student আর teacher-এ শুধু নিজস্ব কলাম, আর তাদের primary key person_id একই সাথে person-এর দিকে foreign key — subclass-এর সারি মানেই “এই মানুষটির বিশেষ পাতা”। কোনো NULL নেই, Jamal-ও আছেন। দাম: একজন শিক্ষার্থীর পূর্ণ তথ্য পেতে person আর student JOIN করতে হয়।
- কৌশল ২ — প্রতি subclass-এ একটি পূর্ণ টেবিল। person টেবিল নেই; name আর phone প্রতিটি subclass টেবিলে কপি। শিক্ষার্থীর সব তথ্য এক টেবিলেই, JOIN লাগে না। কিন্তু Jamal কোনো subclass-এর নন, তাই তাঁকে রাখার জায়গাই নেই। এই কৌশল শুধু total specialization-এ চলে।
- কৌশল ৩ — একটি টেবিল, একটি type কলাম। সবাই এক person টেবিলে, ptype বলে কে কী। JOIN নেই, Jamal-ও আছেন (ptype NULL). কিন্তু গুনে দেখুন: প্রথম তিনজনের দুটি করে ঘর খালি (৬টি), Jamal-এর ptype আর চারটি বিশেষ কলাম খালি (৫টি) — মোট ১১টি NULL. subclass যত বেশি, খালি ঘর তত বেশি।
- দুটি কোয়েরিতে তুলনা। “সবার নাম-ফোন” — কৌশল ১ আর ৩-এ এক টেবিল; কৌশল ২-এ দুই টেবিলের UNION ALL, আর তবুও Jamal বাদ। “শিক্ষার্থীদের পূর্ণ তথ্য” — কৌশল ১-এ JOIN, বাকি দুটিতে এক টেবিল। কোনো কৌশলই সব প্রশ্নে সেরা নয়; কোন প্রশ্ন বেশি আসে, তা দেখে বাছতে হয়।
- Overlap এলে কী হয়। ধরুন নিয়ম বদলে overlapping হলো, আর Bristy একটি ল্যাব কোর্সও পড়াবেন। কৌশল ১-এ teacher-এ শুধু একটি নতুন সারি — name, phone একবারই। কৌশল ২-এ Bristy-র name, phone teacher টেবিলে আবার কপি, ফোন বদলালে দুই জায়গায় বদলাতে হবে। কৌশল ৩-এ ptype-এ একটিই মান ধরে; লাগবে is_student, is_teacher ধরনের হ্যাঁ/না কলাম।
| বিষয় | ১. Class table | ২. Concrete table | ৩. Single table |
|---|---|---|---|
| টেবিল | superclass + প্রতিটি subclass | শুধু প্রতিটি subclass | একটি |
| NULL | নেই | নেই | অনেক — অন্য subclass-এর কলামে |
| একটি subclass-এর পূর্ণ সারি | JOIN লাগে | এক টেবিল | এক টেবিল + WHERE |
| সব superclass entity একসাথে | এক টেবিল | UNION ALL | এক টেবিল |
| subclass কলামে NOT NULL | সরাসরি লেখা যায় | সরাসরি লেখা যায় | লেখা যায় না — CHECK দিয়ে ঘুরিয়ে |
| “যেকোনো মানুষ”-এর দিকে FK (যেমন বই ধার) | সহজ — person-এ | কঠিন — একটি FK দুই টেবিলে যায় না | সহজ — person-এ |
| partial (শুধু superclass entity) | পারে | পারে না | পারে |
| overlapping | স্বাভাবিক | তথ্য কপি হয় | flag কলাম লাগে |
| disjointness রক্ষা | কৌশল লাগে (অংশ ৭) | কঠিন — id দুই টেবিলে ভাগ | type কলামেই রক্ষিত |
| নতুন subclass | নতুন টেবিল | নতুন টেবিল | ALTER TABLE ADD COLUMN |
| কোথায় মানায় | subclass-এর অনেক নিজস্ব কলাম বা relationship; overlap | total + disjoint; superclass ধরে প্রায় কখনো খোঁজা হয় না | অল্প subclass, অল্প নিজস্ব কলাম; দ্রুত পড়া জরুরি |
Elmasri–Navathe-এর বইয়ে এগুলোকে mapping-এর “Step 8”-এর বিকল্প হিসেবে লেখা হয়েছে: 8A (superclass আর subclass সবার আলাদা টেবিল — যেকোনো specialization-এ চলে), 8B (শুধু subclass-এর টেবিল — total হলে), 8C (একটি টেবিল, একটি type attribute — disjoint হলে), আর 8D (একটি টেবিল, প্রতি subclass-এর একটি হ্যাঁ/না type attribute — overlapping-এর জন্য)। পর্ব ১০-এ ER আর EER থেকে টেবিলে যাওয়ার পুরো নিয়ম ধাপে ধাপে দেখবেন; এখানে দেখুন কৌশল ৩ আর ১ আসল SQLite-এ কেমন চলে।
কৌশল ৩ হাতে-কলমে — একটি টেবিল, CHECK দিয়ে পাহারা
এই এডিটরের person টেবিলে সবাই: person_id, name, phone, ptype ('student' বা 'teacher'), আর চারটি nullable কলাম — batch, cgpa (শুধু শিক্ষার্থীর), salary, designation (শুধু শিক্ষকের)। Single table-এ subclass কলামে NOT NULL লেখা যায় না (শিক্ষকের batch তো থাকবেই না), তাই নিয়মটি একটি নাম দেওয়া CHECK-এ: subtype_columns — ptype 'student' হলে batch থাকতেই হবে আর শিক্ষকের কলাম খালি; 'teacher' হলে salary থাকতে হবে আর শিক্ষার্থীর কলাম খালি।
-- একটি টেবিলেই সবাই; ptype বলে কে কোন subclass
SELECT person_id, name, ptype, batch, cgpa, salary, designation
FROM person;
-- একটি subclass চাই? শুধু WHERE — কোনো JOIN নেই
SELECT person_id, name, batch, cgpa
FROM person WHERE ptype = 'student';প্রথম কোয়েরিতে চারটি সারি। Arif আর Bristy-র salary ও designation NULL, Dr. Anisur Rahman আর Shahana Parvin-এর batch ও cgpa NULL — মোট ৮টি খালি ঘর — stepper-এর কৌশল ৩-এর মতোই (সেখানে Jamal-এর ৫টি মিলিয়ে ছিল ১১টি; এই টেবিলে ptype NOT NULL, তাই Jamal-এর মতো কাউকে রাখাই যায় না)। দ্বিতীয় কোয়েরিতে দুটি সারি: 1 | Arif Hossain | 2021 | 3.75 আর 2 | Bristy Saha | 2021 | 3.4. খেয়াল করুন 3.40 দেখাচ্ছে 3.4 — REAL একটি সংখ্যা, শেষের শূন্য তার অংশ নয়। শিক্ষার্থীর তালিকা পেতে কোনো JOIN লাগেনি, এটিই single table-এর সবচেয়ে বড় সুবিধা। এবার দেখুন CHECK কীভাবে পাহারা দেয়:
-- নতুন শিক্ষক, শিক্ষার্থীর কলাম খালি: চলবে
INSERT INTO person (person_id, name, ptype, salary, designation)
VALUES (5, 'Tasnim Chowdhury', 'teacher', 65000, 'Lecturer');
-- শিক্ষকের সারিতে cgpa: subtype_columns ভাঙবে
INSERT INTO person (person_id, name, ptype, salary, cgpa)
VALUES (6, 'Kamrul Hasan', 'teacher', 90000, 3.2);প্রথম INSERT “✓ INSERT — ১টি সারি বদলেছে”। দ্বিতীয়টিতে ত্রুটি: CHECK constraint failed: subtype_columns. constraint-এর নাম দেওয়ায় বার্তাতেই বোঝা যায় কোন নিয়ম ভেঙেছে; নাম না দিলে SQLite পুরো শর্তটি বার্তায় ছাপে। এভাবে single table-এও subclass-এর নিয়ম রক্ষা করা যায়, কিন্তু খরচ আছে: প্রতিটি নতুন subclass বা নতুন কলামে এই CHECK আবার লিখতে হবে, আর পাঁচ-ছয়টি subclass হলে শর্তটি পড়াই কঠিন। ptype-এ 'staff' দিলে আটকাত কলামের নিজস্ব CHECK, ptype IN ('student', 'teacher').
কৌশল ১ হাতে-কলমে — তিন টেবিল, একই primary key
এখানে person (person_id, name, phone), student (person_id, batch, cgpa) আর teacher (person_id, salary, designation). student আর teacher-এর person_id একই সাথে primary key আর REFERENCES person(person_id) ON DELETE CASCADE — মানুষটি মুছলে তাঁর “বিশেষ পাতা”-ও যায়, অংশ ৪-এর মোছার নিয়মের মতো। person-এ পাঁচজন: Arif (1), Bristy (2) শিক্ষার্থী; Dr. Anisur (3), Shahana (4) শিক্ষক; আর Jamal Uddin (5) শুধু PERSON.
-- পূর্ণ শিক্ষার্থী-সারি: person-এর সাধারণ অংশ + student-এর বিশেষ অংশ
SELECT p.person_id, p.name, p.phone, s.batch, s.cgpa
FROM person p JOIN student s ON s.person_id = p.person_id;
-- সবাই, সাথে কে কোন subclass-এ (LEFT JOIN: না মিললে NULL)
SELECT p.person_id, p.name,
CASE WHEN s.person_id IS NOT NULL THEN 'student'
WHEN t.person_id IS NOT NULL THEN 'teacher'
ELSE '(none)' END AS role
FROM person p
LEFT JOIN student s ON s.person_id = p.person_id
LEFT JOIN teacher t ON t.person_id = p.person_id;প্রথম কোয়েরিতে দুটি সারি: 1 | Arif Hossain | 01711000101 | 2021 | 3.75 আর 2 | Bristy Saha | 01711000102 | 2021 | 3.4 — দুই টেবিলের অংশ জোড়া লাগিয়ে পুরো “শিক্ষার্থী” আবার তৈরি হলো। দ্বিতীয়টিতে পাঁচটি সারি: 1 আর 2 student, 3 আর 4 teacher, আর 5 | Jamal Uddin | (none). Jamal কোনো subclass-এ নেই — partial specialization-এ এটি বৈধ; total হলে এই কোয়েরিটিই হতো নিয়মভঙ্গ খোঁজার পরীক্ষা। LEFT JOIN আর CASE বিস্তারিত পর্ব ১৯ আর ১৬-এ। প্রতিবার এই JOIN লিখতে না চাইলে এটিকে একটি view-তে রাখা যায় (পর্ব ২৪)।
৭ · Overlap, disjointness আর completeness — SQL দিয়ে পাহারা
কৌশল ১ সবচেয়ে নমনীয়, কিন্তু তার নিজের কোনো মতামত নেই: student আর teacher দুটি স্বাধীন টেবিল, তাই একই person_id দুটিতেই ঢুকতে পারে। নিয়ম overlapping হলে এটিই চাই; disjoint হলে এটিই ঠেকাতে হবে। প্রথমে overlapping দিক। এই এডিটরে person, student আর ta (person_id, course_id, hours — কোন কোর্সে সপ্তাহে কত ঘণ্টা সহায়তা)। person-এ Arif (1), Bristy (2) আর Dr. Anisur (3); student-এ 1 আর 2; ta এখনো খালি:
-- Bristy (2) শিক্ষার্থী, এখন CSE101-এর TA-ও: overlapping-এ বৈধ
INSERT INTO ta VALUES (2, 'CSE101', 10);
-- যাঁরা দুই subclass-এই: দুটি JOIN
SELECT p.person_id, p.name, s.batch, t.course_id, t.hours
FROM person p
JOIN student s ON s.person_id = p.person_id
JOIN ta t ON t.person_id = p.person_id;INSERT “✓ INSERT — ১টি সারি বদলেছে”, আর SELECT-এ একটি সারি: 2 | Bristy Saha | 2021 | CSE101 | 10. Bristy-র নাম আছে একবারই, person-এ; শিক্ষার্থী আর TA দুই পরিচয় দুটি ছোট টেবিলে। ফোন নম্বর বদলালে বদলাতে হবে এক জায়গায়। এটিই কৌশল ১-এর overlap-এ শক্তি — stepper-এর শেষ ধাপে যা দেখেছেন। একই প্রশ্ন SELECT person_id FROM student INTERSECT SELECT person_id FROM ta দিয়েও করা যায় (পর্ব ২২)।
Disjointness ঠেকানো — type কলাম + composite foreign key
এবার উল্টো নিয়ম: একজন হয় শিক্ষার্থী, নয় শিক্ষক, দুটো নয়। সাধারণ CHECK এটি পারে না, কারণ CHECK শুধু একটি সারির ভেতরে দেখে, অন্য টেবিলে নয় (পর্ব ৬)। একটি পরিচিত কৌশল আছে, শুধু UNIQUE, CHECK আর foreign key দিয়ে:
- person-এ একটি type কলাম রাখুন (
ptype: 'S' বা 'T'), আরUNIQUE (person_id, ptype)দিন (UNIQUE constraint) — যাতে এই জোড়াটি foreign key-র লক্ষ্য হতে পারে। - প্রতিটি subclass টেবিলেও একটি ptype কলাম রাখুন, যার মান CHECK দিয়ে স্থির: student-এ সবসময় 'S', teacher-এ সবসময় 'T'.
- Subclass-এর foreign key হবে জোড়ায় (composite):
(person_id, ptype) REFERENCES person(person_id, ptype).
ফল: person-এ Arif-এর ptype 'S' হলে teacher টেবিলে তাঁর সারির জোড়া হবে (1, 'T') — যা person-এ নেই। Foreign key-ই disjointness রক্ষা করছে।
-- নতুন শিক্ষার্থী: আগে person-এ 'S', তারপর student-এ — চলবে
INSERT INTO person VALUES (2, 'Bristy Saha', 'S');
INSERT INTO student (person_id, batch) VALUES (2, 2021);
-- Arif (1) 'S' — তাঁকে teacher-এও ঢোকানোর চেষ্টা: বাতিল
INSERT INTO teacher (person_id, salary) VALUES (1, 50000);প্রথম দুটি INSERT সফল (প্রতিটিতে “✓ INSERT — ১টি সারি বদলেছে”)। তৃতীয়টিতে FOREIGN KEY constraint failed: teacher-এর ptype ডিফল্টে 'T', তাই জোড়া (1, 'T'), আর person-এ আছে শুধু (1, 'S'). কেউ চালাকি করে ptype = 'S' দিয়ে teacher-এ ঢোকাতে চাইলে আটকাবে teacher-এর CHECK (ptype = 'T'). দুই দিক থেকেই বন্ধ। দাম: প্রতিটি subclass টেবিলে একটি বাড়তি কলাম, যার মান সবসময় এক — বেশিরভাগ ডিজাইনার এই ছোট খরচ নিশ্চিন্তে দেন। আর একজন শিক্ষার্থী শিক্ষক হলে কী হবে? person-এর ptype বদলানোর আগে student-এর সারি মুছতে হবে — ভূমিকা বদল এখন একটি সচেতন, দুই ধাপের কাজ (একটি transaction-এ, পর্ব ৪৪)।
Completeness — total সবচেয়ে কঠিন
Total specialization বলে প্রতিটি person-এর অন্তত একটি subclass-সারি থাকবে। কিন্তু কৌশল ১-এ person-এর সারি আগে ঢোকাতেই হয় (subclass-এর foreign key তার দিকে আঙুল তোলে), তাই ঢোকানোর মাঝখানের মুহূর্তে নিয়মটি ভাঙা থাকবেই। এটি ঠেকানোর তিনটি পথ: PostgreSQL-এর মতো সিস্টেমে transaction-এর শেষে যাচাই করা deferred constraint বা trigger (পর্ব ২৯); সব ঢোকানো একটি stored procedure দিয়ে করানো (পর্ব ২৮); বা নিয়মিত একটি anti join কোয়েরি চালিয়ে নিয়মভঙ্গ খোঁজা — উপরের LEFT JOIN কোয়েরির (none) সারিগুলোই সেই নিয়মভঙ্গ। এজন্যই total + disjoint হলে অনেকে কৌশল ৩ বেছে নেন: ptype NOT NULL লিখলেই completeness রক্ষিত।
আরও গভীরে: SQL:1999 standard-এ structured user-defined type আর typed table যোগ হয়, যেখানে CREATE TABLE student OF student_t UNDER person ধরনের লেখায় DBMS নিজেই table hierarchy রক্ষা করে — একটি সারি একটি supertable-এর সদস্য হিসেবেও দেখা যায়। Oracle-এর object-relational অংশে CREATE TYPE … UNDER আর NOT FINAL দিয়ে type inheritance আছে, আর IBM Db2-ও typed table সমর্থন করে। তবু বাস্তবের বেশিরভাগ সিস্টেম এই তিন কৌশলের একটিই হাতে লেখে, কারণ সেগুলো সব DBMS-এ একইভাবে চলে, আর ORM-গুলো সেগুলোই বোঝে। Fowler-এর দেওয়া তিনটি নামই তাই ORM-এর documentation-এ ঘুরেফিরে আসে।
৮ · PostgreSQL-এর INHERITS আর ORM-এর inheritance
PostgreSQL-এ টেবিলেরই inheritance আছে: CREATE TABLE student (…) INHERITS (person) লিখলে student টেবিল person-এর সব কলাম পায়, আর SELECT * FROM person চালালে student-এর সারিগুলোও আসে। দেখতে ঠিক EER-এর মতো — কিন্তু সাবধান। নিচের ব্লক চালানো যায় না (এই পাতার রানার SQLite):
CREATE TABLE person (
person_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
phone TEXT
);
CREATE TABLE student (
batch INTEGER NOT NULL,
cgpa NUMERIC(3,2)
) INHERITS (person); -- student-এ person_id, name, phone-ও আছে
INSERT INTO student (person_id, name, batch) VALUES (1, 'Arif Hossain', 2021);
SELECT * FROM person; -- Arif-ও আসেন (child টেবিলসহ)
SELECT * FROM ONLY person; -- শুধু person-এর নিজের সারি
INSERT INTO person (person_id, name) VALUES (1, 'Someone Else'); -- আটকায় না!শেষ লাইনটিই সমস্যা। PostgreSQL-এর নিজের documentation এই সীমাবদ্ধতাগুলো স্পষ্ট লিখে রেখেছে:
- Index, UNIQUE আর PRIMARY KEY শুধু একটি টেবিলে খাটে। person-এর primary key student-এ উত্তরাধিকারে যায় না, আর দুই টেবিল জুড়ে person_id-র অনন্যতা কেউ রক্ষা করে না — তাই উপরে person_id 1 দুবার।
- Foreign key-ও উত্তরাধিকারে যায় না, আর child-এর সারি দেখে না। অন্য কোনো টেবিল
REFERENCES person(person_id)লিখলে সেটি student-এর সারি গ্রহণ করবে না — Arif-কে বই ধার দেওয়ার রেকর্ডই রাখা যাবে না। - যা উত্তরাধিকারে যায়: কলাম, NOT NULL আর CHECK constraint (যদি
NO INHERITনা লেখা হয়)।
PostgreSQL 10-এর আগে বড় টেবিলকে ভাগ করার (partitioning) কাজ এই inheritance দিয়েই করা হতো; ২০১৭ সালের PostgreSQL 10-এ আলাদা “declarative partitioning” আসে, আর এখন সেটিই সুপারিশকৃত পথ। EER-এর inheritance-এর জন্য INHERITS সাধারণত বেছে নেওয়া হয় না — অংশ ৬-এর তিন কৌশলই প্রচলিত।
ORM-এ একই তিন কৌশল
Java বা Python-এ ক্লাস হিসেবে Student extends Person লিখলে ORM (যে library object আর টেবিলের মধ্যে অনুবাদ করে) জানতে চায়: কোন কৌশলে টেবিল বানাব? নামগুলো ভিন্ন, কিন্তু ভেতরে সেই তিন কৌশলই:
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) // ডিফল্ট
@DiscriminatorColumn(name = "ptype") // না দিলে নাম DTYPE
public abstract class Person {
@Id Long id;
String name;
}
@Entity
@DiscriminatorValue("student")
public class Student extends Person {
Integer batch; // primitive int নয়: শিক্ষকের সারিতে কলামটি NULL
Double cgpa;
}
// InheritanceType.JOINED → কৌশল ১ (class table)
// InheritanceType.TABLE_PER_CLASS → কৌশল ২ (concrete table)from django.db import models
class Person(models.Model): # নিজের টেবিল আছে
name = models.CharField(max_length=100)
class Student(Person): # multi-table inheritance = কৌশল ১
batch = models.IntegerField() # student টেবিলে person_ptr_id → person
class Base(models.Model): # abstract base class = কৌশল ২
name = models.CharField(max_length=100)
class Meta:
abstract = True # Base-এর কোনো টেবিল হয় নাJPA (Java Persistence API)-তে ডিফল্ট SINGLE_TABLE; JOINED হলো class table, আর TABLE_PER_CLASS হলো concrete table — শেষেরটির সমর্থন JPA specification-এ ঐচ্ছিক, যদিও Hibernate তা দেয়। Django-তে একটি সাধারণ model থেকে subclass বানালে হয় multi-table inheritance: প্রতিটি Student পড়তে নীরবে person-এর সাথে JOIN হয়, যা অনেক নতুন Django ডেভেলপার জানেন না। abstract = True দিলে concrete table, আর proxy model একই টেবিলে শুধু Python-এর আচরণ বদলায়। Ruby on Rails-এর Active Record একটি type কলাম দেখলেই single table inheritance ধরে নেয়, আর SQLAlchemy তিনটিই সমর্থন করে। ORM-এর সুবিধা-ফাঁদ পর্ব ৫০-এ।
ORM-এর polymorphic কোয়েরি সাবধানে দেখুন। “সব Person দাও” — JOINED-এ ORM সাধারণত প্রতিটি subclass টেবিলের সাথে LEFT JOIN করে, TABLE_PER_CLASS-এ সব subclass টেবিলের UNION. দশটি subclass হলে একটি সরল লাইনের পেছনে দশটি JOIN বা দশটি UNION. ORM যে SQL পাঠায়, log চালু করে একবার পড়ে নিন।
৯ · AI-এর যুগে EER নকশা
AI-কে “একটি হাসপাতালের ডেটাবেস ডিজাইন করে দাও” বললে সে প্রায়ই খুব আত্মবিশ্বাসী inheritance-ভরা নকশা দেয়। কিছু ভুল বারবার দেখা যায়, আর এগুলো চোখে পড়ে না, কারণ টেবিলগুলো তৈরি হয়, ডেটাও ঢোকে:
| AI-এর ভুল | ফল | কীভাবে ধরবেন |
|---|---|---|
| disjoint না overlapping, বলেই না | একজন ডাক্তার রোগীও হলে নকশা ভেঙে পড়ে | জিজ্ঞেস করুন: “একজন কি একসাথে দুটি হতে পারেন?” — একটি বাস্তব উদাহরণ দিয়ে |
| single table, কিন্তু subclass নিয়মের CHECK নেই | শিক্ষকের সারিতে CGPA, শিক্ষার্থীর সারিতে বেতন | ভুল subclass-এর কলাম ভরে INSERT করে দেখুন |
| concrete table, অথচ partial specialization | কোনো subclass-এ না পড়া entity রাখার জায়গা নেই | “শুধু superclass” একটি entity ঢোকানোর চেষ্টা করুন |
| class table, কিন্তু subclass-এর PK আলাদা auto id | person আর student-এর সারি মেলানো যায় না | subclass-এর PK = person-এর দিকে FK কি না দেখুন |
| category-তে superclass-দের আলাদা key, surrogate নেই | একটি FK কোন টেবিলের দিকে, বোঝা যায় না | vehicle.owner_id কোন টেবিলের দিকে আঙুল তোলে? |
| অকারণ subclass (gender, city অনুযায়ী) | অপ্রয়োজনীয় টেবিল আর JOIN | প্রতিটি subclass-এর নিজস্ব attribute বা relationship আছে কি? |
| JOIN-এ INNER দিয়ে সব superclass খোঁজা | subclass-হীন entity (Jamal) ফল থেকে নীরবে বাদ | সারির সংখ্যা superclass টেবিলের সাথে মিলিয়ে দেখুন |
AI-কে নকশা চাওয়ার সময় সিদ্ধান্তগুলো আলাদা করে আর যুক্তিসহ চেয়ে নিন, যাতে প্রতিটি আপনি যাচাই করতে পারেন:
AI-এর দেওয়া EER নকশা হাতে পেলে — যাচাইয়ের চেকলিস্ট
পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:
- Specialization আর generalization-এর পার্থক্য? — একই hierarchy-র দুই দিক: specialization উপর থেকে ভাগ করে (top-down), generalization নিচ থেকে মিল খুঁজে superclass বানায় (bottom-up).
- Disjoint আর overlapping, total আর partial কী? — disjoint: সর্বোচ্চ একটি subclass, overlapping: একাধিক চলে; total: প্রত্যেকে অন্তত একটিতে, partial: কেউ কেউ কোনোটিতেই না।
- Shared subclass আর category-র পার্থক্য? — shared subclass-এর সদস্য সব superclass-এর সদস্য (∩), category-র সদস্য অন্তত একটির (∪)।
- Aggregation কখন লাগে? — যখন একটি relationship-কে (যেমন WORKS_ON) আরেকটি relationship-এ (MONITORS) অংশ নিতে হয়।
- Inheritance টেবিলে আনার কৌশলগুলো কী? — class table (JOIN, NULL নেই), concrete table (UNION, partial চলে না), single table (type কলাম, অনেক NULL).
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে ডায়াগ্রাম-আঁকিয়ে থেকে ডেটাবেস-ডিজাইনার বানাবে।
প্র ০১Chen-এর ১৯৭৬ সালের ER model এত জনপ্রিয় হলো, তবু কয়েক বছরের মধ্যেই তাকে “বাড়াতে” হলো কেন? Inheritance-এর ধারণা ডেটাবেসে এল কোথা থেকে?
ER model-এর শক্তি ছিল সরলতা: তিনটি ধারণা — entity, attribute, relationship — দিয়ে প্রায় যেকোনো ব্যবসা আঁকা যায়, আর ছবিটি ব্যবসার মানুষও বোঝেন। কিন্তু “এই জিনিসটি ওই জিনিসেরই একটি বিশেষ রূপ” — মানুষের এই স্বাভাবিক শ্রেণিবিন্যাসের চিন্তা (যানবাহন → মোটরগাড়ি → বাস) বলার উপায় ছিল না। ১৯৭০-এর দশকে কৃত্রিম বুদ্ধিমত্তার গবেষকেরা জ্ঞান উপস্থাপনের জন্য semantic network ব্যবহার করতেন, যেখানে “IS-A” সংযোগ ছিল সবচেয়ে মৌলিক সম্পর্ক। আর ১৯৬৭ সালের Simula 67 প্রোগ্রামিং ভাষায় class আর subclass এসে গিয়েছিল, যা পরে Smalltalk, C++ আর Java-র মাধ্যমে মূলধারায় আসে।
১৯৭৭ সালে John Miles Smith আর Diane Smith-এর “Database Abstractions: Aggregation and Generalization” প্রবন্ধ এই দুই চিন্তা ডেটাবেসে আনে: generalization (কয়েকটি ধরনকে একটি সাধারণ ধরনে তোলা) আর aggregation (কয়েকটি জিনিস ও তাদের সম্পর্ক মিলে একটি উচ্চতর জিনিস)। ১৯৮০-এর দশকজুড়ে “semantic data model” নামে অনেক প্রস্তাব আসে, যার অনেকগুলোই ER-এর সাথে মিশে যায়; Elmasri, Weeldreyer আর Hevner ১৯৮৫ সালে category (union type) যোগ করেন, আর Elmasri–Navathe-এর পাঠ্যবই পুরো সম্প্রসারিত রূপকে “EER” নামে বিশ্বজুড়ে শ্রেণিকক্ষে পৌঁছে দেয়।
মজার ব্যাপার হলো, relational model-এর টেবিলে inheritance কখনো স্বাভাবিকভাবে ঢোকেনি — SQL:1999-এর typed table বা PostgreSQL-এর INHERITS থাকা সত্ত্বেও বেশিরভাগ সিস্টেম আজও তিনটি হাতে-লেখা কৌশলের একটি ব্যবহার করে। অর্থাৎ EER মূলত চিন্তার হাতিয়ার: নকশার সময় সত্যটি স্পষ্ট করে, তারপর আমরা সচেতনভাবে ঠিক করি সেই সত্য টেবিলে কীভাবে রক্ষা হবে।
মূল উপলব্ধি: EER এসেছে মানুষের শ্রেণিবিন্যাস-চিন্তা, AI-এর IS-A আর OOP-এর class থেকে; এর কাজ নকশায় “কে কার বিশেষ রূপ” স্পষ্ট করা — টেবিলে তা রাখার পদ্ধতি আলাদা একটি সিদ্ধান্ত।
প্র ০২Single table inheritance এত সহজ আর দ্রুত হলে অনেক অভিজ্ঞ ডিজাইনার একে নিয়ে সতর্ক কেন? কখন এটিই সেরা পছন্দ?
Single table-এর আকর্ষণ বোঝা সহজ। একটি টেবিল, কোনো JOIN নেই, একটি সারি পড়লেই পুরো entity. Ruby on Rails-এর Active Record একটি type কলাম দেখলেই এই কৌশল ধরে নেয়, আর JPA-তেও এটিই ডিফল্ট। কয়েকটি subclass আর অল্প কয়েকটি নিজস্ব কলাম হলে এটি সত্যিই চমৎকার — যেমন একটি পেমেন্ট টেবিলে card, mobile_wallet, bank_transfer, যাদের নিজস্ব কলাম দু-একটি করে।
সমস্যা শুরু হয় সময়ের সাথে। প্রথম বছরে তিনটি subclass, পাঁচ বছর পরে বারোটি; প্রতিটির চার-পাঁচটি নিজস্ব কলাম। টেবিলটি হয়ে দাঁড়ায় ষাট কলামের, যার প্রতিটি সারিতে পঞ্চাশটি NULL. এর কয়েকটি বাস্তব দাম আছে। প্রথমত, NOT NULL আর অন্যান্য constraint সরাসরি লেখা যায় না, সব যায় একটি বিশাল CHECK-এ, যা কেউ পড়তে চায় না — ফলে অনেক দল CHECK-ই লেখে না, আর ভুল subclass-এর কলামে মান জমে। দ্বিতীয়ত, প্রতিটি নতুন subclass মানে পুরো টেবিলে ALTER TABLE ADD COLUMN; কিছু DBMS-এ বড় টেবিলে এটি সস্তা হলেও, schema-র মালিকানা নিয়ে দলগুলোর মধ্যে টানাপোড়েন বাড়ে। তৃতীয়ত, একটি subclass-এর relationship (শুধু শিক্ষক কোর্স পড়ান) foreign key দিয়ে আর ঠিকভাবে বলা যায় না: courses.instructor_id যেকোনো person-এর দিকে আঙুল তুলতে পারে, শিক্ষার্থীর দিকেও।
NULL-এর জায়গার খরচ নিয়ে ভয় প্রায়ই বাড়িয়ে বলা হয় — PostgreSQL-এ একটি NULL সারির null bitmap-এ মাত্র একটি bit নেয়। আসল সমস্যা জায়গা নয়, অর্থ আর নিয়ম।
তাই প্রচলিত পরামর্শ: subclass কম, নিজস্ব কলাম কম, subclass-নির্দিষ্ট relationship নেই, আর পড়া বেশি — single table. Subclass-এর নিজস্ব জগৎ বড় হলে class table. আর সিদ্ধান্তটি লিখে রাখুন, কারণ পরে বদলানো মানে ডেটা migration.
মূল উপলব্ধি: Single table-এর দাম জায়গায় নয়, হারানো constraint-এ — যা ডায়াগ্রামে স্পষ্ট ছিল (শুধু শিক্ষক পড়ান, শিক্ষকের বেতন বাধ্যতামূলক), টেবিলে তা CHECK আর trigger দিয়ে ফিরিয়ে আনতে হয়।
প্র ০৩Teaching assistant-কে কি PERSON-এর overlapping subclass হিসেবে আঁকবেন, নাকি STUDENT আর EMPLOYEE-এর shared subclass হিসেবে? আর OWNER-কে category না বানিয়ে PERSON আর COMPANY-র superclass বানালে কী ক্ষতি?
দুটি নকশাই বৈধ, কিন্তু তারা ভিন্ন সত্য বলে। TA যদি PERSON-এর overlapping subclass হয় (PERSON → STUDENT / EMPLOYEE / TA, বৃত্তে o), তাহলে নকশা বলছে: TA একটি আলাদা ভূমিকা, যা কেউ শিক্ষার্থী হয়ে বা না হয়েও নিতে পারেন। TA যদি STUDENT আর EMPLOYEE-এর shared subclass হয়, নকশা বলছে আরও কড়া কথা: TA হতে হলে অবশ্যই শিক্ষার্থী আর বেতনভোগী কর্মী দুটোই হতে হবে — TA ⊆ STUDENT ∩ EMPLOYEE. যদি বিশ্ববিদ্যালয়ের নিয়মই এমন হয় (শুধু ভর্তি থাকা শিক্ষার্থীরাই TA হন, আর TA পদ বেতনভুক্ত), shared subclass নিয়মটি ডায়াগ্রামেই ধরে, আর TA পান batch, cgpa, salary — সবকিছু উত্তরাধিকারে। টেবিলে এর মানে ta.person_id দুটি foreign key: student-এর দিকে আর employee-র দিকে। কোনো TA-র ছাত্রত্ব শেষ হলে student-এর সারি মোছা আটকে যাবে — নিয়মটিই আপনাকে জানাবে যে TA পদটিও শেষ করতে হবে।
এবার OWNER. PERSON আর COMPANY-কে একটি superclass (ধরুন LEGAL_ENTITY)-এর নিচে generalize করা যায় কি? যায়, কিন্তু তখন নকশা দাবি করে যে সব PERSON আর সব COMPANY একটি সাধারণ ধরনের সদস্য, আর তাদের একটি সাধারণ key আছে। অথচ আমাদের প্রয়োজন শুধু “যে গাড়ির মালিক”, আর ব্যক্তির NID আর কোম্পানির ট্রেড লাইসেন্সের মধ্যে কোনো সাধারণ attribute নেই। বেশিরভাগ ব্যক্তি গাড়ির মালিক নন, তবু সবাই LEGAL_ENTITY-তে ঢুকবেন। Category বলে ঠিক যতটুকু সত্য: মালিকেরা PERSON বা COMPANY-র একটি উপসেট, আর প্রতিজন উত্তরাধিকার পান শুধু নিজের superclass থেকে। বাস্তবে আইন ও হিসাবের সফটওয়্যারে উল্টো সিদ্ধান্তও দেখা যায় — “party” নামে একটি সাধারণ superclass, যেখানে ব্যক্তি আর প্রতিষ্ঠান দুজনেই আছে — কারণ সেখানে চুক্তি, ঠিকানা, যোগাযোগ সবকিছুই দুজনের জন্য একই। অর্থাৎ সঠিক উত্তর নির্ভর করে কতটুকু সাধারণ আচরণ আছে তার ওপর।
পরীক্ষার জন্য একটি সহজ পরীক্ষা মনে রাখুন: “এটি হতে হলে কি সবগুলো হতে হবে, নাকি যেকোনো একটি হলেই চলে?” সবগুলো হলে shared subclass, যেকোনো একটি হলে category.
মূল উপলব্ধি: Shared subclass হলো ছেদ (∩), category হলো সংযোগ (∪); কোনটি আঁকবেন তা ঠিক করে ব্যবসার নিয়ম, আর ভুলটি আঁকলে নকশা হয় অবৈধ ডেটা মেনে নেয়, নয়তো বৈধ ডেটা ফিরিয়ে দেয়।
প্র ০৪PostgreSQL-এর INHERITS দেখতে EER-এর হুবহু রূপ। তাহলে বেশিরভাগ PostgreSQL ব্যবহারকারী inheritance-এর জন্য একে এড়িয়ে চলেন কেন?
PostgreSQL-এর জন্ম Berkeley-র POSTGRES প্রকল্প থেকে, যার লক্ষ্যই ছিল relational ডেটাবেসে object-ধারণা যোগ করা — তাই “object-relational” পরিচয়, আর টেবিলের inheritance সেই উত্তরাধিকারের অংশ। কাগজে এটি চমৎকার: CREATE TABLE student (…) INHERITS (person), আর SELECT * FROM person সব মানুষ দেখায়, শিক্ষার্থীসহ। ঠিক EER-এর IS-A.
সমস্যা লুকিয়ে আছে constraint-এ, আর PostgreSQL documentation নিজেই একে “গুরুতর সীমাবদ্ধতা” বলে লিখেছে: index — আর তাই UNIQUE ও PRIMARY KEY — আর foreign key শুধু একটি টেবিলে খাটে, child টেবিলে নয়। ফল তিনটি। এক, person_id-র অনন্যতা পুরো hierarchy জুড়ে কেউ রক্ষা করে না: person-এ আর student-এ একই id দুবার থাকতে পারে, আর SELECT * FROM person দুটিই দেখাবে। দুই, অন্য টেবিল যদি REFERENCES person(person_id) লেখে, সেটি student-এর সারি দেখবে না — অর্থাৎ “যেকোনো মানুষকে বই ধার দেওয়া” নিয়মটি শিক্ষার্থীদের জন্য কাজ করবে না, যা EER-এর মূল প্রতিশ্রুতির উল্টো। তিন, student-এর জন্য primary key আলাদা করে ঘোষণা করতে হয়, আর তাতেও দুই টেবিল জুড়ে অনন্যতা আসে না।
INHERITS-এর সফল ব্যবহার ছিল partitioning: বড় লেনদেন-টেবিলকে মাসভিত্তিক child টেবিলে ভাগ করা, প্রতিটিতে CHECK দিয়ে তারিখের সীমা। ২০১৭ সালে PostgreSQL 10-এ আলাদা declarative partitioning আসায় সেই ব্যবহারও সরে যায়।
তাই আজ প্রচলিত পথ: EER-এর inheritance-এর জন্য class table বা single table, সাধারণ PRIMARY KEY, FOREIGN KEY আর CHECK দিয়ে — যেগুলো সব DBMS-এ একইভাবে চলে, আর ORM-গুলোও বোঝে।
মূল উপলব্ধি: একটি বৈশিষ্ট্য দেখতে মডেলের মতো হলেই যথেষ্ট নয়; প্রশ্ন হলো সে মডেলের constraint রক্ষা করে কি না। INHERITS কলাম উত্তরাধিকার দেয়, কিন্তু key আর foreign key দেয় না — আর ডেটাবেসের আসল মূল্য ঠিক ওই constraint-এ।
প্র ০৫Aggregation ছাড়া কর্মী–প্রজেক্ট–ম্যানেজারের তদারকি আঁকার আর কী উপায় আছে? Ternary relationship বা নতুন একটি entity বানালে কী হারায়, কী পাওয়া যায়?
তিনটি বিকল্প তুলনা করি। বিকল্প ১ — ternary relationship MONITORS(EMPLOYEE, PROJECT, MANAGER). সমস্যা: এতে কোনো নিয়ম নেই যে কর্মীটি ওই প্রজেক্টে আসলেই কাজ করেন। “রাকিব P2-তে, ম্যানেজার সাদিয়া” — অথচ রাকিব P2-তে নেই — এমন সারি দিব্যি ঢোকে। আর WORKS_ON আর MONITORS-এ কর্মী–প্রজেক্ট জোড়া দুবার লেখা থাকে; একটি নিয়োগ শেষ হলে দুই জায়গা থেকে মুছতে হয়, একটি ভুলে গেলেই অসংগতি।
বিকল্প ২ — নিয়োগকে একটি entity বানানো: ASSIGNMENT নামে একটি entity, যার নিজস্ব id (assignment_id), আর EMPLOYEE ও PROJECT-এর সাথে দুটি relationship; তারপর MANAGER —MONITORS— ASSIGNMENT. এটি সম্পূর্ণ বৈধ, আর বাস্তবে খুব প্রচলিত — বিশেষত যখন নিয়োগের নিজস্ব জীবন আছে: শুরু-শেষের তারিখ, ভূমিকা, বিল। দাম হলো, এখন (emp_id, proj_id) জোড়ার অনন্যতা আলাদা UNIQUE দিয়ে রক্ষা করতে হয়, আর ডায়াগ্রামে “নিয়োগ আসলে একটি সম্পর্ক” — এই সত্যটি একটু আড়ালে চলে যায়।
বিকল্প ৩ — aggregation: WORKS_ON সম্পর্কটিই থাকে, তাকে বাক্সে ঘিরে MONITORS-এ অংশ নেওয়ানো হয়। টেবিলে তা দাঁড়ায় ঠিক উপরের এডিটরের মতো: monitors-এর composite foreign key (emp_id, proj_id) → works_on. অস্তিত্বহীন নিয়োগের তদারকি অসম্ভব, জোড়া একবারই সংজ্ঞায়িত, আর ডায়াগ্রাম স্পষ্ট বলে “তদারকি একটি নিয়োগের”।
লক্ষ করুন, বিকল্প ২ আর ৩ টেবিলে প্রায় একই চেহারা নেয় — পার্থক্য শুধু works_on-এর key: composite (emp_id, proj_id) নাকি surrogate assignment_id. Silberschatz–Korth–Sudarshan-এর বই একই ধরনের উদাহরণে (শিক্ষক, শিক্ষার্থী, প্রজেক্ট আর মূল্যায়ন) aggregation দেখায়, আর UML-এ একই চিন্তা প্রকাশ পায় “association class” দিয়ে — একটি association, যার নিজেরই class-এর মতো attribute আর সম্পর্ক থাকতে পারে।
মূল উপলব্ধি: Aggregation বলে “এই সম্পর্কটিই একটি জিনিস, যার ওপর আরেকটি সম্পর্ক দাঁড়ায়”; এটি ternary relationship-এর লুকোনো অসংগতি দূর করে, আর টেবিলে একটি composite foreign key হয়ে সেই নিয়ম রক্ষা করে।
অনুশীলন
খাতা-কলম নিন। একটি নকশা, একটি কোয়েরি লেখা, একটি ফল আগে বলা, একটি কৌশল বাছাই।
-
নকশা করুন: একটি হাসপাতালে মানুষ চার রকম: ডাক্তার (বিশেষজ্ঞতা, BMDC রেজিস্ট্রেশন নম্বর), নার্স (শিফট), রোগী (রক্তের গ্রুপ, ভর্তির তারিখ) আর অফিস কর্মচারী। একজন ডাক্তার অসুস্থ হলে নিজের হাসপাতালেই রোগী হতে পারেন। একটি EER hierarchy আঁকুন, আর প্রতিটি specialization-এর d/o ও total/partial যুক্তিসহ লিখুন।
একটি ভালো উত্তর: PERSON (person_id, name, phone, date_of_birth) থেকে দুটি আলাদা specialization. প্রথমটি কর্মীদের জন্য: PERSON → EMPLOYEE (join_date, salary), তারপর EMPLOYEE → DOCTOR (specialty, bmdc_no) / NURSE (shift) / OFFICE_STAFF — disjoint (একজন একসাথে ডাক্তার আর নার্স নন), total (প্রত্যেক কর্মী এই তিনটির একটি)। দ্বিতীয়টি: PERSON → PATIENT (blood_group, admitted_on). এখানে মূল প্রশ্ন EMPLOYEE আর PATIENT-এর সম্পর্ক — একজন ডাক্তার রোগীও হতে পারেন, তাই এক বৃত্তে রাখলে সেটি overlapping; আর রোগীর আত্মীয় বা দর্শনার্থীও PERSON হিসেবে থাকতে পারেন, তাই partial.
অর্থাৎ PERSON —(o, partial)→ EMPLOYEE, PATIENT; EMPLOYEE —(d, total)→ DOCTOR, NURSE, OFFICE_STAFF.
-
কোয়েরি লিখুন: class table কৌশলে তিনটি টেবিল: person (person_id, name, phone), student (person_id, batch, cgpa), teacher (person_id, salary, designation). নিয়ম বদলে specialization total করা হলো। কোন কোন person এই নতুন নিয়ম ভাঙছেন, তা খুঁজে বের করার কোয়েরি লিখুন। অংশ ৬-এর class table এডিটরের ডেটায় ফল কী আসবে?
SELECT p.person_id, p.name FROM person p WHERE NOT EXISTS (SELECT 1 FROM student s WHERE s.person_id = p.person_id) AND NOT EXISTS (SELECT 1 FROM teacher t WHERE t.person_id = p.person_id);ফল একটি সারি:
5 | Jamal Uddin. একই কাজ LEFT JOIN দিয়েও হয়: দুটি LEFT JOIN করেWHERE s.person_id IS NULL AND t.person_id IS NULL— এটিই পর্ব ২০-এর anti join. এই কোয়েরি নিয়মিত চালালে total নিয়মের লঙ্ঘন ধরা পড়ে, যদিও ঠেকানো যায় না; ঠেকাতে trigger বা transaction-এর শেষে যাচাই লাগে। -
ফল আগে বলুন (trace): single table কৌশলের person টেবিল:
ptypeNOT NULL আরCHECK (ptype IN ('student', 'teacher')), সাথে নাম দেওয়া CHECKsubtype_columns— 'student' হলে batch থাকবে আর salary ও designation NULL; 'teacher' হলে salary থাকবে আর batch ও cgpa NULL. ক্রমানুসারে চারটি INSERT (কলাম: person_id, name, ptype, batch, cgpa, salary, designation): (ক)(7, 'Nadia', 'student', 2024, NULL, NULL, NULL)(খ)(8, 'Omar', 'teacher', NULL, NULL, NULL, 'Lecturer')(গ)(9, 'Pavel', 'staff', NULL, NULL, NULL, NULL)(ঘ)(10, 'Rupa', 'teacher', NULL, NULL, 70000, NULL). কোনগুলো ঢুকবে, কোনগুলো কোন constraint-এ আটকাবে?(ক) ঢুকবে। ptype 'student', batch আছে, salary ও designation NULL. cgpa NULL হলেও চলে — নিয়মে শিক্ষার্থীর cgpa বাধ্যতামূলক নয় (নতুন ভর্তি, এখনো ফল আসেনি)।
(খ) আটকাবে — subtype_columns. 'teacher'-এর জন্য salary NOT NULL চাই, কিন্তু salary NULL. বার্তা:
CHECK constraint failed: subtype_columns.(গ) আটকাবে — ptype-এর কলাম CHECK. 'staff' তালিকায় নেই। subtype_columns-এর দুটি শাখাও মিথ্যা, তবে এই পাতার SQLite বার্তা দেয় আগে যাচাই হওয়া কলাম-CHECK থেকে:
CHECK constraint failed: ptype IN ('student', 'teacher'). অন্য DBMS অন্য constraint-এর নাম দেখাতে পারে — যেটিই আসুক, সারিটি ঢুকবে না।(ঘ) ঢুকবে। salary আছে, batch ও cgpa NULL. designation NULL — নিয়মে শিক্ষকের পদবি বাধ্যতামূলক করা হয়নি। চাইলে CHECK-এ
designation IS NOT NULLযোগ করতে পারেন। শেষে নতুন দুটি সারি: Nadia আর Rupa. চাইলে অংশ ৬-এর দ্বিতীয় single table এডিটরে লিখে মিলিয়ে নিন। -
কৌশল বাছুন: একটি ব্যাংকে ACCOUNT → SAVINGS (interest_rate) / CURRENT (overdraft_limit), disjoint আর total. transactions টেবিলের প্রতিটি লেনদেন একটি হিসাবের দিকে foreign key দিয়ে আঙুল তোলে, আর দিনের শেষে “সব হিসাবের মোট ব্যালান্স” রিপোর্ট চলে। তিন কৌশলের কোনটি বেছে নেবেন, কোনটি কখনোই নয়, আর কেন?
কখনোই নয় — concrete table. total আর disjoint হওয়ায় এটি সম্ভব ছিল, কিন্তু transactions.account_id-কে savings আর current দুই টেবিলের দিকে একটি foreign key দিয়ে বাঁধা যায় না; আর দুই টেবিলে একই acc_no থাকা ঠেকানোও কঠিন। “সব হিসাবের মোট ব্যালান্স”-ও প্রতিবার UNION ALL.
সেরা — single table, কারণ subclass মাত্র দুটি, প্রতিটির নিজস্ব কলাম একটি করে; acc_type NOT NULL আর
CHECK (acc_type IN ('savings', 'current'))দিলেই disjoint আর total দুটিই রক্ষিত, আর একটি CHECK দিয়ে নিশ্চিত করা যায় সঞ্চয়ী হিসাবে interest_rate থাকবে ও overdraft_limit NULL থাকবে। transactions-এর foreign key একটি টেবিলের দিকে, রিপোর্ট এক টেবিলে।class table-ও ভালো, বিশেষত যদি ভবিষ্যতে subclass-গুলোর অনেক নিজস্ব কলাম আর relationship আসে (যেমন চলতি হিসাবের চেক বই)। তখন disjointness রক্ষায় অংশ ৭-এর type কলাম + composite foreign key কৌশল লাগবে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমকয়েকটি আলাদা entity type থেকে generalization করে টেবিল পর্যন্ত যাওয়ার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- আলাদা entity type-গুলো আর তাদের attribute ও relationship তালিকা করা
- কোন attribute আর relationship সবার মধ্যে একই অর্থে আছে, তা খুঁজে বের করা
- নতুন superclass বানিয়ে সাধারণ অংশ উপরে তোলা, বিশেষ অংশ subclass-এ রাখা
- disjoint/overlapping আর total/partial ঠিক করা
- NULL, JOIN আর constraint দেখে টেবিলে আনার কৌশল বাছাই করা
একটি বিশ্ববিদ্যালয়ের নকশায় PERSON-এর দুটি subclass STUDENT আর TEACHING_ASSISTANT, আর একজন মানুষ একই সাথে দুটিই হতে পারেন। এই specialization-কে কী বলে?
- Disjoint
- Overlapping
- Total
- Category
Elmasri–Navathe ধরনের EER ডায়াগ্রামে superclass থেকে specialization-এর বৃত্ত পর্যন্ত দুটি সমান্তরাল দাগ কী বোঝায়?
- Specialization-টি disjoint
- Specialization-টি overlapping
- Specialization-টি total — প্রতিটি superclass entity অন্তত একটি subclass-এ
- Specialization-টি attribute-defined
একটি গাড়ির মালিক (OWNER) হতে পারেন একজন ব্যক্তি অথবা একটি কোম্পানি। OWNER-কে PERSON আর COMPANY-র category (union type) হিসেবে আঁকলে কোন কথাটি সত্য?
- প্রতিটি OWNER হয় একজন PERSON, নয়তো একটি COMPANY
- প্রতিটি OWNER একই সাথে PERSON আর COMPANY দুটিই
- প্রতিটি PERSON আর প্রতিটি COMPANY অবশ্যই OWNER
- প্রতিটি OWNER-এর NID আর ট্রেড লাইসেন্স দুটিই থাকে
একটি superclass–subclass hierarchy-কে single table কৌশলে (একটি টেবিল + type কলাম) রাখার প্রধান দুর্বলতা কোনটি?
- একটি subclass-এর সারি পড়তে অনেক JOIN লাগে
- শুধু superclass-এর entity (কোনো subclass-এ নেই) রাখা যায় না
- অন্য টেবিল থেকে foreign key দেওয়া যায় না
- অনেক NULL জমে, আর subclass-এর কলামে সরাসরি NOT NULL দেওয়া যায় না
PERSON → STUDENT / TEACHER specialization partial (কিছু মানুষ কোনো subclass-এ নেই)। কোন টেবিল-কৌশল এখানে ব্যবহার করা যায় না?
- প্রতি class-এ একটি টেবিল (class table)
- প্রতি subclass-এ একটি পূর্ণ টেবিল (concrete table)
- একটি টেবিল + type কলাম (single table)
- তিনটির কোনোটিই নয়
EMPLOYEE আর PROJECT-এর WORKS_ON relationship আছে। নতুন নিয়ম: প্রতিটি কর্মী–প্রজেক্ট নিয়োগ একজন ম্যানেজার তদারকি করেন, আর তদারকি শুধু বিদ্যমান নিয়োগেরই হতে পারে। EER-এ এটি আঁকার সবচেয়ে সঠিক উপায় কোনটি?
- MANAGER আর EMPLOYEE-এর মধ্যে একটি MONITORS relationship
- EMPLOYEE, PROJECT আর MANAGER-এর একটি ternary MONITORS relationship
- WORKS_ON-কে aggregation দিয়ে ঘিরে MANAGER-এর সাথে MONITORS relationship
- MANAGER-কে PROJECT-এর subclass বানানো
PostgreSQL-এ CREATE TABLE student (batch INTEGER) INHERITS (person) লেখা হলো, যেখানে person-এর person_id একটি PRIMARY KEY. কোন কথাটি সত্য?
- person_id-র অনন্যতা person আর student দুই টেবিল জুড়ে রক্ষিত হয় না
- student-এ person-এর কলামগুলো থাকে না
- SELECT * FROM person কখনো student-এর সারি দেখায় না
- person-এর দিকে করা foreign key student-এর সারিও গ্রহণ করে
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Subclass ⊆ superclass (IS-A): subclass-এর entity আর superclass-এর entity একই বাস্তব জিনিস, তাই key একই, আর সব attribute ও relationship উত্তরাধিকারে আসে।
- Specialization উপর থেকে ভাগ করে, generalization নিচ থেকে মিলিয়ে তোলে; subclass তখনই, যখন নিজস্ব attribute বা relationship আছে।
- বৃত্তে d / o (একটি না একাধিক subclass), দাগে দুই / এক (total না partial); single-valued attribute-defined specialization সবসময় disjoint.
- Shared subclass = সব superclass-এর সদস্য (∩), category = অন্তত একটির (∪); aggregation = relationship-কে entity ধরে আরেকটি relationship.
- টেবিলে তিন কৌশল: class table (JOIN), concrete table (UNION, শুধু total), single table (type কলাম, NULL, CHECK) — disjointness রাখতে type + composite FK.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১০ · ER থেকে টেবিল — mapping-এর নিয়মপরবর্তী পর্বER আর EER-এর প্রতিটি অংশ টেবিলে আনার পূর্ণ নিয়ম।
- পর্ব ০৮ · Cardinality, participation আর weak entityআগের পর্বtotal/partial participation — specialization-এর completeness-এর আত্মীয়।
- ডেটাবেস (DBMS) ও SQL · কোর্স হোম৬৭টি পর্বপুরো কোর্সের রোডম্যাপ — ১০টি মডিউল।
- Java · Inheritanceপাশের ট্র্যাকপ্রোগ্রামের class inheritance — EER-এর IS-A-র সাথে মিল আর অমিল।
- Java · Spring Data JPAহাতে-কলমেJPA-র SINGLE_TABLE, JOINED আর TABLE_PER_CLASS বাস্তবে।
- Python · SQLAlchemy ORMহাতে-কলমেPython থেকে টেবিল আর object-এর অনুবাদ।