সরাসরি পর্বে যান
পর্ব ০৪ · ৬৭-এর মধ্যে · মডিউল ১

ডেটা মডেল — hierarchical থেকে relational আর NoSQL

Data Models — Hierarchical, Network, Relational, Object, Semi-structured & NoSQL

Taught in Bangla, this lesson defines a data model by its structure, operations and rules, contrasts conceptual, logical and physical models, and shows the same university data in hierarchical, network and relational form. It covers object-relational models, semi-structured XML and JSON queried live with json_extract and json_each, the four NoSQL families, and choosing a model including polyglot persistence.

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

  • ডেটা মডেলের তিন উপাদান, আর conceptual, logical ও physical মডেলের পার্থক্য
  • একই বিশ্ববিদ্যালয়ের ডেটা hierarchical, network আর relational মডেলে কেমন দেখায় — ধাপে ধাপে
  • Many-to-many সম্পর্ক কেন গাছের মডেলে কষ্টের, আর JOIN কীভাবে সমস্যাটি মিটিয়ে দেয়
  • SQLite-এ JSON ডকুমেন্ট রেখে json_extract আর json_each দিয়ে কোয়েরি
  • NoSQL-এর চার পরিবার, তুলনা-টেবিল, আর কোন কাজে কোন মডেল বাছবেন

১ · ডেটা মডেল আসলে কী — গঠন, অপারেশন আর নিয়ম

পাড়ার মুদি দোকানের কথা ভাবুন। দোকানি বাকির হিসাব রাখেন একটি খাতায়। প্রতি পাতায় একজন ক্রেতা — ওপরে নাম, নিচে তারিখ ধরে কী নিলেন আর কত টাকা। এখানে তিনটি জিনিস লুকিয়ে আছে। প্রথমত, খাতার আকার — এক পাতা এক ক্রেতা, প্রতি লাইনে তারিখ-জিনিস-টাকা। দ্বিতীয়ত, খাতা দিয়ে যা যা করা যায় — নতুন লাইন লেখা, কারও মোট বাকি যোগ করা, টাকা শোধ হলে কেটে দেওয়া। তৃতীয়ত, কিছু নিয়ম — টাকার ঘরে ঋণাত্মক সংখ্যা চলবে না, একই নামে দুই পাতা খোলা যাবে না।

এই তিনটি মিলেই একটি data model (ডেটা মডেল)। ১৯৮০ সালে Edgar F. Codd নিজেই একটি প্রবন্ধে ডেটা মডেলকে ঠিক এভাবে সংজ্ঞায়িত করেছিলেন — ডেটার গঠনের ধরন, সেই গঠনের ওপর চলা অপারেটর, আর integrity-র নিয়ম। আজও বিশ্ববিদ্যালয়ের বইগুলো এই সংজ্ঞাই ব্যবহার করে।

১. গঠন (structure)

ডেটা কোন আকারে সাজানো — গাছ, গ্রাফ, টেবিল, object, নাকি JSON ডকুমেন্ট। কোন জিনিসের সাথে কোন জিনিস কীভাবে যুক্ত।

খাতার পাতা আর লাইনের ছক

২. অপারেশন (operations)

ডেটা পড়া, খোঁজা, যোগ করা, বদলানো, মোছা — কোন ভাষায়, কী ধরনের নির্দেশে। Pointer ধরে হাঁটা, নাকি “কী চাই” বলে দেওয়া।

লেখা, যোগফল, কেটে দেওয়া

৩. নিয়ম (constraints)

যে শর্ত সবসময় সত্য থাকতে হবে — যেমন প্রতিটি শিক্ষার্থীর আইডি আলাদা, যে বিভাগ নেই সেখানে কাউকে ভর্তি করা যাবে না।

ঋণাত্মক টাকা চলবে না

খেয়াল করুন, ডেটা মডেল কোনো সফটওয়্যার নয় — এটি একটি চিন্তার কাঠামো। MySQL, PostgreSQL, SQLite, Oracle আলাদা আলাদা সফটওয়্যার (অর্থাৎ আলাদা DBMS), কিন্তু সবাই মূলত একই relational মডেল মেনে চলে। আবার MongoDB আর Firestore দুটি আলাদা পণ্য, কিন্তু দুটিই document মডেলের। তাই একটি মডেল বুঝলে সেই পরিবারের সব সফটওয়্যারের মূল আচরণ আগেই আন্দাজ করা যায়।

এই পর্ব জুড়ে আমরা একটিমাত্র ছোট জগৎ নিয়ে কাজ করব — একটি বিশ্ববিদ্যালয়। সেখানে বিভাগ (department) আছে, শিক্ষক (instructor), কোর্স আর শিক্ষার্থী আছে; একজন শিক্ষার্থী অনেক কোর্সে ভর্তি হন, আর একটি কোর্সে অনেক শিক্ষার্থী পড়েন। একই জগৎকে আমরা প্রতিটি মডেলে আঁকব, যাতে পার্থক্যটা চোখে পড়ে। Relational অংশের কোয়েরিগুলো এই কোর্সের সাধারণ 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 (course_id, title, dept_id, credits, instructor_id) আর enrollments (student_id, course_id, term, grade) — কে কোন টার্মে কোন কোর্সে ভর্তি হয়েছেন এবং কী গ্রেড পেয়েছেন।

প্রথম লাইভ উদাহরণে মডেলের তিন উপাদানই একসাথে দেখুন। নিচের কোয়েরিটি গঠন (টেবিলের সারি আর কলাম) থেকে একটি অপারেশন (বাছাই) চালাচ্ছে। কোড বদলে দেখতে পারেন — যেমন dept_id = 2 লিখুন।

SELECT student_id, name, dept_id, batch
FROM students
WHERE dept_id = 1;

ফল চারটি সারি:

student_id │ name           │ dept_id │ batch
2101       │ Arif Hossain   │ 1       │ 2021
2102       │ Bristy Saha    │ 1       │ 2021
2201       │ Dilruba Khatun │ 1       │ 2022
2301       │ Gulshan Ara    │ 1       │ 2023

লক্ষ করুন, “Arif CSE বিভাগের” — এই তথ্যটি কোনো pointer বা লিংক দিয়ে রাখা নেই। শুধু dept_id কলামে 1 লেখা আছে, আর departments টেবিলে 1 মানে CSE. সম্পর্ক বোঝানো হয়েছে মান মিলিয়ে — এটিই relational মডেলের মূল কৌশল, যা অংশ ৫-এ বিস্তারিত দেখব।

এবার তৃতীয় উপাদান — নিয়ম। db-uni-তে students.dept_id একটি foreign key: এর মান অবশ্যই departments টেবিলে থাকতে হবে। ৯ নম্বর বিভাগ নেই। দেখুন DBMS কী বলে:

-- ৯ নম্বর বিভাগ নেই, তবু ভর্তি করার চেষ্টা
INSERT INTO students VALUES (2401, 'Jannat Ara', 9, 2024, 3.20);

ফল: FOREIGN KEY constraint failed. সারিটি ঢোকেনি। খাতায় দোকানি নিজে খেয়াল রাখেন, কিন্তু ডেটাবেসে নিয়মটি মডেলের অংশ — কোন অ্যাপ বা কোন মানুষ লিখছে, তাতে কিছু যায় আসে না, DBMS নিজেই আটকে দেয়। এই ধরনের constraint নিয়ে পুরো পর্ব ৬ আছে। (SQLite-এ foreign key যাচাই ডিফল্টে বন্ধ থাকে, PRAGMA foreign_keys = ON দিয়ে চালু করতে হয়; এই পাতার এডিটরে সেটি আগেই চালু করা আছে। PostgreSQL, MySQL-এর InnoDB বা SQL Server-এ এটি ডিফল্টভাবেই চালু।)

২ · তিন স্তরের মডেল — conceptual, logical আর physical

একটি বাড়ি বানানোর কথা ভাবুন। প্রথমে মালিক স্থপতিকে মুখে বলেন — “তিনটা শোবার ঘর, একটা বারান্দা দক্ষিণে।” স্থপতি একটি খসড়া আঁকেন, যেখানে শুধু ঘর আর ঘরের সম্পর্ক। তারপর প্রকৌশলী মাপসহ নকশা করেন — কোন দেয়াল কত ইঞ্চি, কোথায় কলাম। শেষে রাজমিস্ত্রি ঠিক করেন কোন ইট, কত রড, কোন সিমেন্ট। একই বাড়ি, তিনটি স্তরের বর্ণনা।

ডেটাবেস ডিজাইনেও ঠিক তিনটি স্তর:

  • Conceptual model: ব্যবসার ভাষায় কী কী জিনিস (entity) আছে আর তারা কীভাবে যুক্ত। কোনো DBMS-এর নাম নেই, কোনো টাইপ নেই। সাধারণত ER ডায়াগ্রামে আঁকা হয়।
  • Logical model: একটি নির্দিষ্ট ডেটা মডেলে (যেমন relational) রূপ — কোন টেবিল, কোন কলাম, কোনটি primary key, কোনটি foreign key. এখনো ডিস্ক বা ফাইলের কথা নেই।
  • Physical model: নির্দিষ্ট DBMS-এ বাস্তবে কীভাবে থাকবে — ডেটা টাইপ, index, ফাইল, page-এর আকার, partition.

পর্ব ৩-এ যে three-schema architecture (external, conceptual, internal) দেখেছেন, সেটি চালু DBMS-এর ভেতরের স্তরবিন্যাস। আর এখানের conceptual → logical → physical হলো ডিজাইনের ধাপ — কাগজে চিন্তা থেকে বাস্তব টেবিল পর্যন্ত। দুটির মিল আছে, কিন্তু প্রশ্ন আলাদা; পরীক্ষায় দুটি গুলিয়ে ফেলবেন না।

  1. প্রয়োজনসাধারণ ভাষারেজিস্ট্রার যা বলেন
  2. ConceptualER ডায়াগ্রামকী আছে, কীভাবে যুক্ত
  3. Logicalটেবিল ও keyrelational স্কিমা
  4. Physicalটাইপ, index, ফাইলনির্দিষ্ট DBMS-এ
রেজিস্ট্রার অফিসের বর্ণনাকোনো কারিগরি শব্দ নেই
“প্রতিটি বিভাগের একটা নাম আর ভবন আছে।
 প্রত্যেক শিক্ষার্থী একটি বিভাগের।
 একজন শিক্ষার্থী অনেক কোর্সে ভর্তি হন,
 একটি কোর্সে অনেক শিক্ষার্থী পড়েন।
 প্রতিটি ভর্তির একটা টার্ম আর গ্রেড থাকে।”
Conceptual (ER)entity, relationship, cardinality
[DEPARTMENT] 1 ──BELONGS_TO── N [STUDENT]
[DEPARTMENT] 1 ──OFFERS──────── N [COURSE]
[STUDENT]    M ──ENROLLS─────── N [COURSE]
                   (term, grade)
