এই পর্বে যা শিখবেন
- কেন টেবিল বানানোর আগে conceptual ডিজাইন করতে হয়, আর Peter Chen-এর ER মডেল কোথা থেকে এল
- Entity, entity type আর entity set-এর পার্থক্য; attribute-এর সব প্রকার, উদাহরণ আর চিহ্নসহ
- Relationship, relationship set, role name আর relationship-এর নিজের attribute (যেমন গ্রেড)
- Unary, binary আর ternary relationship — আর ternary ভেঙে ফেললে কীভাবে ভুয়া সারি জন্মায়
- একটি চাহিদার বর্ণনা থেকে ধাপে ধাপে ER ডায়াগ্রাম আঁকা, পড়া, আর সাধারণ ভুলগুলো এড়ানো
১ · ডিজাইন আগে, টেবিল পরে — ER মডেল কেন
ধরুন মিরপুরের একটি কোচিং সেন্টারের মালিক আপনাকে ফোন করে বললেন, “ভাই, আমাদের জন্য একটা ডেটাবেস বানিয়ে দেন।” আপনি সোজা ল্যাপটপ খুলে CREATE TABLE students লিখতে শুরু করলেন। দুই সপ্তাহ পরে জানা গেল: একজন শিক্ষার্থী একসাথে তিনটি ব্যাচে পড়েন, প্রত্যেক ব্যাচের ফি আলাদা, অভিভাবকের দুটি ফোন নম্বর রাখতে হয়, আর একজন শিক্ষক বদলি হলে তাঁর ব্যাচ অন্যজনকে দিতে হয়। আপনার টেবিলে এর কোনোটিই ধরে না। এখন টেবিল ভাঙা, ডেটা সরানো, অ্যাপের কোড বদলানো — সব নতুন করে।
হাসপাতালের ক্ষেত্রে ভুলের দাম আরও বেশি। “রোগী” বলতে হাসপাতাল কী বোঝায় — একজন মানুষ, নাকি একবারের ভর্তি? একই রোগী তিনবার ভর্তি হলে কি তিনটি রেকর্ড? একজন রোগীকে কি একাধিক ডাক্তার দেখেন? এই প্রশ্নগুলোর উত্তর না জেনে টেবিল বানালে ভুলটা ধরা পড়ে মাসখানেক পরে, যখন কয়েক হাজার রেকর্ড জমে গেছে।
বাড়ি বানানোর আগে রাজমিস্ত্রি ইট গাঁথেন না, আগে স্থপতি নকশা আঁকেন। নকশা কাগজে, তাই বদলানো সস্তা: “রান্নাঘরটা উত্তর দিকে নিন” বললে শুধু কয়েকটা দাগ মুছতে হয়। ছাদ ঢালাইয়ের পরে একই কথা বললে দেয়াল ভাঙতে হয়। ডেটাবেসে ER ডায়াগ্রাম হলো সেই নকশা — মালিকের সাথে বসে বোঝা যায়, ভুল ধরা যায়, আর তখনো একটি টেবিলও বানাতে হয়নি।
তাই পেশাদার ডিজাইন সাধারণত তিন ধাপে চলে (পর্ব ৪-এ এই স্তরগুলোর নাম দেখেছেন):
- Requirements analysis: যাঁরা ডেটা ব্যবহার করবেন (মালিক, অফিস-সহকারী, শিক্ষক) তাঁদের সাথে কথা বলে লিখে নেওয়া — কী রাখতে হবে, কোন প্রশ্নের উত্তর চাই, কোন নিয়ম মানতে হবে।
- Conceptual model: সেই লেখাকে ER ডায়াগ্রামে আঁকা। এখানে কোনো ডেটা টাইপ নেই, SQL নেই, কোন DBMS তা-ও ঠিক হয়নি — শুধু “কী আছে আর কীভাবে যুক্ত”।
- Logical ও physical ডিজাইন: ডায়াগ্রাম থেকে নিয়ম মেনে টেবিল, key আর foreign key (পর্ব ১০), তারপর ডেটা টাইপ, index ইত্যাদি।
চাহিদা জানার আলাপে (requirements interview) কিছু প্রশ্ন প্রায় সবসময় কাজে লাগে: “একজন শিক্ষার্থীর কি একাধিক ফোন নম্বর থাকতে পারে?”, “একটি ব্যাচ কি কখনো দুজন শিক্ষক মিলে পড়ান?”, “শিক্ষার্থী চলে গেলে তাঁর পুরোনো ফি-র হিসাব কি রাখতে হবে?”, “মাসে কোন কোন রিপোর্ট দেখতে চান?” প্রতিটি উত্তর ER ডায়াগ্রামে একটি রেখা, একটি চিহ্ন বা একটি নতুন বাক্স হয়ে যায়।
তাইওয়ানে জন্ম নেওয়া কম্পিউটার বিজ্ঞানী Peter Pin-Shan Chen (তখন MIT-তে কর্মরত) ১৯৭৬ সালে “The Entity-Relationship Model — Toward a Unified View of Data” নামে একটি প্রবন্ধ প্রকাশ করেন, ACM Transactions on Database Systems জার্নালের একেবারে প্রথম সংখ্যায়। সে সময় network model, Codd-এর relational model আর entity set-ভিত্তিক মডেল নিয়ে বিতর্ক চলছিল। Chen-এর প্রস্তাব ছিল: আগে বাস্তব জগতের একটি “একীভূত” ছবি আঁকুন — জিনিস (entity) আর তাদের সম্পর্ক (relationship) — তারপর সেটিকে যে মডেলে খুশি রূপান্তর করুন। প্রবন্ধটির সাথে আসে আজকের পরিচিত ছবির ভাষা: entity-র জন্য আয়তক্ষেত্র, relationship-এর জন্য হীরক। ৫০ বছর পরেও বিশ্ববিদ্যালয় আর চাকরির পরীক্ষায় ER মডেল ডিজাইন শেখার প্রথম ধাপ, আর এই প্রবন্ধটিকে কম্পিউটার বিজ্ঞানের সবচেয়ে বেশি উদ্ধৃত প্রবন্ধগুলোর একটি ধরা হয়।
২ · Entity, entity type আর entity set
Entity হলো বাস্তব জগতের এমন একটি জিনিস, যাকে আলাদা করে চেনা যায় আর যার সম্পর্কে তথ্য রাখার দরকার আছে। শিক্ষার্থী Arif Hossain একটি entity, কোর্স CSE301 একটি entity, CSE বিভাগ একটি entity. Entity চোখে দেখা যায় এমন হতে হবে না: একটি ব্যাংক অ্যাকাউন্ট, একটি bKash লেনদেন বা একটি ট্রেনের টিকিট বুকিংও entity — এদের প্রত্যেকটি আলাদা, আর প্রত্যেকটির নিজস্ব তথ্য আছে।
তিনটি শব্দ পরীক্ষায় প্রায়ই গুলিয়ে যায়, তাই আলাদা করে দেখুন:
Entity
একটি নির্দিষ্ট জিনিস। “শিক্ষার্থী 2101, Arif Hossain”.
টেবিলের একটি সারিEntity type
ছাঁচ বা নকশা: নাম আর কোন কোন attribute থাকবে। STUDENT(student_id, name, batch, cgpa).
টেবিলের schemaEntity set
এই মুহূর্তে ডেটাবেসে থাকা ওই ধরনের সব entity-র সংগ্রহ। আজ ৮ জন শিক্ষার্থী, কাল ১০ জন।
টেবিলের সব সারিঅনেক বই (যেমন Silberschatz-এর বই) “entity type” আর “entity set” প্রায় একই অর্থে ব্যবহার করে; Elmasri ও Navathe-র বই পার্থক্যটি স্পষ্ট রাখে — type হলো schema (স্থির), set হলো instance (বদলায়)। পর্ব ৩ আর ৫-এ schema আর instance-এর যে পার্থক্য দেখেছেন, এটি ঠিক তাই। ER ডায়াগ্রামে আঁকা হয় entity type, আর সেটি হয় একটি আয়তক্ষেত্র, ভেতরে বড় হাতের অক্ষরে একবচনে নাম: STUDENT, COURSE, DEPARTMENT.
এই পর্বের বেশিরভাগ লাইভ SQL চলবে বিশ্ববিদ্যালয়ের নমুনা ডেটাবেসে (db-uni). এর পাঁচটি টেবিল: departments (dept_id, name, building — ৪টি বিভাগ), instructors (instructor_id, name, dept_id, salary আর mentor_id — একজন শিক্ষকের mentor আরেকজন শিক্ষক; ৬ জন), students (student_id, name, dept_id, batch, cgpa — ৮ জন), courses (course_id, title, dept_id, credits, instructor_id — ৭টি কোর্স) আর enrollments (student_id, course_id, term, grade — কে কোন কোর্সে কোন term-এ ভর্তি, কী গ্রেড)। প্রতিবার চালালে ডেটাবেস নতুন করে তৈরি হয়, তাই নির্ভয়ে বদলে দেখুন। প্রথমে INSTRUCTOR entity set-টি দেখা যাক:
-- INSTRUCTOR entity set: প্রতিটি সারি একটি entity
SELECT instructor_id, name, dept_id FROM instructors;
-- entity set-এ এই মুহূর্তে কয়টি entity?
SELECT COUNT(*) AS entity_count FROM instructors;প্রথম ফলে ৬টি সারি — 1 | Dr. Anisur Rahman | 1 থেকে 6 | Tasnim Chowdhury | 1. প্রতিটি সারি একজন শিক্ষক, অর্থাৎ একটি entity; আর ছয়টি সারি মিলে এই মুহূর্তের INSTRUCTOR entity set. দ্বিতীয় ফল 6. একটি নতুন শিক্ষক INSERT করলে entity set বদলাবে (৭ জন), কিন্তু entity type (কোন কলাম আছে) একই থাকবে। খেয়াল করুন, ER-এর এক entity type সাধারণত একটি টেবিল হয় — কিন্তু সবসময় নয়; multi-valued attribute বা relationship-এর জন্য বাড়তি টেবিল লাগে, যা নিচে দেখবেন।
৩ · Attribute — ছয় রকম চেহারা
প্রতিটি entity-কে বর্ণনা করে তার attribute: শিক্ষার্থীর নাম, রোল, জন্মতারিখ, ফোন নম্বর। ER ডায়াগ্রামে attribute আঁকা হয় একটি উপবৃত্তে (ellipse), রেখা দিয়ে তার entity-র সাথে যুক্ত। কিন্তু সব attribute এক রকম নয় — তিনটি প্রশ্ন দিয়ে এদের ভাগ করা হয়, আর প্রতিটি ভাগের আলাদা চিহ্ন আছে:
একটি STUDENT entity-র attribute — Chen notation-এ
Key underlined · composite has sub-attributes · double ellipse = multi-valued · dashed = derived
প্রশ্ন ১ — ভাঙা যায় কি? Simple বনাম composite
Simple (atomic) attribute আর ছোট অর্থপূর্ণ অংশে ভাঙা হয় না: roll, dob, cgpa. Composite attribute কয়েকটি অংশ মিলে তৈরি, আর অংশগুলো আলাদাভাবেও কাজে লাগে। নাম ভাঙে first_name আর last_name-এ; বাংলাদেশি ঠিকানা ভাঙে বাড়ি/রাস্তা, এলাকা, থানা বা উপজেলা, জেলা আর বিভাগে। কোনটি composite, তা ঠিক করে ব্যবহার: কোচিং সেন্টার যদি “মিরপুর থানার সব শিক্ষার্থী” খুঁজতে চায়, ঠিকানাকে composite রাখতেই হবে; যদি শুধু চিঠির খামে লেখার জন্য লাগে, এক লাইনের simple attribute-ই যথেষ্ট।
db-uni-তে name একটি ঘরেই রাখা, অর্থাৎ simple হিসেবে। প্রথম নাম আলাদা করতে চাইলে কী ঝামেলা হয়, দেখুন:
-- প্রথম ফাঁকা জায়গার আগে প্রথম নাম, পরে শেষ নাম
SELECT name,
substr(name, 1, instr(name, ' ') - 1) AS first_name,
substr(name, instr(name, ' ') + 1) AS last_name
FROM students WHERE batch = 2022;
-- একই কৌশল শিক্ষকদের নামে
SELECT name, substr(name, 1, instr(name, ' ') - 1) AS first_name
FROM instructors WHERE instructor_id <= 3;শিক্ষার্থীদের ক্ষেত্রে ফল ঠিক: Dilruba Khatun | Dilruba | Khatun, Emon Mia | Emon | Mia, Fahim Karim | Fahim | Karim. কিন্তু শিক্ষকদের ফলে Dr. Anisur Rahman | Dr. আর Dr. Mahbub Alam | Dr. — “Dr.”-কে প্রথম নাম ধরা হয়েছে! মাঝের নাম, “মোহাম্মদ”-এর মতো উপসর্গ বা এক-শব্দের নাম এলে এমন জোড়াতালির কোড আরও ভাঙে। শিক্ষা: যদি নামের অংশ আলাদাভাবে লাগে, ডিজাইনের সময়েই name-কে composite ধরুন, পরে লেখা কেটে বের করবেন না। (substr, instr-এর মতো string ফাংশন বিস্তারিত পর্ব ১৭-এ।)
প্রশ্ন ২ — মান কয়টি? Single-valued বনাম multi-valued
Single-valued attribute-এর একটি entity-র জন্য সর্বোচ্চ একটি মান: একজনের জন্মতারিখ একটিই। Multi-valued attribute-এর একাধিক মান হতে পারে: অভিভাবকের দুটি ফোন নম্বর, একজন শিক্ষকের কয়েকটি ডিগ্রি, একটি বইয়ের কয়েকজন লেখক। Chen notation-এ এটি দুই-রেখার ellipse.
Relational টেবিলের প্রতিটি ঘরে একটিমাত্র atomic মান থাকে (পর্ব ৫-এর first normal form). তাই multi-valued attribute কখনো phone1, phone2, phone3 কলাম বা '017…, 018…' লেখা নয় — এটি যায় আলাদা একটি টেবিলে, যার প্রতিটি সারি “একজন শিক্ষার্থী, একটি নম্বর”। নিচের এডিটর db-uni-র সাথে এমন একটি ছোট টেবিল student_phones(student_id, phone) যোগ করে নেয়, যাতে তিনজন শিক্ষার্থীর মোট ৬টি নম্বর আছে:
-- multi-valued attribute: প্রতিটি নম্বর আলাদা সারি
SELECT s.student_id, s.name, p.phone
FROM student_phones AS p
JOIN students AS s ON s.student_id = p.student_id
ORDER BY s.student_id, p.phone;
SELECT student_id, COUNT(*) AS phones
FROM student_phones GROUP BY student_id;
-- নেই এমন শিক্ষার্থীর নম্বর রাখা যাবে না
INSERT INTO student_phones VALUES (9999, '01700000000');প্রথম ফলে ৬টি সারি: Arif Hossain-এর দুটি নম্বর, Bristy Saha-র একটি, Dilruba Khatun-এর তিনটি। দ্বিতীয় ফল: 2101 | 2, 2102 | 1, 2201 | 3 — একজনের কয়টি নম্বর, তার কোনো ঊর্ধ্বসীমা নেই, কলাম যোগ করতে হয়নি। তৃতীয় স্টেটমেন্টে FOREIGN KEY constraint failed: নম্বরটি অবশ্যই একজন বিদ্যমান শিক্ষার্থীর। এভাবে multi-valued attribute একটি নিজস্ব টেবিল হয়, যার key (student_id, phone) — ঠিক কোন নিয়মে, তা পর্ব ১০-এ। (JOIN নিজে পর্ব ১৯-এর বিষয়; এখানে শুধু ব্যবহার দেখুন।)
প্রশ্ন ৩ — জমা রাখি, নাকি হিসাব করি? Stored বনাম derived
জন্মতারিখ একটি stored attribute — এটি অন্য কিছু থেকে বের করা যায় না, জমা রাখতেই হয়। বয়স একটি derived attribute: জন্মতারিখ আর আজকের তারিখ থেকে হিসাব করা যায়। Chen notation-এ derived attribute-এর ellipse ভাঙা রেখায় আঁকা হয়। বয়স জমা রাখলে প্রতি জন্মদিনে কাউকে সেটি বদলাতে হবে — ভুলে গেলে ডেটা মিথ্যা বলবে। তাই বয়স চাওয়ার সময় হিসাব করুন। নিচের এডিটর একটি হাসপাতালের ছোট টেবিল patients(patient_id, name, dob) বানায় আর একটি নির্দিষ্ট দিনের (2026-10-25) হিসাবে বয়স বের করে:
-- age = বছরের পার্থক্য, জন্মদিন এখনো না এলে এক বাদ
SELECT name, dob,
(CAST(strftime('%Y', '2026-10-25') AS INTEGER)
- CAST(strftime('%Y', dob) AS INTEGER))
- (strftime('%m-%d', '2026-10-25') < strftime('%m-%d', dob)) AS age
FROM patients;ফল: Rokeya Begum | 1958-03-14 | 68, Sajib Das | 2001-10-25 | 25, Tania Akter | 1995-12-31 | 30, Baby of Mitu | 2026-09-30 | 0. Sajib-এর আজই জন্মদিন, তাই পুরো ২৫; Tania-র জন্মদিন ডিসেম্বরে, এখনো আসেনি, তাই ২০২৬ − ১৯৯৫ = ৩১ থেকে এক বাদ দিয়ে ৩০ — তুলনাটি ('10-25' < '12-31') সত্য হলে SQLite-এ তার মান 1. শুধু বছর বিয়োগ করলে ভুল হতো। বাস্তব অ্যাপে '2026-10-25'-এর জায়গায় date('now') লিখবেন। অন্য DBMS-এ এর জন্য তৈরি ফাংশন আছে (নিচের ব্লক চালানো যায় না)।
-- PostgreSQL: age() একটি interval দেয়, তা থেকে বছর
SELECT name, date_part('year', age(DATE '2026-10-25', dob)) AS age FROM patients;
-- MySQL: পূর্ণ বছরের সংখ্যা
SELECT name, TIMESTAMPDIFF(YEAR, dob, '2026-10-25') AS age FROM patients;Key attribute আর ঐচ্ছিক (NULL) attribute
যে attribute প্রতিটি entity-কে আলাদা করে চেনায় — পর্ব ৬-এর candidate key-র ER রূপ — তাকে বলে key attribute; ER ডায়াগ্রামে তার নামের নিচে দাগ টানা হয় (roll, student_id, course_id). একাধিক attribute মিলে key হলে সবগুলোর নিচে দাগ। আর কিছু attribute ঐচ্ছিক: সব শিক্ষার্থীর email নেই, নতুন ভর্তি হওয়া শিক্ষার্থীর cgpa এখনো হয়নি। এদের মান না থাকলে টেবিলে NULL বসে — db-uni-তে Fahim Karim-এর cgpa NULL. ER-এ ঐচ্ছিকতার আলাদা চিহ্ন নেই; এটি ডায়াগ্রামের পাশে নোট হিসেবে বা পরে টেবিলে NOT NULL না দিয়ে প্রকাশ করা হয়। মনে রাখুন: key attribute কখনো ঐচ্ছিক হতে পারে না।
| প্রশ্ন | প্রকার | Chen চিহ্ন | উদাহরণ | টেবিলে সাধারণত |
|---|---|---|---|---|
| ভাঙা যায়? | Simple | সাধারণ ellipse | roll, dob | একটি কলাম |
| Composite | ellipse থেকে ছোট ellipse | name, address | অংশগুলো আলাদা কলাম | |
| মান কয়টি? | Single-valued | সাধারণ ellipse | dob | একটি কলাম |
| Multi-valued | দুই-রেখার ellipse | phone, degrees | আলাদা টেবিল | |
| জমা না হিসাব? | Stored | সাধারণ ellipse | dob | কলাম |
| Derived | ভাঙা রেখার ellipse | age, মোট ক্রেডিট | কোয়েরি বা view-এ হিসাব | |
| চেনায়? | Key | নামের নিচে দাগ | student_id | PRIMARY KEY |
৪ · Relationship, relationship set আর relationship-এর attribute
Entity একা অর্থহীন; তথ্যের আসল মূল্য তাদের সংযোগে। “Arif Hossain Database Systems কোর্সে ভর্তি হয়েছেন” — এটি একটি relationship: দুটি নির্দিষ্ট entity-র মধ্যে একটি সম্পর্ক। একই ধরনের সব relationship-এর সংগ্রহ হলো relationship set — সব ভর্তি মিলে ENROLLS. ER ডায়াগ্রামে relationship আঁকা হয় হীরক (diamond) আকারে, নাম সাধারণত একটি ক্রিয়া: ENROLLS, TEACHES, WORKS_IN.
E₁, E₂, …, Eₙ entity set হলে একটি relationship set R হলো {(e₁, e₂, …, eₙ) | e₁ ∈ E₁, …, eₙ ∈ Eₙ}-এর একটি উপসেট — অর্থাৎ entity set-গুলোর cartesian product-এর উপসেট। প্রতিটি tuple (e₁, …, eₙ) একটি relationship instance.
সংজ্ঞাটির একটি গুরুত্বপূর্ণ ফল আছে: R একটি সেট, তাই একই (Arif, CSE301) জোড়া R-এ দুবার থাকতে পারে না। অথচ বাস্তবে একজন শিক্ষার্থী ফেল করে পরের term-এ একই কোর্স আবার নিতে পারেন। db-uni-র enrollments টেবিল তাই key-তে term রেখেছে — ER-এ এটি কীভাবে আঁকা উচিত, সেই সূক্ষ্ম প্রশ্নটি ভাবনার প্রশ্ন ৫-এ।
Relationship-এরও attribute থাকে
Arif-এর Database Systems কোর্সের গ্রেড A- — এই “A-” কার attribute? শিক্ষার্থীর নয়, কারণ Arif-এর অনেক কোর্সে অনেক গ্রেড। কোর্সেরও নয়, কারণ একই কোর্সে বিভিন্ন শিক্ষার্থীর বিভিন্ন গ্রেড। গ্রেডটি ঠিক হয় দুজন মিলে — তাই এটি ENROLLS relationship-এর নিজের attribute, একটি relationship attribute. ডায়াগ্রামে এর ellipse হীরকের সাথে যুক্ত থাকে:
ENROLLS — দুটি entity আর একটি relationship
term and grade belong to the relationship, not to either entity
টেবিলে এই relationship set-টিই enrollments: প্রতিটি সারি একটি (শিক্ষার্থী, কোর্স) জোড়া — দুটি foreign key দিয়ে — আর তার সাথে term ও grade. দুজন শিক্ষার্থীর সব ভর্তি দেখুন:
-- ENROLLS relationship set: (student, course) জোড়া + তার attribute
SELECT s.name, c.title, e.term, e.grade
FROM enrollments AS e
JOIN students AS s ON s.student_id = e.student_id
JOIN courses AS c ON c.course_id = e.course_id
WHERE e.student_id IN (2101, 2201)
ORDER BY e.student_id, e.term, e.course_id;ফলে ৬টি সারি। Arif Hossain: Data Structures | 2022-Spring | A, Database Systems | 2023-Fall | A-, Database Systems Lab | 2023-Fall | A. Dilruba Khatun: Structured Programming | 2022-Fall | A, Data Structures | 2023-Spring | A, Database Systems | 2024-Fall | NULL. শেষ সারির grade NULL — ভর্তি হয়েছে, ফল এখনো আসেনি; relationship attribute-ও ঐচ্ছিক হতে পারে। খেয়াল করুন, “A” গ্রেড Arif-এর সারিতে দুবার, Dilruba-র সারিতেও দুবার — গ্রেড কোনো একজনের নয়, প্রতিটি জোড়ার।
৫ · Degree — unary, binary আর ternary
একটি relationship-এ কয়টি entity type অংশ নেয়, সেটি তার degree. (পর্ব ৫-এ relation-এর degree মানে ছিল কলামের সংখ্যা — একই শব্দ, আলাদা অর্থ।)
- Binary (degree ২): দুটি entity type. STUDENT ENROLLS COURSE, INSTRUCTOR TEACHES COURSE. বাস্তব ডিজাইনের বেশিরভাগ relationship এমন।
- Unary বা recursive (degree ১): একই entity type দুটি আলাদা ভূমিকায়। একজন শিক্ষক আরেকজন শিক্ষকের mentor; একজন কর্মচারী আরেকজনের supervisor; একটি কোর্স আরেকটি কোর্সের prerequisite.
- Ternary (degree ৩): তিনটি entity type একসাথে। একজন supplier একটি part একটি project-এ সরবরাহ করে।
Recursive relationship আর role name
MENTORS relationship-এর দুই প্রান্তেই INSTRUCTOR. তাহলে কে কাকে mentor করছেন, বোঝা যাবে কীভাবে? এর জন্য প্রতিটি রেখায় একটি role name লেখা হয় — এক দিকে mentor, অন্য দিকে mentee. সাধারণ binary relationship-এ role name ঐচ্ছিক (STUDENT-এর ভূমিকা স্পষ্ট), কিন্তু recursive relationship-এ এটি না লিখলে ডায়াগ্রাম অস্পষ্ট।
MENTORS — একটি recursive relationship
Both lines lead to INSTRUCTOR; role names tell them apart
টেবিলে recursive relationship সাধারণত একটি self-referencing foreign key হয় (পর্ব ৬): instructors.mentor_id → instructors.instructor_id. আর একে কোয়েরি করতে একই টেবিলকে দুটি আলাদা নাম দিয়ে নিজের সাথে জুড়তে হয় — একটি কপি “mentee” ভূমিকায় (i), আরেকটি “mentor” ভূমিকায় (m). একে বলে self join; বিস্তারিত পর্ব ২০-এ, এখানে শুধু দেখুন role name কীভাবে alias হয়ে যায়:
-- i = mentee ভূমিকায় শিক্ষক, m = mentor ভূমিকায় শিক্ষক
SELECT i.name AS mentee, m.name AS mentor
FROM instructors AS i
LEFT JOIN instructors AS m ON m.instructor_id = i.mentor_id
ORDER BY i.instructor_id;
-- শুধু যাঁদের mentor আছে (MENTORS-এর relationship instance)
SELECT i.name AS mentee, m.name AS mentor
FROM instructors AS i
JOIN instructors AS m ON m.instructor_id = i.mentor_id;প্রথম ফলে ৬ জন শিক্ষকই আছেন: Shahana Parvin | Dr. Anisur Rahman, Rumana Islam | Dr. Mahbub Alam, Tasnim Chowdhury | Shahana Parvin, আর Dr. Anisur Rahman, Dr. Mahbub Alam ও Kamrul Hasan-এর পাশে mentor NULL — তাঁরা সিনিয়র, কারও mentee নন। দ্বিতীয় ফলে শুধু ৩টি সারি — ঠিক এই তিনটিই MENTORS relationship set-এর instance. মজার দিক: Shahana Parvin একসাথে mentee (Anisur Rahman-এর) আর mentor (Tasnim-এর) — একই entity দুই ভূমিকায়, আর সেজন্যই role name ছাড়া চলে না।
Ternary — যখন তিনজন একসাথে
একটি নির্মাণ প্রতিষ্ঠানের কথা ভাবুন। তারা বিভিন্ন supplier থেকে রড, সিমেন্ট, ইট (part) কেনে, বিভিন্ন project-এর (একটি সেতু, একটি স্কুল ভবন) জন্য। নিয়মটি হলো: “supplier S, part P, project J-এর জন্য কত পরিমাণ দিয়েছে”। এখানে quantity তিনজনের একসাথে — শুধু (S, P) বা শুধু (P, J) জানলে বলা যায় না। এটি একটি ternary relationship SUPPLY:
SUPPLY — একটি ternary relationship
One diamond, three entity types, and an attribute (quantity) of the whole triple
প্রশ্ন জাগে: তিনটি ছোট binary — SUPPLIES_PART (supplier–part), USES (part–project), WORKS_FOR (supplier–project) — দিয়ে কি একই তথ্য রাখা যায় না? ছোট একটি উদাহরণে ধাপে ধাপে দেখুন:
| এই ধাপের সারি | |
| ভুয়া সারি |
- মূল তথ্য। তিনটি সরবরাহ: S1 project J2-তে P1 দিয়েছে (১০০), S1 project J1-এ P2 দিয়েছে (৪০), S2 project J1-এ P1 দিয়েছে (৭৫)। প্রতিটি ত্রয়ী একটি ternary relationship instance.
- জোড়ায় ভাঙা। প্রতিটি ত্রয়ী থেকে তিনটি জোড়া নিই: কে কোন part দেয় (SP), কোন part কোন project-এ লাগে (PJ), কে কোন project-এ কাজ করে (SJ). প্রথম ক্ষতি এখনই চোখে পড়ে: quantity রাখার জায়গা নেই, কারণ এটি কোনো জোড়ার নয়।
- আবার জোড়া — SP ⋈ PJ. একই part-এর ভিত্তিতে SP আর PJ মেলালে ৫টি ত্রয়ী আসে। P1 দুজন supplier-এর আর দুটি project-এর, তাই P1-এর জন্য 2 × 2 = 4টি সমন্বয়, আর P2-এর জন্য ১টি।
- SJ দিয়ে ছাঁকা। যে ত্রয়ীর (supplier, project) জোড়া SJ-এ নেই, তা বাদ: (S2, P1, J2) গেল, কারণ S2 কখনো J2-তে কাজ করেনি। বাকি ৪টি।
- ভুয়া সারি। মূলে ছিল ৩টি, ফলে ৪টি। (S1, P1, J1) কখনো ঘটেনি: S1, P1 দেয় (J2-তে), J1-এ P1 লাগে (S2 দেয়), আর S1, J1-এ কাজ করে (P2 দিয়ে) — তিনটি সত্য জোড়া মিলে একটি মিথ্যা ত্রয়ী। একে বলে spurious tuple. অর্থাৎ এই তিনটি binary ternary-র সমান তথ্য রাখে না।
এবার একই কাজ আসল SQLite-এ। এডিটরটি supply(supplier, part, project, quantity) টেবিলে উপরের তিনটি সারি রাখে; কোড তিনটি জোড়া-টেবিল বানিয়ে আবার জোড়া দেয়:
-- ternary থেকে তিনটি binary (জোড়া)
CREATE TABLE sp AS SELECT DISTINCT supplier, part FROM supply;
CREATE TABLE pj AS SELECT DISTINCT part, project FROM supply;
CREATE TABLE sj AS SELECT DISTINCT supplier, project FROM supply;
-- তিন জোড়া মিলিয়ে ternary ফেরত আনার চেষ্টা
SELECT sp.supplier, sp.part, pj.project
FROM sp
JOIN pj ON pj.part = sp.part
JOIN sj ON sj.supplier = sp.supplier AND sj.project = pj.project
ORDER BY 1, 2, 3;তিনটি CREATE-এর প্রতিটির পর ✓, তারপর SELECT-এর ফলে ৪টি সারি: S1 | P1 | J1, S1 | P1 | J2, S1 | P2 | J1, S2 | P1 | J1. প্রথমটিই ভুয়া — ঠিক stepper-এর মতো। এখন data-setup-এ সারিগুলো বদলে দেখুন: যেমন ('S1','P1','J1'), ('S2','P2','J2'), ('S1','P1','J2') দিলে কোনো ভুয়া সারি আসে না। অর্থাৎ কিছু ডেটায় binary-গুলো কাজ করে যায় — কিন্তু ডিজাইন ঠিক করতে হয় সব বৈধ ডেটার জন্য, তাই প্রশ্নটি ব্যবসার নিয়মের: “S1, P1 দেয়, J1-এ P1 লাগে আর S1, J1-এ কাজ করে” হলে কি S1 সত্যিই J1-এ P1 দেয়? না হলে ternary লাগবেই।
আরও গভীরে: “তিনটি জোড়া জুড়লে মূল ত্রয়ী ফেরত আসে” — এই শর্তটির তাত্ত্বিক নাম join dependency, লেখা হয় ⋈{SP, PJ, SJ}. এটি সব বৈধ instance-এ সত্য হলে ternary-কে ক্ষতি ছাড়াই (lossless) তিনটি binary-তে ভাঙা যায়, আর না হলে যায় না। দুই ভাগে ভাঙার শর্তটি পরিচিত multivalued dependency (4NF-এর ভিত্তি); তিন বা ততোধিক ভাগে ভাঙার সাধারণ শর্তটিই join dependency, আর তার থেকে আসে পঞ্চম normal form (5NF) — পর্ব ৩৭-এ যা দেখবেন। Join-এর গাণিতিক রূপ পর্ব ৩৩-এ।
৬ · চাহিদার বর্ণনা থেকে ER ডায়াগ্রাম — ধাপে ধাপে
এবার অংশ ১-এর কোচিং সেন্টারে ফিরি। মালিকের সাথে আলাপের পর আপনি একটি ছোট অনুচ্ছেদ লিখলেন। এই লেখা থেকে entity, attribute আর relationship খুঁজে বের করার একটি পুরোনো কিন্তু কার্যকর কৌশল আছে: বিশেষ্য (noun) সাধারণত entity বা attribute, ক্রিয়া (verb) সাধারণত relationship. Chen নিজেই ১৯৮৩ সালের একটি প্রবন্ধে ইংরেজি বাক্যের গঠন আর ER ডায়াগ্রামের এই মিল দেখিয়েছিলেন। কৌশলটি নিয়ম নয়, একটি শুরুর বিন্দু — তবে পরীক্ষার হলে আর বাস্তব আলাপে দুই জায়গাতেই কাজ দেয়।
| Entity | |
| Attribute | |
| Relationship |
- চাহিদাটি পড়ুন। মালিকের সাথে আলাপের পর লেখা ১৩ লাইনের বর্ণনা। এখনো কোনো টেবিল নেই, কোনো কলামের টাইপ নেই — শুধু সাধারণ বাংলা। পুরো অনুচ্ছেদ একবার পড়ে নিন, তারপর “পরে” চাপুন।
- বিশেষ্য খুঁজুন — entity-প্রার্থী। শিক্ষার্থী, ব্যাচ, শিক্ষক বারবার আসছে, আর প্রত্যেকের নিজস্ব বিবরণ (রোল, কোড, আইডি) আছে। যে বিশেষ্যের নিজের পরিচয় আর নিজের তথ্য আছে, সেটি entity. “ফোন নম্বর” বা “ঠিকানা”-ও বিশেষ্য, কিন্তু তাদের নিজের কোনো বিবরণ রাখতে বলা হয়নি — তাই এরা attribute-প্রার্থী।
- বিবরণ খুঁজুন — attribute. শিক্ষার্থীর ৬টি: রোল, নাম, ঠিকানা (তিন অংশে, তাই composite), জন্মতারিখ, অভিভাবকের ফোন (“এক বা একাধিক”, তাই multi-valued), আর বয়স (জন্মতারিখ থেকে হিসাব করা যায়, তাই derived). ব্যাচের ৩টি, শিক্ষকের ৩টি। রোল, কোড আর আইডি — এরাই key.
- ক্রিয়া খুঁজুন — relationship. “শিক্ষক ব্যাচ পড়ান” থেকে TEACHES (TEACHER–BATCH), “শিক্ষার্থী ব্যাচে ভর্তি হন” থেকে ENROLLS (STUDENT–BATCH). দুটিই binary. “রাখতে হবে”, “দেখাতে হবে” ক্রিয়া হলেও সম্পর্ক নয় — এগুলো সিস্টেমের কাজ।
- যে বিবরণ কারও একার নয়। ভর্তির তারিখ শিক্ষার্থীর নয় (তিনি কয়েকটি ব্যাচে কয়েক তারিখে ভর্তি), ব্যাচেরও নয় (প্রত্যেকে আলাদা দিনে)। এটি ENROLLS-এর attribute. মাসিক ফি-ও আপাতত তাই ধরলাম — কিন্তু এখানে একটি প্রশ্ন জমা রইল।
- খসড়া আর মালিককে প্রশ্ন। খসড়া তৈরি: ৩টি entity, ২টি relationship, মোট ১৪টি attribute. এবার মালিককে জিজ্ঞেস করুন: “একটি ব্যাচের সব শিক্ষার্থী কি একই ফি দেন?” হ্যাঁ হলে monthly_fee আসলে BATCH-এর attribute; ছাড়-সুবিধা থাকলে ENROLLS-এর। “একটি ব্যাচ কি কখনো দুজন শিক্ষক পড়ান?” — এর উত্তর ঠিক করবে TEACHES-এর cardinality (পর্ব ৮)।
শেষ ধাপটিই আসল শিক্ষা: বর্ণনা থেকে অর্ধেক উত্তর মেলে, বাকি অর্ধেক মেলে প্রশ্ন থেকে। “মাসিক ফি” একটি শব্দ, কিন্তু এটি কোথায় বসবে তা ঠিক করে একটি ব্যবসার নিয়ম, যা লেখায় ছিল না।
৭ · db-uni-র ER ডায়াগ্রাম — আঁকা আর জোরে পড়া
এ পর্যন্ত যে বিশ্ববিদ্যালয়ের ডেটাবেসে SQL চালিয়েছেন, তার পেছনের ER ডায়াগ্রামটি কেমন? উল্টো দিক থেকে আঁকা যাক — টেবিল দেখে ধারণায় ফেরা (একে reverse engineering বলে)। প্রতিটি ধাপে ডানের টেবিলে দেখবেন ছবির অংশটি db-uni-র কোন কলামে আছে। ছোট পর্দায় ছবিটি পরিষ্কার রাখতে প্রতিটি entity-র সব attribute আঁকা হয়নি — key আর কয়েকটি নমুনা।
| এই ধাপে যোগ হলো | |
| টেবিলে কোথায় |
- Entity type খুঁজুন। db-uni-র যে টেবিলগুলোর নিজস্ব পরিচয় আছে — departments, instructors, students, courses — এরা চারটি entity type: DEPARTMENT, INSTRUCTOR, STUDENT, COURSE. enrollments আলাদা: এর নিজের কোনো id নেই, এটি দুটি entity-কে জোড়ে — তাই এটি relationship হবে।
- Key আর attribute. প্রতিটি entity-র key-র নিচে দাগ: dept_id, instructor_id, student_id, course_id. সাথে নমুনা হিসেবে বিভাগের name আর কোর্সের title. salary, batch, cgpa, credits-ও attribute, শুধু ছবিতে জায়গা বাঁচাতে বাদ।
- Foreign key → relationship. instructors.dept_id বলছে “শিক্ষক একটি বিভাগে কাজ করেন” — WORKS_IN. students.dept_id বলছে “শিক্ষার্থী একটি বিভাগে পড়েন” — MAJORS_IN. টেবিলে যা একটি কলাম, ER-এ তা একটি হীরক। খেয়াল করুন, dept_id কলামটি STUDENT-এর attribute হিসেবে আঁকা হয়নি — সম্পর্ক attribute নয়।
- আরও দুটি binary. courses.dept_id থেকে OFFERS (বিভাগ কোর্স দেয়), courses.instructor_id থেকে TEACHES (শিক্ষক কোর্স পড়ান)। এখন জোরে পড়ুন: “একজন INSTRUCTOR একটি COURSE TEACHES করেন; একটি DEPARTMENT একটি COURSE OFFERS করে।” — বাক্যটি অর্থপূর্ণ হলে রেখাগুলো ঠিক জায়গায়।
- Relationship-এর attribute-সহ ENROLLS. enrollments টেবিলটি পুরোটাই একটি relationship set: STUDENT আর COURSE-এর জোড়া, সাথে term আর grade — দুটিই হীরকের সাথে যুক্ত, কোনো entity-র সাথে নয়। (term-কে ঘিরে একটি সূক্ষ্ম প্রশ্ন আছে — ভাবনার প্রশ্ন ৫।)
- Recursive: MENTORS. instructors.mentor_id একই টেবিলের দিকে নির্দেশ করে, তাই হীরকের দুই রেখাই INSTRUCTOR-এ যায় — একটিতে role name mentor, অন্যটিতে mentee. এটিই সেই relationship, যা অংশ ৫-এর self join দিয়ে কোয়েরি করেছেন।
- পুরো ছবি জোরে পড়ুন। “প্রতিটি শিক্ষক একটি বিভাগে কাজ করেন আর কোর্স পড়ান; কোনো শিক্ষক অন্য শিক্ষকের mentor; বিভাগ কোর্স দেয়; শিক্ষার্থী একটি বিভাগে পড়েন আর কোর্সে ভর্তি হন — প্রতিটি ভর্তির একটি term আর একটি grade.” ছবিটি যদি এভাবে বাক্যে পড়া যায় আর বিশ্ববিদ্যালয়ের লোকেরা মাথা নাড়েন, ডিজাইন ঠিক পথে।
ER ডায়াগ্রাম জোরে পড়ার নিয়ম
ডায়াগ্রাম আঁকা হয় অ-প্রযুক্তিগত মানুষকে দেখানোর জন্য, তাই প্রতিটি অংশ একটি সাধারণ বাক্যে পড়া যাওয়া চাই। তিনটি ছাঁচ মনে রাখুন:
- Entity + attribute: “প্রতিটি STUDENT-এর একটি student_id (যা দিয়ে তাঁকে চেনা যায়), একটি নাম আর এক বা একাধিক ফোন নম্বর আছে।”
- Relationship: “[entity ১] [relationship-এর ক্রিয়া] [entity ২]” — “STUDENT ENROLLS COURSE”: একজন শিক্ষার্থী একটি কোর্সে ভর্তি হন।
- Relationship attribute: “প্রতিটি ভর্তির একটি grade আছে” — “শিক্ষার্থীর grade আছে” বললে ভুল, কারণ তখন মনে হয় শিক্ষার্থীর একটিই গ্রেড।
পড়তে গিয়ে আটকে গেলে — “একটি DEPARTMENT একটি STUDENT-কে … কী করে?” — সেটিই ডিজাইনের দুর্বল জায়গা। Relationship-এর নাম এমন রাখুন যাতে বাক্য দুই দিক থেকেই পড়া যায়, বা অন্তত তীর-চিহ্ন বা role name দিয়ে দিক বোঝান। কয়টি শিক্ষার্থী কয়টি কোর্সে — এই “কতজন” প্রশ্নের চিহ্ন (cardinality) আর “সবাইকে কি থাকতেই হবে” প্রশ্নের চিহ্ন (participation) পর্ব ৮-এর বিষয়।
৮ · তিন রকম notation — Chen, Crow's foot আর UML
ER মডেল একটি ধারণা; তাকে আঁকার রীতি একাধিক। একই তথ্য — “শিক্ষার্থী কোর্সে ভর্তি হন, প্রতিটি ভর্তির term আর grade আছে” — তিন রীতিতে দেখুন (ট্যাব বদলান):
Chen notation (Chen notation): আয়তক্ষেত্র, ellipse, হীরক — Chen-এর ১৯৭৬ সালের রীতি। M আর N মানে “অনেক-অনেক” (পর্ব ৮)। সুবিধা: ধারণাগুলো আলাদা চোখে পড়ে, তাই পাঠ্যবই আর পরীক্ষায় এটিই চলে। অসুবিধা: attribute-এর ellipse অনেক জায়গা নেয়; ২০টি entity-র ডায়াগ্রাম এক পাতায় আঁটে না।
Crow's foot (Crow's foot notation): প্রতিটি entity একটি বাক্স, ভেতরে attribute-এর তালিকা — দেখতে প্রায় টেবিলের মতো। রেখার মাথায় তিন-কাঁটা “কাকের পা” মানে “অনেক”, দুটি খাড়া দাগ মানে “ঠিক একটি”, গোল চিহ্ন মানে “শূন্যও হতে পারে” (বিস্তারিত পর্ব ৮)। এই রীতিতে রেখার ওপর attribute বসানো যায় না, তাই grade-ওয়ালা বহু-বহু সম্পর্কটি একটি মাঝের বাক্স ENROLLMENT হয়ে গেছে — একে বলে associative entity. ছবিটি টেবিলের অনেক কাছাকাছি, তাই শিল্পক্ষেত্রের টুলগুলো সাধারণত এটিই আঁকে।
UML (UML class diagram): সফটওয়্যার নকশার মানক ভাষা UML-এর class diagram. বাক্সে class-এর নাম আর attribute (টাইপসহ), রেখার প্রান্তে multiplicity — * মানে “অনেক”, 1 মানে “একটি”। grade-এর মতো সম্পর্কের attribute থাকে একটি association class-এ, ভাঙা রেখায় যুক্ত। Java বা Python-এর ORM দিয়ে কাজ করা ডেভেলপারদের কাছে এই ছবি পরিচিত (পর্ব ৫০)।
Chen-এর ১৯৭৬ সালের প্রবন্ধের পরপরই অন্য রীতিও আসে। কাকের পায়ের চিহ্নটি সাধারণত Gordon Everest-এর ১৯৭৬ সালের কাজের সাথে যুক্ত করা হয় (তিনি একে “inverted arrow” বলেছিলেন); ১৯৮০-র দশকে James Martin ও Clive Finkelstein-এর Information Engineering পদ্ধতি আর পরে নানা CASE টুলের মাধ্যমে এটি শিল্পক্ষেত্রে জনপ্রিয় হয়। অন্যদিকে Grady Booch, James Rumbaugh আর Ivar Jacobson-এর আলাদা object-oriented পদ্ধতি মিলে তৈরি হয় UML, যা ১৯৯৭ সালে Object Management Group (OMG)-এর মানক হিসেবে গৃহীত হয়। তিন রীতির কোনোটিই “ঠিক” বা “ভুল” নয় — প্রশ্ন শুধু কে পড়বেন, কেন পড়বেন।
আজকের টুল
কাগজ-কলম আর হোয়াইটবোর্ড আজও ডিজাইনের প্রথম আলাপের সেরা সঙ্গী। পরিষ্কার ছবির জন্য কয়েকটি প্রচলিত টুল (কোনোটির পক্ষে সুপারিশ নয়; সবই নিজের প্রয়োজন মিলিয়ে বেছে নিন):
- draw.io (diagrams.net): ব্রাউজারে চলে, বিনামূল্যে; Chen, Crow's foot, UML সব চিহ্নের আকার আছে। পরীক্ষার প্রস্তুতির জন্য Chen ডায়াগ্রাম আঁকতে সুবিধাজনক।
- dbdiagram.io: ছবি টেনে নয়, লেখা (DBML নামের একটি ছোট ভাষা) থেকে Crow's foot-ধাঁচের ডায়াগ্রাম বানায়; লেখাটি git-এ রাখা যায়।
- pgAdmin 4-এর ERD Tool আর MySQL Workbench-এর EER Diagram: চালু ডেটাবেসের টেবিল থেকে ডায়াগ্রাম বানাতে পারে (reverse engineering), আবার ডায়াগ্রাম থেকে CREATE TABLE-ও লিখতে পারে (forward engineering).
- Mermaid: Markdown-এর ভেতরে
erDiagramলিখলে GitHub-এর মতো জায়গায় ছবি দেখায় — দলের ডকুমেন্টেশনে কাজে লাগে।
Table students {
student_id integer [pk]
name varchar
}
Table courses {
course_id varchar [pk]
title varchar
}
Table enrollments {
student_id integer [ref: > students.student_id]
course_id varchar [ref: > courses.course_id]
term varchar
grade varchar
}খেয়াল করুন, এই টুলগুলোর বেশিরভাগ আসলে টেবিল-স্তরের (logical) ছবি আঁকে — attribute-এর ধরন (multi-valued, derived) বা ternary relationship সরাসরি দেখাতে পারে না। ধারণা-স্তরের চিন্তাটি তাই আগে মাথায় বা কাগজে করে নিতে হয়।
৯ · মডেলিংয়ের প্রচলিত ভুল
ভুল ১ — attribute নাকি entity? “শহর”-এর প্রশ্ন
গ্রাহকের city কি একটি attribute, নাকি CITY একটি আলাদা entity? নিয়মটি হলো: যদি জিনিসটির নিজের কোনো তথ্য রাখতে হয়, বা অনেক entity একে ভাগ করে নেয় আর বানান একই রাখা জরুরি, তাহলে সেটি entity. শুধু ঠিকানায় লেখার জন্য হলে attribute-ই যথেষ্ট। কিন্তু দোকান যদি জেলাভিত্তিক ডেলিভারি চার্জ, বিভাগভিত্তিক বিক্রির রিপোর্ট বা “ঢাকার ভেতরে একদিনে ডেলিভারি” চায়, তাহলে CITY বা DISTRICT-এর নিজের attribute (চার্জ, বিভাগ) আছে — সেটি entity. City যখন মুক্ত লেখা, তখন কী হয় দেখুন। এডিটরটি একটি ছোট customers(customer_id, name, city) টেবিল বানায়, যেখানে বিভিন্ন জন বিভিন্নভাবে শহরের নাম লিখেছেন:
-- শহরভিত্তিক গ্রাহক গণনা
SELECT city, COUNT(*) AS n
FROM customers GROUP BY city ORDER BY city;
-- জোড়াতালি: ছোট হাতের অক্ষর আর বাড়তি ফাঁকা ছেঁটে
SELECT lower(trim(city)) AS city_fixed, COUNT(*) AS n
FROM customers GROUP BY lower(trim(city)) ORDER BY 1;প্রথম ফলে ৬টি “শহর”: Chattogram | 1, Chittagong | 1, Comilla | 1, Cumilla | 1, Dhaka | 2, আর chattogram | 1 (শেষে একটি ফাঁকা)। আসলে শহর মাত্র তিনটি! দ্বিতীয় কোয়েরি ফাঁকা আর বড়-ছোট হাতের অক্ষর মিলিয়ে chattogram | 2 করেছে, কিন্তু chittagong আর comilla এখনো আলাদা — কারণ এগুলো পুরোনো বানান (২০১৮ সালে সরকার কয়েকটি জেলার ইংরেজি বানান বদলায়)। কোনো string ফাংশন এই সমস্যা সমাধান করতে পারে না। সমাধান ডিজাইনে: CITY একটি entity (একটি ছোট lookup টেবিল, যেখানে প্রতিটি শহর একবার), আর গ্রাহক তার id দিয়ে foreign key-তে যুক্ত। অ্যাপে তখন মুক্ত লেখার বদলে তালিকা থেকে বেছে নেওয়া।
ভুল ২ — relationship নাকি entity?
“ভর্তি” কি শুধু STUDENT আর BATCH-এর relationship, নাকি নিজেই একটি entity? যতক্ষণ এর তথ্য অল্প (তারিখ, ফি), relationship-ই ভালো। কিন্তু ভর্তির নিজের রসিদ নম্বর, প্রতি মাসের কিস্তি, ছাড়ের অনুমোদন — এসব যুক্ত হতে থাকলে, বা অন্য relationship ভর্তির সাথে যুক্ত হতে চাইলে (“প্রতিটি কিস্তি একটি ভর্তির”), ভর্তিকে ENROLLMENT entity বানানোই পরিষ্কার। সাধারণ সংকেত: যে জিনিসের দিকে অন্য সম্পর্ক আঙুল তোলে, বা যার নিজের পরিচয়-নম্বর আছে, সেটি entity. হাসপাতালে “ভর্তি” (admission) প্রায় সবসময় entity — একই রোগী একই ওয়ার্ডে বহুবার ভর্তি হন, আর প্রতিটি ভর্তির বিল, ছাড়পত্র আলাদা।
ভুল ৩ — derived মান জমা রাখা
db-uni-র students টেবিলে cgpa একটি কলাম — অথচ cgpa আসলে enrollments-এর গ্রেড আর courses-এর credits থেকে হিসাব করা যায়, অর্থাৎ derived. জমা রাখা আর হিসাব করা মান মেলে কি না, দেখা যাক। নিচের কোয়েরি বাংলাদেশের বহু বিশ্ববিদ্যালয়ে প্রচলিত গ্রেড-স্কেল (A+ = 4.00, A = 3.75, A- = 3.50 …) দিয়ে প্রতিটি শিক্ষার্থীর credit-ভারিত গড় হিসাব করে:
SELECT s.name, s.cgpa AS stored,
ROUND(SUM(c.credits * CASE e.grade
WHEN 'A+' THEN 4.0 WHEN 'A' THEN 3.75 WHEN 'A-' THEN 3.5
WHEN 'B+' THEN 3.25 WHEN 'B' THEN 3.0 WHEN 'B-' THEN 2.75
WHEN 'C+' THEN 2.5 WHEN 'C' THEN 2.25 WHEN 'D' THEN 2.0
ELSE 0 END) / SUM(c.credits), 2) AS computed
FROM students AS s
JOIN enrollments AS e ON e.student_id = s.student_id
JOIN courses AS c ON c.course_id = e.course_id
WHERE e.grade IS NOT NULL
GROUP BY s.student_id
ORDER BY s.student_id;৭টি সারি, আর একটিও মেলেনি: Arif Hossain | 3.75 | 3.65, Bristy Saha | 3.4 | 3.13, Chayon Roy | 2.95 | 2.5, Dilruba Khatun | 3.9 | 3.75, Emon Mia | 3.1 | 2.75, Fahim Karim | NULL | 0, Gulshan Ara | 3.55 | 3.38. Habib Ullah ফলেই নেই — তাঁর কোনো ভর্তি নেই, অথচ জমা রাখা cgpa 2.80. এই নমুনা ডেটায় সব কোর্স রাখা হয়নি, তাই পার্থক্য স্বাভাবিক — কিন্তু ঠিক এটাই সমস্যা: ডেটাবেস দেখে বলা যায় না, কোন সংখ্যাটি সত্য। Derived মান জমা রাখলে প্রতিটি গ্রেড বদলের সাথে তাকেও বদলাতে হয়, নইলে দুটি “সত্য” তৈরি হয়। গতির জন্য জমা রাখা কখনো যুক্তিসংগত (পর্ব ৩৮-এর denormalization), তবে তখন trigger বা নিয়মিত যাচাই লাগে।
ভুল ৪ — ternary যেখানে লাগে না, আর লাগে যেখানে
দুই দিকেই ভুল হয়। অংশ ৫-এ দেখেছেন, SUPPLY-কে তিনটি binary-তে ভাঙলে ভুয়া সারি আসে — সেখানে ternary বাধ্যতামূলক। উল্টো ভুলটিও সমান প্রচলিত: STUDENT–COURSE–INSTRUCTOR একটি ternary “TAKES_FROM” বানানো, যখন বিশ্ববিদ্যালয়ের নিয়ম হলো “প্রতিটি কোর্স একজন শিক্ষকই পড়ান”। তখন শিক্ষক কোর্স থেকেই নির্ধারিত, ternary-তে প্রতিটি ভর্তির সাথে শিক্ষকের নাম বারবার লেখা হবে (redundancy), আর শিক্ষক বদলালে শত সারি বদলাতে হবে। db-uni ঠিক কাজটি করেছে: শিক্ষক courses-এ (TEACHES binary), ভর্তি enrollments-এ (ENROLLS binary). পরীক্ষার প্রশ্ন: “কোন একটি জোড়া জানলে তৃতীয়টি নির্ধারিত হয়ে যায়?” — হলে সম্ভবত binary যথেষ্ট; কোনো জোড়াই তৃতীয়টি ঠিক করতে না পারলে, আর তিনজনের একসাথে attribute (quantity) থাকলে, ternary.
আরও কয়েকটি ছোট ভুল
- Foreign key-কে attribute হিসেবে আঁকা: ER-এ STUDENT-এর পাশে “dept_id” ellipse আঁকা ভুল — সম্পর্কটি MAJORS_IN হীরক; foreign key আসবে টেবিলে রূপান্তরের সময় (পর্ব ১০)।
- বহুবচন বা অস্পষ্ট নাম: “STUDENTS”, “DATA”, “INFO” নয় — একবচন, অর্থপূর্ণ: STUDENT, PAYMENT.
- Key ছাড়া entity: যে entity-কে চেনার উপায় নেই, তার নকশা অসম্পূর্ণ (নিজের key না থাকা “দুর্বল” entity-র কথা পর্ব ৮-এ)।
- Multi-valued-কে কলামে ভাগ করা: phone1, phone2 — চতুর্থ নম্বর এলে schema বদলাতে হয়।
১০ · AI-এর যুগে ER ডিজাইন
AI-কে “একটা হাসপাতালের ER diagram বানিয়ে দাও” বললে কয়েক সেকেন্ডে চমৎকার দেখতে একটি ডায়াগ্রাম বা Mermaid কোড চলে আসে। সমস্যাটি এখানেই: AI আপনার ব্যবসার নিয়ম জানে না, তাই সে “সাধারণ” হাসপাতাল ধরে নেয়। AI প্রায়ই যা করে — ধারণার ভুল, কোয়েরির ভুল দুটিই:
| AI-এর ভুল | ফল | কীভাবে ধরবেন |
|---|---|---|
| চাহিদায় নেই এমন entity বা কলাম যোগ করে (insurance, blood_group) | অপ্রয়োজনীয় জটিলতা; কোয়েরিতে নেই এমন কলামের নাম | প্রতিটি অংশের জন্য জিজ্ঞেস করুন: চাহিদার কোন বাক্য এটি দাবি করে? |
| age, total_amount, cgpa জমা রাখে | দুটি “সত্য”, যা একসময় মেলে না | প্রতিটি কলামে প্রশ্ন: এটি কি অন্য কিছু থেকে হিসাব করা যায়? |
| phone1, phone2 বা কমা দিয়ে লেখা তালিকা | multi-valued attribute ভুলভাবে | “একজনের কয়টি থাকতে পারে?” জিজ্ঞেস করুন |
| ternary-কে নীরবে তিনটি binary বানায়, বা উল্টো | ভুয়া সারি, বা redundancy | ছোট ডেটায় জোড়াগুলো join করে মূলের সাথে সারি গুনে মেলান |
| self join-এ LEFT-এর বদলে INNER JOIN | mentor নেই এমন সবাই ফল থেকে উধাও | ফলের সারি গুনুন: মোট entity-র সংখ্যার সাথে মেলে? |
| সম্পর্কের attribute (grade) entity-তে বসায় | একজনের একটিই গ্রেড রাখা যায় | ডায়াগ্রামটি জোরে পড়ুন: “শিক্ষার্থীর grade” নাকি “ভর্তির grade”? |
AI-কে ডিজাইনার নয়, আলাপের সঙ্গী হিসেবে ব্যবহার করুন — তাকে প্রশ্ন করতে বলুন, অনুমানগুলো আলাদা করে লিখতে বলুন:
AI-এর দেওয়া ডিজাইন হাতে পেলে — যাচাইয়ের চেকলিস্ট
পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:
- Entity আর entity set-এর পার্থক্য? — Entity একটি নির্দিষ্ট জিনিস (একটি সারি); entity set একই ধরনের সব entity-র সংগ্রহ (টেবিলের সব সারি)।
- Composite আর multi-valued attribute-এর উদাহরণ দিন। — ঠিকানা (এলাকা, থানা, জেলা) composite; একজনের কয়েকটি ফোন নম্বর multi-valued.
- Derived attribute কী, Chen notation-এ কীভাবে আঁকেন? — অন্য ডেটা থেকে হিসাব করা মান, যেমন জন্মতারিখ থেকে বয়স; ভাঙা রেখার ellipse.
- Recursive relationship-এ role name কেন লাগে? — দুই প্রান্তেই একই entity type, তাই কে কোন ভূমিকায় (mentor না mentee) তা না লিখলে অর্থ অস্পষ্ট।
- একটি ternary কি সবসময় তিনটি binary দিয়ে বদলানো যায়? — না; জোড়াগুলো join করলে ভুয়া সারি আসতে পারে, আর পুরো ত্রয়ীর attribute (quantity) রাখার জায়গা থাকে না।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোয়েরি-লেখক থেকে ডেটাবেস-ডিজাইনার বানাবে।
প্র ০১Codd-এর relational model ১৯৭০ সালেই ছিল। তাহলে ১৯৭৬ সালে Chen-এর ER মডেলের দরকার পড়ল কেন, আর ৫০ বছর পরেও এটি টিকে আছে কেন?
Relational model বলে ডেটা কীভাবে রাখা হবে: টেবিল, key, মান দিয়ে সংযোগ। কিন্তু সেটি বলে না, বাস্তব জগতের কোন জিনিস কোন টেবিল হবে। ১৯৭০-এর দশকের মাঝামাঝি ডেটাবেস জগৎ তিন শিবিরে ভাগ ছিল — CODASYL-এর network model (pointer আর record-এর শিকল), Codd-এর relational model, আর entity set-ভিত্তিক কিছু মডেল। প্রতিটির নিজস্ব ভাষা, আর ব্যবসার মানুষের কাছে সবই দুর্বোধ্য।
Chen-এর প্রবন্ধের শিরোনামেই তাঁর উদ্দেশ্য: “Toward a Unified View of Data”. তিনি প্রস্তাব করলেন, বাস্তব জগৎকে আগে এমন ভাষায় আঁকুন যা কোনো নির্দিষ্ট DBMS-এর ওপর নির্ভর করে না — জিনিস, তাদের গুণ আর সম্পর্ক। তারপর সেই ছবিকে network বা relational যেকোনো মডেলে রূপান্তর করা যায়। প্রবন্ধে তিনি নিজেই দেখিয়েছিলেন ER থেকে relational আর network দুই রূপেই যাওয়া যায়। অর্থাৎ ER মডেল relational model-এর প্রতিদ্বন্দ্বী নয়, তার আগের ধাপ — পর্ব ৩-এর three-schema architecture-এর conceptual স্তরের জন্য একটি ভাষা।
টিকে থাকার তিনটি কারণ। প্রথমত, এটি মানুষের ভাষার কাছাকাছি: বিশেষ্য entity, ক্রিয়া relationship — Chen পরে ১৯৮৩ সালের একটি প্রবন্ধে ইংরেজি বাক্যের গঠনের সাথে এই মিল বিস্তারিত দেখান। তাই হাসপাতালের প্রশাসক বা কোচিং সেন্টারের মালিক ছবিটি দেখে ভুল ধরতে পারেন, যা CREATE TABLE দেখে পারতেন না। দ্বিতীয়ত, প্রযুক্তি বদলালেও ধারণা বদলায় না — আজ যে ডেটা PostgreSQL-এ, কাল তা MongoDB-তে গেলেও “শিক্ষার্থী কোর্সে ভর্তি হন” সত্যই থাকে। তৃতীয়ত, এর থেকেই এসেছে EER (পর্ব ৯), UML class diagram-এর ডেটা অংশ আর শিল্পক্ষেত্রের Crow's foot টুল — অর্থাৎ নাম বদলালেও ধারণাটি সব জায়গায়।
মূল উপলব্ধি: ER মডেল “ডেটা কীভাবে রাখব” নয়, “কী রাখব” প্রশ্নের উত্তর দেয়। সেই প্রশ্ন কোনো DBMS-এর চেয়ে পুরোনো, আর কোনো DBMS-এর চেয়ে বেশি দিন টিকবে।
প্র ০২একটি ই-কমার্স সাইটের ডিজাইনে একজন বললেন “city তো শুধু একটা লেখা, attribute-ই যথেষ্ট”, আরেকজন বললেন “CITY একটা entity হওয়া উচিত”। বাংলাদেশের প্রেক্ষাপটে কে ঠিক?
দুজনেই ঠিক হতে পারেন — নির্ভর করে ব্যবসা ঠিকানা দিয়ে কী করবে তার ওপর। ER-এর একটি পুরোনো নিয়ম: কোনো জিনিসের নিজের তথ্য রাখতে হলে, বা অন্য সম্পর্ক তার দিকে আঙুল তুললে, সেটি entity; শুধু বর্ণনার জন্য লাগলে attribute.
যদি দোকান ঠিকানা শুধু পার্সেলের গায়ে ছাপায়, city একটি attribute — বরং পুরো ঠিকানা এক লাইনে রাখাই সহজ। কিন্তু বাংলাদেশের বেশিরভাগ ই-কমার্সে ঠিকানা ব্যবসার নিয়মের অংশ: ঢাকার ভেতরে আর বাইরে ডেলিভারি চার্জ আলাদা, কিছু উপজেলায় ক্যাশ-অন-ডেলিভারি নেই, কুরিয়ারের ভাড়া জেলা অনুযায়ী। তখন DISTRICT-এর নিজের attribute আছে (চার্জ, কোন বিভাগে, কোন কুরিয়ার যায়) — এটি entity. প্রশাসনিক কাঠামোও স্তরভিত্তিক: ৮টি বিভাগ, ৬৪টি জেলা, তার নিচে প্রায় পাঁচশো উপজেলা — DIVISION, DISTRICT, UPAZILA তিনটি entity আর তাদের মধ্যে “অন্তর্ভুক্ত” relationship.
বানানের সমস্যা দ্বিতীয় কারণ। অংশ ৯-এর এডিটরে দেখেছেন, মুক্ত লেখায় একই শহর চার রূপে আসে। বাস্তব উদাহরণও আছে: ২০১৮ সালে সরকার কয়েকটি জেলার ইংরেজি বানান বদলায় — Chittagong থেকে Chattogram, Comilla থেকে Cumilla, Barisal থেকে Barishal, Jessore থেকে Jashore, Bogra থেকে Bogura. City যদি প্রতিটি গ্রাহকের সারিতে মুক্ত লেখা হয়, পুরোনো আর নতুন বানান চিরকাল পাশাপাশি থাকবে, আর “চট্টগ্রামের মোট বিক্রি” রিপোর্ট ভুল আসবে। City একটি entity হলে বানান থাকে একটি সারিতে; বদলাতে হয় একবার, আর সব গ্রাহক নিজে থেকেই নতুন নাম দেখান।
দাম: একটি বাড়তি টেবিল, একটি join, আর তালিকা হালনাগাদ রাখার দায়িত্ব। ছোট অ্যাপে, যেখানে ঠিকানা দিয়ে কোনো হিসাব হয় না, এটি বাড়াবাড়ি।
মূল উপলব্ধি: “এটি attribute না entity” প্রশ্নের উত্তর জিনিসটির মধ্যে নয়, ব্যবহারে। জিজ্ঞেস করুন: এর নিজের কোনো তথ্য আছে? অনেকে কি একে ভাগ করে নেয়? এর নাম কি বদলাতে পারে? উত্তর হ্যাঁ হলে entity.
প্র ০৩“Derived attribute জমা রাখবেন না” — কিন্তু ব্যাংক প্রতিটি অ্যাকাউন্টের balance জমা রাখে, যা আসলে সব লেনদেনের যোগফল। তাহলে নিয়মটি কখন ভাঙা যায়, আর ভাঙলে কী সাবধানতা লাগে?
ধারণার স্তরে balance সত্যিই derived: প্রথম দিন থেকে সব জমা বিয়োগ সব উত্তোলন। কিন্তু একটি পুরোনো অ্যাকাউন্টে লাখো লেনদেন থাকতে পারে, আর ATM থেকে টাকা তোলার প্রতিটি অনুরোধে সব যোগ করা অসম্ভব রকম ধীর। তাই ব্যাংক balance জমা রাখে — এটি একটি সচেতন সিদ্ধান্ত, যাকে বলে denormalization (পর্ব ৩৮)। মূল কথা হলো, conceptual মডেলে balance-কে derived হিসেবেই চিহ্নিত করুন (ভাঙা রেখার ellipse), আর physical ডিজাইনে জমা রাখার সিদ্ধান্তটি আলাদা করে, যুক্তিসহ নিন।
জমা রাখলে তিনটি সাবধানতা লাগে। এক, হালনাগাদ একসাথে: লেনদেনের সারি ঢোকানো আর balance বদলানো একটি transaction-এ (পর্ব ৪৪), যাতে মাঝপথে বিদ্যুৎ গেলেও একটি হয়ে অন্যটি বাদ না পড়ে। দুই, স্বয়ংক্রিয় পাহারা: trigger (পর্ব ২৯) বা অ্যাপের একটিমাত্র পথ, যাতে কেউ শুধু একটি দিক বদলাতে না পারে। তিন, নিয়মিত মিলানো: হিসাবরক্ষণে দিন শেষে যেমন খাতা মেলানো হয় (reconciliation), তেমনি একটি কোয়েরি চালিয়ে জমা রাখা আর হিসাব করা মান মেলে কি না দেখা — অংশ ৯-এর cgpa এডিটর ঠিক এই পরীক্ষাই করেছে, আর একটিও মেলেনি।
আধুনিক DBMS মাঝামাঝি একটি পথও দেয়: generated column. MySQL 5.7, PostgreSQL 12 আর SQLite 3.31 থেকে একটি কলামকে অন্য কলামের সূত্র হিসেবে ঘোষণা করা যায় (যেমন total = quantity * unit_price), আর DBMS নিজেই তাকে সঠিক রাখে। কিন্তু এর সীমা আছে: সূত্রটি শুধু একই সারির কলাম দেখতে পারে, আর ফল নির্দিষ্ট (deterministic) হতে হয়। তাই বয়স generated column হতে পারে না — এটি আজকের তারিখের ওপর নির্ভর করে, যা প্রতিদিন বদলায়। আর cgpa-ও না — সেটি অন্য টেবিলের অনেক সারি থেকে আসে; এর জন্য view (পর্ব ২৪) বা materialized view লাগে।
মূল উপলব্ধি: Derived মান জমা রাখা নিষেধ নয়, কিন্তু সেটি গতির জন্য নেওয়া একটি ঋণ। ঋণ নিলে হিসাব রাখতে হয় — একসাথে হালনাগাদ, স্বয়ংক্রিয় পাহারা আর নিয়মিত মিলানো।
প্র ০৪Crow's foot-ভিত্তিক বেশিরভাগ টুলে ternary relationship সরাসরি আঁকাই যায় না। তাহলে সেখানে SUPPLY-এর মতো তথ্য কীভাবে মডেল করা হয়, আর এতে কি কোনো তথ্য হারায়?
Crow's foot রীতিতে প্রতিটি রেখা ঠিক দুটি বাক্সকে জোড়ে, আর রেখার ওপর কোনো attribute বসে না। তাই ternary বা বহু-বহু সম্পর্ককে সেখানে একটি associative entity বানাতে হয়: SUPPLY একটি বাক্স, যার attribute supplier_id, part_id, project_id (তিনটি মিলে key) আর quantity, আর তিনটি আলাদা রেখা SUPPLIER, PART, PROJECT-এর দিকে। খেয়াল করুন — এগুলো তিনটি binary relationship, কিন্তু SUPPLIER–PART, PART–PROJECT ধরনের নয়; প্রত্যেকটি SUPPLY বাক্স থেকে একটি entity-তে। SUPPLY-এর প্রতিটি সারি এখনো একটি পূর্ণ ত্রয়ী, তাই কোনো তথ্য হারায় না, ভুয়া সারিও আসে না। টেবিলে দুটি রূপ আসলে একই: একটি supply টেবিল, যাতে তিনটি foreign key.
তথ্য হারায় তখনই, যখন কেউ associative entity না বানিয়ে সরাসরি তিন জোড়া entity-র মধ্যে তিনটি বহু-বহু সম্পর্ক আঁকেন — অংশ ৫-এর stepper-এর ভুল। এটি তত্ত্বের ভাষায় একটি join dependency-র প্রশ্ন: ⋈{SP, PJ, SJ} সব বৈধ instance-এ সত্য হলে তিন জোড়া যথেষ্ট, নইলে নয়। মজার ব্যাপার, এমন নিয়ম বাস্তবে বিরল কিন্তু অসম্ভব নয় — যেমন কোনো কোম্পানির নীতি যদি হয় “যে supplier কোনো project-এ কাজ করে আর যে part সরবরাহ করে, সেই project-এ ওই part লাগলে তাকেই দিতে হবে”, তাহলে binary-গুলোই সঠিক, আর ternary রাখলে redundancy আসে। Ronald Fagin ১৯৭৯ সালে এই ধরনের নির্ভরতা থেকে Project-Join Normal Form (5NF) সংজ্ঞায়িত করেন, যা পর্ব ৩৭-এ দেখবেন।
UML-এ আবার n-ary association আঁকার নিজস্ব চিহ্ন আছে (মাঝে একটি হীরক), যদিও বাস্তবে ডেভেলপাররা প্রায়ই association class বা আলাদা class দিয়েই কাজ সারেন। আর Chen notation-এ ternary সবচেয়ে স্বাভাবিক — একটি হীরক, তিনটি রেখা।
মূল উপলব্ধি: Notation ternary আঁকতে না পারলেও ধারণাটি হারায় না — associative entity-ই সেই ternary. বিপদ notation-এ নয়, “জোড়ায় ভেঙে দিই” ভাবনায়; সেটি ঠিক কি না, বলে কেবল ব্যবসার নিয়ম।
প্র ০৫Relationship set একটি সেট, তাই একই (শিক্ষার্থী, কোর্স) জোড়া দুবার থাকতে পারে না। অথচ শিক্ষার্থী ফেল করে একই কোর্স আবার নেন, রোগী একই ওয়ার্ডে আবার ভর্তি হন। ER-এ এটি সঠিকভাবে কীভাবে আঁকবেন?
সমস্যাটি সূক্ষ্ম, আর অনেক ডিজাইনে চুপচাপ ভুল থেকে যায়। অংশ ৪-এর সংজ্ঞা অনুযায়ী ENROLLS ⊆ STUDENT × COURSE, অর্থাৎ প্রতিটি instance একটি (student, course) জোড়া, আর সেটে একই জোড়া একবারই থাকে। term-কে ENROLLS-এর একটি সাধারণ attribute বানালে (ছবি ৭.২) মানে দাঁড়ায় “প্রতিটি জোড়ার একটিই term” — Fahim যদি EEE101-এ F পেয়ে পরের term-এ আবার নেন, সেই দ্বিতীয় ভর্তি এই মডেলে রাখাই যায় না।
সমাধানের তিনটি প্রচলিত পথ:
- TERM-কে entity বানানো: ENROLLS হয় STUDENT–COURSE–TERM-এর ternary relationship, attribute grade. এখন (Fahim, EEE101, 2023-Spring) আর (Fahim, EEE101, 2024-Spring) দুটি আলাদা ত্রয়ী। টেবিলে ঠিক db-uni-র enrollments: key (student_id, course_id, term). অর্থাৎ db-uni আসলে এই ternary-টিই বাস্তবায়ন করেছে।
- ঘটনাকে entity বানানো: ENROLLMENT একটি entity, নিজের enrollment_id-সহ, আর দুটি binary relationship STUDENT ও COURSE-এর সাথে। হাসপাতালের ADMISSION সাধারণত এভাবেই — প্রতিটি ভর্তির নিজের নম্বর, বিল, ছাড়পত্র আছে, আর অন্য entity (BILL) তার দিকে আঙুল তোলে।
- কিছু বই “multi-valued relationship attribute” ধরনের সমাধান দেখায়, কিন্তু টেবিলে রূপান্তরের সময় সেটিও শেষে উপরের দুটির একটিতে পৌঁছায়।
কোনটি বেছে নেবেন? ঘটনার নিজের পরিচয় বা অন্য সম্পর্ক থাকলে entity-পথ; না থাকলে ternary-পথ সরল। মূল কথা হলো প্রশ্নটি জিজ্ঞেস করা: “একই জোড়া কি কখনো দুবার ঘটতে পারে?” কোচিং সেন্টারে একজন শিক্ষার্থী একই ব্যাচে চলে গিয়ে আবার ফিরে এলে কী হবে — ঠিক এই প্রশ্নটি অংশ ৬-এর মালিককে করা বাকি রয়ে গেছে। এই পার্থক্যটি পর্ব ১০-এ ER থেকে টেবিল বানানোর সময় সরাসরি key-র আকার ঠিক করবে।
মূল উপলব্ধি: Relationship-এর পরিচয় তার অংশগ্রহণকারীরা। একই অংশগ্রহণকারী বারবার সম্পর্কে এলে আরেকজন অংশগ্রহণকারী (TERM) যোগ করুন, অথবা সম্পর্কটিকেই একটি entity বানান।
অনুশীলন
খাতা-কলম নিন। একটি শ্রেণিবিভাগ, একটি কোয়েরি, একটি হাতে হিসাব, একটি ডিজাইন।
-
Attribute চিনুন (হাতে): একটি লাইব্রেরির MEMBER entity-র তথ্য: member_id, নাম (প্রথম ও শেষ অংশ আলাদা লাগে), ঠিকানা (বাড়ি, রাস্তা, এলাকা, জেলা), এক বা একাধিক ফোন নম্বর, জন্মতারিখ, বয়স, এই মুহূর্তে কয়টি বই ধার নিয়েছেন, আর email (সবার নেই)। প্রতিটির প্রকার লিখুন আর Chen notation-এ কোন চিহ্নে আঁকবেন বলুন।
- member_id — simple, single-valued, stored, key: সাধারণ ellipse, নামের নিচে দাগ।
- নাম — composite (first_name, last_name): ellipse থেকে দুটি ছোট ellipse.
- ঠিকানা — composite (চারটি অংশ)।
- ফোন নম্বর — multi-valued: দুই-রেখার ellipse; টেবিলে আলাদা member_phones.
- জন্মতারিখ — simple, stored.
- বয়স — derived (জন্মতারিখ থেকে): ভাঙা রেখার ellipse.
- ধার নেওয়া বইয়ের সংখ্যা — derived: আসলে MEMBER আর BOOK-এর একটি BORROWS relationship-এর সারি গুনে পাওয়া যায়। জমা রাখলে প্রতিটি ধার আর ফেরতে বদলাতে হবে।
- email — simple, single-valued, ঐচ্ছিক (NULL চলে)।
-
কোয়েরি লিখুন: db-uni-তে এমন একটি কোয়েরি লিখুন, যা প্রতিটি mentor-এর নাম আর তিনি কতজনের mentor তা দেখায় (যাঁরা কারও mentor নন, তাঁরা ফলে না এলেও চলবে)। অংশ ৫-এর mentor এডিটরে চালিয়ে দেখুন।
SELECT m.name AS mentor, COUNT(*) AS mentees FROM instructors AS i JOIN instructors AS m ON m.instructor_id = i.mentor_id GROUP BY m.instructor_id, m.name ORDER BY m.instructor_id;ফল ৩টি সারি:
Dr. Anisur Rahman | 1,Shahana Parvin | 1,Dr. Mahbub Alam | 1. এখানেimentee ভূমিকায়,mmentor ভূমিকায় — role name দুটিই alias হয়েছে। প্রতিটি mentee-র সারি তাঁর mentor-এর দলে পড়ে, তাই COUNT(*) মানে mentee-র সংখ্যা। INNER JOIN হওয়ায় যাঁদের mentor_id NULL, সেই সারিগুলো আগেই বাদ। (GROUP BY পর্ব ১৮-এ।) -
ফল আগে বলুন (trace): একটি ternary SUPPLY(supplier, part, project)-এ তিনটি সারি: (S1, P1, J1), (S2, P2, J2), (S1, P1, J2). এ থেকে তিনটি জোড়া-টেবিল SP, PJ, SJ বানিয়ে আবার join করলে কয়টি সারি আসবে? কোনো ভুয়া সারি আসবে কি? এ থেকে কি বলা যায় যে SUPPLY-কে তিনটি binary দিয়ে বদলানো নিরাপদ?
SP = {(S1,P1), (S2,P2)}; PJ = {(P1,J1), (P2,J2), (P1,J2)}; SJ = {(S1,J1), (S2,J2), (S1,J2)}. SP ⋈ PJ: (S1,P1) দুটি project-এর সাথে → (S1,P1,J1), (S1,P1,J2); (S2,P2) → (S2,P2,J2). মোট ৩টি। SJ-তে (S1,J1), (S1,J2), (S2,J2) তিনটিই আছে, তাই কিছু বাদ পড়ে না। ফল ৩টি সারি, কোনো ভুয়া নেই — মূলের সাথে হুবহু এক। অংশ ৫-এর এডিটরের data-setup বদলে নিজে মিলিয়ে নিন।
কিন্তু না, এ থেকে বলা যায় না যে বদলানো নিরাপদ। একটি নির্দিষ্ট instance-এ কাজ করা মানে শুধু “এখনো খণ্ডন হয়নি” — পর্ব ৬-এর key-র মতোই। কাল (S2, P1, J1) ধরনের একটি সারি এলে ভুয়া সারি জন্মাতে পারে। নিরাপদ কি না, তা ঠিক করে ব্যবসার নিয়ম (join dependency সব বৈধ ডেটায় সত্য কি না), আর quantity-র মতো ত্রয়ীর attribute থাকলে উত্তর সরাসরি “না”।
-
ডিজাইন করুন: একটি ছোট হাসপাতালের চাহিদা: “প্রতিটি রোগীর একটি নম্বর, নাম, জন্মতারিখ আর এক বা একাধিক ফোন নম্বর থাকে। ডাক্তারের আইডি, নাম আর বিশেষজ্ঞতা রাখা হয়। একজন রোগী একাধিকবার ভর্তি হতে পারেন; প্রতিটি ভর্তির তারিখ, ছাড়ার তারিখ আর ওয়ার্ড রাখতে হয়। প্রতিটি ভর্তিতে একজন ডাক্তার দায়িত্বে থাকেন। কিছু ডাক্তার অন্য ডাক্তারের তত্ত্বাবধায়ক।” Entity, attribute (প্রকারসহ), relationship (degree আর role name-সহ) তালিকা করুন।
- Entity: PATIENT, DOCTOR, WARD, আর ADMISSION — একই রোগী বহুবার ভর্তি হন, আর প্রতিটি ভর্তির নিজের তথ্য আছে, তাই এটি relationship নয়, entity (ভাবনার প্রশ্ন ৫)।
- PATIENT: patient_no (key), name, dob (stored), age (derived, প্রয়োজনে), {phone} (multi-valued).
- DOCTOR: doctor_id, name, specialty. WARD: ward_no, প্রয়োজনে name. (শুধু নম্বর লাগলে ward ADMISSION-এর attribute-ও হতে পারে; ওয়ার্ডের নিজের তথ্য — শয্যা সংখ্যা — রাখতে হলে entity.)
- ADMISSION: admission_id, admit_date, discharge_date (ঐচ্ছিক — এখনো ভর্তি থাকলে NULL).
- Relationship: HAS_ADMISSION (PATIENT–ADMISSION, binary), IN_WARD (ADMISSION–WARD, binary), RESPONSIBLE_FOR (DOCTOR–ADMISSION, binary), SUPERVISES (DOCTOR–DOCTOR, unary, role name: supervisor আর supervisee).
- মালিককে প্রশ্ন: একটি ভর্তিতে কি দায়িত্বের ডাক্তার বদলাতে পারেন? হলে RESPONSIBLE_FOR-এ শুরুর আর শেষের তারিখ লাগবে, আর সম্পর্কটি আবার ভাবতে হবে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি নতুন ডেটাবেসের ER ডিজাইনের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- ব্যবহারকারীদের সাথে কথা বলে চাহিদা লিখে নেওয়া
- বর্ণনার বিশেষ্য থেকে entity খুঁজে বের করা
- প্রতিটি entity-র attribute, তাদের প্রকার আর key ঠিক করা
- ক্রিয়া থেকে relationship, তাদের degree আর role name ঠিক করা
- সম্পর্কের attribute কোথায় বসবে, তা যাচাই করা
- ডায়াগ্রামটি ব্যবহারকারীদের সামনে জোরে পড়ে মিলিয়ে নেওয়া
Chen notation-এ দুই-রেখার (double) ellipse কী বোঝায়?
- Key attribute
- Multi-valued attribute
- Derived attribute
- Composite attribute
একটি ভর্তি রেকর্ডে “গ্রেড” রাখতে হবে। শিক্ষার্থী অনেক কোর্স নেন, আর একটি কোর্সে অনেক শিক্ষার্থী। ER মডেলে গ্রেড কোথায় বসবে?
- STUDENT-এর attribute হিসেবে
- COURSE-এর attribute হিসেবে
- STUDENT আর COURSE-এর মধ্যকার ENROLLS relationship-এর attribute হিসেবে
- একটি আলাদা GRADE entity হিসেবে
“একজন কর্মচারী আরেকজন কর্মচারীর supervisor” — এই relationship-এর degree কত, আর ডায়াগ্রামে কী অবশ্যই লিখতে হয়?
- Unary (recursive); দুই রেখায় role name
- Binary; কোনো বাড়তি চিহ্ন লাগে না
- Ternary; quantity attribute
- Degree নির্ভর করে কর্মচারীর সংখ্যার ওপর
একটি ternary relationship SUPPLY(supplier, part, project)-এ তিনটি সারি: (S1, P1, J2), (S1, P2, J1), (S2, P1, J1). এ থেকে (supplier, part), (part, project) আর (supplier, project) জোড়ার তিনটি টেবিল বানিয়ে আবার join করলে কয়টি সারি আসবে?
- ৩টি
- ৫টি
- ২৭টি
- ৪টি
patients টেবিলে জন্মতারিখ (dob) আর বয়স (age) দুটিই কলাম হিসেবে জমা রাখা হয়েছে। এই ডিজাইনের প্রধান সমস্যা কী?
- age কলাম অনেক বেশি জায়গা নেয়
- age dob থেকে হিসাব করা যায়, আর প্রতি জন্মদিনে জমা মানটি পুরোনো হয়ে যায়
- dob রাখা অপ্রয়োজনীয়, শুধু age যথেষ্ট
- SQL-এ একই টেবিলে তারিখ আর সংখ্যা রাখা যায় না
একটি অনলাইন দোকান জেলা অনুযায়ী আলাদা ডেলিভারি চার্জ নেয়, আর জেলার নামের বানানও বদলাতে পারে। গ্রাহকের “জেলা” কীভাবে মডেল করা ভালো?
- CUSTOMER-এর একটি সাধারণ লেখা (text) attribute হিসেবে
- CUSTOMER-এর একটি multi-valued attribute হিসেবে
- একটি আলাদা DISTRICT entity হিসেবে, যার attribute ডেলিভারি চার্জ
- CUSTOMER-এর একটি derived attribute হিসেবে
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- আগে চাহিদা আর ER ডায়াগ্রাম, পরে টেবিল — নকশা কাগজে বদলানো সস্তা, ডেটা জমার পরে দামি।
- Entity একটি জিনিস (সারি), entity type তার ছাঁচ (schema), entity set এই মুহূর্তের সবগুলো (টেবিলের সব সারি)।
- Attribute: simple/composite, single/multi-valued (double ellipse), stored/derived (dashed), আর key-র নিচে দাগ; multi-valued যায় আলাদা টেবিলে।
- Relationship-এর নিজের attribute থাকতে পারে (grade); degree unary (role name বাধ্যতামূলক), binary, ternary — ternary জোড়ায় ভাঙলে ভুয়া সারি আসতে পারে।
- Chen, Crow's foot আর UML — চিহ্ন আলাদা, ধারণা এক; আর “attribute না entity” প্রশ্নের উত্তর দেয় ব্যবহার।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ০৮ · Cardinality, participation আর weak entityপরবর্তী পর্বরেখাগুলোতে “কতজন” আর “বাধ্যতামূলক কি না” যোগ করা।
- ডেটাবেস (DBMS) ও SQL · কোর্স হোম৬৭টি পর্বপুরো কোর্সের রোডম্যাপ — ১০টি মডিউল।
- DSA · Graphপাশের ট্র্যাকER ডায়াগ্রাম আসলে একটি graph; recursive relationship মানে নিজের দিকে ফেরা edge.
- Python · SQLAlchemy ORMহাতে-কলমেEntity আর relationship কীভাবে Python class আর relationship() হয়ে যায়।
- Java · Spring Data JPAহাতে-কলমে@Entity, @ManyToMany — ER ধারণার Java রূপ।