এই পর্বে যা শিখবেন
- SELECT কোয়েরির প্রতিটি অংশ (clause) কী করে, আর কেন
SELECT *প্রোডাকশনে বিপজ্জনক - Alias, হিসাব করা কলাম আর DISTINCT (এক বা একাধিক কলামে)
- WHERE-এ তুলনা, AND/OR/NOT-এর অগ্রাধিকার, লেখার বড়-ছোট হাতের অক্ষর আর তারিখের তুলনা
- ORDER BY-এর সব খুঁটিনাটি — একাধিক key, NULL-এর অবস্থান, tiebreaker — আর LIMIT/OFFSET দিয়ে pagination
- লজিক্যাল প্রসেসিং ক্রম — যা দিয়ে SQL-এর অনেক “অদ্ভুত” error-এর ব্যাখ্যা মেলে
১ · SELECT-এর গঠন — একটি প্রশ্নের ব্যাকরণ
আগের পর্বগুলোতে টেবিল বানিয়েছেন (পর্ব ১৩) আর ডেটা ঢুকিয়েছেন-বদলেছেন (পর্ব ১৪)। এবার আসল কাজ — ডেটা থেকে প্রশ্নের উত্তর বের করা। SQL-এ প্রায় সব প্রশ্ন শুরু হয় একটি শব্দে: SELECT.
পাড়ার মুদি দোকানের মালিককে ভাবুন। হিসাবের খাতায় সব পণ্যের তালিকা আছে। কর্মচারীকে তিনি বলেন: “খাতা থেকে (FROM) যেসব পণ্যের দাম হাজার টাকার কম (WHERE), তাদের শুধু নাম আর দাম (SELECT) লিখে দাও, সস্তা থেকে দামি (ORDER BY), প্রথম পাঁচটা (LIMIT).” তিনি বলেননি কোন পাতা আগে উল্টাতে হবে — শুধু বলেছেন কী চাই। SQL ঠিক এই রকম একটি declarative ভাষা।
একটি পূর্ণ SELECT-এর কাঠামো এমন (বর্গবন্ধনীর অংশগুলো ঐচ্ছিক; GROUP BY আর HAVING আসবে পর্ব ১৮-তে):
SELECT [DISTINCT] কলাম বা হিসাব [AS নাম], ...
FROM টেবিল
[WHERE সারি বাছাইয়ের শর্ত]
[ORDER BY কলাম [ASC | DESC], ...]
[LIMIT কয়টি সারি [OFFSET কয়টি বাদ]];প্রতিটি অংশকে বলে একটি clause. কোয়েরির ফল সবসময় আরেকটি টেবিল — একে বলে result set. তাই একটি কোয়েরির ফল আরেকটি কোয়েরির ইনপুট হতে পারে (পর্ব ২১-এর subquery-তে দেখবেন)।
এই পর্বের বেশিরভাগ উদাহরণ চলবে দোকানের নমুনা ডেটাবেস db-shop-এ। মূল টেবিল দুটি: products — product_id, name, category_id (1 = Electronics, 2 = Books, 3 = Clothing, 4 = Grocery), price (টাকা) আর stock (গুদামে কয়টি আছে); আর customers — customer_id, name, city, phone আর joined_on (যোগ দেওয়ার তারিখ, 'YYYY-MM-DD' লেখা হিসেবে)। orders টেবিলে আছে order_id, customer_id, order_date আর status. এডিটরের নিচের “নমুনা ডেটাবেস” বাক্স খুললে পুরো ডেটা দেখতে পাবেন।
-- সব কলাম, সব সারি
SELECT * FROM categories;
-- শুধু দুটি কলাম
SELECT name, price FROM products;
-- FROM ছাড়াও SELECT চলে: একটি হিসাব
SELECT 2 + 3 AS answer;প্রথম কোয়েরি categories-এর দুটি কলাম আর ৪টি সারি দেখায় (Electronics, Books, Clothing, Grocery). * মানে “সব কলাম, টেবিলে যে ক্রমে আছে”। দ্বিতীয়টি ১০টি পণ্যের শুধু নাম আর দাম দেয় — Mobile Phone 15000 থেকে Smart Watch 3500 পর্যন্ত। কলাম বেছে নেওয়ার এই কাজকে relational algebra-য় বলে projection (পর্ব ৩২)। তৃতীয়টি কোনো টেবিল ছাড়াই একটি সারি দেয়: answer = 5. SQLite, MySQL আর PostgreSQL-এ FROM ছাড়া SELECT চলে; Oracle-এ পুরোনো সংস্করণে লিখতে হয় FROM dual.
এই পর্বে বারবার “স্ট্যান্ডার্ড SQL” কথাটি আসবে। SQL-এর প্রথম আনুষ্ঠানিক মান দেয় ANSI ১৯৮৬ সালে, পরের বছর ISO. তারপর বড় সংস্করণগুলো: SQL-92 (আজকের মূল syntax-এর বেশিরভাগ), SQL:1999 (recursive query, trigger), SQL:2003 (window function), SQL:2008 (FETCH FIRST), এরপর ২০১১, ২০১৬ (JSON) আর ২০২৩ সালের সংস্করণ। কোনো ডেটাবেস পুরো মান মানে না, আবার প্রতিটি নিজের কিছু বাড়তি সুবিধা যোগ করে — যেমন LIMIT, যা স্ট্যান্ডার্ডে নেই, অথচ SQLite, MySQL আর PostgreSQL তিনটিতেই চলে। তাই “এটি কি স্ট্যান্ডার্ড, নাকি এই ডেটাবেসের নিজস্ব?” — এই প্রশ্নটি করার অভ্যাস রাখুন (পর্ব ৩১)।
প্রোডাকশন কোডে SELECT * কেন বিপজ্জনক। ডেটা ঘেঁটে দেখার সময় * চমৎকার। কিন্তু অ্যাপের ভেতরে স্থায়ী কোয়েরিতে SELECT * পাঁচ রকম সমস্যা আনে:
- অপ্রয়োজনীয় ডেটা: পরে কেউ টেবিলে বড় একটি কলাম (পণ্যের বিবরণ, ছবি) যোগ করলে প্রতিটি কোয়েরি সেটাও টানবে — নেটওয়ার্ক আর মেমরির অপচয়।
- কোড ভাঙে: যে কোড কলামকে অবস্থান দিয়ে পড়ে (“তৃতীয় কলাম = দাম”), কলাম যোগ বা সরালে সে চুপচাপ ভুল মান পড়ে।
INSERT INTO archive SELECT * FROM ...কলামের সংখ্যা না মিললে ব্যর্থ হয়। - গোপন তথ্য ফাঁস: নতুন যোগ হওয়া
password_hashবা NID কলাম অজান্তেই API-র উত্তরে চলে যেতে পারে। - Index-এর সুবিধা নষ্ট: শুধু দরকারি কলাম চাইলে অনেক সময় index থেকেই উত্তর মেলে, টেবিলে যেতে হয় না (covering index, পর্ব ৪১)।
*সেই পথ বন্ধ করে। - পড়তে কঠিন: কোয়েরি দেখে বোঝা যায় না কোন কলাম আসলে লাগবে।
নিয়ম: অ্যাপের কোডে কলামের নাম লিখুন। শুধু খুঁজে দেখার সময় *.
২ · Alias আর হিসাব করা কলাম
SELECT-এর তালিকায় শুধু কলামের নাম নয়, যেকোনো হিসাব (expression) লেখা যায় — যেমন price * stock, মানে গুদামের মালের মোট মূল্য। এমন কলামকে বলে computed column. এর একটি পড়ার মতো নাম দিতে লেখা হয় AS নাম — একে বলে column alias.
-- গুদামে প্রতিটি পণ্যের মোট মূল্য
SELECT name AS product, price, stock,
price * stock AS stock_value
FROM products;
-- ফাঁকা জায়গা বা বাংলা নাম: ডাবল কোটে
SELECT name AS "পণ্যের নাম", price * stock AS "stock value"
FROM products;
-- কমা ভুলে গেলে?
SELECT name price FROM products;প্রথম কোয়েরির শেষ কলাম stock_value: Mobile Phone-এর 15000 × 12 = 180000, Headphone-এর 48000, Power Bank-এর stock 0, তাই 0. টেবিলে এমন কোনো কলাম নেই — প্রতিটি সারির জন্য এই মুহূর্তে হিসাব হলো। দ্বিতীয়টিতে কলামের শিরোনাম হয় “পণ্যের নাম” আর “stock value”. তৃতীয়টি সবচেয়ে মজার: কোনো error নেই! ফলে একটিই কলাম, যার শিরোনাম price, কিন্তু ভেতরে পণ্যের নাম — Mobile Phone, Headphone… কারণ AS শব্দটি ঐচ্ছিক, তাই name price মানে “name কলাম, যার alias price”. তাই সবসময় AS লিখুন; এক নজরে বোঝা যায় কোনটি alias.
কোট নিয়ে তিনটি নিয়ম। SQL স্ট্যান্ডার্ডে একক কোট 'Dhaka' মানে লেখা (string), আর ডাবল কোট "stock value" মানে নাম (identifier). MySQL ডিফল্টে নামের জন্য backtick (`stock value`) চায়, আর SQL Server-এ চলে বর্গবন্ধনী [stock value]. SQLite কিছু ভুলও মেনে নেয় — যেমন AS 'নাম' — কিন্তু অন্য ডেটাবেসে সেটা ভাঙতে পারে। সবচেয়ে নিরাপদ: alias-এ ফাঁকা জায়গা না রেখে stock_value-এর মতো snake_case নাম।
হিসাব করা কলামে দুটি ছোট ফাঁদ মনে রাখুন। প্রথমত, দুটি পূর্ণসংখ্যার ভাগে SQLite আর PostgreSQL ভাগফলের দশমিক অংশ ফেলে দেয়: SELECT 7 / 2 দেয় 3, 3.5 নয়। দশমিক চাইলে একটিকে দশমিক সংখ্যা করুন: 7 / 2.0. MySQL-এ আবার 7 / 2 দেয় 3.5000, আর পূর্ণসংখ্যার ভাগের জন্য আলাদা অপারেটর DIV. দ্বিতীয়ত, হিসাবের যেকোনো অংশ NULL হলে পুরো ফলই NULL: দাম জানা না থাকলে price * stock-ও অজানা। db-shop-এ price আর stock দুটোই NOT NULL, তাই এখানে সমস্যা নেই — কিন্তু বাস্তব টেবিলে সবসময় দেখে নিন।
৩ · DISTINCT — একই সারি একবার
পর্ব ০৫-এ দেখেছেন, SQL-এর ফল আসলে bag — একই সারি একাধিকবার থাকতে পারে। “আমাদের গ্রাহকেরা কোন কোন শহরের?” প্রশ্নে ঢাকা তিনবার দেখতে চান না। DISTINCT লিখলে ফলের প্রতিটি সারি একবারই আসে।
SELECT city FROM customers; -- ৮টি সারি
SELECT DISTINCT city FROM customers; -- আলাদা শহর
-- কয়েকটি কলামে: আলাদা জোড়া
SELECT DISTINCT customer_id, status FROM orders;প্রথম কোয়েরি ৮টি সারি দেয় — Dhaka তিনবার, আর একটি NULL (Imran Hossain-এর শহর জানা নেই)। দ্বিতীয়টি দেয় ৬টি: Dhaka, Chattogram, Rajshahi, Khulna, Sylhet আর NULL. খেয়াল করুন, DISTINCT সব NULL-কে একটি দল ধরে একবার দেখায়। তৃতীয়টিতে DISTINCT কাজ করে পুরো সারির ওপর: orders-এর ১০টি সারি থেকে ৯টি আলাদা (customer_id, status) জোড়া আসে। শুধু (2, delivered) দুবার ছিল (অর্ডার 1002 আর 1007), তাই একবার বাদ। কিন্তু (1, delivered), (1, shipped), (1, pending) তিনটিই থাকে — customer 1 একই হলেও জোড়া আলাদা।
DISTINCT কোনো ফাংশন নয়। SELECT DISTINCT(city), name FROM customers লিখলে মনে হয় শুধু city-তে DISTINCT হচ্ছে। আসলে বন্ধনী শুধু city-কে ঘিরে আছে; DISTINCT তবুও পুরো সারি (city, name) দেখে। আর DISTINCT বিনা খরচে আসে না — duplicate খুঁজতে DBMS-কে সারিগুলো sort করতে হয় বা hash table-এ রাখতে হয়। কোনো JOIN-এর পর হঠাৎ duplicate এলে DISTINCT দিয়ে ঢেকে দেওয়ার আগে কারণ খুঁজুন (পর্ব ১৯)। PostgreSQL-এ আলাদা একটি সুবিধা আছে — DISTINCT ON (কলাম), যা প্রতিটি দলের প্রথম সারি রাখে; এটি স্ট্যান্ডার্ড SQL নয়।
৪ · WHERE — শর্ত দিয়ে সারি ছাঁকা
SELECT কলাম বাছে; WHERE বাছে সারি। WHERE-এর শর্ত প্রতিটি সারির জন্য আলাদা করে যাচাই হয়; যে সারিতে ফল TRUE, শুধু সেটি থাকে। শর্ত লেখার মূল হাতিয়ার comparison operator:
| অপারেটর | অর্থ | উদাহরণ |
|---|---|---|
= | সমান | city = 'Dhaka' |
<> (বা !=) | সমান নয় | status <> 'cancelled' |
<, > | ছোট, বড় | price < 1000 |
<=, >= | ছোট বা সমান, বড় বা সমান | stock >= 10 |
<> হলো স্ট্যান্ডার্ড; != প্রায় সব ডেটাবেসে চলে। SQLite-এ ==-ও চলে, কিন্তু অন্য কোথাও নয় — অভ্যাস করবেন না। BETWEEN, IN, LIKE-এর পূর্ণ আলোচনা আর NULL-এর সঙ্গে তুলনা আসছে পর্ব ১৬-তে। এখন সংখ্যা আর তারিখ দিয়ে ছাঁকি:
-- হাজার টাকার কম দামের পণ্য, সস্তা থেকে দামি
SELECT name, price FROM products
WHERE price < 1000
ORDER BY price;
-- কোন পণ্য গুদামে শেষ?
SELECT name, stock FROM products WHERE stock = 0;
-- অক্টোবর ২০২৫ বা তার পরের অর্ডার
SELECT order_id, order_date, status FROM orders
WHERE order_date >= '2025-10-01';প্রথম কোয়েরি ৪টি পণ্য দেয়: Mustard Oil 1L (260), Rice 5kg (420), SQL Book (550), DSA Book (650). দ্বিতীয়টি একটি: Power Bank, stock 0. তৃতীয়টি ৪টি অর্ডার: 1007 (2025-10-01), 1008, 1009 আর 1010. >= বলে 1007-ও এসেছে — ঠিক ১ অক্টোবরের অর্ডার। কিন্তু order_date তো লেখা (TEXT), তারিখ নয় — তুলনা ঠিক হলো কীভাবে? নিচে দেখুন।
SQLite-এ আলাদা DATE টাইপ নেই; তারিখ সাধারণত লেখা হিসেবে রাখা হয়, ISO 8601 রূপে: 'YYYY-MM-DD'. লেখার তুলনা হয় অভিধানের মতো, বাঁ থেকে এক অক্ষর করে। এই রূপে বছর আগে, তারপর মাস, তারপর দিন, আর প্রতিটির দৈর্ঘ্য নির্দিষ্ট (09, 9 নয়)। তাই অভিধানের ক্রম আর সময়ের ক্রম হুবহু এক: '2025-09-30' < '2025-10-01'. কিন্তু বাংলাদেশে প্রচলিত '30/09/2025' রূপে রাখলে '30/09/2025' > '01/10/2025' হয়ে যায় — কারণ প্রথম অক্ষর 3 > 0. আর '2025-9-5'-এর মতো শূন্য ছাড়া লেখাও ক্রম ভাঙে। PostgreSQL, MySQL-এ আসল DATE টাইপ আছে, সেখানে এই সমস্যা নেই (পর্ব ১৭-তে তারিখের ফাংশন)।
লেখার তুলনা: বড় হাতের অক্ষর আর ছোট হাতের অক্ষর
লেখা (string) তুলনার নিয়ম ঠিক করে collation — অক্ষর সাজানো আর মেলানোর নিয়মের একটি সেট। এখানেই ডেটাবেসগুলো সবচেয়ে বেশি আলাদা:
-- ঢাকার গ্রাহক, যোগ দেওয়ার তারিখের ক্রমে
SELECT name, joined_on FROM customers
WHERE city = 'Dhaka'
ORDER BY joined_on;
SELECT name FROM customers WHERE city = 'dhaka'; -- ছোট হাতের d
SELECT name FROM customers WHERE city LIKE 'dhaka'; -- LIKE দিয়ে
SELECT name FROM customers WHERE city = 'dhaka' COLLATE NOCASE;প্রথম কোয়েরি ৩ জন দেয়: Rahim Uddin (2025-01-15), Sabbir Hasan (2025-03-21), Mitu Rani (2025-08-12). দ্বিতীয়টি দেয় “(কোনো সারি নেই)” — SQLite-এ = ডিফল্টে case-sensitive: 'dhaka' আর 'Dhaka' আলাদা। তৃতীয়টি আবার ৩ জন দেয়, কারণ SQLite-এর LIKE ডিফল্টে ইংরেজি (ASCII) অক্ষরের বড়-ছোট মানে না। চতুর্থটিও ৩ জন — COLLATE NOCASE বলে দিল “এই তুলনায় বড়-ছোট অক্ষর এক ধরো”।
| ডেটাবেস | 'Dhaka' = 'dhaka' | LIKE |
|---|---|---|
| SQLite | মিথ্যা (BINARY collation) | ASCII অক্ষরে বড়-ছোট মানে না |
| PostgreSQL | মিথ্যা | বড়-ছোট মানে; না মানতে ILIKE |
| MySQL 8 | সত্য (ডিফল্ট collation utf8mb4_0900_ai_ci — ci মানে case-insensitive) | বড়-ছোট মানে না |
| SQL Server | সাধারণত সত্য (ইনস্টলের সময় বাছা collation-এর ওপর নির্ভর করে) | collation অনুযায়ী |
অর্থাৎ একই কোয়েরি MySQL-এ ৩ জন আর PostgreSQL-এ ০ জন দিতে পারে। একটি কোয়েরি এক ডেটাবেস থেকে আরেকটিতে নিলে লেখার তুলনাগুলো আবার যাচাই করুন। ব্যবহারকারীর লেখা খোঁজার সময় সাধারণত দুই দিকেই একই রূপ করে নেওয়া হয়, যেমন LOWER(city) = LOWER(?) (পর্ব ১৭)।
Collation শুধু = নয়, <, > আর ORDER BY-কেও নিয়ন্ত্রণ করে। SQLite-এর BINARY collation অক্ষর তুলনা করে তাদের কোড নম্বর দিয়ে, আর ASCII-তে সব বড় হাতের অক্ষর (A–Z) ছোট হাতের অক্ষরের (a–z) আগে। তাই সেখানে 'Zebra' < 'apple' সত্য, আর নাম সাজালে “Zahid” আসবে “abir”-এর আগে। ডেটা ঢোকানোর সময় নামের বানান-রীতি এক রাখা (প্রথম অক্ষর বড়) এই ঝামেলা অনেকটা কমায়।
৫ · AND, OR, NOT — আর বন্ধনীর দাম
কয়েকটি শর্ত জুড়তে লাগে logical operator: AND (দুটোই সত্য হতে হবে), OR (অন্তত একটি সত্য), NOT (উল্টে দেয়)। গণিতে যেমন গুণ আগে, যোগ পরে, SQL-এও তেমনি একটি operator precedence আছে: আগে তুলনা (=, < …), তারপর NOT, তারপর AND, সবশেষে OR.
ধরুন মালিক চান: “Electronics বা Books-এর পণ্য, যাদের দাম হাজার টাকার কম।” স্বাভাবিক ভাষা থেকে সরাসরি লিখলে যা হয়, ধাপে ধাপে দেখুন:
| শর্তের রূপ | |
| ফলে সারি |
- প্রশ্ন আর শর্ত। শর্তে তিনটি অংশ: A = Electronics কি না, B = Books কি না, C = দাম ১০০০-এর কম কি না। লেখা হয়েছে
A OR B AND C— কোনো বন্ধনী নেই। - SQL কীভাবে পড়ে। AND-এর অগ্রাধিকার OR-এর চেয়ে বেশি, তাই SQL পড়ে
A OR (B AND C): “Electronics-এর সব পণ্য, অথবা সস্তা বই।” মালিক চেয়েছিলেন(A OR B) AND C. - প্রতিটি সারিতে হিসাব। ১০টি পণ্যের প্রতিটিতে A, B, C-এর মান বসাই। যেমন 101 Mobile Phone: A = T, B = F, C = F, তাই B AND C = F; কিন্তু A OR F = T.
- ভুল ফল — ৬টি সারি। 101 (15000), 102 (1200), 103 (1800) আর 110 (3500) — চারটি দামি Electronics পণ্যও এসেছে, শুধু A সত্য বলে। সাথে আছে দুটি সস্তা বই। কোয়েরি চলেছে, error নেই — শুধু উত্তর ভুল।
- বন্ধনী দিয়ে ঠিক করা।
(A OR B) AND C: আগে “Electronics বা Books”, তারপর সেই দলের মধ্যে দাম ১০০০-এর কম। Electronics-এর কোনো পণ্যই ১০০০-এর কম নয় (সবচেয়ে সস্তা Headphone 1200), তাই ফল শুধু দুটি: 104 DSA Book আর 105 SQL Book.
এবার নিজে চালিয়ে দুটি ফল মিলিয়ে নিন:
-- ফাঁদ: বন্ধনী নেই
SELECT product_id, name, category_id, price FROM products
WHERE category_id = 1 OR category_id = 2 AND price < 1000;
-- ঠিক: বন্ধনী দিয়ে অর্থ স্পষ্ট
SELECT product_id, name, category_id, price FROM products
WHERE (category_id = 1 OR category_id = 2) AND price < 1000;
-- NOT: ঢাকার বাইরের গ্রাহক
SELECT name, city FROM customers WHERE NOT city = 'Dhaka';প্রথম কোয়েরি ৬টি সারি দেয় (101, 102, 103, 104, 105, 110), দ্বিতীয়টি ২টি (104 DSA Book 650, 105 SQL Book 550) — ঠিক ডায়াগ্রামের মতো। তৃতীয়টি ৪ জন দেয়: Karima Begum (Chattogram), Nusrat Jahan, Tanvir Ahmed আর Farhana Akter. কিন্তু গ্রাহক তো ৮ জন, ঢাকার ৩ জন — বাকি ৫ জন আসার কথা নয়? Imran Hossain-এর শহর NULL. NULL = 'Dhaka'-এর ফল TRUE বা FALSE নয়, UNKNOWN; আর NOT UNKNOWN-ও UNKNOWN. WHERE শুধু TRUE রাখে, তাই তিনি বাদ পড়েন। এই তিন-মানের যুক্তি পুরোটা পর্ব ১৬-তে।
অভ্যাস: একই WHERE-এ AND আর OR দুটোই থাকলে সবসময় বন্ধনী দিন — নিয়ম জানা থাকলেও। বন্ধনী শুধু DBMS-এর জন্য নয়, পরের পাঠকের (আর ছয় মাস পরের আপনার) জন্যও। NOT-এর ক্ষেত্রে De Morgan-এর সূত্র মনে রাখুন: NOT (A OR B) = NOT A AND NOT B, আর NOT (A AND B) = NOT A OR NOT B.
৬ · ORDER BY — সাজানো, আর সমান মানের সমস্যা
পর্ব ০৫-এ দেখেছেন, relation-এ সারির কোনো ক্রম নেই। তাই ক্রম চাইলে বলতে হয় ORDER BY দিয়ে। ASC মানে ছোট থেকে বড় (ডিফল্ট), DESC মানে বড় থেকে ছোট। একাধিক key দিলে প্রথমটি দিয়ে সাজানো হয়; প্রথমটিতে সমান হলে দ্বিতীয়টি দিয়ে, এভাবে — ঠিক অভিধানে শব্দ সাজানোর মতো।
এবার বিশ্ববিদ্যালয়ের নমুনা ডেটাবেস db-uni-র students টেবিল: student_id, name, dept_id, batch (ভর্তির বছর) আর cgpa (Fahim Karim-এর cgpa এখনো NULL).
-- CGPA 3.5 বা বেশি: সবচেয়ে বেশি থেকে
SELECT name, batch, cgpa FROM students
WHERE cgpa >= 3.5
ORDER BY cgpa DESC;
-- আগে নতুন ব্যাচ, প্রতিটি ব্যাচের ভেতরে বেশি CGPA আগে
SELECT name, batch, cgpa FROM students
ORDER BY batch DESC, cgpa DESC;প্রথম কোয়েরি ৩ জন দেয়: Dilruba Khatun 3.9, Arif Hossain 3.75, Gulshan Ara 3.55. দ্বিতীয়টি ৮ জনকেই দেয়: প্রথমে 2023 ব্যাচ (Gulshan Ara 3.55, Habib Ullah 2.8), তারপর 2022 (Dilruba 3.9, Emon Mia 3.1, Fahim Karim NULL), তারপর 2021 (Arif 3.75, Bristy 3.4, Chayon 2.95). দ্বিতীয় key cgpa DESC কাজে লেগেছে শুধু একই batch-এর ভেতরে। আর খেয়াল করুন: Fahim-এর NULL তাঁর ব্যাচের একেবারে শেষে।
NULL কোথায় যাবে — ডেটাবেস ভেদে আলাদা
NULL কোনো সংখ্যা নয়, তাই “NULL ছোট না বড়” প্রশ্নের কোনো গাণিতিক উত্তর নেই। SQL স্ট্যান্ডার্ড বিষয়টি প্রতিটি ডেটাবেসের ওপর ছেড়ে দিয়েছে, আর সঙ্গে দিয়েছে স্পষ্ট করে বলার উপায়: NULLS FIRST / NULLS LAST. SQLite-এ এটি এসেছে ৩.৩০ সংস্করণে (২০১৯)।
SELECT name, cgpa FROM students ORDER BY cgpa; -- SQLite: NULL আগে
SELECT name, cgpa FROM students ORDER BY cgpa NULLS LAST;
SELECT name, cgpa FROM students ORDER BY cgpa DESC NULLS FIRST;প্রথম কোয়েরিতে Fahim Karim (NULL) সবার ওপরে, তারপর Habib Ullah 2.8 থেকে Dilruba Khatun 3.9. SQLite NULL-কে সবচেয়ে ছোট মান ধরে। দ্বিতীয়টিতে একই ক্রম, শুধু Fahim সবার শেষে। তৃতীয়টিতে বড় থেকে ছোট (Dilruba 3.9 → Habib 2.8), কিন্তু Fahim জোর করে সবার আগে।
| ডেটাবেস | NULL ধরা হয় | ASC-এ NULL | NULLS FIRST/LAST লেখা যায়? |
|---|---|---|---|
| SQLite | সবচেয়ে ছোট | আগে | হ্যাঁ (৩.৩০ থেকে) |
| MySQL | সবচেয়ে ছোট | আগে | না — লিখুন ORDER BY cgpa IS NULL, cgpa |
| SQL Server | সবচেয়ে ছোট | আগে | না — CASE দিয়ে (পর্ব ১৬) |
| PostgreSQL | সবচেয়ে বড় | শেষে | হ্যাঁ |
| Oracle | সবচেয়ে বড় | শেষে | হ্যাঁ |
Alias আর অবস্থান দিয়ে সাজানো
ORDER BY-তে SELECT-এর alias ব্যবহার করা যায়: ORDER BY stock_value DESC. কলামের অবস্থান দিয়েও লেখা যায়: SELECT name, price FROM products ORDER BY 2 DESC — “দ্বিতীয় কলাম দিয়ে সাজাও”। এটি ছোট, কিন্তু ভঙ্গুর: কেউ পরে SELECT-এ নতুন কলাম ঢোকালে 2 চুপচাপ অন্য কলাম বোঝাবে, কোনো error ছাড়া। SQL-92 স্ট্যান্ডার্ডে এই রীতিকে পুরোনো (deprecated) ঘোষণা করা হয়েছিল, পরে স্ট্যান্ডার্ড থেকে বাদও দেওয়া হয় — যদিও প্রায় সব ডেটাবেস এখনো মানে। দ্রুত খোঁজার সময় চলে; স্থায়ী কোডে নাম লিখুন।
ORDER BY-তে শুধু কলাম নয়, যেকোনো হিসাবও দেওয়া যায় — যেমন ORDER BY price * stock DESC, যা SELECT তালিকায় না থাকলেও চলে। আর একেক key-এর দিক আলাদা হতে পারে: ORDER BY batch DESC, name ASC. খেয়াল রাখবেন, DESC শুধু তার ঠিক আগের key-তে খাটে; ORDER BY batch, cgpa DESC-এ batch ছোট থেকে বড়ই থাকে।
যে key দিয়ে সাজাচ্ছেন, তাতে দুটি সারির মান সমান হলে তাদের পরস্পরের ক্রম SQL নিশ্চিত করে না। ORDER BY batch দিলে 2021 ব্যাচের তিনজন যেকোনো ক্রমে আসতে পারেন — আজ এক রকম, index বা ডেটাবেসের সংস্করণ বদলালে অন্য রকম। ক্রমটি deterministic করতে শেষে একটি tiebreaker যোগ করুন — এমন কলাম, যার মান প্রতিটি সারিতে আলাদা, সাধারণত primary key: ORDER BY batch, student_id. LIMIT বা pagination-এর আগে এটি বাধ্যতামূলক; নইলে একই সারি দুই পাতায় আসতে পারে, বা কোনো পাতাতেই না।
| ORDER BY | |
| ফল নিশ্চিত? |
- শুরুর অবস্থা। আটজন শিক্ষার্থী, তিনটি ব্যাচ। 2021 ব্যাচে তিনজন — Arif, Bristy আর Chayon.
- শুধু batch দিয়ে সাজানো। ব্যাচগুলো ঠিক ক্রমে আসে, কিন্তু 2021-এর তিনজনের batch একই — তাঁদের নিজেদের মধ্যে কে আগে, SQL তা বলে না। আজ টেবিলের ক্রমে আসছে, কাল index বদলালে অন্য ক্রমে আসতে পারে।
- LIMIT 2 যোগ করলে বিপদ। “প্রথম দুজন” এখন অস্পষ্ট: Arif আর Bristy, অথবা Chayon আর Arif — দুটো ফলই নিয়মসম্মত। মেধাবৃত্তির তালিকা বা “শীর্ষ ৩ বিক্রেতা” এমন কোয়েরিতে বানালে ভিন্ন দিনে ভিন্ন নাম আসতে পারে।
- দ্বিতীয় key: cgpa DESC. এখন Arif (3.75) আর Bristy (3.40) — এই ডেটায় নিশ্চিত। কিন্তু নিশ্চয়তা ডেটার ওপর নির্ভর করছে: দুজনের cgpa কখনো সমান হলে সমস্যা ফিরে আসবে।
- শেষে tiebreaker: student_id. student_id primary key, তাই কোনো দুটি সারিতে সমান নয়। এখন ORDER BY পুরো সারির একটি সম্পূর্ণ ক্রম দেয় — যেকোনো ডেটায়, যেকোনো দিন একই ফল।
৭ · LIMIT, OFFSET আর pagination
ই-কমার্স সাইটে হাজার পণ্য একসাথে দেখানো হয় না; দেখানো হয় পাতায় পাতায় — একে বলে pagination. SQLite, MySQL আর PostgreSQL-এ LIMIT n বলে “সর্বোচ্চ n-টি সারি”, আর OFFSET k বলে “তার আগে প্রথম k-টি বাদ দাও”। প্রতি পাতায় n-টি হলে পাতা p-এর জন্য OFFSET = (p − ১) × n.
-- সবচেয়ে দামি ৩টি পণ্য
SELECT name, price FROM products ORDER BY price DESC LIMIT 3;
-- পাতা ২ (প্রতি পাতায় ৪টি): প্রথম ৪টি বাদ
SELECT product_id, name, price FROM products
ORDER BY price, product_id
LIMIT 4 OFFSET 4;
-- একই পাতা, keyset পদ্ধতিতে: পাতা ১-এর শেষ সারি ছিল (650, 104)
SELECT product_id, name, price FROM products
WHERE price > 650 OR (price = 650 AND product_id > 104)
ORDER BY price, product_id
LIMIT 4;প্রথম কোয়েরি: Mobile Phone 15000, Saree 4500, Smart Watch 3500. দ্বিতীয় আর তৃতীয়টি হুবহু একই ৪টি সারি দেয়: 102 Headphone 1200, 103 Power Bank 1800, 106 Panjabi 2200, 110 Smart Watch 3500. পাতা ১-এ ছিল সবচেয়ে সস্তা চারটি (260, 420, 550, 650). প্রথম পথ বলে “৪টি গুনে বাদ দাও”, দ্বিতীয় পথ বলে “(650, 104)-এর পরের গুলো দাও”। এই ছোট টেবিলে দুটোর খরচ সমান, কিন্তু লাখ সারিতে পার্থক্য বিশাল। ORDER BY-তে product_id tiebreaker হিসেবে আছে, যাতে দুটি পণ্যের দাম কখনো সমান হলেও পাতার সীমানা পরিষ্কার থাকে।
| পদ্ধতি | |
| পড়া সারি | |
| পড়ে ফেলে দেওয়া | |
| ফেরত |
- সাজানো তালিকা। ধরুন
(price, product_id)-এর ওপর একটি index আছে — তাহলে পণ্যগুলো index-এ আগে থেকেই এই ক্রমে সাজানো (পর্ব ৪০)। index থাকুক বা না থাকুক, ক্রমটি এমনই: ১০টি পণ্য, 260 থেকে 15000. - পাতা ১: OFFSET 0. শুরু থেকে ৪টি পড়ে ৪টিই ফেরত। কোনো অপচয় নেই।
- পাতা ২: OFFSET 4. DBMS জানে না পঞ্চম সারি কোথায় — তাকে প্রথম ৪টি এক এক করে পড়ে গুনে বাদ দিতে হয়, তারপর পরের ৪টি ফেরত। মোট ৮টি পড়া, ৪টি অপচয়।
- পাতা ৩: OFFSET 8. ১০টি পড়া, ৮টি ফেলে দেওয়া, ফেরত মাত্র ২টি (Saree, Mobile Phone). সাধারণভাবে পাতা p পেতে প্রায় p × n সারি পড়তে হয়; প্রতি পাতায় ২০টি করে ৫০,০০০তম পাতায় প্রায় ১০ লাখ সারি — শুধু ২০টি দেখাতে।
- Keyset: শেষ দেখা মানের পর থেকে। পাতা ১-এর শেষ সারি (650, 104) মনে রেখে বলি “এর চেয়ে বড়গুলো দাও”। index-এ সরাসরি সেই জায়গায় খোঁজা যায় — B+ tree-তে কয়েকটি page পড়লেই হয় — তারপর ঠিক ৪টি পড়া। কোনো সারি ফেলে দিতে হয় না; পাতা যত দূরেই হোক, খরচ প্রায় একই।
- মাঝখানে নতুন সারি এলে। ব্যবহারকারী পাতা ১ দেখার পর 300 টাকার Lentil 1kg যোগ হলো। এখন OFFSET 4 দেয় 104, 102, 103, 106 — DSA Book আবার দেখা গেল, আর Smart Watch পাতা ৩-এ সরে গেল। Keyset আগের মতোই 102, 103, 106, 110 দেয়, কারণ সে গোনে না, মান দিয়ে খোঁজে।
খরচের হিসাবটি নিখুঁত করে বলি। N-টি সারি, প্রতি পাতায় n-টি, আর (price, product_id)-এ B+ tree index আছে। OFFSET k দিয়ে পাতা আনতে index-এর শুরু থেকে k + n-টি entry পার হতে হয় — খরচ Θ(k + n), কারণ সাধারণ B+ tree জানে না কোন entry ঠিক কততম; গুনতে হলে হাঁটতে হয়। Keyset-এ প্রথমে গাছের মূল থেকে পাতা পর্যন্ত একবার নামা — গাছের উচ্চতা h ≈ logF N (F = প্রতিটি node-এ কয়টি শাখা, সাধারণত কয়েকশো), তাই বাস্তবে ৩–৪টি page — তারপর n-টি entry: খরচ O(log N + n). Index না থাকলে দুই পদ্ধতিতেই সব সারি পড়ে সাজাতে হয়, তাই keyset-এর সুবিধা পেতে index লাগবেই। Keyset-এর সীমাবদ্ধতা: “সরাসরি ৫৭ নম্বর পাতায় যাও” করা যায় না, আর sort key-কে অনন্য হতে হয়।
পুরো তালিকা পাতায় পাতায় পড়লে পার্থক্য আরও স্পষ্ট হয় — যেমন একটি রাতের ব্যাচ-জব, যা সব পণ্য ২০টি করে প্রসেস করে। মোট N = P × n সারি, P-টি পাতা। OFFSET-এ পাতা p-এর খরচ প্রায় p × n, তাই মোট n × (১ + ২ + … + P) = n × P(P + ১)/২ ≈ N²/(২n). Keyset-এ প্রতিটি পাতা log N + n, মোট প্রায় N + P log N. দশ লাখ সারি আর n = ২০ হলে OFFSET-এ মোট প্রায় ২,৫০০ কোটি entry পার হতে হয়, keyset-এ ১০ লাখের সামান্য বেশি। প্রথমটি বর্গের হারে বাড়ে, দ্বিতীয়টি প্রায় সরলরেখায়।
LIMIT সব ডেটাবেসে একই রকম লেখা হয় না। SQL:2008 স্ট্যান্ডার্ডের রূপ FETCH FIRST; এগুলো এই পাতায় চলবে না, শুধু চিনে রাখুন:
-- সবচেয়ে দামি ৩টি
SELECT name, price FROM products
ORDER BY price DESC
FETCH FIRST 3 ROWS ONLY;
-- পাতা ২ (প্রতি পাতায় ৪টি)
SELECT product_id, name, price FROM products
ORDER BY price, product_id
OFFSET 4 ROWS FETCH NEXT 4 ROWS ONLY;-- পুরোনো রীতি: TOP
SELECT TOP 3 name, price FROM products
ORDER BY price DESC;
-- পাতা ২: SQL Server 2012 থেকে OFFSET ... FETCH (ORDER BY বাধ্যতামূলক)
SELECT product_id, name, price FROM products
ORDER BY price, product_id
OFFSET 4 ROWS FETCH NEXT 4 ROWS ONLY;-- LIMIT n OFFSET k চলে; পুরোনো সংক্ষিপ্ত রূপ LIMIT k, n
SELECT product_id, name, price FROM products
ORDER BY price, product_id
LIMIT 4, 4; -- আগে offset, পরে সংখ্যা!MySQL-এর LIMIT 4, 4-এ প্রথম সংখ্যাটি offset — উল্টো পড়ে ভুল করা খুব সাধারণ। স্ট্যান্ডার্ডে একটি সুন্দর বাড়তি সুবিধাও আছে: FETCH FIRST 3 ROWS WITH TIES — তৃতীয় সারির সমান মানের সব সারিও ফলে রাখে (PostgreSQL ১৩ থেকে, Oracle 12c থেকে; SQL Server-এ TOP 3 WITH TIES).
Index না থাকলেও “LIMIT n” বুদ্ধিমান DBMS-এ পুরো sort এড়ায়। PostgreSQL-এর EXPLAIN ANALYZE-এ তখন দেখা যায় Sort Method: top-N heapsort — সব N সারি একবার পড়া হয়, কিন্তু মেমরিতে রাখা হয় শুধু k + n আকারের একটি heap, খরচ O(N log(k + n)), পুরো sort-এর O(N log N) নয়। MySQL-ও LIMIT-যুক্ত filesort-এ একই রকম priority queue ব্যবহার করতে পারে। কিন্তু এতে শুধু sort-এর খরচ কমে, N সারি পড়ার খরচ নয় — বড় টেবিলে আসল সমাধান ORDER BY-এর সঙ্গে মেলে এমন index আর keyset (পর্ব ৪১, ৪২)।
৮ · লজিক্যাল প্রসেসিং ক্রম — SQL আসলে কোন ক্রমে ভাবে
আমরা লিখি SELECT দিয়ে শুরু করে। কিন্তু অর্থের দিক থেকে কোয়েরিটি চলে অন্য ক্রমে। একে বলে logical processing order:
প্রতিটি ধাপ আগের ধাপের ফল-টেবিল হাতে পায়, আর নিজের ফল দেয় পরের ধাপকে। একটি বাস্তব কোয়েরিতে প্রতিটি ধাপের পর কয়টি সারি থাকে, দেখুন — প্রশ্নটি: “২০০০ টাকার কম দামের পণ্য আছে এমন category, বড় নম্বর থেকে, প্রথম দুটি।”
| clause | |
| সারি | |
| alias cat চেনা? |
- FROM. প্রথমে ঠিক হয় কোন টেবিল থেকে কাজ —
products-এর ১০টি সারি, সব কলামসহ। পর্ব ১৯-এ এখানেই JOIN হবে। - WHERE. প্রতিটি সারিতে
price < 2000যাচাই। টিকে থাকে ৬টি: 102 (1200), 103 (1800), 104 (650), 105 (550), 108 (420), 109 (260). এই মুহূর্তেcatনামে কিছু নেই — SELECT এখনো চলেনি। - SELECT. এখন প্রতিটি সারি থেকে শুধু
category_idনেওয়া হয়, আর তার নাম হয়cat. ৬টি মান: 1, 1, 2, 2, 4, 4. Alias-এর জন্ম ঠিক এই ধাপে। - DISTINCT. SELECT-এর ফল থেকে duplicate বাদ: 1, 2, 4 — ৩টি সারি। DISTINCT SELECT-এর পরে বলেই এটি SELECT-এর কলামগুলোর ওপর কাজ করে, মূল টেবিলের ওপর নয়।
- ORDER BY.
cat DESC: 4, 2, 1. ORDER BY SELECT-এর পরে চলে, তাই aliascatসে চেনে। - LIMIT. প্রথম দুটি রাখা হয়: 4 (Grocery) আর 2 (Books). LIMIT সবার শেষে — তাই আগে সাজানো, তারপর কাটা। উল্টো হলে “সবচেয়ে বড় দুটি” পাওয়া যেত না।
এই ক্রম থেকেই একটি পরিচিত নিয়মের ব্যাখ্যা মেলে: SELECT-এর alias ORDER BY-তে চলে, কিন্তু WHERE-এ চলে না — কারণ WHERE চলার সময় alias জন্মায়ইনি। এখানে SQLite একটু ব্যতিক্রম; নিজে চালিয়ে দেখুন:
-- ১) ডায়াগ্রামের কোয়েরি
SELECT DISTINCT category_id AS cat FROM products
WHERE price < 2000 ORDER BY cat DESC LIMIT 2;
-- ২) ORDER BY-তে alias: সব ডেটাবেসে চলে
SELECT name, price * stock AS stock_value FROM products
ORDER BY stock_value DESC LIMIT 3;
-- ৩) WHERE-এ alias: SQLite চালায়, স্ট্যান্ডার্ড SQL-এ error
SELECT name, price * stock AS stock_value FROM products
WHERE stock_value > 40000;
-- ৪) একই SELECT-এ এক alias দিয়ে আরেকটি হিসাব: সবখানে error
SELECT name, price * stock AS stock_value,
stock_value / 1000 AS thousands FROM products;প্রথমটি দেয় 4 আর 2 — ঠিক ডায়াগ্রামের শেষ ধাপ। দ্বিতীয়টি দেয় Mobile Phone 180000, Headphone 48000, Rice 5kg 42000. তৃতীয়টিও এই পাতায় সেই ৩টি সারি দেয়! SQLite নিজস্ব সুবিধা হিসেবে WHERE-এ alias খুঁজে নেয়। কিন্তু PostgreSQL, MySQL, SQL Server বা Oracle-এ একই কোয়েরি error দেয় (নিচে দেখুন)। চতুর্থটিতে SQLite-ও থামে, error দেয়: no such column: stock_value. একই SELECT তালিকার সব expression যুক্তির দিক থেকে একসাথে হিসাব হয়, তাই একটি আরেকটির alias দেখতে পায় না। সমাধান: হিসাবটি আবার লিখুন (price * stock / 1000), অথবা subquery বা CTE ব্যবহার করুন (পর্ব ২১, ২৫)।
SELECT name, price * stock AS stock_value FROM products
WHERE stock_value > 40000;
-- ERROR: column "stock_value" does not exist
-- ঠিক উপায়: WHERE-এ হিসাবটি নিজেই লিখুন
SELECT name, price * stock AS stock_value FROM products
WHERE price * stock > 40000;SQLite-এর সুবিধাটিতে ভরসা করবেন না। এটি শুধু তখনই কাজ করে, যখন alias-এর নামে কোনো আসল কলাম নেই। Alias আর কলামের নাম এক হলে WHERE আসল কলামটিই নেয়। আর MySQL-এ error আসে এমন: Unknown column 'stock_value' in 'where clause'. যে কোয়েরি অন্য ডেটাবেসে যেতে পারে, তাতে WHERE-এ alias লিখবেন না।
মনে রাখবেন, এটি লজিক্যাল ক্রম — ফলটি কী হবে তার সংজ্ঞা। ভেতরে query optimizer ফল না বদলে কাজের ক্রম বদলাতে পারে — যেমন ORDER BY-এর সাথে মেলে এমন index থাকলে আলাদা sort না করে সরাসরি সাজানো ক্রমে পড়া, আর LIMIT পূর্ণ হলেই থেমে যাওয়া (পর্ব ৪৩)।
পরীক্ষায় একটি নামের বিভ্রান্তি প্রায়ই ধরা হয়: SQL-এর SELECT আর relational algebra-র “select” (σ) এক জিনিস নয়। SQL-এর WHERE হলো σ (selection — সারি বাছাই), আর SQL-এর SELECT তালিকা হলো π (projection — কলাম বাছাই)। তাই SELECT DISTINCT name, price FROM products WHERE price < 1000 = πname, price(σprice < 1000(products)). DISTINCT লাগল, কারণ algebra-র π একটি set দেয়, আর SQL ডিফল্টে bag দেয় (পর্ব ৩২)।
কেন σ আগে, π পরে? ধরুন শর্ত c-তে যেসব attribute আছে তাদের সেট attr(c), আর projection-এর তালিকা L. যদি attr(c) ⊆ L হয়, তবে πL(σc(R)) = σc(πL(R)) — কারণ প্রতিটি tuple t-এর জন্য c-এর মান শুধু L-এর attribute-গুলোর ওপর নির্ভর করে, আর π সেগুলো অক্ষত রাখে; তাই আগে-পরে যেকোনো ক্রমে একই tuple-গুলো টিকে থাকে। কিন্তু attr(c) ⊄ L হলে ডানদিকটির অর্থই থাকে না: π-এর পর c-এর দরকারি কলাম আর নেই। যেমন SELECT name FROM products WHERE price < 1000-এ শর্তের price ফলে নেই। SQL তাই সংজ্ঞা দেয় সাধারণ রূপটি দিয়ে — আগে WHERE (পুরো সারি হাতে থাকতে), পরে SELECT. ∎
একটি SELECT লেখার অভ্যাস — লজিক্যাল ক্রম ধরে
- প্রশ্নটি এক বাক্যে লিখুন — “২০০০ টাকার কম দামের পণ্য আছে এমন category, বড় নম্বর থেকে, প্রথম দুটি।”
- FROM: উত্তর কোন টেবিলে আছে? WHERE: কোন সারিগুলো লাগবে? এখানে আগে
SELECT COUNT(*)চালিয়ে দেখুন কয়টি সারি টিকছে। - SELECT: কোন কলাম আর হিসাব দেখাবেন, কী নামে? duplicate আসতে পারে কি না ভেবে DISTINCT ঠিক করুন।
- ORDER BY: কোন ক্রমে, আর শেষে অনন্য tiebreaker আছে কি না। LIMIT: সবশেষে।
- যাচাই: নমুনা ডেটায় হাতে দু-একটি সারি মিলিয়ে নিন — বিশেষ করে NULL আর সীমানার মান (ঠিক ২০০০ টাকার পণ্য কি থাকবে?
<না<=?)।
- SELECT কোয়েরির লজিক্যাল প্রসেসিং ক্রম লিখুন। FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT/OFFSET.
- WHERE-এ alias কেন চলে না, অথচ ORDER BY-তে চলে? WHERE চলে SELECT-এর আগে, ORDER BY পরে; alias তৈরি হয় SELECT ধাপে।
a OR b AND c-এর অর্থ?a OR (b AND c), কারণ AND-এর অগ্রাধিকার বেশি।- ORDER BY ছাড়া
LIMIT 10কোন ১০টি দেয়? নিশ্চিত নয় — DBMS-এর সুবিধামতো যেকোনো ১০টি। - বড় টেবিলে OFFSET pagination ধীর কেন, বিকল্প কী? k-টি সারি পড়ে ফেলে দিতে হয়; বিকল্প keyset (
WHERE key > শেষ_মান ORDER BY key LIMIT n), index-সহ।
৯ · AI-এর যুগে SELECT
“হাজার টাকার কম দামের বই বা ইলেকট্রনিক্স দেখাও” — AI এক সেকেন্ডে কোয়েরি লিখে দেবে, আর সেটা চলবেও। কিন্তু এই পর্বের প্রতিটি ফাঁদ — বন্ধনী, NULL-এর ক্রম, tiebreaker, লেখার বড়-ছোট অক্ষর, ডেটাবেস ভেদে আলাদা আচরণ — ঠিক সেই জায়গা, যেখানে AI-এর কোয়েরি চলে কিন্তু ভুল ফল দেয়।
| AI-এর ভুল | কী ঘটে | কীভাবে ধরবেন |
|---|---|---|
| AND-OR মেশানো, বন্ধনী নেই | বাড়তি সারি, কোনো error নেই | প্রতিটি AND-OR মিশ্রণে বন্ধনী খুঁজুন; ফলের প্রতিটি সারি শর্তে মেলে কি না দেখুন |
| ORDER BY ছাড়া LIMIT, বা tiebreaker ছাড়া pagination | প্রতিবার ভিন্ন “প্রথম ১০”, পাতায় duplicate | ORDER BY-এর শেষে অনন্য কলাম আছে কি না |
অন্য ডেটাবেসের রীতি (TOP, ILIKE, MySQL-এর LIMIT k, n) | syntax error, বা উল্টো offset | প্রম্পটে ডেটাবেসের নাম আর সংস্করণ বলুন |
| WHERE-এ alias (SQLite-এ চলে বলে) | PostgreSQL/MySQL-এ নিলে error | alias শুধু ORDER BY-তে |
বানানো কলামের নাম (product_name, created_at) | “no such column”, বা ভুল কলাম | schema দিয়ে প্রশ্ন করুন, নাম মিলিয়ে নিন |
AI-কে schema, ডেটাবেসের নাম আর ডেটার বিশেষ অবস্থা আগে থেকেই বলে দিন:
AI-এর লেখা SELECT হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১SQL-এ SELECT আগে লেখা হয়, অথচ যুক্তির দিক থেকে FROM আগে চলে। ভাষাটি এভাবে বানানো হলো কেন, আর এর দাম কী?
১৯৭৪ সালে IBM-এর Donald Chamberlin আর Raymond Boyce যখন SEQUEL (পরে SQL) প্রস্তাব করেন, তাঁদের লক্ষ্য ছিল এমন একটি ভাষা, যা প্রোগ্রামার নন এমন মানুষও পড়তে পারবেন — হিসাবরক্ষক, ব্যবস্থাপক। SEQUEL নামটিই ছিল “Structured English Query Language”-এর সংক্ষেপ। ইংরেজিতে আমরা বলি “Get the names of products from the catalogue where price is below 1000” — আগে কী চাই, পরে কোথা থেকে। তাই SELECT আগে, FROM পরে। পড়তে সহজ, তবে যুক্তির ক্রমের সঙ্গে মেলে না।
এর দাম আমরা আজও দিই:
- শেখার বিভ্রান্তি: “WHERE-এ alias কেন চলে না” — এই প্রশ্ন প্রায় প্রত্যেক নতুন শিক্ষার্থীর মনে আসে, কারণ লেখার ক্রমে SELECT আগে।
- টুলের অসুবিধা: এডিটর SELECT-এর পর কলামের নাম সাজেস্ট করতে পারে না, কারণ টেবিলের নাম তখনো লেখা হয়নি।
পরের ভাষাগুলো এই শিক্ষা নিয়েছে। Microsoft ২০০৭ সালে C#-এর LINQ-এ লিখল from p in products where ... select p.name — FROM আগে, যাতে এডিটর সাজেস্ট করতে পারে। DuckDB-তে FROM products SELECT name লেখা যায়। Google-এর গবেষকেরা ২০২৪ সালে একটি প্রবন্ধে SQL-এ “pipe syntax” প্রস্তাব করেন, যেখানে ধাপগুলো ঠিক লজিক্যাল ক্রমে |> দিয়ে জোড়া হয়। তবু ক্লাসিক SQL বদলায়নি — পঞ্চাশ বছরের কোড, বই আর অভ্যাস ভাঙা যায় না।
ব্যবহারিক কৌশল: কঠিন কোয়েরি লেখার সময় মাথায় FROM থেকে শুরু করুন — কোন টেবিল, কোন সারি, তারপর কোন কলাম, তারপর ক্রম।
মূল উপলব্ধি: SQL-এর লেখার ক্রম পাঠকের সুবিধার জন্য, আর লজিক্যাল ক্রম অর্থের সংজ্ঞা। দুটো আলাদা জানলে SQL-এর বহু “অদ্ভুত” নিয়ম যৌক্তিক মনে হয়।
প্র ০২ORDER BY ছাড়া সারির ক্রম “প্রায় সবসময়” একই আসে। তাহলে কেন বলা হয় এর ওপর ভরসা করা যাবে না?
ছোট টেবিলে, একজন ব্যবহারকারী, একই সংস্করণ — এই অবস্থায় ক্রম সাধারণত ঢোকানোর ক্রমের মতোই আসে, কারণ DBMS টেবিলটি শুরু থেকে শেষ পর্যন্ত পড়ে। কিন্তু SQL স্ট্যান্ডার্ড স্পষ্ট বলে, ORDER BY না থাকলে ক্রম implementation-dependent — DBMS যা সুবিধা, তাই করবে। “প্রায় সবসময়” বাস্তবে কয়েকভাবে ভাঙে:
- নতুন index: optimizer নতুন index দিয়ে পড়লে সারি আসে index-এর ক্রমে, টেবিলের ক্রমে নয়।
- Parallel query: PostgreSQL বড় টেবিল কয়েকটি worker দিয়ে একসাথে পড়তে পারে; কোন worker আগে শেষ করে, তার ওপর ক্রম নির্ভর করে।
- Synchronized scan: PostgreSQL-এ
synchronize_seqscansসেটিং (ডিফল্টে চালু) থাকলে দুটি কোয়েরি একই বড় টেবিল পড়লে দ্বিতীয়টি প্রথমটির মাঝখান থেকে পড়া শুরু করতে পারে — ফলে প্রথম সারিটিই বদলে যায়। - UPDATE আর VACUUM: PostgreSQL-এ UPDATE আসলে নতুন সংস্করণের সারি লেখে, প্রায়ই অন্য জায়গায়; তাই আপডেট হওয়া সারি পরের scan-এ অন্য অবস্থানে আসে।
একটি বিখ্যাত বাস্তব উদাহরণ MySQL-এর। ৫.x সংস্করণে GROUP BY নিজে থেকেই ফল সাজিয়ে দিত, আর বহু অ্যাপ নিজের অজান্তে এর ওপর নির্ভর করত। MySQL 8.0-তে এই অঘোষিত sort বাদ দেওয়া হয়; যেসব কোয়েরিতে ORDER BY ছিল না, সেগুলোর ক্রম রাতারাতি বদলে যায়। যাঁরা ORDER BY লিখেছিলেন, তাঁদের কিছুই হয়নি।
LIMIT-এর সাথে বিপদ আরও বড়: “সর্বশেষ ১০টি অর্ডার” দেখাতে ORDER BY না দিলে যেকোনো ১০টি আসতে পারে — কোনো error ছাড়াই।
মূল উপলব্ধি: ORDER BY ছাড়া ক্রম একটি দুর্ঘটনা, প্রতিশ্রুতি নয়। যে ফলের ক্রম অর্থবহ, সেখানে ORDER BY আর একটি অনন্য tiebreaker লিখুন — আজ না ভাঙলেও আপগ্রেডের দিন ভাঙবে।
প্র ০৩Keyset pagination যদি এত দ্রুত হয়, তাহলে প্রায় সব সাইট এখনো “পাতা ১ ২ ৩ … ৫০” কেন দেখায়?
কারণ দুই পদ্ধতি আলাদা ব্যবহারকারী-অভিজ্ঞতা দেয়, আর প্রতিটির নিজস্ব দাম আছে।
OFFSET-এর সুবিধা: যেকোনো পাতায় সরাসরি যাওয়া যায় (“পাতা ৩৭”), লেখা সহজ, ORM আর ফ্রেমওয়ার্কে তৈরি থাকে। ছোট টেবিলে, বা মানুষ সাধারণত প্রথম কয়েকটি পাতাই দেখেন এমন ক্ষেত্রে, OFFSET-এর অপচয় নগণ্য। একটি অনলাইন দোকানে কয়েক হাজার পণ্য আর মানুষ পাতা ২–৩-এর বেশি যান না — OFFSET যথেষ্ট।
OFFSET-এর দাম: পাতা যত গভীর, খরচ তত বেশি — এই পর্বের হিসাবে Θ(k + n). তার চেয়েও সূক্ষ্ম সমস্যা হলো সঠিকতা: দুই পাতার মাঝে সারি যোগ বা মুছলে সারি বাদ পড়ে বা দুবার আসে। নিউজ ফিড, লেনদেনের ইতিহাস, লগ — যেখানে প্রতি সেকেন্ডে নতুন সারি আসে — সেখানে এটি বাস্তব সমস্যা।
Keyset-এর দাম: “পাতা ৩৭”-এ সরাসরি যাওয়া যায় না, শুধু “পরের” আর “আগের”। Sort key অনন্য হতে হয় (তাই (price, product_id)), আর একাধিক কলাম, ভিন্ন দিক (একটি ASC, একটি DESC) বা NULL থাকলে শর্ত লেখা জটিল হয়। মোট পাতার সংখ্যা জানাতে আলাদা COUNT(*) লাগে, যা নিজেও দামি।
এই কারণেই অনেক বড় প্ল্যাটফর্মের API “cursor” দেয় — আগের উত্তরের সঙ্গে একটি টোকেন আসে, যার ভেতরে আসলে শেষ সারির key লুকানো থাকে। মোবাইল অ্যাপের “নিচে স্ক্রল করলে আরও আসে” (infinite scroll) প্রায় পুরোপুরি keyset-এর জন্য উপযুক্ত, কারণ সেখানে পাতার নম্বরই নেই। ডেটাবেস-পারফরম্যান্স নিয়ে লেখক Markus Winand বহু বছর ধরে “no offset” প্রচারণা চালিয়ে এই পদ্ধতিকে জনপ্রিয় করেছেন।
মূল উপলব্ধি: Pagination একটি নকশার সিদ্ধান্ত, শুধু SQL নয়। ব্যবহারকারী কী চান (নির্দিষ্ট পাতা, নাকি অবিরাম স্ক্রল), ডেটা কত দ্রুত বদলায়, আর টেবিল কত বড় — এই তিন প্রশ্নের উত্তরে পদ্ধতি বাছুন।
প্র ০৪বাংলা লেখা তুলনা আর সাজানোয় কী কী সমস্যা হতে পারে? collation এখানে কী ভূমিকা রাখে?
ইংরেজিতে প্রধান প্রশ্ন বড়-ছোট হাতের অক্ষর। বাংলায় বড়-ছোট অক্ষর নেই, কিন্তু সমস্যা অন্য জায়গায় — আর NID, জমির রেকর্ড বা শিক্ষাবোর্ডের মতো বড় ডেটাবেসে নাম খোঁজার সময় এগুলো সত্যিকারের ঝামেলা তৈরি করতে পারে।
- একই অক্ষর, দুই রকম এনকোডিং: Unicode-এ “য়” একটি আলাদা অক্ষর (U+09DF) হিসেবেও আছে, আবার “য” + নুক্তা (U+09AF U+09BC) হিসেবেও লেখা যায়। পর্দায় দুটো হুবহু এক, কিন্তু বাইট আলাদা — তাই BINARY তুলনায়
=মিথ্যা দেয়। “ড়”, “ঢ়”-এর ক্ষেত্রেও একই কথা। সমাধান: ডেটা ঢোকানোর আগে সব লেখা একটি নির্দিষ্ট Unicode normalization রূপে (সাধারণত NFC) আনা। - সাজানোর ক্রম: BINARY collation অক্ষর সাজায় Unicode code point দিয়ে। বাংলা ব্লকে বেশিরভাগ অক্ষর বর্ণমালার ক্রমেই আছে, কিন্তু “ড়, ঢ়, য়” পরে যোগ হওয়ায় ব্লকের শেষ দিকে বসে — ফলে binary সাজানোয় এরা অভিধানের জায়গায় আসে না।
- ভাষাভিত্তিক collation: PostgreSQL ১০ থেকে ICU লাইব্রেরির collation ব্যবহার করা যায়, যা বিভিন্ন ভাষার সাজানোর নিয়ম জানে; MySQL 8-এর
utf8mb4_0900_*collation-গুলোও Unicode Collation Algorithm-ভিত্তিক। SQLite ডিফল্টে শুধু BINARY, NOCASE আর RTRIM দেয়; NOCASE-ও শুধু ইংরেজি অক্ষরের বড়-ছোট মেলায়।
ইংরেজির ক্ষেত্রেও collation নিরীহ নয়। MySQL-এর ডিফল্ট _ai_ci collation শুধু case নয়, accent-ও মানে না — “resume” আর “résumé” সমান। আবার UNIQUE constraint-ও collation মেনে চলে: case-insensitive collation-এ 'Rahim' আর 'rahim' একসাথে রাখা যাবে না।
মূল উপলব্ধি: লেখার তুলনা “অক্ষর মেলানো” মনে হলেও আসলে একটি নীতিগত সিদ্ধান্ত — কোন দুটি লেখাকে “একই” ধরা হবে। এই সিদ্ধান্ত collation-এ লেখা থাকে, আর ডেটাবেস বদলালে সেটি নিঃশব্দে বদলে যেতে পারে।
প্র ০৫তারিখকে 'YYYY-MM-DD' লেখা হিসেবে রাখলে তুলনা ঠিক হয় কেন? এই রূপটি কোথা থেকে এল, আর লেখা হিসেবে তারিখ রাখার ঝুঁকি কী?
রূপটি এসেছে আন্তর্জাতিক মান ISO 8601 থেকে, যা প্রথম প্রকাশিত হয় ১৯৮৮ সালে। এর মূল নীতি “big-endian” — বড় একক আগে: বছর, তারপর মাস, তারপর দিন, তারপর ঘণ্টা, মিনিট। প্রতিটি অংশের দৈর্ঘ্য নির্দিষ্ট, শূন্য দিয়ে ভরা।
কেন তুলনা ঠিক হয় — একটি ছোট প্রমাণ: দুটি তারিখ x আর y নিন, দুটোই একই দৈর্ঘ্যের YYYY-MM-DD রূপে। লেখার তুলনা বাঁ থেকে প্রথম যে অবস্থানে অক্ষর আলাদা, সেখানে থামে। যদি বছর আলাদা হয়, প্রথম পার্থক্য বছরের চার অঙ্কের মধ্যেই আসে; সমান দৈর্ঘ্যের অঙ্কের লেখায় অভিধানের ক্রম আর সংখ্যার ক্রম এক, তাই ছোট বছর আগে। বছর সমান হলে একই যুক্তি মাসে, তারপর দিনে খাটে। আর হাইফেনগুলো সবসময় একই অবস্থানে, তাই তুলনায় কোনো প্রভাব ফেলে না। অতএব লেখার ক্রম = সময়ের ক্রম। ∎ শর্ত দুটি — নির্দিষ্ট দৈর্ঘ্য আর বড় একক আগে — না মানলেই প্রমাণ ভাঙে: '2025-9-5' বনাম '2025-10-01'-এ ষষ্ঠ অক্ষরে '9' > '1', তাই সেপ্টেম্বর অক্টোবরের “পরে” চলে যায়।
দুই অঙ্কের বছরের শিক্ষা: ১৯৯০-এর দশকের শেষে বিশ্বজুড়ে Y2K সমস্যা নিয়ে বিপুল খরচ হয়েছিল, কারণ পুরোনো সিস্টেম বছর রাখত দুই অঙ্কে — '99'-এর পর '00' এলে তা “ছোট” হয়ে যায়। চার অঙ্কের বছর এই ধরনের ভুল এড়ায়।
ঝুঁকি: TEXT কলাম যেকোনো লেখা নেয় — কেউ '31/12/2025' বা '2025-02-30' ঢোকালে ডেটাবেস আটকায় না। প্রতিকার: CHECK (joined_on = date(joined_on))-এর মতো নিয়ম (SQLite-এ date() ভুল রূপের লেখায় NULL দেয়, আর '2025-02-30'-কে বদলে '2025-03-02' বানায় — দুই ক্ষেত্রেই সমতা মিথ্যা হয়, তাই CHECK আটকায়), বা যেখানে সম্ভব আসল DATE টাইপ। সময়সহ রাখলে টাইমজোনও একটি — সাধারণত UTC-তে রাখা হয়।
মূল উপলব্ধি: ISO 8601 একটি চতুর নকশা — লেখার সাধারণ তুলনাকেই সময়ের তুলনা বানিয়ে দেয়। কিন্তু সেই সুবিধা শুধু তখনই, যখন প্রতিটি সারি নিয়ম মেনে লেখা।
অনুশীলন
ওপরের যেকোনো db-shop এডিটরে কোড বদলে নিজের উত্তর যাচাই করতে পারবেন।
-
কোয়েরি লিখুন: db-shop-এর
customersথেকে ঢাকা বা চট্টগ্রামের (Chattogram) যেসব গ্রাহক ২০২৫ সালের ১ জুলাইয়ের আগে যোগ দিয়েছেন, তাঁদের নাম, শহর আর যোগ দেওয়ার তারিখ — নামের বর্ণানুক্রমে। কয়টি সারি আসবে?SELECT name, city, joined_on FROM customers WHERE (city = 'Dhaka' OR city = 'Chattogram') AND joined_on < '2025-07-01' ORDER BY name;৩টি সারি: Karima Begum (Chattogram, 2025-02-03), Rahim Uddin (Dhaka, 2025-01-15), Sabbir Hasan (Dhaka, 2025-03-21). Mitu Rani ঢাকার, কিন্তু যোগ দিয়েছেন 2025-08-12 — বাদ। বন্ধনী ছাড়া লিখলে অর্থ হতো “ঢাকার সবাই, অথবা ১ জুলাইয়ের আগের চট্টগ্রামের গ্রাহক” — তখন Mitu Rani-ও আসতেন, মোট ৪ জন।
-
হাতে ট্রেস করুন:
SELECT DISTINCT city FROM customers WHERE joined_on >= '2025-03-01' ORDER BY city LIMIT 3;— লজিক্যাল ক্রম ধরে প্রতিটি ধাপের পর কী থাকে লিখুন। (গ্রাহক: Rahim Dhaka 2025-01-15, Karima Chattogram 2025-02-03, Sabbir Dhaka 2025-03-21, Nusrat Rajshahi 2025-04-10, Tanvir Khulna 2025-05-05, Farhana Sylhet 2025-06-18, Imran NULL 2025-07-01, Mitu Dhaka 2025-08-12.) SQLite আর PostgreSQL-এ ফল কি একই হবে?FROM: ৮ জন। WHERE: ১ মার্চ বা পরে — Sabbir, Nusrat, Tanvir, Farhana, Imran, Mitu: ৬ জন। SELECT: Dhaka, Rajshahi, Khulna, Sylhet, NULL, Dhaka. DISTINCT: Dhaka, Rajshahi, Khulna, Sylhet, NULL — ৫টি। ORDER BY city: SQLite-এ NULL সবচেয়ে ছোট, তাই NULL, Dhaka, Khulna, Rajshahi, Sylhet. LIMIT 3: NULL, Dhaka, Khulna. PostgreSQL-এ ASC-এ NULL শেষে যায়, তাই ফল Dhaka, Khulna, Rajshahi — একই কোয়েরি, ভিন্ন ফল! দুই জায়গায় একই ফল চাইলে লিখুন
ORDER BY city NULLS LAST, অথবাWHERE city IS NOT NULLযোগ করুন। -
ফল অনুমান করুন: ডেলিভারি চার্জ ১০০ টাকা যোগ করে দাম দেখাতে কেউ লিখলেন
SELECT name, price + 100 AS price FROM products WHERE price > 600 ORDER BY price;db-shop-এ (দাম: 260, 420, 550, 650, 1200, 1800, 2200, 3500, 4500, 15000) SQLite কয়টি সারি দেবে? SQL Book (550 + 100 = 650) কি আসবে?৭টি সারি, প্রথমটি DSA Book 750, শেষটি Mobile Phone 15100. SQL Book আসবে না। WHERE চলে SELECT-এর আগে, তাই WHERE-এর
priceহলো টেবিলের আসল কলাম (550), alias নয়; 550 > 600 মিথ্যা। SQLite alias খোঁজে শুধু তখন, যখন ওই নামে আসল কলাম নেই — এখানে আছে। অন্যদিকে ORDER BY-এরpriceনিয়েছে SELECT-এর alias (750, 1300…)। এক কোয়েরিতে একই নাম দুই অর্থে — এজন্যই alias-এর নাম আসল কলামের নাম থেকে আলাদা রাখুন, যেমনprice_with_delivery. -
ভুল খুঁজুন: একজন লিখলেন
SELECT name FROM customers WHERE joined_on < 2025-03-01;— উদ্দেশ্য ১ মার্চের আগে যোগ দেওয়া গ্রাহক (উত্তর হওয়ার কথা Rahim Uddin আর Karima Begum). কিন্তু ফল এল “(কোনো সারি নেই)”। কেন? কোনো error এল না কেন?কোট নেই, তাই
2025-03-01কোনো তারিখ নয় — এটি একটি অঙ্ক: 2025 − 3 − 1 = 2021. শর্তটি হয়ে গেলjoined_on < 2021.joined_onএকটি TEXT কলাম, তাই SQLite সংখ্যাটিকে লেখা '2021'-এ রূপান্তর করে তুলনা করে: '2025-01-15' < '2021'? প্রথম তিনটি অক্ষর মেলে, চতুর্থটিতে '5' > '1' — তাই মিথ্যা। সব গ্রাহক ২০২৫ সালের, তাই কেউ আসে না। বৈধ SQL বলে error নেই — সবচেয়ে বিপজ্জনক ধরনের ভুল। ঠিক রূপ:WHERE joined_on < '2025-03-01'.
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি SELECT কোয়েরির ধাপগুলো লজিক্যাল প্রসেসিং-এর ক্রমে সাজান (▲▼ চেপে সরান)
- FROM — কোন টেবিল থেকে
- WHERE — কোন সারি থাকবে
- SELECT — কোন কলাম আর হিসাব, alias-এর জন্ম
- DISTINCT — duplicate বাদ
- ORDER BY — সাজানো
- LIMIT / OFFSET — কয়টি সারি
SQL-এ WHERE a = 1 OR b = 2 AND c = 3 শর্তটি কীভাবে পড়া হয়?
- (a = 1 OR b = 2) AND c = 3
- a = 1 OR (b = 2 AND c = 3)
- বাঁ থেকে ডানে, সমান অগ্রাধিকারে
- এটি syntax error, বন্ধনী বাধ্যতামূলক
একটি তালিকায় প্রতি পাতায় ২০টি সারি দেখানো হয়। পঞ্চম পাতা আনতে LIMIT আর OFFSET কী হবে?
- LIMIT 20 OFFSET 100
- LIMIT 5 OFFSET 20
- LIMIT 20 OFFSET 80
- LIMIT 100
students টেবিলে cgpa কলামে একটি সারিতে NULL. PostgreSQL-এ SELECT name, cgpa FROM students ORDER BY cgpa; চালালে NULL-এর সারি কোথায় আসবে?
- সবার আগে
- ফল থেকে বাদ পড়বে
- যেকোনো জায়গায়, প্রতিবার আলাদা
- সবার শেষে
একটি টেবিলে চারটি সারি: (Dhaka, A), (Dhaka, B), (Dhaka, A), (Khulna, A) — কলাম city আর grade. SELECT DISTINCT city, grade কয়টি সারি দেবে?
- ৩টি
- ২টি
- ৪টি
- ১টি
স্ট্যান্ডার্ড SQL-এ SELECT price * stock AS stock_value FROM products WHERE stock_value > 40000; error দেয় কেন?
- হিসাব করা কলামের ওপর শর্ত দেওয়া যায় না
- WHERE চলে SELECT-এর আগে, তখনো alias তৈরি হয়নি
- AS শব্দটি WHERE-এর সঙ্গে চলে না
- WHERE-এর আগে ORDER BY লিখতে হয়
SQLite-এর একটি টেবিলে city কলামে 'Dhaka' লেখা আছে। কোন শর্তটি এই সারি পাবে না?
- city LIKE 'dhaka'
- city = 'dhaka' COLLATE NOCASE
- city = 'dhaka'
- city = 'Dhaka'
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- লজিক্যাল ক্রম: FROM → WHERE → SELECT → DISTINCT → ORDER BY → LIMIT. Alias ORDER BY-তে চলে, WHERE-এ নয় (SQLite-এর ছাড়ে ভরসা নয়)।
- অ্যাপের কোডে কলামের নাম লিখুন,
SELECT *নয়; সবসময়ASদিয়ে alias. - AND আগে, OR পরে — দুটো মেশালে বন্ধনী দিন। DISTINCT পুরো সারির ওপর কাজ করে।
- ক্রম শুধু ORDER BY দেয়; শেষে অনন্য tiebreaker, আর NULL-এর জায়গা ডেটাবেস ভেদে আলাদা (দরকারে NULLS FIRST/LAST).
- LIMIT n OFFSET (p − ১) × n সহজ, কিন্তু গভীর পাতায় ধীর; বড় টেবিলে index-সহ keyset. লেখার তুলনা collation মেনে চলে, তারিখ রাখুন
'YYYY-MM-DD'রূপে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১৬ · অপারেটর, CASE আর NULL-এর তিন-মানের যুক্তিপরবর্তী পর্বBETWEEN, IN, LIKE, CASE আর NULL নিয়ে প্রতিটি শর্ত কীভাবে TRUE, FALSE বা UNKNOWN হয়।
- ডেটাবেস (DBMS) ও SQL · কোর্স হোম৬৭টি পর্বপুরো কোর্সের রোডম্যাপ — ER ডিজাইন থেকে transaction আর NoSQL পর্যন্ত।
- Python থেকে SQLite ডেটাবেসপাশের ট্র্যাকএই পর্বের কোয়েরিগুলো Python-এর sqlite3 দিয়ে অ্যাপ থেকে চালানো, প্যারামিটারসহ।
- DSA · B-tree আর B+ treeগভীরে যানKeyset pagination কেন কয়েকটি page পড়েই সঠিক জায়গায় পৌঁছায় — গাছের উচ্চতার হিসাব।
- DSA · বাস্তবে sortingপাশের ট্র্যাকORDER BY-এর পেছনের sort অ্যালগরিদম, stability আর top-k-এর কৌশল।