Logical (relational)PK = primary key, FK = foreign key
departments (dept_id PK, name, building)
students    (student_id PK, name, dept_id FK, batch, cgpa)
courses     (course_id PK, title, dept_id FK, credits, ...)
enrollments (student_id FK, course_id FK, term = PK, grade)
Physical (SQLite)একই টেবিল, ডিস্কের সিদ্ধান্ত
student_id INTEGER PRIMARY KEY   -- rowid, B-tree-এর key
cgpa REAL CHECK (cgpa BETWEEN 0 AND 4)
CREATE INDEX idx_enr_course ON enrollments(course_id);
-- পুরো ডেটাবেস একটি ফাইল, ভেতরে 4096-byte page
  1. প্রয়োজন। সব ডিজাইন শুরু হয় মানুষের কথা থেকে। এখানে কোনো টেবিল নেই, শুধু নিয়ম আর তথ্য। এই বাক্যগুলোর “অনেক … অনেক” অংশটি পরের সব মডেলের পরীক্ষা নেবে।
  2. Conceptual. কথাগুলো থেকে বেরোল তিনটি entity আর তিনটি সম্পর্ক। ENROLLS হলো M:N — অনেক শিক্ষার্থী, অনেক কোর্স — আর term ও grade সম্পর্কটির নিজের গুণ। এখনো কোনো DBMS বা মডেল বাছা হয়নি; বিস্তারিত পর্ব ৭ ও ৮-এ।
  3. Logical. relational মডেল বাছলাম, তাই M:N সম্পর্কটি নিজেই একটি টেবিল হলো — enrollments, যার primary key তিনটি কলাম মিলে। Hierarchical বা network বাছলে এই ধাপটি একেবারে অন্যরকম দেখাত (অংশ ৩ ও ৪)।
  4. Physical. এখন DBMS নির্দিষ্ট: SQLite-এ INTEGER PRIMARY KEY আসলে rowid, প্রতিটি টেবিল ডিস্কে একটি B-tree, আর পরিচিত page আকার 4096 byte. “কোর্স ধরে খোঁজা দ্রুত হোক” — এই সিদ্ধান্তে একটি index যোগ হলো। এই স্তর বদলালেও logical স্তরের কোয়েরি বদলাতে হয় না — অংশ ৫-এ নিজেই চালিয়ে দেখবেন।

৩ · Hierarchical মডেল — গাছের মতো ডেটা (IMS)

পরিবারের বংশতালিকা, অফিসের অর্গানোগ্রাম, কম্পিউটারের ফোল্ডার — এগুলো সবই গাছ (tree). ওপরে একটি মূল, তার নিচে শাখা, শাখার নিচে আরও শাখা। প্রতিটি জিনিসের ঠিক একজন অভিভাবক (parent), কিন্তু অনেক সন্তান (child) থাকতে পারে। Hierarchical model ডেটাকে ঠিক এভাবেই রাখে।

১৯৬০-এর দশকে আমেরিকার Apollo চন্দ্রাভিযানের রকেট তৈরিতে লক্ষ লক্ষ যন্ত্রাংশের হিসাব রাখা দরকার ছিল — কোন অংশ কোন বড় অংশের ভেতরে (bill of materials), যা স্বভাবতই একটি গাছ। এই কাজে ১৯৬৬ সাল থেকে IBM, North American Rockwell আর Caterpillar মিলে একটি সফটওয়্যার বানাতে শুরু করে; ১৯৬৮ সালে প্রথম ব্যবহারে আসে, পরে নাম হয় IMS (Information Management System). বিস্ময়কর হলো, IBM আজও IMS বিক্রি ও হালনাগাদ করে, আর কিছু বড় ব্যাংক ও বিমা প্রতিষ্ঠানের mainframe-এ এটি এখনো চলে।

IMS-এ প্রতিটি রেকর্ডের নাম segment. গাছের মূলকে বলে root segment. ডেটা পড়ার জন্য ছিল DL/I নামের একটি ভাষা, যার কলগুলো আসলে “গাছে হাঁটার” নির্দেশ: GU (Get Unique — শর্ত মিলিয়ে একটি segment-এ যাওয়া), GN (Get Next — গাছের ক্রমে পরেরটি), GNP (Get Next within Parent — একই অভিভাবকের নিচে পরেরটি)। “গাছের ক্রম” মানে ওপর থেকে নিচে, বাঁ থেকে ডানে — DSA-র ভাষায় preorder traversal.

আমাদের বিশ্ববিদ্যালয়কে গাছে বসাতে হলে প্রথমেই একটি সিদ্ধান্ত নিতে হবে: কে কার নিচে? ধরুন বেছে নিলাম বিভাগ → কোর্স → শিক্ষার্থী (অর্থাৎ ভর্তির রেকর্ড)। ছোট রাখতে CSE-এর তিনটি আর EEE-এর একটি কোর্স নিচ্ছি, ভর্তির তথ্য db-uni থেকেই নেওয়া।

IMS-ধাঁচের গাছসবুজ = এই ধাপে লক্ষ করুন

            
প্রতিটি ধাপের পর
DL/I কল
পড়া segment
Arif-এর কপি
  1. Root segment. গাছের মূলে বিভাগ: CSE আর EEE. প্রতিটি বিভাগ একটি আলাদা গাছের মূল — IMS-এর ভাষায় একেকটি “database record”.
  2. Child segment — কোর্স। প্রতিটি কোর্স ঠিক একটি বিভাগের নিচে বসল। এখানে কোনো সমস্যা নেই, কারণ বিভাগ-কোর্স সম্পর্কটি ১:N — একটি কোর্সের একটিই বিভাগ। গাছ এই ধরনের সম্পর্কের জন্যই তৈরি।
  3. শিক্ষার্থী বসাতে গিয়েই ঝামেলা। Arif তিনটি কোর্সে পড়েন, কিন্তু গাছে প্রতিটি segment-এর একজনই অভিভাবক থাকতে পারে। তাই Arif-কে তিনটি কোর্সের নিচে তিনবার লিখতে হলো, Bristy আর Dilruba-কে দুবার করে। মোট segment ২ + ৪ + ৯ = ১৫, যার অনেকগুলো একই মানুষের কপি।
  4. যে প্রশ্ন গাছের দিকে, সেটি সহজ। “CSE-এর সব কোর্স আর তার শিক্ষার্থী” — GU দিয়ে CSE-তে পৌঁছে GNP দিয়ে নিচে নামতে থাকলেই হলো। পড়তে হলো CSE-এর গাছের ১১টি segment (১টি বিভাগ, ৩টি কোর্স, ৭টি ভর্তি)। ডিস্কে এগুলো প্রায়ই পাশাপাশি থাকে, তাই এই পথে IMS খুব দ্রুত।
  5. উল্টো প্রশ্ন কঠিন। “Arif কোন কোন কোর্সে পড়েন?” — গাছ শিক্ষার্থী দিয়ে সাজানো নয়, তাই পুরো ১৫টি segment ঘুরে Arif খুঁজতে হবে। আর Arif-এর ফোন নম্বর বদলালে তিন জায়গায় বদলাতে হবে; একটি ভুলে গেলে ডেটা পরস্পরবিরোধী হয়ে যাবে। IMS এর সমাধান করেছিল “logical relationship” নামের pointer segment দিয়ে — কাজ চলে, কিন্তু জটিলতা বাড়ে।
মূল ধারণা

Hierarchical মডেল ১:N সম্পর্কে দারুণ, M:N সম্পর্কে দুর্বল। গাছের আকার আগেই একটি “প্রধান প্রশ্ন” ধরে নেয়। সেই প্রশ্নে গতি অসাধারণ, কিন্তু অন্য দিক থেকে প্রশ্ন এলে হয় পুরো গাছ ঘুরতে হয়, নয়তো ডেটা কপি করে আরেকটি গাছ বানাতে হয়। বংশতালিকার উদাহরণেও একই সমস্যা: প্রত্যেক মানুষের দুজন অভিভাবক (মা ও বাবা) — খাঁটি গাছে সেটাও আঁটে না।

তবু গাছ হারিয়ে যায়নি। Windows Registry, কম্পিউটারের ফোল্ডার, HTML পাতার DOM, Active Directory-র মতো ডিরেক্টরি সার্ভিস — সবই hierarchical. যেখানে ডেটা স্বভাবতই গাছ আর প্রশ্ন সবসময় ওপর থেকে নিচে, সেখানে এই মডেল এখনো সবচেয়ে সরল।

৪ · Network মডেল — CODASYL আর pointer ধরে পথ চলা

প্রথমেই একটি ভুল ধারণা দূর করি: এখানে “network” মানে কম্পিউটার নেটওয়ার্ক বা ইন্টারনেট নয়। Network model-এ “network” মানে গ্রাফ — যেখানে একটি রেকর্ডের একাধিক অভিভাবক থাকতে পারে। গাছের এক-অভিভাবকের বাঁধন খুলে দেওয়াই এর মূল উদ্দেশ্য।

১৯৬০-এর দশকের শুরুতে General Electric-এ Charles Bachman তৈরি করেন IDS (Integrated Data Store), যাকে অনেকে প্রথম সত্যিকারের DBMS বলেন। সেই ধারণা থেকে COBOL ভাষার মানদণ্ড ঠিক করা কমিটি CODASYL-এর Data Base Task Group (DBTG) ১৯৬৯ ও ১৯৭১ সালে network ডেটাবেসের মানদণ্ড প্রকাশ করে। ১৯৭৩ সালে Bachman Turing Award পান; তাঁর ভাষণের শিরোনাম ছিল “The Programmer as Navigator” — প্রোগ্রামার যেন জাহাজের নাবিক, ডেটার সমুদ্রে pointer ধরে পথ খুঁজে চলেন।

CODASYL-এ দুটি মূল ধারণা: record type (যেমন DEPT, STUDENT, COURSE) আর set type. একটি set মানে একটি ১:N সম্পর্ক — একজন owner আর তার অনেক member. যেমন set DEPT_STU-তে owner একটি বিভাগ, member সেই বিভাগের শিক্ষার্থীরা। ভেতরে প্রতিটি set আসলে pointer-এর একটি শিকল: owner থেকে প্রথম member, সেখান থেকে পরের member, শেষে আবার owner-এ ফেরা।

M:N সম্পর্ক এখানে সমাধান হয় একটি মাঝের রেকর্ড দিয়ে — ENROLL (link record). প্রতিটি ENROLL রেকর্ড দুটি set-এর member: STU_ENR (owner = শিক্ষার্থী) আর CRS_ENR (owner = কোর্স)। ফলে একজন শিক্ষার্থীর তথ্য এখন একবারই থাকে, কপি লাগে না। এটি hierarchical মডেলের চেয়ে বড় উন্নতি।

   DEPT ──DEPT_STU──▶ STUDENT ──STU_ENR──▶ ENROLL ◀──CRS_ENR── COURSE
  (owner)            (member/owner)      (link record)          (owner)

কিন্তু দাম আছে। ডেটা পড়তে প্রোগ্রামারকে নিজে বলে দিতে হয় কোন set ধরে কোন দিকে হাঁটবেন। DBMS প্রতিটি record type ও set-এর জন্য মনে রাখে “এই মুহূর্তে কোন রেকর্ডে আছি” — একে বলে currency indicator. এই হাঁটাকে বলে navigational access. নিচে CODASYL ধাঁচের একটি প্রোগ্রাম (সহজ করা pseudocode; আসলে এটি COBOL-এর ভেতরে লেখা হতো) “CSE-এর প্রতিটি শিক্ষার্থী আর তাঁর কোর্স” ছাপছে। প্রতিটি set-এর member কোর্স আইডি অনুযায়ী সাজানো ধরে নিচ্ছি।

