অধ্যায় 4 · ফাংশন ও সারাংশ
CASE WHEN, শর্তসাপেক্ষ গণনা আর ব্যবসার প্রশ্ন
- পৃষ্ঠা 15 / 22
- 17 মিনিট পড়া
আসল প্রশ্নের সাথে শর্ত জুড়ে থাকে। "প্রতিটা কাস্টমারের কয়টা অর্ডার, আর তার কয়টা ডেলিভার হয়েছে?" "প্রোডাক্টগুলোকে বাজেট, মাঝারি আর প্রিমিয়াম, এই তিন ভাগে ভাগ করতে হবে।" "এই কাস্টমার কি আবার কিনেছেন: হ্যাঁ, নাকি না?" CASE হলো SQL-এর if-then-else, আর GROUP BY-এর সাথে মিলে এটা একজন ম্যানেজার, অ্যানালিস্ট বা ডেটা সায়েন্টিস্টের আনা বেশিরভাগ প্রশ্নের উত্তর দেয়। ডেটাবেস থেকে সরাসরি মডেলের লেবেল আর ক্যাটেগরিক্যাল ফিচার বানানোর উপায়ও এটাই: দামের ব্যান্ড, হ্যাঁ/না ফ্ল্যাগ, প্রতি ক্যাটাগরিতে একটা কলাম।
যা শিখবেন
- সার্চড
CASE WHENআর সিম্পলCASE, আর এরাELSEও NULL-কে কীভাবে সামলায়। - মানগুলোকে ব্যান্ডে ভাগ (বাকেটিং) করে প্রতিটা ব্যান্ড গোনা।
- শর্তসাপেক্ষ অ্যাগ্রিগেশন:
SUM(CASE …),COUNT(CASE …)আরFILTER (WHERE …)। - মাস ধরে গ্রুপ, মোটের শতাংশ, আর
ROLLUPদিয়ে বহুস্তরের মোট। - ডুপ্লিকেট আর হারানো ক্যাটাগরি খোঁজা, আর ব্যবসার প্রশ্নকে SQL-এ রূপ দেওয়া।
CASE WHEN: কোয়েরিতে if-then-else
সার্চড রূপটা ওপর থেকে নিচে শর্তগুলো যাচাই করে, আর প্রথম যেটা সত্য, তার মান ফেরত দেয়। কোনোটাই সত্য না হলে দেয় ELSE-এর মান:
SELECT product_id, name, price,
CASE
WHEN price < 2000 THEN 'budget'
WHEN price < 5000 THEN 'mid' -- কেবল price >= 2000 হলেই এখানে আসে
ELSE 'premium'
END AS band
FROM products
ORDER BY price, product_id;+------------+-----------------------------+-------+---------+
| product_id | name | price | band |
+------------+-----------------------------+-------+---------+
| 4 | Wireless Mouse | 900 | budget |
| 11 | Gift Card | 1000 | budget |
| 1 | Python Crash Course | 1200 | budget |
| 8 | Laptop Stand | 1500 | budget |
| 9 | USB-C Hub | 2200 | mid |
| 2 | Hands-On Machine Learning | 2500 | mid |
| 6 | SQL Masterclass | 3000 | mid |
| 3 | Mechanical Keyboard | 4500 | mid |
| 10 | Noise-Cancelling Headphones | 7500 | premium |
| 7 | AI Engineering Bootcamp | 15000 | premium |
| 5 | 27-inch Monitor | 18500 | premium |
+------------+-----------------------------+-------+---------+প্রথম মিলটাই জেতে বলে দ্বিতীয় শর্তে price >= 2000 AND লেখার দরকার নেই। পুরো CASE … END একটাই এক্সপ্রেশন, তাই এর অ্যালিয়াস দেওয়া যায়, আর যেখানে একটা মান বসে, সেখানেই এটা বসানো যায়।
সিম্পল রূপটা একটা এক্সপ্রেশনকে কয়েকটা মানের সাথে মেলায়। ELSE বাদ দিলে যা মেলে না তা হয় NULL, যেমন এখানে বাতিল ৫ নম্বর অর্ডার:
SELECT order_id, status,
CASE status
WHEN 'delivered' THEN 'done'
WHEN 'shipped' THEN 'on the way'
WHEN 'pending' THEN 'waiting'
END AS label
FROM orders
WHERE order_id = 5 OR order_id >= 11
ORDER BY order_id;+----------+-----------+------------+
| order_id | status | label |
+----------+-----------+------------+
| 5 | cancelled | NULL |
| 11 | delivered | done |
| 12 | shipped | on the way |
| 13 | pending | waiting |
| 14 | delivered | done |
+----------+-----------+------------+CASE ORDER BY-এও চলে, এমন ক্রমের জন্য যা বর্ণানুক্রমিকও নয়, সংখ্যাগতও নয়। সাপোর্ট টিম চায় যেসব অর্ডারে কাজ বাকি, সেগুলো আগে আসুক:
SELECT order_id, status
FROM orders
ORDER BY CASE status WHEN 'pending' THEN 1 WHEN 'shipped' THEN 2 WHEN 'cancelled' THEN 3 ELSE 4 END,
order_id
LIMIT 5;+----------+-----------+
| order_id | status |
+----------+-----------+
| 13 | pending |
| 12 | shipped |
| 5 | cancelled |
| 1 | delivered |
| 2 | delivered |
+----------+-----------+বাকেটিং: প্রতি ব্যান্ডে গণনা
CASE এক্সপ্রেশন ধরে গ্রুপ করলে প্রতিটা বাকেটের গণনা পাবেন, হিস্টোগ্রামের SQL রূপ। MIN(price) অনুযায়ী সাজালে ব্যান্ডগুলো বর্ণানুক্রমে না এসে স্বাভাবিক ক্রমে আসে:
SELECT CASE
WHEN price < 2000 THEN 'budget'
WHEN price < 5000 THEN 'mid'
ELSE 'premium'
END AS band,
COUNT(*) AS products,
MIN(price) AS from_price,
MAX(price) AS to_price
FROM products
GROUP BY band
ORDER BY MIN(price);+---------+----------+------------+----------+
| band | products | from_price | to_price |
+---------+----------+------------+----------+
| budget | 4 | 900 | 1500 |
| mid | 4 | 2200 | 4500 |
| premium | 3 | 7500 | 18500 |
+---------+----------+------------+----------+SQLite, MySQL আর PostgreSQL তিনটাই GROUP BY-এ band অ্যালিয়াস মেনে নেয়; কড়া স্ট্যান্ডার্ড SQL-এ (আর SQL Server-এ) সেখানে পুরো CASE-টা আবার লিখতে হয়। মডেল বা রিপোর্টের যখন সঠিক মানের বদলে রেঞ্জ দরকার, তখন একটা সংখ্যাকে ক্যাটাগরি ফিচারে (বয়সের দল, দামের স্তর, অর্ডারের আকার) বদলানো হয় এভাবেই।
শর্তসাপেক্ষ অ্যাগ্রিগেশন: এক কোয়েরিতেই অনেক গণনা
একটা অ্যাগ্রিগেটের ভেতরে CASE রাখলে প্রতিটা অ্যাগ্রিগেট শুধু আপনার বাছাই করা সারিগুলো গোনে। একটা কোয়েরিতেই প্রতিটা স্ট্যাটাসের জন্য একটা কলাম, ছোট্ট একটা "পিভট টেবিল":
SELECT customer_id,
COUNT(*) AS orders,
SUM(CASE WHEN status = 'delivered' THEN 1 ELSE 0 END) AS delivered,
COUNT(CASE WHEN status IN ('pending', 'shipped') THEN 1 END) AS open_orders,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled
FROM orders
GROUP BY customer_id
ORDER BY customer_id;+-------------+--------+-----------+-------------+-----------+
| customer_id | orders | delivered | open_orders | cancelled |
+-------------+--------+-----------+-------------+-----------+
| 1 | 3 | 3 | 0 | 0 |
| 2 | 3 | 2 | 1 | 0 |
| 3 | 3 | 3 | 0 | 0 |
| 4 | 1 | 0 | 0 | 1 |
| 5 | 1 | 1 | 0 | 0 |
| 6 | 2 | 1 | 1 | 0 |
| 7 | 1 | 1 | 0 | 0 |
+-------------+--------+-----------+-------------+-----------+দুটো সমতুল্য ধরন: SUM(CASE … THEN 1 ELSE 0 END) এক আর শূন্য যোগ করে; COUNT(CASE … THEN 1 END) NULL নয় এমন ফলগুলো গোনে, আর ELSE না থাকায় বাকিগুলো NULL হয়। টাকার অঙ্কেও একই কৌশল: SUM(CASE WHEN method = 'bkash' THEN amount ELSE 0 END)।
PostgreSQL-এ (আর SQLite 3.30+-এ) আছে আরও পরিচ্ছন্ন স্ট্যান্ডার্ড সিনট্যাক্স, FILTER। MySQL-এ FILTER নেই, তবে সেখানে তুলনার ফল এমনিতেই ১ বা ০, তাই SUM(condition) দিয়েই গোনা যায়:
-- PostgreSQL
SELECT customer_id,
COUNT(*) AS orders,
COUNT(*) FILTER (WHERE status = 'delivered') AS delivered,
COUNT(*) FILTER (WHERE status IN ('pending', 'shipped')) AS open_orders
FROM orders
GROUP BY customer_id
ORDER BY customer_id
LIMIT 3;+-------------+--------+-----------+-------------+
| customer_id | orders | delivered | open_orders |
+-------------+--------+-----------+-------------+
| 1 | 3 | 3 | 0 |
| 2 | 3 | 2 | 1 |
| 3 | 3 | 3 | 0 |
+-------------+--------+-----------+-------------+-- MySQL
SELECT customer_id,
COUNT(*) AS orders,
SUM(status = 'delivered') AS delivered,
SUM(status IN ('pending', 'shipped')) AS open_orders
FROM orders
GROUP BY customer_id
ORDER BY customer_id
LIMIT 3;+-------------+--------+-----------+-------------+
| customer_id | orders | delivered | open_orders |
+-------------+--------+-----------+-------------+
| 1 | 3 | 3 | 0 |
| 2 | 3 | 2 | 1 |
| 3 | 3 | 3 | 0 |
+-------------+--------+-----------+-------------+মাস ধরে গ্রুপ
অ্যানালিটিক্সে সবচেয়ে বেশি কাজ হয় টাইম সিরিজ নিয়ে। SQLite-এ strftime('%Y-%m', …) দিয়ে প্রতিটা তারিখকে বছর-মাসের টেক্সটে বদলান (দেখুন টেক্সট, সংখ্যা ও তারিখের ফাংশন, CAST আর COALESCE), তারপর সেটা ধরে গ্রুপ করুন। প্রতি মাসে আসা টাকা, পেমেন্ট মেথড অনুযায়ী ভাগ করে:
SELECT strftime('%Y-%m', paid_on) AS month,
COUNT(*) AS payments,
SUM(amount) AS received,
SUM(CASE WHEN method = 'bkash' THEN amount ELSE 0 END) AS bkash,
SUM(CASE WHEN method = 'card' THEN amount ELSE 0 END) AS card,
SUM(CASE WHEN method = 'cash' THEN amount ELSE 0 END) AS cash
FROM payments
GROUP BY month
ORDER BY month;+---------+----------+----------+-------+-------+------+
| month | payments | received | bkash | card | cash |
+---------+----------+----------+-------+-------+------+
| 2026-01 | 2 | 6600 | 2100 | 4500 | 0 |
| 2026-02 | 2 | 7000 | 3000 | 0 | 4000 |
| 2026-03 | 4 | 21400 | 7400 | 14000 | 0 |
| 2026-04 | 2 | 23450 | 0 | 23450 | 0 |
| 2026-05 | 2 | 8500 | 5500 | 3000 | 0 |
| 2026-06 | 1 | 3100 | 0 | 0 | 3100 |
+---------+----------+----------+-------+-------+------+সেরা মাস এপ্রিল, মূলত কার্ডে পরিশোধ করা ১৮,৫০০ টাকার একটা মনিটরের জন্য। '2026-03'-এর মতো টেক্সট ঠিকঠাক সাজে, কারণ বছরটা আগে লেখা আর এক অঙ্কের মাসের আগে শূন্য বসানো। সার্ভারে তাদের নিজস্ব ফরম্যাটিং ফাংশন ব্যবহার করুন:
-- PostgreSQL
SELECT TO_CHAR(paid_on, 'YYYY-MM') AS month, SUM(amount) AS received
FROM payments
GROUP BY month
ORDER BY month;+---------+----------+
| month | received |
+---------+----------+
| 2026-01 | 6600 |
| 2026-02 | 7000 |
| 2026-03 | 21400 |
| 2026-04 | 23450 |
| 2026-05 | 8500 |
| 2026-06 | 3100 |
+---------+----------+MySQL-এ লিখুন DATE_FORMAT(paid_on, '%Y-%m'); PostgreSQL-এ আরও আছে DATE_TRUNC('month', paid_on), যা আসল তারিখ হিসেবেই রাখে। একটা সীমা জেনে রাখুন: যে মাসে কোনো পেমেন্ট নেই, তার কোনো সারিই আসে না। ফাঁকগুলো ভরতে লাগে একটা ক্যালেন্ডার টেবিল, যা অ্যাডভান্সড টিউটোরিয়ালে রিকার্সিভ CTE দিয়ে বানানো হয় (দেখুন CTE আর রিকার্সিভ কোয়েরি)।
শতাংশ আর এক সারির সারাংশ
শর্তসাপেক্ষ যোগফলকে মোট দিয়ে ভাগ করলে অংশ পাওয়া যায়, সব একটা সারিতে। টাকার অঙ্কে পেমেন্ট মেথডের ভাগ:
SELECT ROUND(SUM(CASE WHEN method = 'bkash' THEN amount END) * 100.0 / SUM(amount), 1) AS bkash_pct,
ROUND(SUM(CASE WHEN method = 'card' THEN amount END) * 100.0 / SUM(amount), 1) AS card_pct,
ROUND(SUM(CASE WHEN method = 'cash' THEN amount END) * 100.0 / SUM(amount), 1) AS cash_pct
FROM payments;+-----------+----------+----------+
| bkash_pct | card_pct | cash_pct |
+-----------+----------+----------+
| 25.7 | 64.2 | 10.1 |
+-----------+----------+----------+* 100.0 মনে রাখবেন: অঙ্কগুলো পূর্ণসংখ্যা, তাই শুধু * 100 দিলে পূর্ণসংখ্যার ভাগ হয়ে আসত ২৫, ৬৪ আর ১০।
উপমোট আর সর্বমোট: ROLLUP
রিপোর্টে প্রায়ই প্রতিটা গ্রুপ আর একটা মোটের সারি চাওয়া হয়। PostgreSQL-এ আছে GROUP BY ROLLUP (…), MySQL-এ GROUP BY … WITH ROLLUP। বাড়তি সারিটায় গ্রুপ করা কলাম হয় NULL, COALESCE সেটাকে 'ALL' লেবেল দেয়, আর সেই সারিতে GROUPING(method) হয় ১, যা সারিটাকে শেষে রাখে:
-- PostgreSQL
SELECT COALESCE(method, 'ALL') AS method, COUNT(*) AS payments, SUM(amount) AS total
FROM payments
GROUP BY ROLLUP (method)
ORDER BY GROUPING(method), total DESC;+--------+----------+-------+
| method | payments | total |
+--------+----------+-------+
| card | 6 | 44950 |
| bkash | 5 | 18000 |
| cash | 2 | 7100 |
| ALL | 13 | 70050 |
+--------+----------+-------+-- MySQL
SELECT COALESCE(method, 'ALL') AS method, COUNT(*) AS payments, SUM(amount) AS total
FROM payments
GROUP BY method WITH ROLLUP
ORDER BY GROUPING(method), total DESC;+--------+----------+-------+
| method | payments | total |
+--------+----------+-------+
| card | 6 | 44950 |
| bkash | 5 | 18000 |
| cash | 2 | 7100 |
| ALL | 13 | 70050 |
+--------+----------+-------+দুটো কলাম দিলে ROLLUP (month, method) দেয় প্রতি মাস আর মেথডে একটা সারি, প্রতি মাসের উপমোট, আর একটা সর্বমোট। SQLite-এ ROLLUP নেই; সেখানে মোটটা দ্বিতীয় একটা কোয়েরিতে বের করুন, অথবা দুটো UNION ALL দিয়ে একটার নিচে আরেকটা জুড়ুন (দেখুন UNION, INTERSECT ও EXCEPT)।
ডুপ্লিকেট খোঁজা
"কোন মানগুলো একাধিকবার আছে?" মানে GROUP BY আর HAVING COUNT(*) > 1। payments টেবিলে:
SELECT order_id, COUNT(*) AS payments, SUM(amount) AS paid
FROM payments
GROUP BY order_id
HAVING COUNT(*) > 1;+----------+----------+-------+
| order_id | payments | paid |
+----------+----------+-------+
| 6 | 2 | 15000 |
+----------+----------+-------+৬ নম্বর অর্ডারের টাকা দেওয়া হয়েছে দুই ভাগে (৫,০০০ বিকাশে, তারপর ১০,০০০ কার্ডে)। এটা বৈধ, ভুল নয়: কিছু মোছার আগে সবসময় ডুপ্লিকেটগুলো দেখে নিন। আসল ডুপ্লিকেট সাধারণত ছোটখাটো পার্থক্যের আড়ালে লুকিয়ে থাকে। নিচে একটা নিউজলেটার সাইন-আপের তালিকা, যেখানে লোকজন নিজের ইমেইল নানাভাবে টাইপ করেছেন:
CREATE TABLE signups (signup_id INTEGER PRIMARY KEY, email TEXT NOT NULL, signed_up_on TEXT NOT NULL);
INSERT INTO signups (email, signed_up_on) VALUES
('nadia@example.com', '2026-03-01'),
('Nadia@Example.com ', '2026-03-04'),
('tanvir@example.com', '2026-03-05'),
('mitu@example.com', '2026-03-07'),
(' NADIA@example.com', '2026-03-09'),
('mitu@example.com', '2026-03-10');
SELECT email, COUNT(*) AS n FROM signups GROUP BY email HAVING COUNT(*) > 1;
SELECT LOWER(TRIM(email)) AS clean_email,
COUNT(*) AS n,
MIN(signup_id) AS keep_id,
MIN(signed_up_on) AS first_signup
FROM signups
GROUP BY clean_email
HAVING COUNT(*) > 1
ORDER BY clean_email;+------------------+---+
| email | n |
+------------------+---+
| mitu@example.com | 2 |
+------------------+---+
+-------------------+---+---------+--------------+
| clean_email | n | keep_id | first_signup |
+-------------------+---+---------+--------------+
| mitu@example.com | 2 | 4 | 2026-03-07 |
| nadia@example.com | 3 | 1 | 2026-03-01 |
+-------------------+---+---------+--------------+কাঁচা টেক্সট ধরে গ্রুপ করলে শুধু মিতুর হুবহু পুনরাবৃত্তি ধরা পড়ে; আগে নরমালাইজ করলে দেখা যায় নাদিয়া তিনবার সাইন আপ করেছেন। MIN(signup_id) বলে দেয় কোন সারিটা রাখবেন। ML-এ এটা খুব জরুরি: ডুপ্লিকেট উদাহরণ কিছু রেকর্ডকে বাড়তি গুরুত্ব দেয়, আর যে ডুপ্লিকেট ট্রেনিং আর টেস্ট দুই সেটেই পড়ে, তা মডেলকে আসলের চেয়ে ভালো দেখায়।
হারানো ক্যাটাগরি: LEFT JOIN-এর প্রথম ঝলক
"প্রতিটা ক্যাটাগরিতে কয়টা প্রোডাক্ট?" শুধু products টেবিল থেকে:
SELECT category_id, COUNT(*) AS products
FROM products
GROUP BY category_id
ORDER BY category_id;+-------------+----------+
| category_id | products |
+-------------+----------+
| NULL | 1 |
| 1 | 2 |
| 2 | 4 |
| 3 | 2 |
| 4 | 2 |
+-------------+----------+দুটো সমস্যা: গিফট কার্ড একটা NULL গ্রুপ বানিয়েছে, আর Furniture (ক্যাটাগরি ৫) পুরোপুরি অনুপস্থিত, কারণ GROUP BY শুধু যে সারিগুলো আছে, সেগুলো থেকেই গ্রুপ বানাতে পারে, আর কোনো প্রোডাক্টের সারিতে "5" লেখা নেই। categories টেবিল থেকে শুরু না করলে খালি ক্যাটাগরি অদৃশ্যই থাকে। এর জন্য লাগে জয়েন:
আগাম ঝলক (ঠিকভাবে ব্যাখ্যা আছে LEFT, RIGHT, FULL, CROSS আর সেলফ জয়েন-এ):
LEFT JOINপ্রতিটা ক্যাটাগরি রাখে আর থাকলে তার প্রোডাক্টগুলো জুড়ে দেয়।COUNT(p.product_id)শুধু আসল মিলগুলো গোনে, তাই Furniture পায় ০।cআরpহলো টেবিলের ছোট নাম বা টেবিল অ্যালিয়াস, যা শিখবেন জয়েন: INNER JOIN আর টেবিল অ্যালিয়াস-এ। আপাতত শুধু ফলাফলটা দেখুন।
-- আগাম ঝলক: যে ক্যাটাগরিতে প্রোডাক্ট নেই, LEFT JOIN সেটাও রাখে
SELECT c.category_id, c.name, COUNT(p.product_id) AS products
FROM categories AS c
LEFT JOIN products AS p ON p.category_id = c.category_id
GROUP BY c.category_id, c.name
ORDER BY c.category_id;+-------------+-------------+----------+
| category_id | name | products |
+-------------+-------------+----------+
| 1 | Books | 2 |
| 2 | Electronics | 4 |
| 3 | Courses | 2 |
| 4 | Accessories | 2 |
| 5 | Furniture | 0 |
+-------------+-------------+----------+ব্যবসার প্রশ্ন, উত্তরসহ
এই কৌশলগুলো একসাথে কাজে লাগালে ব্যবসার আসল প্রশ্নের উত্তর পাওয়া যায়, প্রতিটা মাত্র একটা কোয়েরিতে।
কত শতাংশ অর্ডার বাতিল হয়েছে, আর কয়টা এখনো খোলা?
SELECT COUNT(*) AS orders,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled,
SUM(CASE WHEN status IN ('pending', 'shipped') THEN 1 ELSE 0 END) AS still_open,
ROUND(AVG(CASE WHEN status = 'cancelled' THEN 1.0 ELSE 0 END) * 100, 1) AS cancel_pct
FROM orders;+--------+-----------+------------+------------+
| orders | cancelled | still_open | cancel_pct |
+--------+-----------+------------+------------+
| 14 | 1 | 2 | 7.1 |
+--------+-----------+------------+------------+০/১ কলামের গড়ই একটা হার: ১৪টার মধ্যে ১টা অর্ডার মানে ৭.১%।
কোন কাস্টমাররা আবার কেনেন? "তাঁরা কি ফিরে আসবেন?" ধরনের মডেলের এটা একটা সাধারণ লেবেল: বাতিল নয় এমন অর্ডার অন্তত দুটো হলে ১, নইলে ০। CASE একটা অ্যাগ্রিগেটও যাচাই করতে পারে:
SELECT customer_id,
COUNT(*) AS orders,
CASE WHEN COUNT(*) >= 2 THEN 1 ELSE 0 END AS repeat_buyer
FROM orders
WHERE status <> 'cancelled'
GROUP BY customer_id
ORDER BY customer_id;+-------------+--------+--------------+
| customer_id | orders | repeat_buyer |
+-------------+--------+--------------+
| 1 | 3 | 1 |
| 2 | 3 | 1 |
| 3 | 3 | 1 |
| 5 | 1 | 0 |
| 6 | 2 | 1 |
| 7 | 1 | 0 |
+-------------+--------+--------------+৪ নম্বর (শুধু একটা বাতিল অর্ডার) আর ৮ নম্বর (কোনো অর্ডারই নেই) কাস্টমারের এখানে কোনো সারি নেই; ট্রেনিং টেবিলে তাঁদের লেবেল ০ দিয়ে রাখতেই হবে, আর সেটাও customers থেকে একটা LEFT JOIN-এর কাজ (এটাও LEFT, RIGHT, FULL, CROSS আর সেলফ জয়েন-এ)। ফলাফল থেকে কে বাদ পড়ল, তা খেয়াল রাখাই ভালো বিশ্লেষণের অর্ধেক।
বাস্তব সিস্টেমে কোথায় দেখবেন
- ফিচার ইঞ্জিনিয়ারিং: ওয়ান-হট (one-hot) কলাম (
CASE WHEN city = 'Dhaka' THEN 1 ELSE 0 END AS is_dhaka), ব্যান্ডে ভাগ করা সংখ্যা আর হ্যাঁ/না ফ্ল্যাগ ডেটা scikit-learn-এ পৌঁছানোর আগেই SQL-এ বানানো হয়। - LLM ইভ্যালুয়েশন রিপোর্ট: মডেল আর প্রম্পট ভার্সন ধরে
COUNT(*) FILTER (WHERE verdict = 'correct'), এক টেবিলে পাশাপাশি। - ব্যবসার ড্যাশবোর্ড: চ্যানেল অনুযায়ী মাসিক আয়, বাতিলের হার, পেমেন্টের ভাগ: মাস ধরে গ্রুপ করা শর্তসাপেক্ষ অ্যাগ্রিগেশন।
সাধারণ ভুল
- ভুল ক্রমে শর্ত। প্রথম সত্য
WHEN-ই জেতে, তাইWHEN price > 1000 … WHEN price > 5000 …-এ দ্বিতীয় শাখায় কখনো পৌঁছানো যায় না। সমাধান: সবচেয়ে নির্দিষ্টটা আগে, অথবা<দিয়ে ছোট থেকে বড়। - NULL ভুলে যাওয়া। NULL কোনো
WHEN-এর সাথে মেলে না, গিয়ে পড়েELSE-এ:CASE WHEN stock = 0 THEN 'out' ELSE 'ok' ENDকোর্সগুলোকে (stock NULL) বলে "ok"। সমাধান: আগে এটাই সামলান,WHEN stock IS NULL THEN 'not tracked'। COUNT(CASE … THEN 1 ELSE 0 END)। ০ NULL নয়, তাইCOUNTপ্রতিটা সারিই গোনে। সমাধান:COUNT-এর সাথেELSEবাদ দিন, অথবাSUM(… THEN 1 ELSE 0 END)ব্যবহার করুন।- পূর্ণসংখ্যার শতাংশ।
part * 100 / totalভগ্নাংশ কেটে ফেলে। সমাধান:part * 100.0 / total, তারপরROUND। - না দেখে "ডুপ্লিকেট" মোছা। ৬ নম্বর অর্ডারের দুটো পেমেন্টই আসল। সমাধান:
GROUP BY … HAVING COUNT(*) > 1দিয়ে দেখুন, আগে টেক্সট নরমালাইজ করুন, আর কোনোDELETE-এর আগে ঠিক করুন কোন সারি রাখবেন (MIN(id))।
নিজে চেষ্টা করুন
- সহজ: প্রতিটা প্রোডাক্টের স্টককে লেবেল দিন
'not tracked'(NULL),'out of stock'(০),'low'(১০-এর কম) বা'ok', আর প্রতিটা লেবেলে কয়টা প্রোডাক্ট গুনুন। গণনা অনুযায়ী সাজান (বেশি আগে), তারপর লেবেল। - মাঝারি:
order_date-এর প্রতিটা মাসে অর্ডারের সংখ্যা, কয়টা ডেলিভার হয়েছে আর কয়টা হয়নি (অন্য যেকোনো স্ট্যাটাস) দেখান। মাস অনুযায়ী সাজান। - কঠিন: orders টেবিল থেকে প্রতি কাস্টমারের একটা সারি বানান: মোট অর্ডার, ডেলিভার হওয়া অর্ডার, শতাংশে
delivered_rate(১ দশমিক ঘর), শেষ অর্ডারের মাস, আর একটাsegment: ৩ বা বেশি অর্ডারে'loyal', ২টায়'returning', ১টায়'new'। অর্ডার অনুযায়ী সাজান (বেশি আগে), তারপরcustomer_id।
উত্তর
-- সহজ
SELECT CASE
WHEN stock IS NULL THEN 'not tracked'
WHEN stock = 0 THEN 'out of stock'
WHEN stock < 10 THEN 'low'
ELSE 'ok'
END AS stock_label,
COUNT(*) AS products
FROM products
GROUP BY stock_label
ORDER BY products DESC, stock_label;+--------------+----------+
| stock_label | products |
+--------------+----------+
| ok | 5 |
| not tracked | 3 |
| low | 2 |
| out of stock | 1 |
+--------------+----------+-- মাঝারি
SELECT strftime('%Y-%m', order_date) AS month,
COUNT(*) AS orders,
SUM(CASE WHEN status = 'delivered' THEN 1 ELSE 0 END) AS delivered,
SUM(CASE WHEN status <> 'delivered' THEN 1 ELSE 0 END) AS not_delivered
FROM orders
GROUP BY month
ORDER BY month;+---------+--------+-----------+---------------+
| month | orders | delivered | not_delivered |
+---------+--------+-----------+---------------+
| 2026-01 | 2 | 2 | 0 |
| 2026-02 | 3 | 2 | 1 |
| 2026-03 | 3 | 3 | 0 |
| 2026-04 | 2 | 2 | 0 |
| 2026-05 | 2 | 1 | 1 |
| 2026-06 | 2 | 1 | 1 |
+---------+--------+-----------+---------------+-- কঠিন
SELECT customer_id,
COUNT(*) AS orders,
SUM(CASE WHEN status = 'delivered' THEN 1 ELSE 0 END) AS delivered,
ROUND(AVG(CASE WHEN status = 'delivered' THEN 1.0 ELSE 0 END) * 100, 1) AS delivered_rate,
strftime('%Y-%m', MAX(order_date)) AS last_order_month,
CASE
WHEN COUNT(*) >= 3 THEN 'loyal'
WHEN COUNT(*) = 2 THEN 'returning'
ELSE 'new'
END AS segment
FROM orders
GROUP BY customer_id
ORDER BY orders DESC, customer_id;+-------------+--------+-----------+----------------+------------------+-----------+
| customer_id | orders | delivered | delivered_rate | last_order_month | segment |
+-------------+--------+-----------+----------------+------------------+-----------+
| 1 | 3 | 3 | 100.0 | 2026-04 | loyal |
| 2 | 3 | 2 | 66.7 | 2026-05 | loyal |
| 3 | 3 | 3 | 100.0 | 2026-06 | loyal |
| 6 | 2 | 1 | 50.0 | 2026-06 | returning |
| 4 | 1 | 0 | 0.0 | 2026-02 | new |
| 5 | 1 | 1 | 100.0 | 2026-03 | new |
| 7 | 1 | 1 | 100.0 | 2026-05 | new |
+-------------+--------+-----------+----------------+------------------+-----------+সারসংক্ষেপ
CASE WHEN … THEN … ELSE … ENDপ্রথম সত্য শর্তের মান ফেরত দেয়;ELSEনা থাকলে বাকিগুলো NULL, আর NULL ইনপুট গড়িয়ে পড়েELSE-এ। সিম্পল রূপCASE col WHEN value …একটা এক্সপ্রেশনকে মেলায়।- মান বাকেটে ভাগ করতে
CASEধরে গ্রুপ করুন; বাকেট সাজানMIN(…)দিয়ে, বাORDER BY-এ একটাCASEদিয়ে। - শর্তসাপেক্ষ অ্যাগ্রিগেশন (
SUM(CASE …),COUNT(CASE …),FILTER, MySQL-এরSUM(condition)) এক কোয়েরিতেই পিভট আর হার বানায়। - মাসিক হিসাবের জন্য
strftime('%Y-%m', date)(বাTO_CHAR/DATE_FORMAT) ধরে গ্রুপ করুন; সার্ভারেROLLUPমোট যোগ করে। HAVING COUNT(*) > 1ডুপ্লিকেট খোঁজে (আগে টেক্সট নরমালাইজ করুন); একটা টেবিল থেকে বানানো গ্রুপ দেখাতে পারে না কী বাদ পড়েছে, সেটা জয়েনের কাজ।
এরপর: জয়েন: INNER JOIN আর টেবিল অ্যালিয়াস। এতক্ষণ প্রতিটা কোয়েরি একটা টেবিলেই ছিল, তাই প্রোডাক্টের নাম, অর্ডারের স্ট্যাটাস আর খালি ক্যাটাগরি বারবার হাত ফসকে যাচ্ছিল। জয়েন টেবিলগুলো জোড়া লাগায়, ফলে অবশেষে প্রোডাক্টের নাম, কাস্টমার আর ক্যাটাগরি ধরে আয়ের রিপোর্ট বানাতে পারবেন।