Skip to content

The Relational Model & Basic Queries

Table, row, column, type — আর যে SELECT statement দিয়ে ডেটা আবার টেনে বের করা হয়।

গল্পে বুঝি

ধরুন আপনি একটা স্কুলের অফিসে বসে আছেন। কেরানি ইবনে সিনা সাহেবের টেবিলে দুটো আলাদা রেজিস্টার খাতা। একটা “ছাত্র রেজিস্টার” — প্রতি পাতায় এক লাইনে একজন ছাত্রের তথ্য: রোল নম্বর, নাম, বয়স, কোন ক্লাসে পড়ে তার ক্লাস-আইডি। প্রতিটা লাইনের বাঁয়ে বসানো রোল নম্বরটাই ওই ছাত্রকে আলাদা করে চেনায় — আল-খোয়ারিজমি আর ফাতিমা আল-ফিহরির নাম এক হলেও রোল আলাদা, তাই কখনো গুলিয়ে যায় না। আরেকটা খাতা “ক্লাস রেজিস্টার” — এক লাইনে এক ক্লাস: ক্লাস-আইডি, ক্লাসের নাম, ঘরের নম্বর।

এখন হেডস্যার এসে বললেন, “ক্লাস নাইনের সব ছাত্রের একটা লিস্ট দাও।” ইবনে সিনা সাহেব ছাত্র রেজিস্টারটা খুলে উপর থেকে নিচে চোখ বোলান, আর শুধু যাদের ক্লাস-আইডি নাইনের সাথে মেলে তাদের লাইনগুলোই আলাদা কাগজে টুকে নেন — বাকিদের বাদ। কারো ক্লাসের ঘর কোথায় জানতে হলে ছাত্রের ক্লাস-আইডি দিয়ে ক্লাস রেজিস্টারে গিয়ে মিলিয়ে দেখেন। দুই খাতা এই আইডি দিয়েই একে অপরের সাথে বাঁধা।

গল্পের প্রতিটা রেজিস্টার খাতা আসলে একটা table, খাতার এক-একটা লাইন হলো একটা row, আর রোল/নাম/বয়সের মতো ঘরগুলো হলো column। রোল নম্বরটা প্রতিটা row-কে unique করে চেনায় — এটাই key। দুই খাতাকে ক্লাস-আইডি দিয়ে জোড়া লাগানোটাই দুই table-এর মধ্যে relation। আর “শুধু ক্লাস নাইনের লাইনগুলো টুকে নাও” — এটাই SELECT ... WHERE: পুরো table থেকে শর্তে মেলা row-গুলো বেছে নেওয়া। বাস্তবে একটা database ঠিক এভাবেই কাজ করে, শুধু ইবনে সিনা সাহেবের হাতে চোখ বোলানোর বদলে কাজটা মিলিসেকেন্ডে সেরে ফেলে — স্কুলের হাজার হাজার ছাত্রের হাজিরা বা রেজাল্ট সিস্টেম এই একই ধারণার উপর দাঁড়িয়ে।

The Relational Model

একটা relational database ডেটা রাখে table-এ (আনুষ্ঠানিকভাবে, relation)। একটা table হলো একটা grid:

  • একটা row (বা tuple) হলো একটা একক record — একজন user, একটা order, একটা event।
  • একটা column (বা attribute) প্রতিটা row-এর একটা property বর্ণনা করে — একটা নাম, একটা দাম, একটা created timestamp।
  • প্রতিটা column-এর একটা data type থাকে, যা ঠিক করে দেয় সেটা কী কী value ধরে রাখতে পারবে।

এই মডেলের শুরু Edgar Codd-এর ১৯৭০ সালের পেপার থেকে, আর এর শক্তি আসে একটা সহজ ধারণা থেকে: ডেটা মানে শুধু row-এর কিছু set, আর আপনি বর্ণনা করেন কী চান, কীভাবে আনতে হবে সেটা নয়। “কীভাবে”-টা database নিজেই বের করে নেয়।

users নামের একটা table দেখতে এমন হতে পারে:

idemailagecreated_at
1lubna@example.com362026-01-04 09:12:00
2nusayba@example.com412026-02-18 14:30:00
3harun@example.com2026-03-01 08:00:00

লক্ষ্য করুন row 3-এর কোনো age নেই — ওই খালি cell-টা একটা NULL, যেটায় আমরা আবার ফিরব।

Data Types

সঠিক type বেছে নেওয়া গুরুত্বপূর্ণ: এটা storage size নিয়ন্ত্রণ করে, কোন operation বৈধ তা ঠিক করে, আর database কীভাবে value sort ও compare করবে সেটা নির্ধারণ করে। PostgreSQL-এর কমন কিছু type:

Typeযার জন্য
integer / bigintপূর্ণসংখ্যা, ID, count
numeric(p, s)নির্ভুল দশমিক — টাকাপয়সা, এখানে কখনো float নয়
text / varchar(n)String
booleanTrue/false flag
date / timestamptzতারিখ আর timezone-সচেতন timestamp
uuidGlobally unique identifier
jsonbSemi-structured document

টাকাপয়সা কখনো floating point-এ রাখবেন না। float আর double 0.1-এর মতো value নির্ভুলভাবে ধরে রাখতে পারে না, তাই total এক সেন্টের ভগ্নাংশ পরিমাণে সরে যায়। যেখানে নির্ভুলতা জরুরি সেখানে numeric (যাকে decimal-ও বলে) ব্যবহার করুন।

Creating a Table (DDL)

যেসব statement structure সংজ্ঞায়িত করে — CREATE, ALTER, DROP — সেগুলোকে বলা হয় DDL (Data Definition Language)।