1FIND ANY DEPT USING name = 'CSE'2FIND FIRST STUDENT WITHIN DEPT_STU3WHILE status = OK4  GET STUDENT; PRINT name5  FIND FIRST ENROLL WITHIN STU_ENR6  WHILE status = OK7    FIND OWNER WITHIN CRS_ENR; GET COURSE; PRINT8    FIND NEXT ENROLL WITHIN STU_ENR9  FIND NEXT STUDENT WITHIN DEPT_STU
Currency (এই মুহূর্তে কোথায়)
DEPT
STUDENT
ENROLL
COURSE
status
  1. লাইন ১ — প্রবেশপথ খোঁজা। নাম দিয়ে CSE রেকর্ডে পৌঁছানো হলো। DEPT-এর currency এখন CSE. এখান থেকেই সব হাঁটা শুরু।
  2. লাইন ২ — set ধরে প্রথম member. CSE-এর DEPT_STU শিকলের প্রথম pointer অনুসরণ করে প্রথম শিক্ষার্থী Arif-এ পৌঁছালাম। STUDENT-এর currency = Arif.
  3. লাইন ৪ — রেকর্ড পড়া। FIND শুধু জায়গা খোঁজে, GET আসল ডেটা প্রোগ্রামে আনে। নাম ছাপা হলো: Arif Hossain.
  4. লাইন ৫ — আরেকটি set-এ ঢোকা। এবার Arif owner, STU_ENR শিকলের প্রথম member — ভর্তির রেকর্ড Arif·CSE201. কিন্তু এতে কোর্সের নাম নেই, আছে শুধু শিকলের সংযোগ।
  5. লাইন ৭ — owner-এর দিকে উঠে যাওয়া। এই ENROLL রেকর্ডটি CRS_ENR set-এরও member, তাই FIND OWNER দিয়ে তার কোর্স CSE201-এ পৌঁছানো গেল। ছাপা হলো “CSE201 Data Structures”.
  6. লাইন ৮ — পরের ভর্তি। STU_ENR শিকলে পরের pointer: Arif·CSE301. status এখনো OK, তাই ভেতরের লুপ চলবে।
  7. লাইন ৭ আবার। owner = CSE301, ছাপা হলো “CSE301 Database Systems”. খেয়াল করুন, প্রতিটি কোর্সের নাম পেতে আলাদা করে একবার শিকল বেয়ে উঠতে হচ্ছে।
  8. লাইন ৮ — তৃতীয় ভর্তি। Arif·CSE302.
  9. লাইন ৭ — owner CSE302. ছাপা হলো “CSE302 Database Systems Lab”. Arif-এর তিনটি কোর্সই এসেছে, ঠিক db-uni-র ডেটার মতো।
  10. লাইন ৮ — শিকলের শেষ। আর কোনো member নেই, status = END-OF-SET. ব্যর্থ FIND currency বদলায় না, তাই ENROLL-এর currency এখনো শেষ রেকর্ডে। ভেতরের লুপ থামল।
  11. লাইন ৯ — পরের শিক্ষার্থী। DEPT_STU শিকলে ফিরে পরের member: Bristy. এখন লাইন ৪ থেকে একই নাচ আবার শুরু হবে। ভাবুন — শুধু একজন শিক্ষার্থী আর তাঁর তিনটি কোর্স আনতেই ১২টি FIND/GET নির্দেশ লাগল (লাইন ২ থেকে ধরলে), আর কোন set ধরে কোন দিকে যেতে হবে, তা প্রোগ্রামারকে মনে রাখতে হলো।

এই পদ্ধতির দুটি গুরুতর অসুবিধা। এক, প্রোগ্রাম ডেটার ভৌত পথ-এর সাথে বাঁধা। কাল যদি DBA ঠিক করেন DEPT_STU set বাদ দিয়ে অন্যভাবে সাজাবেন, এই প্রোগ্রাম ভেঙে যাবে। দুই, প্রতিটি নতুন প্রশ্নের জন্য নতুন প্রোগ্রাম লিখতে হয়, আর সেটা পারতেন শুধু অভিজ্ঞ প্রোগ্রামাররা। ব্যবস্থাপক যদি জিজ্ঞেস করতেন “গত বছর কোন বিভাগে কতজন F পেয়েছে?” — উত্তর আসতে কয়েক দিন লেগে যেত।

Pseudocode-এর FIND, GET, WITHIN শব্দগুলো CODASYL-এর আসল DML থেকে নেওয়া, তবে বাস্তবের COBOL প্রোগ্রামে আরও বিস্তারিত লিখতে হতো এবং status যাচাই হতো নির্দিষ্ট কোড দিয়ে। এই ধাঁচের একটি পণ্য — IDMS — আজও কিছু mainframe-এ চলে।

৫ · Relational মডেল — টেবিল আর “কী চাই” বলার ভাষা

১৯৭০ সালের জুনে IBM-এর গবেষক Edgar F. Codd Communications of the ACM পত্রিকায় একটি প্রবন্ধ প্রকাশ করেন: “A Relational Model of Data for Large Shared Data Banks”. প্রবন্ধের শুরুতেই তিনি তখনকার ব্যবস্থার তিনটি সমস্যা চিহ্নিত করেন — প্রোগ্রাম নির্ভর করে ডেটার ক্রম-এর ওপর (ordering dependence), index-এর ওপর (indexing dependence), আর pointer-এর পথ-এর ওপর (access path dependence). অংশ ৩ ও ৪-এ ঠিক এগুলোই আমরা দেখেছি।

Codd-এর সমাধান অবিশ্বাস্য রকম সরল: সব ডেটা শুধু টেবিলে (গণিতের ভাষায় relation). কোনো pointer নেই। দুটি জিনিসের সম্পর্ক বোঝানো হবে শুধু মান মিলিয়ে। Arif CSE বিভাগের, কারণ তাঁর সারির dept_id-তে 1, আর CSE-এর সারির dept_id-ও 1. ব্যস। এই মডেলটিই relational model, যার খুঁটিনাটি — relation, tuple, attribute, domain — পরের পর্ব ৫-এর বিষয়।

বিশ্ববিদ্যালয়ের পরীক্ষা নিয়ন্ত্রকের অফিসে আলাদা আলাদা রেজিস্টার খাতা থাকে — বিভাগের খাতা, শিক্ষার্থীর খাতা, কোর্সের খাতা, ভর্তির খাতা। ভর্তির খাতায় শুধু রোল আর কোর্স কোড লেখা। কোনো পাতায় কোনো সুতো বাঁধা নেই! কেউ যদি জানতে চান “Arif কোন কোর্সগুলো নিয়েছেন”, কেরানি ভর্তির খাতায় Arif-এর রোল খোঁজেন, তারপর কোর্সের খাতায় সেই কোড মিলিয়ে নাম বের করেন। Relational DBMS ঠিক এই কেরানির কাজটিই করে — শুধু অনেক দ্রুত, আর কোন খাতা আগে খুলবে তা নিজেই ঠিক করে।

দ্বিতীয় বড় পরিবর্তন অপারেশনে। এখন আর “এই pointer ধরা, তারপর ওই pointer” নয়। আপনি শুধু বলেন কী চান — একে বলে declarative query. কোন ক্রমে টেবিল পড়া হবে, কোন index লাগবে, তা ঠিক করে DBMS-এর query optimizer (পর্ব ৪৩)। অংশ ৪-এর CODASYL প্রোগ্রামের পুরো কাজটি SQL-এ নিচের একটি বাক্য — চালিয়ে দেখুন:

-- CSE-এর প্রতিটি শিক্ষার্থী আর তাঁর কোর্স
SELECT s.name AS student, c.course_id, c.title, e.term
FROM departments d
JOIN students s    ON s.dept_id = d.dept_id
JOIN enrollments e ON e.student_id = s.student_id
JOIN courses c     ON c.course_id = e.course_id
WHERE d.name = 'CSE'
ORDER BY s.name, c.course_id;

ফল ১০টি সারি:

student        │ course_id │ title                  │ term
Arif Hossain   │ CSE201    │ Data Structures        │ 2022-Spring
Arif Hossain   │ CSE301    │ Database Systems       │ 2023-Fall
Arif Hossain   │ CSE302    │ Database Systems Lab   │ 2023-Fall
Bristy Saha    │ CSE201    │ Data Structures        │ 2022-Spring
Bristy Saha    │ CSE301    │ Database Systems       │ 2023-Fall
Dilruba Khatun │ CSE101    │ Structured Programming │ 2022-Fall
Dilruba Khatun │ CSE201    │ Data Structures        │ 2023-Spring
Dilruba Khatun │ CSE301    │ Database Systems       │ 2024-Fall
Gulshan Ara    │ CSE101    │ Structured Programming │ 2023-Fall
Gulshan Ara    │ ENG101    │ English Language       │ 2023-Fall

প্রথম তিন সারি হুবহু CODASYL stepper-এর আউটপুট। পার্থক্য হলো, এখানে কোনো currency, কোনো লুপ, কোনো FIND নেই। প্রতিটি JOIN শুধু বলছে “এই কলামের মান ওই কলামের মানের সমান হলে সারি দুটি জোড়া দেওয়া হবে”। খেয়াল করুন Gulshan-এর ENG101 — কোর্সটি English বিভাগের, তবু এসেছে, কারণ শর্ত শুধু শিক্ষার্থীর বিভাগের ওপর। JOIN-এর পুরো নিয়ম পর্ব ১৯-এ।

এবার সেই প্রশ্ন, যা গাছের মডেলে কষ্টের ছিল — উল্টো দিক থেকে। “CSE201 কোর্সে কারা পড়েছেন, তাঁরা কোন বিভাগের?” Relational মডেলে কোনো “প্রধান দিক” নেই; একই টেবিল, শুধু শর্ত বদলান:

SELECT s.name AS student, d.name AS dept, e.term, e.grade
FROM enrollments e
JOIN students s    ON s.student_id = e.student_id
JOIN departments d ON d.dept_id = s.dept_id
WHERE e.course_id = 'CSE201'
ORDER BY e.term, s.name;
student        │ dept │ term        │ grade
Arif Hossain   │ CSE  │ 2022-Spring │ A
Bristy Saha    │ CSE  │ 2022-Spring │ B+
Dilruba Khatun │ CSE  │ 2023-Spring │ A

তিনজন। এখানে কোনো ডেটা কপি নেই — Arif-এর নাম students টেবিলে একবারই আছে, তাই ফোন নম্বর বদলালেও এক জায়গায় বদলালেই চলবে। CODASYL-এ এই প্রশ্নের জন্য আলাদা প্রোগ্রাম লিখতে হতো (CRS_ENR set ধরে কোর্স থেকে শুরু করে); IMS-এ হয় পুরো গাছ ঘোরা, নয়তো আগে থেকে বানানো logical relationship.

তৃতীয় সুবিধাটিই সবচেয়ে গভীর — data independence. ধরুন ডেটা বেড়ে গেছে, আর DBA কোর্স ধরে খোঁজা দ্রুত করতে একটি index যোগ করলেন। এটি physical স্তরের পরিবর্তন। আগের কোয়েরি কি বদলাতে হবে?

-- physical পরিবর্তন: নতুন index
CREATE INDEX idx_enr_course ON enrollments(course_id);

-- কোয়েরি হুবহু আগেরটি, এক অক্ষরও বদলায়নি
SELECT s.name AS student, d.name AS dept, e.term, e.grade
FROM enrollments e
JOIN students s    ON s.student_id = e.student_id
JOIN departments d ON d.dept_id = s.dept_id
WHERE e.course_id = 'CSE201'
ORDER BY e.term, s.name;

প্রথম বাক্যে ✓ (index তৈরি হলো), তারপর ফল হুবহু আগের তিনটি সারি — Arif, Bristy, Dilruba. কোয়েরি বদলায়নি, ফলও বদলায়নি; বদলেছে শুধু DBMS ভেতরে কোন পথে ডেটা আনবে। এত ছোট টেবিলে SQLite-এর optimizer হয়তো index-টি ব্যবহারই করবে না — সেটাও তার নিজের সিদ্ধান্ত, আপনার কোডে লেখার দরকার নেই। Network মডেলে একটি set বদলালে প্রোগ্রাম নতুন করে লিখতে হতো। এই স্বাধীনতাই relational মডেলের জয়ের সবচেয়ে বড় কারণ (পর্ব ৩-এর physical data independence).

মূল ধারণা

Relational মডেলের তিন স্তম্ভ: (১) একটিই গঠন — টেবিল; সম্পর্ক মান দিয়ে, pointer দিয়ে নয়। (২) Declarative অপারেশন — set ধরে কাজ (পুরো টেবিল বা শর্ত মেলা সব সারি একসাথে), যার ভিত্তি গণিতের set theory আর predicate logic (relational algebra ও calculus, পর্ব ৩২–৩৪)। (৩) ঘোষণা করা নিয়ম — primary key, foreign key, CHECK — যা DBMS নিজে রক্ষা করে।

