অধ্যায় 2 · ডেটা পড়া
সারি বাছাই: WHERE, AND, OR ও NOT
- পৃষ্ঠা 7 / 22
- 12 মিনিট পড়া
কোয়েরি কোন সারিগুলো রাখবে, তা ঠিক করে WHERE। SELECT আর FROM-এর পরে SQL-এ এটাই সবচেয়ে বেশি ব্যবহৃত ক্লজ, আর ডেটার কাজে সাধারণত সবচেয়ে গুরুত্বপূর্ণও: "শুধু ডেলিভার হওয়া অর্ডার", "শুধু ঢাকার কাস্টমার", "গতকাল যে API কলগুলো ব্যর্থ হয়েছে শুধু সেগুলো", "ট্রেনিং সেটের জন্য শুধু লেবেল করা উদাহরণ"।
সব লোড করে পরে Python-এ না ছেঁকে ডেটাবেসেই ছাঁকা, এই অভ্যাসটাও এখনই গড়ে তোলা ভালো। ৫ কোটি সারির একটা লগ টেবিল আপনার ল্যাপটপে pandas DataFrame-এ ধরবে না; আপনার WHERE যে ২,০০০টা সারি বেছে আনে, সেগুলো ধরবে।
যা শিখবেন
WHEREআর তুলনার অপারেটর=,<>,<,<=,>,>=দিয়ে সারি রাখা।- টেক্সট, সংখ্যা আর তারিখ কীভাবে তুলনা হয়, আর তিন ডেটাবেস কোথায় আলাদা।
AND,ORআরNOTদিয়ে শর্ত জোড়া।- অপারেটরের অগ্রাধিকার (precedence), আর AND/OR-এর সেই পুরোনো বাগ, যা বন্ধনী দিয়ে সারে।
- Python থেকে নিরাপদে ফিল্টারের মান পাঠানো।
শর্ত যেখানে সত্য, WHERE সেই সারিগুলো রাখে
SELECT name, price
FROM products
WHERE price < 2000
ORDER BY price;+---------------------+-------+
| name | price |
+---------------------+-------+
| Wireless Mouse | 900 |
| Gift Card | 1000 |
| Python Crash Course | 1200 |
| Laptop Stand | 1500 |
+---------------------+-------+ডেটাবেস products-এর প্রতিটা সারির জন্য price < 2000 শর্তটা যাচাই করে। যেখানে সত্য, সেই সারি থাকে; বাকিগুলো অন্য কিছু ঘটার আগেই বাদ পড়ে। কোয়েরির লজিক্যাল ক্রমে WHERE চলে ঠিক FROM-এর পরে:
FROM products ১১টা সারিই নাও
→ WHERE price < 2000 যে ৪টা পাস করে, সেগুলো রাখো
→ SELECT name, price কলাম বাছো
→ ORDER BY price যা থাকল, সাজাও
→ LIMIT কাটো (চাওয়া হলে)এ থেকে দুটো জিনিস আসে। এক, যে কলাম সিলেক্ট করেননি, WHERE সেটাও যাচাই করতে পারে। দুই, SELECT-এ বানানো অ্যালিয়াস WHERE দেখতে পায় না, কারণ SELECT তখনো চলেনি (এ নিয়ে আরও আছে সাধারণ ভুল অংশে)।
তুলনার অপারেটর
| অপারেটর | মানে | উদাহরণ |
|---|---|---|
= | সমান (একটা =, == নয়) | status = 'shipped' |
<> বা != | সমান নয় (<> স্ট্যান্ডার্ড বানান) | status <> 'cancelled' |
<, <= | ছোট, ছোট বা সমান | stock <= 5 |
>, >= | বড়, বড় বা সমান | order_date >= '2026-04-01' |
একটা তুলনা নিজেই একটা মান: সত্য বা মিথ্যা। SQLite আর MySQL সত্যকে দেখায় 1, মিথ্যাকে 0; PostgreSQL দেখায় true আর false। সিলেক্ট করে দেখতে পারেন:
SELECT name, stock, stock <= 5 AS low_stock
FROM products
WHERE category_id = 2
ORDER BY product_id;+-----------------------------+-------+-----------+
| name | stock | low_stock |
+-----------------------------+-------+-----------+
| Mechanical Keyboard | 12 | 0 |
| Wireless Mouse | 60 | 0 |
| 27-inch Monitor | 5 | 1 |
| Noise-Cancelling Headphones | 8 | 0 |
+-----------------------------+-------+-----------+টেক্সট, সংখ্যা আর তারিখ
সংখ্যা লিখুন উদ্ধৃতিচিহ্ন ছাড়াই (2000), আর টেক্সট ও তারিখ একক উদ্ধৃতিচিহ্নে ('Dhaka', '2026-03-01')। মানগুলো কীভাবে তুলনা হবে, তা তাদের টাইপের ওপর নির্ভর করে:
- সংখ্যা তুলনা হয় মান ধরে: ৯০০ < ২০০০।
- টেক্সট তুলনা হয় অক্ষর ধরে ধরে, অভিধানের শব্দের মতো, তবে অক্ষরের কোড অনুযায়ী: আগে অঙ্ক, তারপর বড় হাতের, তারপর ছোট হাতের অক্ষর। তাই
'9' < '10'মিথ্যা (প্রথম অক্ষর'9'আর'1'-ই ফয়সালা করে দেয়), আর'Zebra' < 'apple'সত্য। - SQLite-এ তারিখ হলো ISO 8601 রূপের টেক্সট (
'YYYY-MM-DD')। এই রূপটা বানানোই হয়েছে এমনভাবে যাতে টেক্সটের ক্রম আর সময়ের ক্রম এক হয়; এ কারণেইorder_date >= '2026-04-01'কাজ করে।'01/04/2026'লেখা তারিখ ঠিকমতো তুলনা হতো না।
SELECT 9 < 10 AS numbers,
'9' < '10' AS texts,
'Zebra' < 'apple' AS by_code,
'Dhaka' = 'dhaka' AS same_case;+---------+-------+---------+-----------+
| numbers | texts | by_code | same_case |
+---------+-------+---------+-----------+
| 1 | 0 | 1 | 0 |
+---------+-------+---------+-----------+শেষ কলাম দেখাচ্ছে, SQLite-এ টেক্সটের ওপর = বড়-ছোট হাতের অক্ষর আলাদা ধরে (case-sensitive): WHERE city = 'dhaka' কাউকে খুঁজে পায় না। PostgreSQL-ও একই আচরণ করে। MySQL করে না: এর ডিফল্ট কোলেশন (টেক্সট তুলনার নিয়ম; এখানে utf8mb4_0900_ai_ci, যেখানে ci মানে case-insensitive) দুটোকে সমান ধরে:
-- MySQL
SELECT name
FROM customers
WHERE city = 'dhaka'
ORDER BY customer_id;+---------------+
| name |
+---------------+
| Nadia Rahman |
| Farhana Akter |
| Mitu Das |
+---------------+একই কোয়েরি MySQL-এ তিনটা সারি দেয়, আর SQLite বা PostgreSQL-এ একটাও না। কোয়েরি এক ডেটাবেস থেকে আরেকটায় নিলে, বা কোনো LLM আপনার জন্য SQL লিখে দিলে, সবার আগে টেক্সট তুলনাগুলো যাচাই করুন। (LOWER() দিয়ে সব ডেটাবেসে চলে এমন case-insensitive খোঁজা আছে পরের পাতায়।)
টেক্সট যখন সংখ্যার মুখোমুখি
সংখ্যার সাথে টেক্সটের তুলনাতেই ডায়ালেক্টগুলো সবচেয়ে বেশি আলাদা। SQLite-এ price কলাম INTEGER হিসেবে ঘোষিত, তাই তুলনার আগে SQLite '2000'-কে সংখ্যায় বদলে নেয়, আর price > '2000' কাকতালীয়ভাবে কাজ করে যায়। PostgreSQL-ও পূর্ণসংখ্যার কলামের জন্য উদ্ধৃতিচিহ্নে লেখা সংখ্যা মেনে নেয়, কিন্তু টেক্সটটা সংখ্যা না হলে আটকে দেয়:
-- PostgreSQL
SELECT name
FROM products
WHERE price > 'cheap';Error: invalid input syntax for type integer: "cheap"MySQL চুপচাপ 'cheap'-কে 0 বানিয়ে নেয় (শুধু একটা ওয়ার্নিং দিয়ে) আর সব প্রোডাক্ট ফেরত দেয়। ভুল উত্তরের চেয়ে এরর ভালো। সব জায়গায় নিরাপদ অভ্যাস: সংখ্যার সাথে সংখ্যা তুলনা করুন, উদ্ধৃতিচিহ্ন ছাড়া, আর টেক্সটের সাথে উদ্ধৃতিচিহ্নে লেখা টেক্সট।
AND, OR আর NOT
আসল ফিল্টারে একাধিক শর্ত থাকে। AND-এ দুই দিকই সত্য হতে হয়, OR-এ অন্তত একটা, আর NOT সত্য-মিথ্যা উল্টে দেয়।
| A | B | A AND B | A OR B | NOT A |
|---|---|---|---|---|
| সত্য | সত্য | সত্য | সত্য | মিথ্যা |
| সত্য | মিথ্যা | মিথ্যা | সত্য | মিথ্যা |
| মিথ্যা | সত্য | মিথ্যা | সত্য | সত্য |
| মিথ্যা | মিথ্যা | মিথ্যা | মিথ্যা | সত্য |
-- ঢাকার যে কাস্টমাররা ২০২৬-এ যোগ দিয়েছেন
SELECT name, joined_on
FROM customers
WHERE city = 'Dhaka' AND joined_on >= '2026-01-01'
ORDER BY customer_id;+---------------+------------+
| name | joined_on |
+---------------+------------+
| Farhana Akter | 2026-01-09 |
| Mitu Das | 2026-03-30 |
+---------------+------------+-- যে অর্ডারগুলো এখনো শেষ হয়নি
SELECT order_id, status
FROM orders
WHERE status = 'shipped' OR status = 'pending'
ORDER BY order_id;+----------+---------+
| order_id | status |
+----------+---------+
| 12 | shipped |
| 13 | pending |
+----------+---------+-- যে অর্ডারগুলো ডেলিভার হয়নি
SELECT order_id, status
FROM orders
WHERE NOT status = 'delivered'
ORDER BY order_id;+----------+-----------+
| order_id | status |
+----------+-----------+
| 5 | cancelled |
| 12 | shipped |
| 13 | pending |
+----------+-----------+NOT status = 'delivered' আর status <> 'delivered' একই কথা; যেটা পড়তে ভালো লাগে সেটা লিখুন। NOT সবচেয়ে কাজে লাগে বন্ধনীতে ঘেরা কয়েকটা শর্তের সামনে, যেমন NOT (a OR b)।
অগ্রাধিকার: AND/OR-এর পুরোনো বাগ
কাজটা: "Electronics (ক্যাটাগরি ২) বা Accessories (ক্যাটাগরি ৪)-এর যে প্রোডাক্টগুলোর দাম ২০০০ টাকার কম।" মুখে যেভাবে বলি, সেভাবে লিখলে:
SELECT name, category_id, price
FROM products
WHERE category_id = 2 OR category_id = 4 AND price < 2000
ORDER BY product_id;+-----------------------------+-------------+-------+
| name | category_id | price |
+-----------------------------+-------------+-------+
| Mechanical Keyboard | 2 | 4500 |
| Wireless Mouse | 2 | 900 |
| 27-inch Monitor | 2 | 18500 |
| Laptop Stand | 4 | 1500 |
| Noise-Cancelling Headphones | 2 | 7500 |
+-----------------------------+-------------+-------+১৮,৫০০ টাকার মনিটর "২০০০-এর কম" নয়। কোনো এরর নেই, শুধু ভুল উত্তর। কারণটা অগ্রাধিকার: পাটিগণিতে যেমন × আগে হয় +-এর চেয়ে, তেমনি NOT আগে হয় AND-এর চেয়ে, আর AND আগে হয় OR-এর চেয়ে। ডেটাবেস শর্তটা পড়েছে এভাবে:
category_id = 2 OR (category_id = 4 AND price < 2000)
─────────────── ──────────────────────────────────
সব Electronics শুধু সস্তা Accessoriesআপনি যা বোঝাতে চেয়েছেন, বন্ধনী তা-ই বলে দেয়:
SELECT name, category_id, price
FROM products
WHERE (category_id = 2 OR category_id = 4) AND price < 2000
ORDER BY product_id;+----------------+-------------+-------+
| name | category_id | price |
+----------------+-------------+-------+
| Wireless Mouse | 2 | 900 |
| Laptop Stand | 4 | 1500 |
+----------------+-------------+-------+সহজ নিয়ম: WHERE-এ AND আর OR মিশলেই বন্ধনী দিন, ডিফল্ট ঠিক হলেও। কোয়েরিটা পরে যিনি পড়বেন (বা এক মাস পরের আপনি), তাঁকে যেন অগ্রাধিকারের নিয়ম মনে রাখতে না হয়।
শর্ত জোড়া দেওয়া
বাস্তবের ফিল্টারে এগুলোর কয়েকটা একসাথে থাকে। "কার্ড বা ক্যাশে করা অন্তত ৪০০০ টাকার পেমেন্ট":
SELECT payment_id, method, amount, paid_on
FROM payments
WHERE (method = 'card' OR method = 'cash')
AND amount >= 4000
ORDER BY payment_id;+------------+--------+--------+------------+
| payment_id | method | amount | paid_on |
+------------+--------+--------+------------+
| 2 | card | 4500 | 2026-01-20 |
| 4 | cash | 4000 | 2026-02-16 |
| 6 | card | 10000 | 2026-03-10 |
| 7 | card | 4000 | 2026-03-15 |
| 9 | card | 4950 | 2026-04-02 |
| 10 | card | 18500 | 2026-04-19 |
+------------+--------+--------+------------+প্রতিটা শর্ত আলাদা লাইনে রাখলে, আর লাইনের শুরুতে AND দিলে, লম্বা ফিল্টার পড়া সহজ হয়, আর ডিবাগ করার সময় একটা শর্ত বন্ধ করাও সহজ হয় (-- দিয়ে একটা লাইন কমেন্ট করে দিন)।
NULL-এর সাথে প্রথম দেখা
আরেকটা যাচাই। কে কে ঢাকায় থাকেন না?
SELECT name, city
FROM customers
WHERE city <> 'Dhaka'
ORDER BY customer_id;+---------------+------------+
| name | city |
+---------------+------------+
| Tanvir Ahmed | Chattogram |
| Rafiq Islam | Sylhet |
| Imran Hossain | Khulna |
| Karim Uddin | Rajshahi |
+---------------+------------+সাদিয়া চৌধুরী (কাস্টমার ৫) ফলাফলে নেই, অথচ তিনি যে ঢাকায় থাকেন এমন কোনো তথ্যও নেই। তাঁর city হলো NULL (অজানা), আর অজানা মানের সাথে তুলনা সত্যও নয়, মিথ্যাও নয়, বরং অজানা; তাই WHERE সারিটা বাদ দেয়। SQL-এর সবচেয়ে সাধারণ নীরব বাগগুলোর একটা এটা; পরের পাতায় IS NULL দিয়ে এর পুরো ব্যাখ্যা আছে।
Python থেকে ফিল্টার
অ্যাপ্লিকেশনের কোডে ফিল্টারের মান সাধারণত অন্য কোথাও থেকে আসে: ব্যবহারকারী, কনফিগ ফাইল, কোনো লুপ। মানগুলো SQL স্ট্রিংয়ে বসিয়ে না দিয়ে প্যারামিটার হিসেবে পাঠান (sqlite3-এ ?):
from sqlhelp import show
max_price = 2000
show("SELECT name, price FROM products WHERE price < ? ORDER BY price", (max_price,))
status = "pending"
show("SELECT order_id, customer_id FROM orders WHERE status = ? ORDER BY order_id", (status,))+---------------------+-------+
| name | price |
+---------------------+-------+
| Wireless Mouse | 900 |
| Gift Card | 1000 |
| Python Crash Course | 1200 |
| Laptop Stand | 1500 |
+---------------------+-------+
+----------+-------------+
| order_id | customer_id |
+----------+-------------+
| 13 | 6 |
+----------+-------------+ডেটাবেস মানটা SQL থেকে আলাদা করে পায়, তাই x' OR '1'='1-এর মতো মান কোডের অংশ হয় না, স্রেফ একটা অদ্ভুত স্ট্যাটাস হয়ে থাকে যা কিছুর সাথে মেলে না। f-string দিয়ে SQL বানানো থেকেই SQL ইনজেকশন ঘটে; অ্যাডভান্সড টিউটোরিয়ালে আক্রমণটা আর তার সমাধান বিস্তারিত দেখানো আছে।
কোথায় কাজে লাগবে
- ট্রেনিং সেট বানানো:
WHERE label IS NOT NULL AND language = 'bn' AND created_at >= '2026-01-01'। প্রতিটা শর্ত হলো মডেল কী থেকে শিখবে তা নিয়ে একটা সিদ্ধান্ত, তাই সেটা কারও মাথায় না রেখে কোয়েরিতে লিখে রাখুন। - AI অ্যাপ ডিবাগ করা: লগ টেবিলে
WHERE status_code <> 200 AND model = 'gpt-x'কয়েক সেকেন্ডে ব্যর্থ কলগুলো খুঁজে দেয়। - RAG-এর মেটাডেটা ফিল্টার: ভেক্টর সার্চের আগে
WHERE tenant_id = ? AND doc_type = 'policy'ঠিক করে দেয় কোন চাঙ্কগুলো আদৌ ফেরত আসতে পারবে। এই ফিল্টার ভুল হলে এক কাস্টমারের ডকুমেন্ট আরেকজনের কাছে ফাঁস হয়ে যায়।
সাধারণ ভুল
- বন্ধনী ছাড়া AND আর OR মেশানো।
a OR b AND cমানেa OR (b AND c)। আপনি যদি(a OR b) AND cবোঝান, সেটাই লিখুন। - WHERE-এ SELECT-এর অ্যালিয়াস ব্যবহার। SQLite বাড়তি সুবিধা হিসেবে এটা মেনে নেয়, তাই কোয়েরি অন্য ডেটাবেসে না যাওয়া পর্যন্ত ভুলটা লুকিয়ে থাকে:
-- PostgreSQL SELECT order_id, quantity * unit_price AS line_total FROM order_items WHERE line_total > 10000;MySQL বলেError: column "line_total" does not existUnknown column 'line_total' in 'where clause'। এর বদলে এক্সপ্রেশনটাই আবার লিখুন:WHERE quantity * unit_price > 10000। - সংখ্যা উদ্ধৃতিচিহ্নে লেখা, বা টেক্সট হিসেবে রাখা সংখ্যা তুলনা করা।
'9' < '10'মিথ্যা। সংখ্যা রাখুন সংখ্যার কলামে, আর তুলনা করুন উদ্ধৃতিচিহ্ন ছাড়া। - টেক্সটে ভুল হাতের অক্ষর বা বাড়তি স্পেস। SQLite আর PostgreSQL-এ
WHERE status = 'Delivered'কিছুই ফেরত দেয় না। আগে আসল মানগুলো দেখে নিন:SELECT DISTINCT status FROM orders। <>-এ NULL-এর কথা ভুলে যাওয়া।city <> 'Dhaka'যেসব সারিতে city NULL, সেগুলোও বাদ দেয়। ওগুলো যদি গোনার কথা থাকে, তা স্পষ্ট বলুন:WHERE city <> 'Dhaka' OR city IS NULL।
নিজে চেষ্টা করুন
- সহজ: যে প্রোডাক্টগুলোর দাম অন্তত ৫০০০ টাকা, সেগুলো বের করুন, সবচেয়ে দামিটা আগে।
- মাঝারি: কাস্টমার ১ বা কাস্টমার ৩-এর যে অর্ডারগুলো ডেলিভার হয়েছে আর ১ মার্চ ২০২৬ বা তার পরে দেওয়া, সেগুলো বের করুন, পুরোনোটা আগে।
- মাঝারি: Data ডিপার্টমেন্টের যে কর্মীরা ১,০০,০০০-এর বেশি পান, অথবা ২০২৩-এর আগে যোগ দেওয়া যেকোনো কর্মীকে বের করুন।
- কঠিন: যে পেমেন্টগুলো হয় অন্তত ৫০০০ টাকার কার্ড পেমেন্ট, নয়তো ৩০০০ টাকার কম bKash পেমেন্ট, সেগুলো খুঁজুন। তারপর "যে অর্ডারগুলো ডেলিভারও হয়নি, বাতিলও হয়নি" কোয়েরিটা দুইভাবে লিখুন: একবার
NOT ( … OR … )দিয়ে, একবারNOTছাড়া, আর মিলিয়ে দেখুন দুটো একই সারি দেয় কিনা।
উত্তর
-- 1
SELECT name, price
FROM products
WHERE price >= 5000
ORDER BY price DESC;
-- 2: বন্ধনী "কাস্টমার ১ বা ৩"-কে একসাথে রাখে
SELECT order_id, customer_id, order_date
FROM orders
WHERE (customer_id = 1 OR customer_id = 3)
AND status = 'delivered'
AND order_date >= '2026-03-01'
ORDER BY order_date, order_id;
-- 3: AND আগে হয়, কিন্তু বন্ধনী ব্যাপারটা স্পষ্ট করে
SELECT name, department, salary, hired_on
FROM employees
WHERE (department = 'Data' AND salary > 100000)
OR hired_on < '2023-01-01'
ORDER BY employee_id;
-- 4a
SELECT payment_id, method, amount
FROM payments
WHERE (method = 'card' AND amount >= 5000)
OR (method = 'bkash' AND amount < 3000)
ORDER BY payment_id;
-- 4b: NOT (A OR B) আর NOT A AND NOT B একই
SELECT order_id, status
FROM orders
WHERE NOT (status = 'delivered' OR status = 'cancelled')
ORDER BY order_id;
SELECT order_id, status
FROM orders
WHERE status <> 'delivered' AND status <> 'cancelled'
ORDER BY order_id;4b-এর দুটো কোয়েরিই দেয় অর্ডার ১২ (shipped) আর ১৩ (pending)। এরা যে নিয়মটা দেখায়, NOT (A OR B) = NOT A AND NOT B, তার নাম ডি মরগানের সূত্র; কোনো ফিল্টারকে উল্টে লিখতে হলে এটা খুব কাজের।
সারসংক্ষেপ
- শর্ত যেখানে সত্য,
WHEREসেই সারিগুলো রাখে। এটা চলেFROM-এর ঠিক পরে,SELECT-এর আগে, তাই SELECT-এর অ্যালিয়াস ব্যবহার করতে পারে না (SQLite-এর ছাড় বাদে)। - তুলনা:
= <> < <= > >=। সংখ্যা উদ্ধৃতিচিহ্ন ছাড়া, টেক্সট আর তারিখ একক উদ্ধৃতিচিহ্নে; ISO তারিখ টেক্সট হিসেবেও ঠিকঠাক তুলনা হয়। - টেক্সটের তুলনা SQLite আর PostgreSQL-এ case-sensitive, MySQL-এর ডিফল্ট কোলেশনে case-insensitive।
- আগে
NOT, তারপরAND, তারপরOR। AND আর OR মিশলে বন্ধনী দিন। - Python থেকে ফিল্টারের মান প্যারামিটার হিসেবে পাঠান, কখনো SQL টেক্সটে বসিয়ে নয়।
এরপর: BETWEEN, IN, LIKE ও NULL। রেঞ্জ আর লিস্ট আরও সংক্ষেপে লিখবেন, প্যাটার্ন দিয়ে টেক্সট খুঁজবেন, আর অবশেষে NULL-কে ঠিকমতো সামলাবেন; শেষ কোয়েরি থেকে সাদিয়া কেন হারিয়ে গেলেন, সেটার কারণও এই NULL।