CREATE TABLE users (
  id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  email      text NOT NULL UNIQUE,
  age        integer,
  created_at timestamptz NOT NULL DEFAULT now()
);

একটু ভেঙে দেখা যাক:

  • PRIMARY KEY id-কে প্রতিটা row-এর unique identifier হিসেবে চিহ্নিত করে; এটা স্বয়ংক্রিয়ভাবে NOT NULL আর indexed।
  • GENERATED ALWAYS AS IDENTITY Postgres-কে দিয়ে আপনার হয়ে ধারাবাহিক id বসিয়ে দেয়।
  • NOT NULL ওই column-এ missing value নিষিদ্ধ করে।
  • UNIQUE দুটো row-কে একই email শেয়ার করতে দেয় না।
  • DEFAULT now() আপনি না দিলে created_at স্বয়ংক্রিয়ভাবে ভরে দেয়।

Row insert করতে DML (Data Manipulation Language) ব্যবহার হয়:

INSERT INTO users (email, age) VALUES
  ('lubna@example.com', 36),
  ('nusayba@example.com', 41),
  ('harun@example.com', NULL);

SELECT: Reading Data

SELECT statement হলো আপনার প্রশ্ন করার উপায়। এর মূল রূপ:

SELECT email, age
FROM users
WHERE age > 30
ORDER BY age DESC
LIMIT 10;

প্রতিটা clause একটা করে কাজ করে:

  • SELECT — কোন column ফেরত দেওয়া হবে। SELECT * সবগুলো ফেরত দেয় (ইন্টারঅ্যাক্টিভভাবে সুবিধাজনক, তবে application code-এ column-গুলো স্পষ্ট করে লিখুন, যাতে পরে একটা column যোগ করলে সেটা আপনাকে চমকে না দেয়)।
  • FROM — কোন table থেকে পড়া হবে।
  • WHERE — একটা filter; শুধু যেসব row-তে শর্তটা সত্য, সেগুলোই ফেরত আসে।
  • ORDER BY — sort করার ক্রম, ASC (default) বা DESC
  • LIMIT — কতগুলো row ফেরত আসবে তার সীমা।

Filtering with WHERE

WHERE-এর শর্তগুলো AND, OR আর NOT দিয়ে comparison জোড়া লাগায়:

SELECT * FROM users
WHERE age >= 18 AND age < 65;

SELECT * FROM users
WHERE email LIKE '%@example.com';

SELECT * FROM users
WHERE age IN (36, 41, 50);

SELECT * FROM users
WHERE created_at BETWEEN '2026-01-01' AND '2026-03-31';

যখন আপনি একটা column-কে একটা literal-এর সাথে তুলনা করেন — যেমন, “৩০-এর কম বয়সী সবাইকে দাও” — সেটা prose-এ হয় WHERE age < 30, কিন্তু একটা code block-এর ভেতরে আপনি সাধারণভাবে শুধু age < 30 লিখবেন। LIKE pattern matching করে, যেখানে % মানে “যেকোনো ক্রমের অক্ষর”।

DISTINCT

DISTINCT রেজাল্ট থেকে ডুপ্লিকেট row সরিয়ে দেয়:

SELECT DISTINCT age FROM users ORDER BY age;

এটা প্রতিটা আলাদা age একবার করে ফেরত দেয়। DISTINCT পুরো selected row-এর ওপর প্রযোজ্য, তাই SELECT DISTINCT age, email দুটো column-এর সমন্বয়ের ওপর দেখে deduplicate করে।

NULL Semantics

NULL মানে “অজানা” বা “অনুপস্থিত” — এটা zero নয়, আর empty string-ও নয়। শুরুতে এটা প্রায় সবাইকে বিভ্রান্ত করে, কারণ comparison-এ NULL কোনো value-এর মতো আচরণ করে না।

NULL-এর সাথে যেকোনো comparison-এর ফল NULL (যাকে “সত্য নয়” হিসেবে ধরা হয়), তাই:

SELECT * FROM users WHERE age = NULL;   -- returns ZERO rows, always
SELECT * FROM users WHERE age <> 25;    -- excludes NULL-age rows!

null পরীক্ষা করতে হলে আপনাকে বিশেষ IS operator ব্যবহার করতেই হবে:

SELECT * FROM users WHERE age IS NULL;
SELECT * FROM users WHERE age IS NOT NULL;

arithmetic-এও NULL ছোঁয়াচে। 5 + NULL হলো NULL, আর 'hi' || NULL হলো NULL। কোনো column null হতে পারে এমন হলে fallback value বসাতে COALESCE(age, 0) ব্যবহার করুন — এটা প্রথম non-null argument-টা ফেরত দেয়।

একটা বাস্তব ফলাফল: আপনি যদি চান “যাদের age 25 নয় এমন সবাই, সহ যাদের age অজানা”, তাহলে আপনাকে স্পষ্ট হতে হবে:

SELECT * FROM users WHERE age <> 25 OR age IS NULL;

Recap

এখন আপনার ভিত্তিটা তৈরি: table typed row ধরে রাখে, DDL সেগুলো সংজ্ঞায়িত করে, আর WHERE / ORDER BY / LIMIT সহ SELECT সেগুলো আবার পড়ে। NULL-এর নিয়মগুলো মাথায় রাখুন — অন্য যেকোনো feature-এর চেয়ে এগুলোই বেশি সূক্ষ্ম bug-এর উৎস। এরপর আমরা aggregation দিয়ে অনেকগুলো row-কে একক উত্তরে সারসংক্ষেপ করব।