Codd-এর ধারণা প্রথমে অনেকে “সুন্দর তত্ত্ব, কিন্তু ধীর” বলে উড়িয়ে দেন। প্রমাণ এল দুটি গবেষণা প্রকল্প থেকে: IBM-এর System R (১৯৭০-এর দশকের মাঝামাঝি), যেখানে জন্ম নেয় SEQUEL ভাষা — পরে নাম হয় SQL, আর Berkeley-র Ingres (Michael Stonebraker ও Eugene Wong). ১৯৭৯ সালে Relational Software Inc. (পরে Oracle) প্রথম বাণিজ্যিক SQL DBMS বাজারে আনে, ১৯৮৩ সালে IBM আনে DB2, আর ১৯৮৬ সালে SQL ANSI মানদণ্ড হয়। ১৯৮১ সালে Codd Turing Award পান।

Conceptual স্তরে — ER মডেল, ডিজাইনের খসড়া খাতা

Relational মডেল টেবিলের ভাষা, কিন্তু ব্যবসার মানুষ টেবিলে ভাবেন না। রেজিস্ট্রার বলেন “শিক্ষার্থী”, “কোর্স”, “ভর্তি হওয়া” — জিনিস আর তাদের সম্পর্ক। ১৯৭৬ সালে Peter Chen ACM Transactions on Database Systems-এ প্রকাশ করেন “The Entity-Relationship Model — Toward a Unified View of Data”. তাঁর ER model-এ তিনটি মূল উপাদান: entity (জিনিস — শিক্ষার্থী), attribute (গুণ — নাম, CGPA), আর relationship (সম্পর্ক — ভর্তি হওয়া)।

ER মূলত একটি conceptual মডেল — কোনো DBMS এটি সরাসরি চালায় না। অংশ ২-এর stepper-এর দ্বিতীয় ধাপটিই ছিল ER. এর জোর হলো, একই ER ডায়াগ্রাম থেকে relational, network, এমনকি document মডেলেও রূপান্তর করা যায়; Chen নিজেও প্রবন্ধে দেখিয়েছিলেন ER কীভাবে তখনকার তিন মডেলকে এক ছাতার নিচে আনে। আজ ER ডায়াগ্রাম প্রায় সবসময় relational টেবিলে রূপান্তরের জন্যই আঁকা হয় — সেই নিয়ম পর্ব ১০-এ।

  ┌────────────┐          ◇──────────◇          ┌──────────┐
  │  STUDENT   │──M──────▶│ ENROLLS  │◀─────N──│  COURSE  │
  │ student_id │          │term,grade│          │course_id │
  │ name, cgpa │          ◇──────────◇          │ title    │
  └────────────┘                                └──────────┘

ER মডেলের পুরো ভাষা — cardinality (১:১, ১:N, M:N), participation, weak entity, আর EER-এর specialization — পুরো মডিউল ২ জুড়ে (পর্ব ৭–১১)। এখন শুধু মনে রাখুন: ER = “কী আছে” আঁকার খাতা; relational = “কীভাবে রাখব” তার নকশা।

৬ · Object আর object-relational মডেল

১৯৮০-এর দশকে C++ আর Smalltalk-এর মতো object-oriented ভাষা জনপ্রিয় হলো। প্রোগ্রামে একটি Student object-এর ভেতরে থাকে নাম, ঠিকানা (আরেকটি object), শখের তালিকা (array), আর কোর্সের তালিকা (অন্য object-এর reference). কিন্তু ডেটাবেসে রাখতে গেলে সেটিকে ভেঙে কয়েকটি টেবিলের সারিতে ছড়াতে হয়, আর পড়ার সময় আবার জোড়া দিতে হয়।

এই অমিলের নাম impedance mismatch — বিদ্যুৎ প্রকৌশলের শব্দ থেকে ধার করা। ভাবুন একটি পুরো জামা ভাঁজ করে আলমারির আলাদা তাকে হাতা, কলার, বোতাম রাখছেন, আর প্রতিদিন সকালে আবার সেলাই করছেন! অমিল কয়েক রকম: object-এর ভেতরে তালিকা থাকে, টেবিলের ঘরে (সাধারণত) একটি মান; object-এ inheritance আছে, টেবিলে নেই; প্রোগ্রাম চলে একটি একটি object ধরে, SQL চলে পুরো set ধরে।

🧱 Object database (OODBMS)

Object যেমন আছে তেমনই রাখা — reference সহ। ১৯৮০-এর শেষ থেকে ১৯৯০-এর দশকে GemStone, ObjectStore, O2, Versant-এর মতো পণ্য আসে; ১৯৯৩ সালে ODMG নামের একটি মানদণ্ডও হয়।

এক ভাষার সাথে ঘনিষ্ঠ

🧩 Object-relational (ORDBMS)

টেবিল থাকুক, কিন্তু ঘরে জটিল টাইপ রাখা যাক — নিজের বানানো টাইপ, array, inheritance. Berkeley-র POSTGRES প্রকল্প (১৯৮৬ থেকে) এই পথ দেখায়; SQL:1999 মানদণ্ডে এগুলো যোগ হয়।

আজকের PostgreSQL

🔁 ORM

ডেটাবেস relational-ই থাকুক, প্রোগ্রামের ভেতরে একটি library object আর সারির মধ্যে অনুবাদ করুক — Hibernate, SQLAlchemy, Django ORM. বাস্তবে এই পথটিই সবচেয়ে বেশি চলে।

পর্ব ৫০-এ বিস্তারিত

খাঁটি object database ব্যাপক জনপ্রিয় হয়নি — কারণগুলো ভাবনার প্রশ্ন ৩-এ। জিতেছে মাঝের পথ: relational DBMS নিজেই object-এর কিছু সুবিধা নিয়ে নিল। PostgreSQL-এ নিচের কোড সরাসরি চলে (SQLite-এ নয় — SQLite-এ composite type বা array টাইপ নেই, তাই এটি চালানোর বাটন ছাড়া দেখানো হলো):

CREATE TYPE address AS (street TEXT, city TEXT, post_code TEXT);

CREATE TABLE teachers (
  id        SERIAL PRIMARY KEY,
  name      TEXT NOT NULL,
  home      address,        -- composite (object-এর মতো) টাইপ
  subjects  TEXT[]          -- array টাইপ
);
INSERT INTO teachers (name, home, subjects)
VALUES ('Shahana Parvin', ROW('Road 5', 'Dhaka', '1205'), ARRAY['DBMS', 'DSA']);

SELECT name, (home).city, subjects[1] FROM teachers;
-- Shahana Parvin | Dhaka | DBMS     (PostgreSQL-এ array-র সূচি 1 থেকে শুরু)

এভাবে একটি টেবিলের ঘরে এখন ছোট একটি object বা তালিকা বসতে পারে। একই চিন্তার পরের ধাপ — পুরো একটি JSON ডকুমেন্ট একটি ঘরে রাখা — আমাদের পরের অংশের বিষয়, আর সেটি SQLite-এও চলে।

৭ · Semi-structured ডেটা — XML আর JSON

NID-র ফর্মে প্রত্যেকের জন্য একই ঘর — নাম, বাবার নাম, জন্মতারিখ। এটি structured ডেটা: আকার আগে থেকে ঠিক। অন্য দিকে একটি চিঠি বা ছবি unstructured. মাঝামাঝি আছে semi-structured data — ডেটার ভেতরেই ঘরের নাম লেখা থাকে, কিন্তু সবার ঘর এক হতে হবে এমন বাধ্যবাধকতা নেই। কোনো শিক্ষার্থীর প্রোফাইলে অভিভাবকের তথ্য আছে, কারও আছে ক্লাবের নাম, কারও বৃত্তির তথ্য।

এর দুটি বিখ্যাত রূপ। XML (W3C-র মানদণ্ড, ১৯৯৮) — ট্যাগ দিয়ে গাছের মতো ডেটা; ২০০০-এর আশেপাশে native XML ডেটাবেস আর XQuery ভাষা আসে, আর SQL:2003 মানদণ্ডে SQL/XML যোগ হয়। পরে আরও হালকা JSON ওয়েব আর মোবাইল অ্যাপের প্রধান ভাষা হয়ে ওঠে। লক্ষ করুন, দুটিই আসলে গাছ — hierarchical মডেল নতুন পোশাকে ফিরে এসেছে!

<student id="2101">                     {"id": 2101,
  <phone>01700000001</phone>              "phone": "01700000001",
  <hobby>cricket</hobby>                  "hobbies": ["cricket", "chess"]}
  <hobby>chess</hobby>
</student>                    XML  ↔  JSON

Relational টেবিলে আগে থেকে কলাম ঠিক করতে হয় — একে বলে schema-on-write. Semi-structured ডেটায় লেখার সময় কোনো আকার যাচাই হয় না; পড়ার সময় প্রোগ্রাম ঠিক করে কোন ঘর খুঁজবে — এর নাম schema-on-read. নমনীয়তা বাড়ে, কিন্তু ভুল বানানের ঘর ("phnoe") কেউ আটকায় না।

আধুনিক relational DBMS-এ JSON রাখার ব্যবস্থা আছে। SQLite-এ JSON সাধারণ TEXT কলামে থাকে, আর json_extract, json_each-এর মতো ফাংশন দিয়ে ভেতরে ঢোকা যায়। নিচের এডিটরগুলোতে db-uni-র সাথে একটি নতুন টেবিল যোগ করা আছে — student_profiles (student_id, profile) — যেখানে profile কলামে প্রতিটি শিক্ষার্থীর একটি JSON ডকুমেন্ট। চারজনের প্রোফাইল চার রকম: 2101-এর ফোন, শখ আর অভিভাবক; 2102-এর ফোন, শখ আর বৃত্তি ("scholarship": true); 2201-এর শখ আর শুধু অভিভাবকের নাম; 2301-এর ফোন আর ক্লাব। নমুনা ডেটার বাক্সে নতুন টেবিলটিও দেখতে পাবেন।

SELECT student_id,
       json_extract(profile, '$.phone')         AS phone,
       json_extract(profile, '$.guardian.name') AS guardian,
       json_extract(profile, '$.scholarship')   AS scholarship
FROM student_profiles;
student_id │ phone       │ guardian      │ scholarship
2101       │ 01700000001 │ Abdul Hossain │ NULL
2102       │ 01700000002 │ NULL          │ 1
2201       │ NULL        │ Rokeya Khatun │ NULL
2301       │ 01700000004 │ NULL          │ NULL

'$.guardian.name' হলো একটি JSON path: $ মানে পুরো ডকুমেন্ট, তারপর .guardian ঘরে ঢোকা, তার ভেতরে .name. যে ডকুমেন্টে ঘরটি নেই, সেখানে কোনো error নয় — শুধু NULL. আর JSON-এর true SQLite-এ এসেছে 1 হয়ে, কারণ SQLite-এ আলাদা boolean টাইপ নেই। PostgreSQL বা MySQL-এ একই ফাংশন অন্য নামে, আর ফলের টাইপও আলাদা হতে পারে — এই পার্থক্য নিচে দেখুন।

JSON path কীভাবে ধাপে ধাপে মান খুঁজে বের করে, 2101-এর ডকুমেন্টে দেখুন:

student 2101-এর profileসবুজ = path এখন যেখানে

            
json_extract-এর হিসাব
path
এখনকার মান
SQL-এ ফল
  1. $ — মূল। path সবসময় $ দিয়ে শুরু, যার অর্থ পুরো ডকুমেন্ট। এটি একটি object, যার তিনটি ঘর: phone, hobbies, guardian.
  2. .guardian — এক ধাপ নিচে। guardian ঘরের মান নিজেই একটি object. json_extract ভেতরের object চাইলে সেটি JSON text হিসেবে ফেরত দেয়।
  3. .name — পাতায় পৌঁছানো। এবার মানটি একটি সাধারণ string: Abdul Hossain. SQL ফলে এটি সাধারণ TEXT — উদ্ধৃতিচিহ্ন ছাড়া।
  4. $.hobbies — নতুন path, একটি array. array-র ভেতরের মানগুলোর কোনো নাম নেই, আছে শুধু অবস্থান: 0, 1, …
  5. [1] — অবস্থান ধরে বাছাই। সূচি 0 থেকে শুরু, তাই [1] মানে দ্বিতীয় মান: chess. (PostgreSQL-এর আসল array শুরু হয় 1 থেকে, কিন্তু JSON array-র সূচি সব সিস্টেমেই 0 থেকে।)
  6. $.club — নেই এমন ঘর। 2101-এর ডকুমেন্টে club নেই, তাই ফল NULL — কোনো error নয়। এটি schema-on-read-এর দুই দিক: নমনীয়, কিন্তু বানান ভুল হলেও নীরবে NULL আসবে।

Array-র প্রতিটি মান আলাদা সারি হিসেবে চাইলে json_each — এটি একটি table-valued function, অর্থাৎ FROM-এ টেবিলের মতো বসে আর প্রতিটি মানের জন্য একটি সারি দেয় (key = অবস্থান, value = মান):

SELECT p.student_id, h.key AS pos, h.value AS hobby
FROM student_profiles p, json_each(p.profile, '$.hobbies') h
ORDER BY p.student_id, h.key;
student_id │ pos │ hobby
2101       │ 0   │ cricket
2101       │ 1   │ chess
2102       │ 0   │ singing
2201       │ 0   │ coding
2201       │ 1   │ reading
2201       │ 2   │ chess

৬টি সারি। 2301-এর কোনো hobbies নেই, তাই তাঁর কোনো সারিই আসেনি — json_each খালি ফল দিয়েছে, আর সাধারণ join-এ খালি দিকের সারি বাদ পড়ে। গাছের ভেতরের তালিকাকে এভাবে টেবিলের সারিতে মেলে দেওয়াকে বলে unnest বা shred করা।

এবার আসল শক্তি — JSON আর সাধারণ টেবিল একই কোয়েরিতে। “যাঁদের শখ chess, তাঁদের নাম, বিভাগ আর CGPA”:

SELECT s.name, d.name AS dept, s.cgpa
FROM students s
JOIN departments d      ON d.dept_id = s.dept_id
JOIN student_profiles p ON p.student_id = s.student_id
WHERE EXISTS (SELECT 1 FROM json_each(p.profile, '$.hobbies')
              WHERE value = 'chess')
ORDER BY s.name;
name           │ dept │ cgpa
Arif Hossain   │ CSE  │ 3.75
Dilruba Khatun │ CSE  │ 3.9

নাম, বিভাগ আর CGPA এসেছে relational টেবিল থেকে, শখের শর্ত JSON থেকে। EXISTS (পর্ব ২১) শুধু জিজ্ঞেস করছে “এই প্রোফাইলের hobbies-এ chess আছে কি?” — তাই একজনের তালিকায় chess দুবার থাকলেও তিনি একবারই আসবেন। এক ডেটাবেসে দুই মডেলের এই মিশ্রণকে অনেকে multi-model বা hybrid বলেন।

একই কাজ অন্য সিস্টেমে লিখতে হয় নিজস্ব ভঙ্গিতে (নিচের কোড এখানে চলবে না):

-- profile কলামের টাইপ jsonb ধরে
SELECT student_id,
       profile ->> 'phone'              AS phone,     -- ->> মানে text হিসেবে
       profile -> 'guardian' ->> 'name' AS guardian
FROM student_profiles
WHERE profile @> '{"hobbies": ["chess"]}';         -- containment; GIN index নিতে পারে
SELECT student_id,
       JSON_UNQUOTE(JSON_EXTRACT(profile, '$.phone')) AS phone,
       profile ->> '$.guardian.name'                  AS guardian
FROM student_profiles
WHERE JSON_CONTAINS(profile -> '$.hobbies', '"chess"');

JSON কলাম সুবিধাজনক, কিন্তু অন্ধভাবে নয়। JSON-এর ভেতরের মানে foreign key দেওয়া যায় না, ঘরের নাম বা টাইপ কেউ যাচাই করে না, আর ঠিকমতো index না দিলে প্রতিটি কোয়েরিতে প্রতিটি ডকুমেন্ট পার্স করতে হয়। নিয়মিত খোঁজা হয় এমন, সবার আছে এমন তথ্য (যেমন ফোন) আলাদা কলামেই রাখা ভালো; সত্যিই বৈচিত্র্যময় তথ্য JSON-এ। JSON, full-text আর বিশেষ টাইপ নিয়ে পুরো পর্ব ৩০ আছে।

৮ · NoSQL পরিবার — এক নজরে

২০০০-এর দশকে Google, Amazon, Facebook-এর মতো প্রতিষ্ঠানের ডেটা এত বড় হলো যে একটি সার্ভারে আর আঁটে না। হাজার সার্ভারে ডেটা ছড়িয়ে দিতে গিয়ে তারা দেখল, প্রচলিত relational DBMS-এর কিছু নিশ্চয়তা (যেমন সব টেবিল জুড়ে JOIN আর কঠোর transaction) বিশাল মাপে খুব ব্যয়বহুল। ২০০৬ সালে Google-এর Bigtable আর ২০০৭ সালে Amazon-এর Dynamo প্রবন্ধ নতুন পথ দেখায়। ২০০৯ সালে San Francisco-তে এ ধরনের ডেটাবেস নিয়ে একটি meetup-এর নাম দেওয়া হয় “NoSQL”, আর শব্দটি দ্রুত ছড়িয়ে পড়ে। (শব্দটি অবশ্য ১৯৯৮ সালে Carlo Strozzi তাঁর একটি ছোট ডেটাবেসের নাম হিসেবে প্রথম ব্যবহার করেছিলেন।) আজ এর প্রচলিত অর্থ “Not only SQL”.

NoSQL কোনো একটি মডেল নয়, চারটি প্রধান পরিবারের ছাতা-নাম:

🔑 Key-value

একটি বিশাল অভিধান: key দিলে value. ভেতরে কী আছে DBMS জানে না, জানার দরকারও নেই। অতি দ্রুত, অতি সরল। কাজ: login session, cache, ঝুড়ির (cart) তথ্য।

Redis, Amazon DynamoDB

📄 Document

প্রতিটি রেকর্ড একটি JSON-এর মতো ডকুমেন্ট, ভেতরের ঘর ধরে খোঁজা যায়। সম্পর্কিত তথ্য একসাথে এক ডকুমেন্টে — এক পড়াতেই পুরোটা। কাজ: পণ্যের ক্যাটালগ, প্রোফাইল, কনটেন্ট।

MongoDB, CouchDB, Firestore

🗂️ Wide-column

সারির key আর column family; প্রতিটি সারিতে ভিন্ন ভিন্ন কলাম থাকতে পারে। বিপুল লেখার চাপ আর বহু সার্ভারে ছড়ানোর জন্য নকশা করা। কাজ: সেন্সর ডেটা, মেসেজ, লগ।

Apache Cassandra, HBase, Bigtable

🕸️ Graph

Node (মানুষ, অ্যাকাউন্ট) আর edge (বন্ধু, টাকা পাঠিয়েছে)। “বন্ধুর বন্ধু” বা “টাকার চক্র” খোঁজা — অনেক ধাপের সম্পর্ক — এখানে স্বাভাবিক। কাজ: সুপারিশ, জালিয়াতি শনাক্ত, নেটওয়ার্ক।

Neo4j, Amazon Neptune

লক্ষ করুন, ইতিহাস যেন ঘুরে ফিরে আসে। Document মডেল আসলে hierarchical মডেলের আধুনিক রূপ — এক ডকুমেন্টের ভেতরে গাছ। Graph ডেটাবেস network মডেলের উত্তরসূরি — তবে এবার একটি declarative কোয়েরি ভাষাসহ (যেমন Cypher, আর ২০২৪ সালে ISO-র মানদণ্ড GQL). অংশ ৩-এর many-to-many সমস্যা document ডেটাবেসেও ফিরে আসে: শিক্ষার্থীর ডকুমেন্টে কোর্সের নাম কপি করলে, কোর্সের নাম বদলালে সব কপি বদলাতে হয়। প্রতিটি পরিবারের ভেতরের কাজ, CAP theorem আর sharding — পর্ব ৫৪ ও ৫৬ থেকে ৫৯-এ।

ট্রেনের টিকিট বুকিং ভাবুন। কোন আসন কার নামে, টাকা কাটা হলো কি না — এখানে দুজনকে একই আসন দেওয়া চলবে না, তাই কঠোর নিয়মের relational DBMS. কিন্তু “আপনি সর্বশেষ কোন স্টেশন খুঁজেছিলেন” মনে রাখা — হারালেও ক্ষতি নেই, লাগে শুধু গতি — তাই key-value cache. একই অ্যাপ, দুই রকম ডেটা, দুই রকম মডেল।

৯ · তুলনা, relational-এর জয় আর polyglot persistence

মডেলগঠনকোয়েরির ধরননমনীয়তাসাধারণ ব্যবহার
Hierarchicalগাছ (parent-child)Navigational (GU, GN)কম; M:N কষ্টেরপুরোনো mainframe, ফোল্ডার, registry
Networkরেকর্ড আর set-এর গ্রাফNavigational (FIND, GET)কম; পথ বদলালে প্রোগ্রাম বদলায়পুরোনো mainframe
Relationalটেবিল, মান দিয়ে সম্পর্কDeclarative (SQL)নতুন প্রশ্নে খুব নমনীয়; স্কিমা বদলাতে migrationব্যাংক, ই-কমার্স, প্রায় সব ব্যবসায়িক অ্যাপ
EREntity, relationship(কোয়েরি নেই — ডিজাইনের মডেল)—ডেটাবেস ডিজাইন
Object / object-relationalObject, জটিল টাইপObject navigation বা SQLজটিল টাইপে ভালোCAD, বৈজ্ঞানিক ডেটা, PostgreSQL-এর টাইপ
Semi-structured (XML/JSON)নিজের বর্ণনা নিজেই বহনকারী গাছPath (XPath, JSON path)খুব বেশি; schema-on-readAPI, কনফিগ, প্রোফাইল
Key-valuekey → valueশুধু key দিয়ে get/putvalue-তে যা খুশিCache, session
DocumentJSON-এর মতো ডকুমেন্টঘর ধরে কোয়েরি, aggregationবেশিক্যাটালগ, কনটেন্ট
Wide-columnrow key + column familykey-কেন্দ্রিক, সীমিতসারিভেদে কলামবিপুল লেখা, time-series
GraphNode আর edgeTraversal (Cypher, GQL)সম্পর্কে নমনীয়সুপারিশ, জালিয়াতি শনাক্ত

Relational মডেল কেন জিতল

  1. Data independence: টেবিল আর কোয়েরি ভৌত পথ থেকে আলাদা। Index যোগ, ফাইল সাজানো বদল — অ্যাপ অপরিবর্তিত (অংশ ৫-এর তৃতীয় এডিটর)। ব্যবসার কোড দশকের পর দশক টিকে থাকে।
  2. Declarative SQL: নতুন প্রশ্ন মানে নতুন প্রোগ্রাম নয়, নতুন একটি বাক্য। প্রোগ্রামার নন এমন বিশ্লেষকও প্রশ্ন করতে পারেন। আর optimizer প্রতিটি নতুন হার্ডওয়্যার ও ডেটার মাপে নিজেই ভালো পথ বেছে নেয়।
  3. গাণিতিক ভিত্তি: Relation হলো set, কোয়েরি হলো relational algebra-র অপারেশন। তাই দুটি কোয়েরি সমান কি না প্রমাণ করা যায়, optimizer নিরাপদে কোয়েরি রূপান্তর করতে পারে, আর normalization-এর মতো ডিজাইনের নিয়ম গাণিতিকভাবে যুক্তিসিদ্ধ (মডিউল ৫)।
  4. একটিই সরল গঠন: সবকিছু টেবিল — ডেটাও, এমনকি DBMS-এর নিজের catalog-ও। শেখা, শেখানো আর টুল বানানো সহজ।
  5. মানদণ্ড ও বাজার: ১৯৮৬ থেকে SQL একটি ANSI/ISO মানদণ্ড। একজনের শেখা দক্ষতা অন্য DBMS-এও বহুলাংশে কাজে লাগে।

অন্য মডেল কোথায় এখনো এগিয়ে

যখন প্রশ্ন সবসময় একই দিকে আর গতি সবচেয়ে জরুরি (IMS-এর ব্যাংকিং লেনদেন, key-value cache); যখন ডেটার আকার রেকর্ডভেদে খুব আলাদা (document); যখন লেখার চাপ বিশাল আর বহু ডেটা সেন্টারে ছড়াতে হয় (wide-column); যখন প্রশ্নই হলো অনেক ধাপের সম্পর্ক — “এই অ্যাকাউন্ট থেকে ৫ ধাপের মধ্যে কোনো চক্র আছে কি” (graph). SQL-এ এমন প্রশ্নও লেখা যায় (recursive CTE, পর্ব ২৫), কিন্তু প্রতিটি ধাপ একটি নতুন JOIN, আর গভীরতা বাড়লে খরচ দ্রুত বাড়ে।

Polyglot persistence — এক অ্যাপে অনেক মডেল

একটি বড় ই-কমার্স অ্যাপের কথা ভাবুন। অর্ডার আর পেমেন্ট — relational (PostgreSQL বা MySQL), কারণ টাকার হিসাবে ভুল চলবে না। লগইন session আর পণ্যের দাম cache — key-value (Redis). পণ্যের খোঁজ — search engine (যেমন Elasticsearch বা OpenSearch). “যাঁরা এটি কিনেছেন তাঁরা আরও কিনেছেন” — হয়তো graph. প্রতিটি কাজের জন্য উপযুক্ত মডেল বাছার এই চিন্তার নাম polyglot persistence; ২০১০-এর দশকের শুরুতে Martin Fowler ও Pramod Sadalage-এর মতো লেখকেরা শব্দটি জনপ্রিয় করেন।

Polyglot-এর দামও আছে: প্রতিটি নতুন ডেটাবেস মানে নতুন backup, নতুন monitoring, নতুন দক্ষতা — আর দুটি ডেটাবেসের তথ্য মিলিয়ে রাখার (consistency) ঝামেলা। ছোট প্রকল্পে প্রায়ই একটি ভালো relational DBMS (দরকারে তার JSON কলামসহ) যথেষ্ট। নতুন মডেল আনুন যখন একটি নির্দিষ্ট, মাপা যায় এমন সমস্যা তা দাবি করে — ফ্যাশনের জন্য নয়।

আরও গভীরে: ২০০৫ সালে Michael Stonebraker ও Joseph Hellerstein “What Goes Around Comes Around” প্রবন্ধে দেখান, ডেটা মডেলের ইতিহাস বারবার একই চক্রে ঘোরে — নতুন মডেল আসে, পরে relational মডেল তার ভালো দিকগুলো নিজের ভেতরে টেনে নেয়। ২০২৪ সালে Stonebraker ও Andrew Pavlo এর পরবর্তী প্রবন্ধে (“What Goes Around Comes Around… And Around…”) NoSQL যুগের পরেও একই সিদ্ধান্তে আসেন: বহু NoSQL সিস্টেম পরে SQL-ধাঁচের ভাষা ও transaction যোগ করেছে, আর relational সিস্টেম যোগ করেছে JSON, graph কোয়েরি ও vector. SQLite নিজেও ৩.৪৫ সংস্করণ (২০২৪) থেকে JSON-কে দ্রুত পড়ার জন্য একটি binary রূপ (JSONB) রাখতে পারে।

পরীক্ষা ও ইন্টারভিউতে — এমন প্রশ্ন প্রায়ই আসে:

  • ডেটা মডেল কাকে বলে? তার উপাদান কী? — ডেটার গঠন, অপারেশন আর integrity constraint-এর একটি সমষ্টি, যা দিয়ে ডেটা বর্ণনা ও ব্যবহার করা হয়।
  • Hierarchical আর network মডেলের মূল পার্থক্য? — Hierarchical-এ প্রতিটি রেকর্ডের একটিই parent (গাছ); network-এ set-এর মাধ্যমে একাধিক owner থাকতে পারে (গ্রাফ), তাই M:N সহজে প্রকাশ করা যায়।
  • Relational মডেল কেন জনপ্রিয় হলো? — Data independence, declarative কোয়েরি (SQL), গাণিতিক ভিত্তি আর একটিমাত্র সরল গঠন — টেবিল।
  • ER মডেল কোন ধরনের মডেল? — Conceptual (high-level) মডেল; পরে logical মডেলে, সাধারণত relational টেবিলে রূপান্তর হয়।
  • কখন SQL-এর বদলে NoSQL বাছবেন? — যখন নির্দিষ্ট প্রয়োজন আছে: বিপুল মাপে ছড়ানো, খুব বৈচিত্র্যময় গঠন, শুধু key দিয়ে অতি দ্রুত পড়া বা গভীর সম্পর্কের traversal — আর কঠোর multi-row transaction ততটা জরুরি নয়।

ইতিহাসের সময়রেখা

সময়ঘটনামডেল
১৯৬০-এর দশকের শুরুCharles Bachman-এর IDS, General ElectricNetwork-এর পূর্বসূরি
১৯৬৬–৬৮IBM-এর IMS তৈরি, Apollo প্রকল্পের যন্ত্রাংশের হিসাবেHierarchical
১৯৬৯, ১৯৭১CODASYL DBTG-এর রিপোর্টNetwork
১৯৭০Codd-এর “A Relational Model of Data…”Relational
১৯৭৪Codd বনাম Bachman — বিখ্যাত “Great Debate”; System R ও Ingres-এর কাজ চলছেRelational বনাম network
১৯৭৬Peter Chen-এর ER মডেলConceptual
১৯৭৯–১৯৮৬Oracle (১৯৭৯), DB2 (১৯৮৩), SQL মানদণ্ড (১৯৮৬)Relational বাজারে
১৯৮০-এর শেষ–১৯৯০-এর দশকObject database; ODMG মানদণ্ড (১৯৯৩); POSTGRES; SQL:1999-এ object-relational বৈশিষ্ট্যObject / object-relational
১৯৯৮–২০০০-এর দশকXML ১.০ (১৯৯৮), native XML ডেটাবেস, SQL/XML (২০০৩)Semi-structured
২০০৬–২০০৭Google Bigtable, Amazon Dynamo প্রবন্ধWide-column, key-value
২০০৯“NoSQL” শব্দ জনপ্রিয়; একই বছর MongoDB ও Redis প্রকাশNoSQL
২০১০-এর দশকRelational DBMS-এ JSON (PostgreSQL jsonb ২০১৪, MySQL ২০১৫, SQLite ২০১৫); SQL:2016-এ JSONমডেলের মিলন

১০ · AI-এর যুগে ডেটা মডেল বাছাই

আজ অনেকে AI-কে জিজ্ঞেস করেন, “আমার অ্যাপের জন্য কোন ডেটাবেস নেব?” AI খুব আত্মবিশ্বাসী উত্তর দেয়, কিন্তু সে আপনার ডেটা, আপনার প্রশ্ন আর আপনার দলের দক্ষতা জানে না। কয়েকটি পরিচিত ভুল:

AI যা বলে বা করেআসলে সমস্যা
“MongoDB schema-less, তাই ডিজাইন লাগবে না”Schema থাকে — অ্যাপের কোডের ভেতরে লুকিয়ে। ডিজাইন না করলে M:N সম্পর্কে কপি আর অমিল জমে।
“বড় হলে NoSQL লাগবেই”বেশিরভাগ অ্যাপ একটি ভালো index করা relational DBMS-এই বহু বছর চলে। মাপ আগে মাপুন।
নেই এমন কলাম বা JSON ঘরের নাম বানিয়ে কোয়েরি লেখেJSON path ভুল হলে error নয়, নীরবে NULL — ফল ফাঁকা দেখে ভুল সিদ্ধান্ত।
JOIN-এ duplicate সারি বা NULL-এর ক্ষেত্র উপেক্ষা করেকোয়েরি চলে, কিন্তু সংখ্যা ভুল আসে।
Transaction আর index-এর কথাই তোলে নাটাকা-পয়সার কাজে আংশিক লেখা আর ধীর কোয়েরি — দুটিই বাস্তব ক্ষতি।

ডেটা মডেল নিয়ে AI-কে প্রশ্ন করার সময় প্রেক্ষাপট দিন, আর তাকে trade-off ব্যাখ্যা করতে বাধ্য করুন:

আমি একটি বিশ্ববিদ্যালয়ের রেজাল্ট ম্যানেজমেন্ট অ্যাপ বানাচ্ছি। ডেটা: বিভাগ, শিক্ষার্থী (প্রায় ২০ হাজার), কোর্স, প্রতি টার্মে ভর্তি আর গ্রেড। সবচেয়ে বেশি যে প্রশ্নগুলো হবে: (১) একজন শিক্ষার্থীর সব কোর্স আর গ্রেড, (২) একটি কোর্সের সব শিক্ষার্থী, (৩) বিভাগভিত্তিক গড় CGPA. গ্রেড বদলানোর সময় ভুল হওয়া চলবে না। ১. relational আর document মডেল — দুটিতে এই ডেটা কীভাবে সাজাবে, ছোট উদাহরণসহ দেখাও। ২. তিনটি প্রশ্ন প্রতিটি মডেলে কীভাবে লিখবে, আর কোথায় ডেটা কপি হবে বলো। ৩. কোনটি সুপারিশ করো, কেন — আর কোন পরিস্থিতিতে তোমার সুপারিশ ভুল হবে, সেটাও বলো। ৪. কোনো কলাম বা ফাংশনের নাম নিশ্চিত না হলে সেটা স্পষ্ট করে বলো।

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

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

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

প্র ০১১৯৭০ সালে Codd-এর ধারণা এল, কিন্তু relational DBMS বাজার দখল করতে আরও প্রায় পনেরো বছর লাগল কেন?

কারণ সুন্দর তত্ত্ব আর দ্রুত চলা সফটওয়্যার এক জিনিস নয়। ১৯৭০-এর দশকের শুরুতে কম্পিউটার ছিল ধীর আর মেমরি অতি দামি। IMS আর CODASYL ব্যবহারকারীদের যুক্তি ছিল: “প্রোগ্রামার নিজে pointer ধরে হাঁটলে সবচেয়ে কম ডিস্ক পড়তে হয়। একটি সফটওয়্যার নিজে নিজে ভালো পথ বের করবে — এটা অসম্ভব।”

১৯৭৪ সালে ACM-এর একটি কর্মশালায় Codd আর Bachman-এর মুখোমুখি বিতর্ক হয়, যা “Great Debate” নামে পরিচিত। Bachman-এর পক্ষের লোকেরা বললেন relational কোয়েরি ধীর হবে আর বাস্তব প্রোগ্রামারদের জন্য দুর্বোধ্য; Codd-এর পক্ষের লোকেরা বললেন navigational প্রোগ্রাম ডেটার গঠনের সাথে এমনভাবে বাঁধা যে প্রতিটি পরিবর্তনে প্রোগ্রাম ভাঙে, আর এর গাণিতিক ভিত্তি নেই।

বিতর্কের মীমাংসা হলো কথায় নয়, কাজে। IBM-এর System R দল দেখাল একটি cost-based optimizer আসলেই ভালো পথ বেছে নিতে পারে — Patricia Selinger ও সহকর্মীদের ১৯৭৯ সালের optimizer-প্রবন্ধ আজও এই বিষয়ের ভিত্তি (পর্ব ৪৩)। Berkeley-র Ingres একই কথা প্রমাণ করল এবং পরে অনেক বাণিজ্যিক পণ্যের জন্ম দিল। ১৯৭৯ সালে Oracle আর ১৯৮৩ সালে IBM-এর DB2 বাজারে এল। সঙ্গে হার্ডওয়্যারও দ্রুত সস্তা হলো, তাই “কয়েকটা বাড়তি ডিস্ক পড়া” আর প্রোগ্রামারের সময়ের চেয়ে দামি থাকল না।

আর ব্যবসায়ীরা দেখলেন, নতুন রিপোর্টের জন্য নতুন COBOL প্রোগ্রাম লেখানোর বদলে একটি SQL লিখলেই হয়। প্রোগ্রামারের সময় বাঁচানো তখন যন্ত্রের সময় বাঁচানোর চেয়ে বড় সুবিধা হয়ে দাঁড়াল।

মূল উপলব্ধি: একটি ভালো বিমূর্ত ধারণা (abstraction) জেতে তখনই, যখন তার বাস্তবায়ন যথেষ্ট দ্রুত হয় আর হার্ডওয়্যারের দাম তার পক্ষে যায়। আজ AI-কে দিয়ে কোড লেখানোর বিতর্কেও একই ধরন দেখা যায় — “মানুষ নিজে লিখলে বেশি দক্ষ” বনাম “উঁচু স্তরে বলে দিলেই হয়”।

প্র ০২IMS-এর জন্ম ১৯৬০-এর দশকে — তবু আজও কিছু ব্যাংক এটি চালায় কেন? পুরোনো মডেল বদলে ফেলা এত কঠিন কেন?

প্রথমত, যে কাজের জন্য তৈরি, সে কাজে IMS এখনো খুব দক্ষ। ব্যাংকের একটি লেনদেন প্রায় সবসময় একই পথে যায়: গ্রাহক → অ্যাকাউন্ট → লেনদেন। গাছটি ঠিক সেই পথে সাজানো, তাই কোনো JOIN লাগে না, ডিস্কে সম্পর্কিত রেকর্ড পাশাপাশি থাকে। দিনে কোটি কোটি একই ধরনের ছোট লেনদেনে এই অনুমান-যোগ্য গতিই মূল্যবান। IBM-ও দশকের পর দশক এটি হালনাগাদ করেছে।

দ্বিতীয় আর বড় কারণ — স্থানান্তরের খরচ ও ঝুঁকি। একটি পুরোনো ব্যাংকে IMS-এর ওপর হয়তো কয়েক হাজার COBOL প্রোগ্রাম, যার অনেকগুলো লিখেছেন এমন মানুষ যাঁরা অবসরে গেছেন। প্রতিটি প্রোগ্রামের ভেতরে navigational যুক্তি — কোন segment-এর পর কোনটি — লেখা আছে, অর্থাৎ ব্যবসার নিয়ম আর ডেটার পথ মিশে আছে। নতুন relational সিস্টেমে নিতে হলে শুধু ডেটা নয়, এই সব যুক্তিও নতুন করে লিখে প্রমাণ করতে হবে যে ফল হুবহু এক। একটি ভুলে গ্রাহকের টাকা ভুল হিসাব হতে পারে। বিশ্বজুড়ে অনেক ব্যাংকের “core banking” বদলের প্রকল্প বছরের পর বছর লেগেছে বা ব্যর্থ হয়েছে — এমন খবর মাঝেমধ্যেই শোনা যায়।

এখানেই data independence-এর আসল মূল্য বোঝা যায়। যে প্রোগ্রাম ভৌত পথের সাথে বাঁধা, তাকে নড়ানো মানে পুরো বাড়ির ভিত নাড়ানো। Codd ঠিক এই বাঁধন কাটতে চেয়েছিলেন। আর ডেটা মডেল একবার বাছলে তা সাধারণত সফটওয়্যারের যেকোনো অংশের চেয়ে বেশি দিন টেকে — অ্যাপ বদলায়, UI বদলায়, ডেটা থেকে যায়।

বাংলাদেশেও ব্যাংক, বিমা বা সরকারি প্রতিষ্ঠানে পুরোনো সিস্টেম থেকে নতুন সিস্টেমে যাওয়ার প্রকল্পে একই চ্যালেঞ্জ দেখা দেওয়া স্বাভাবিক — আজ যে ডেটাবেস ডিজাইন করছেন, সেটি হয়তো আপনার পরেও কেউ চালাবেন।

মূল উপলব্ধি: ডেটা মডেলের সিদ্ধান্ত দীর্ঘস্থায়ী। “আজ কোনটা দ্রুত” শুধু নয়, “দশ বছর পর নতুন প্রশ্ন এলে কত সহজে উত্তর দেওয়া যাবে” — সেটাও ভাবুন।

প্র ০৩Object database impedance mismatch দূর করার প্রতিশ্রুতি দিয়েছিল। তবু relational DBMS-কে সরাতে পারল না কেন?

১৯৮০-এর শেষে আর ১৯৯০-এর দশকে মনে হচ্ছিল object database-ই ভবিষ্যৎ। ১৯৮৯ সালে একদল গবেষক “The Object-Oriented Database System Manifesto” প্রকাশ করেন; ১৯৯০ সালে Stonebraker-সহ আরেকদল জবাবে লেখেন “Third-Generation Database System Manifesto”, যেখানে যুক্তি ছিল — relational মডেলকে ফেলে না দিয়ে তাকে প্রসারিত করা হোক। শেষ পর্যন্ত দ্বিতীয় দলের পথই জিতল। কারণগুলো শিক্ষণীয়:

  • এক ভাষার সাথে বাঁধা: অনেক OODBMS একটি নির্দিষ্ট ভাষার (প্রায়ই C++ বা Smalltalk) object-এর সাথে ঘনিষ্ঠভাবে যুক্ত ছিল। কিন্তু ব্যবসার ডেটা একাধিক অ্যাপ, একাধিক ভাষা, রিপোর্টিং টুল — সবাই ব্যবহার করে।
  • Ad-hoc কোয়েরি দুর্বল: object reference ধরে হাঁটা আসলে navigational প্রবেশ — CODASYL-এর পুরোনো সমস্যা নতুন পোশাকে। ODMG-র OQL ছিল, কিন্তু SQL-এর মতো পরিপক্ব optimizer আর পরিবেশ গড়ে ওঠেনি।
  • বাজার আর দক্ষতা: ততদিনে কোটি কোটি ডলারের SQL টুল, প্রশিক্ষিত DBA আর রিপোর্টিং সফটওয়্যার তৈরি।
  • Relational সিস্টেম মানিয়ে নিল: POSTGRES-এর ধারণা থেকে user-defined type, inheritance; SQL:1999-এ structured type; আর অ্যাপের দিকে ORM — অমিলটা পুরো দূর না করেও যথেষ্ট কমিয়ে দিল।

তবে OODB-র ধারণা হারায়নি। CAD, টেলিকম আর কিছু বৈজ্ঞানিক কাজে এগুলো দীর্ঘদিন ব্যবহৃত হয়েছে, আর আজকের document ডেটাবেসও একভাবে “object যেমন আছে তেমন রাখা” চিন্তারই উত্তরসূরি। ২০০৬ সালে Ted Neward ORM-এর জটিলতাকে বিখ্যাতভাবে “the Vietnam of computer science” বলেছিলেন — অর্থাৎ সমস্যাটি এখনো পুরো মেটেনি।

মূল উপলব্ধি: প্রতিষ্ঠিত প্রযুক্তিকে সরাতে শুধু “ভালো” হলে চলে না, অনেক গুণ ভালো হতে হয় — নইলে পুরোনো প্রযুক্তি নতুনের সেরা ধারণাগুলো নিজের ভেতরে টেনে নেয়।

প্র ০৪২০০৯-এর NoSQL ঢেউকে অনেকে “SQL-এর মৃত্যু” বলেছিলেন। পনেরো বছর পরে ফিরে তাকালে কী দেখা যায়?

NoSQL ঢেউ এসেছিল আসল সমস্যা থেকে। Google-এর Bigtable আর Amazon-এর Dynamo দেখিয়েছিল, হাজার সার্ভারে ছড়ানো ডেটায় কিছু নিশ্চয়তা ছেড়ে দিলে (যেমন সব জায়গায় সাথে সাথে একই মান, বা যেকোনো টেবিলে JOIN) বিপুল মাপ আর সার্বক্ষণিক প্রাপ্যতা পাওয়া যায়। ওয়েব স্টার্টআপরাও পছন্দ করল যে আগে থেকে স্কিমা ঠিক না করেই JSON লিখে ফেলা যায়।

কিন্তু এরপর যা ঘটল, তা আকর্ষণীয়:

  • NoSQL সিস্টেম SQL-এর দিকে এল: Cassandra-র কোয়েরি ভাষা CQL দেখতে প্রায় SQL; Couchbase আনল SQL-ধাঁচের ভাষা; MongoDB ২০১৮ সালে (সংস্করণ ৪.০) multi-document ACID transaction যোগ করল। অর্থাৎ বাস্তবের অ্যাপের transaction আর declarative কোয়েরি দরকার ছিলই।
  • Relational সিস্টেম NoSQL-এর দিকে এল: PostgreSQL-এর jsonb, MySQL-এর JSON টাইপ, SQLite-এর JSON ফাংশন — এই পর্বের এডিটরেই দেখেছেন।
  • “NewSQL” এল: Google-এর Spanner (প্রবন্ধ ২০১২), পরে CockroachDB-র মতো সিস্টেম দেখাল বহু সার্ভারে ছড়ানো ডেটাতেও SQL আর শক্ত transaction দেওয়া সম্ভব।

তাহলে NoSQL কি ব্যর্থ? একদম না। Redis cache হিসেবে প্রায় সর্বব্যাপী, Cassandra বিপুল লেখার কাজে, MongoDB বহু অ্যাপের প্রধান ডেটাবেস, graph ডেটাবেস জালিয়াতি শনাক্তে। বদলেছে শুধু শব্দের মানে — “No SQL” থেকে “Not only SQL”.

এই ঢেউ থেকে আরেকটি শিক্ষা: সামাজিক মাধ্যমে কোনো প্রযুক্তির “মৃত্যু” ঘোষণা প্রায়ই অতিরঞ্জিত। নির্দিষ্ট সমস্যার মাপে নিজের প্রকল্পকে মিলিয়ে দেখুন — Google-এর সমস্যা আপনার স্থানীয় দোকানের অ্যাপের সমস্যা নয়।

মূল উপলব্ধি: ডেটা মডেলগুলো একে অপরকে সরিয়ে দেয় না, একে অপরের কাছ থেকে শেখে। দক্ষ প্রকৌশলী জানেন প্রতিটি মডেল কোন নিশ্চয়তা দেয় আর কোনটি ছেড়ে দেয়।

প্র ০৫একটি ই-কমার্স সাইটে মোবাইলের আছে RAM আর ক্যামেরা, শাড়ির আছে কাপড় আর দৈর্ঘ্য। এই বৈচিত্র্যময় তথ্য কোথায় রাখবেন — আলাদা কলাম, আলাদা টেবিল, নাকি JSON কলাম?

এটি বাস্তবের খুব পরিচিত ডিজাইন-প্রশ্ন, আর তিনটি প্রচলিত উত্তরের প্রতিটির দাম আছে।

  • সব গুণের জন্য আলাদা কলাম (ram, camera, fabric, length …): টাইপ আর CHECK দেওয়া যায়, index সহজ। কিন্তু শত শত ক্যাটাগরি হলে কয়েকশো কলাম, যার বেশিরভাগ ঘর NULL; নতুন ক্যাটাগরি মানে নতুন ALTER TABLE.
  • EAV (entity-attribute-value) টেবিল — (product_id, attribute, value): খুব নমনীয়, কিন্তু সব মান একই টাইপে (সাধারণত text), constraint দেওয়া কঠিন, আর একটি পণ্যের সব গুণ এক সারিতে আনতে জটিল pivot কোয়েরি লাগে (পর্ব ২৭)।
  • JSON কলাম — attributes-এ পুরো ডকুমেন্ট: পড়া-লেখা সহজ, অ্যাপের JSON-এর সাথে মেলে। কিন্তু ভেতরের মানে foreign key নেই, বানান ভুল ধরা পড়ে না, আর দ্রুত খোঁজার জন্য আলাদা ব্যবস্থা লাগে — PostgreSQL-এ jsonb-র ওপর GIN index, SQLite বা MySQL-এ json_extract(...)-এর ওপর expression বা generated column index.

বাস্তবে অনেক দল একটি মিশ্র পথ নেয়: যে তথ্য সব পণ্যের আছে আর যা দিয়ে নিয়মিত খোঁজা বা হিসাব হয় — নাম, দাম, স্টক, ক্যাটাগরি — সেগুলো সাধারণ কলাম, constraint সহ। ক্যাটাগরিভেদে আলাদা আর মূলত দেখানোর জন্য লাগে এমন তথ্য — JSON. আর কোনো JSON ঘর যদি পরে ফিল্টারে খুব ব্যবহার হয় (যেমন “৮ GB RAM-এর ফোন”), তখন সেটিকে generated column বানিয়ে index দেওয়া, বা নিয়মিত কলামে তুলে আনা।

এখানে normalization-এর চিন্তাও আসে (পর্ব ৩৬): JSON-এর ভেতরে যদি অন্য টেবিলের তথ্য কপি করেন — যেমন বিক্রেতার নাম — তাহলে অংশ ৩-এর সেই পুরোনো সমস্যাই ফিরবে: এক জায়গায় বদলালে অন্য কপিগুলো পুরোনো থেকে যাবে।

মূল উপলব্ধি: “JSON নাকি টেবিল” কোনো ধর্মযুদ্ধ নয়। প্রশ্ন করুন: এই তথ্যের নিয়ম কে রক্ষা করবে, কীভাবে খোঁজা হবে, আর কতবার বদলাবে — উত্তর অনুযায়ী প্রতিটি ঘরের জায়গা ঠিক করুন।

অনুশীলন

প্রথমটি SQL লেখার কাজ — উপরের যেকোনো db-uni এডিটরে লিখে চালাতে পারেন। বাকিগুলো হাতে-কলমে ভাবার।

  1. কোয়েরি লিখুন: db-uni থেকে যাঁরা CSE301 কোর্সে ভর্তি হয়েছেন, তাঁদের নাম, বিভাগের নাম আর গ্রেড বের করুন, নাম অনুযায়ী সাজিয়ে। এরপর ভাবুন — CODASYL মডেলে একই কাজে কোন set ধরে শুরু করতেন?
    SELECT s.name, d.name AS dept, e.grade
    FROM enrollments e
    JOIN students s    ON s.student_id = e.student_id
    JOIN departments d ON d.dept_id = s.dept_id
    WHERE e.course_id = 'CSE301'
    ORDER BY s.name;

    ফল তিনটি সারি: Arif Hossain │ CSE │ A-, Bristy Saha │ CSE │ B, Dilruba Khatun │ CSE │ NULL. Dilruba-র গ্রেড NULL, কারণ ২০২৪-এর টার্মের ফল এখনো আসেনি — NULL মানে “অজানা”, শূন্য বা খালি string নয় (পর্ব ১৬)। CODASYL-এ শুরু করতেন COURSE রেকর্ড CSE301 খুঁজে, তারপর CRS_ENR set ধরে প্রতিটি ENROLL member, প্রতিটির জন্য STU_ENR-এ FIND OWNER করে শিক্ষার্থী, আবার DEPT_STU-তে FIND OWNER করে বিভাগ — অর্থাৎ অংশ ৪-এর প্রোগ্রামের উল্টো দিকের একটি নতুন প্রোগ্রাম।

  2. হাতে হিসাব — ফল আগে বলুন: একটি TEXT কলামে আছে {"name":"Rahim","orders":[{"id":1001,"total":17400},{"id":1003,"total":1780}],"city":null}. SQLite-এ নিচের চারটির ফল কী? (ক) json_extract(doc,'$.orders[1].id') (খ) json_extract(doc,'$.orders[2].id') (গ) json_extract(doc,'$.city') (ঘ) json_array_length(doc,'$.orders')

    (ক) 1003 — সূচি 0 থেকে, তাই [1] দ্বিতীয় অর্ডার। (খ) NULL — তৃতীয় অর্ডার নেই; error নয়, NULL. (গ) NULL — এখানে ঘরটি আছে, কিন্তু মান JSON null, যা SQL NULL হয়ে আসে। লক্ষ করুন, (খ) আর (গ)-এর ফল দেখতে এক, কিন্তু কারণ আলাদা — “ঘর নেই” আর “ঘর আছে, মান null” আলাদা করতে json_type(doc,'$.city') ব্যবহার করা যায়, যা এখানে 'null' দেয় আর না-থাকা ঘরে SQL NULL. (ঘ) 2 — orders array-তে দুটি মান। (এই সংখ্যাগুলো db-shop-এর গ্রাহক Rahim-এর অর্ডার 1001 আর 1003-এর মোট দামের সাথে মেলে: 15000 + 2 × 1200 = 17400 আর 3 × 420 + 2 × 260 = 1780.)

  3. বংশতালিকা মডেল করুন: দাদা-দাদি, তাঁদের দুই সন্তান, আর প্রত্যেকের সন্তান — একটি ছোট পরিবারকে (ক) hierarchical গাছে, (খ) relational টেবিলে আঁকুন। কোথায় সমস্যা হলো?

    (ক) গাছে প্রথমেই প্রশ্ন: একটি শিশুকে বাবার নিচে বসাবেন, নাকি মায়ের? প্রত্যেক মানুষের দুজন অভিভাবক, কিন্তু গাছে একজনই parent হতে পারে। তাই হয় একজনকে বাদ দিতে হয় (পুরোনো বংশতালিকা যেমন শুধু পিতৃধারা দেখাত), নয়তো শিশুকে দুই জায়গায় কপি করতে হয়। আর বিয়ে করে আসা সদস্য — যিনি অন্য পরিবারের গাছ থেকে এসেছেন — তাঁকে কোথায় বসাবেন? (খ) relational-এ সহজ: persons(person_id, name, birth_year) আর parent_of(parent_id, child_id) — প্রতিটি শিশুর দুটি সারি, কোনো কপি নেই। “কারা চাচাতো ভাই-বোন” খুঁজতে একই টেবিলের সাথে কয়েকবার JOIN (self join, পর্ব ২০), আর সব পূর্বপুরুষ খুঁজতে recursive CTE (পর্ব ২৫)। Graph ডেটাবেসে এটি আরও স্বাভাবিক: মানুষ node, PARENT_OF edge.

  4. মডেল বাছাই: প্রতিটির জন্য কোন ডেটা মডেল বাছবেন, এক লাইনে কারণসহ — (ক) একটি মোবাইল ওয়ালেটের লেনদেনের খাতা, (খ) সেই অ্যাপের লগইন session, (গ) একটি অনলাইন বইয়ের দোকানের ক্যাটালগ, যেখানে বই, স্টেশনারি আর গ্যাজেট সবই বিক্রি হয়, (ঘ) একাধিক অ্যাকাউন্ট ঘুরে টাকা পাচারের চক্র খোঁজা।

    (ক) Relational — টাকা কাটা আর জমা একই transaction-এ হতে হবে, ব্যালান্স কখনো ঋণাত্মক নয় (CHECK), প্রতিটি লেনদেন বৈধ অ্যাকাউন্টের (foreign key). (খ) Key-value — session id দিয়ে খোঁজা, মেয়াদ শেষে মুছে যাবে, অতি দ্রুত পড়া দরকার; হারালে ক্ষতি সামান্য (আবার লগইন)। (গ) Document, অথবা relational + JSON কলাম — ক্যাটাগরিভেদে গুণ আলাদা; তবে দাম আর স্টক নিয়মিত কলামে রাখাই নিরাপদ (ভাবনার প্রশ্ন ৫)। (ঘ) Graph — অ্যাকাউন্ট node, টাকা পাঠানো edge; অজানা দৈর্ঘ্যের চক্র খোঁজা graph traversal-এর স্বাভাবিক কাজ (SQL-এ recursive CTE দিয়েও সম্ভব, তবে গভীরতা বাড়লে কঠিন)। চারটি একসাথে মানে polyglot persistence — আর তখন প্রশ্ন আসবে, এদের তথ্য মিলিয়ে রাখবেন কীভাবে।

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

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

ক্রমঘটনাগুলো সময়ের ক্রমে সাজান (▲▼ চেপে সরান)

  1. IBM-এর IMS — hierarchical মডেল
  2. CODASYL DBTG-এর network মডেলের রিপোর্ট
  3. Codd-এর relational মডেলের প্রবন্ধ
  4. Peter Chen-এর ER মডেল
  5. Object database-এর ODMG মানদণ্ড
  6. “NoSQL” শব্দের জনপ্রিয়তা

IBM-এর IMS কোন ডেটা মডেলের ওপর তৈরি?

  • Relational
  • Hierarchical
  • Network
  • Document

CODASYL network মডেলে “set” বলতে কী বোঝায়?

  • ডুপ্লিকেট ছাড়া মানের একটি সংগ্রহ
  • একটি টেবিলের index
  • একটি owner রেকর্ড আর তার member রেকর্ডগুলোর ১:N সম্পর্ক
  • একটি SQL কোয়েরির ফল

একটি relational ডেটাবেসে DBA নতুন একটি index যোগ করলেন। অ্যাপের পুরোনো SELECT কোয়েরিগুলো বদলাতে হলো না। এটি কোন ধারণার উদাহরণ?

  • Normalization
  • Impedance mismatch
  • Schema-on-read
  • Physical data independence

SQLite-এ json_extract('{"a":{"b":[10,20,30]}}', '$.a.b[1]')-এর ফল কী?

  • 20
  • 10
  • NULL
  • একটি error

Object-relational impedance mismatch বলতে কী বোঝায়?

  • ডেটাবেস সার্ভার আর অ্যাপ সার্ভারের নেটওয়ার্ক গতির পার্থক্য
  • প্রোগ্রামের object আর ডেটাবেসের টেবিল-সারির গঠনের অমিল
  • MySQL আর PostgreSQL-এর SQL-এর পার্থক্য
  • INTEGER আর BIGINT-এর আকারের পার্থক্য

একটি সামাজিক মাধ্যম অ্যাপে “আপনার বন্ধুর বন্ধুর বন্ধু, যাঁকে আপনি এখনো চেনেন না” — এমন সুপারিশ বারবার, দ্রুত খুঁজতে হবে। কোন NoSQL পরিবার স্বভাবতই সবচেয়ে উপযুক্ত?

  • Key-value
  • Document
  • Graph
  • Wide-column

শব্দকোষ

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

মনে রাখুন

  1. ডেটা মডেল = গঠন + অপারেশন + constraint; ডিজাইনে তিন স্তর: conceptual → logical → physical.
  2. Hierarchical (IMS) = গাছ, এক parent; ১:N-এ দ্রুত, M:N-এ কপি আর উল্টো প্রশ্নে পুরো গাছ ঘোরা।
  3. Network (CODASYL) = record type আর set-এর গ্রাফ; M:N সম্ভব, কিন্তু প্রোগ্রামার pointer ধরে নেভিগেট করেন।
  4. Relational (Codd, ১৯৭০) = টেবিল, মান দিয়ে সম্পর্ক, declarative SQL, data independence আর গাণিতিক ভিত্তি — তাই জিতেছে।
  5. ER = conceptual ডিজাইন; object-relational, JSON আর NoSQL নির্দিষ্ট সমস্যার সমাধান — কাজ অনুযায়ী বাছুন, দরকারে polyglot.

পরের পর্বে: আজ দেখলেন relational মডেল কেন জিতল। এবার তার ভেতরে ঢুকব — relation, tuple, attribute আর domain আসলে কী, টেবিল আর relation কেন হুবহু এক নয়, আর NULL কীভাবে এই ছবিতে ঢোকে।

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

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