Aggregate Functions, GROUP BY এবং HAVING

এতক্ষণ আমরা শুধু ডেটা তুলে এনেছি (SELECT)। কিন্তু বস যদি আপনাকে জিজ্ঞেস করে, "আমাদের মোট কতজন ইউজার আছে?" বা "গত মাসে মোট কত টাকার বিক্রি হয়েছে?"—তখন আপনি হাজার হাজার রো (Row) গুনে বের করবেন না।

তখন আমাদের দরকার হয় Aggregate Functions। এগুলো অনেকগুলো রো-কে প্রসেস করে একটি সিঙ্গেল ভ্যালু রিটার্ন করে। একে Reporting Query ও বলা হয়।

৪.১ Aggregate Functions (গণিতের জাদু)

SQL-এ প্রধান ৫টি অ্যাগ্রিগেট ফাংশন আছে যা আপনার জীবন সহজ করে দেবে।

১. COUNT() — গণনা করা

কতগুলো ডেটা আছে তা গুনতে এটি ব্যবহার হয়।

সব রো গোনা (NULL সহ):

SELECT COUNT(*) FROM users;

শর্ত সাপেক্ষে গোনা:

SELECT COUNT(*) FROM orders WHERE status = 'completed';

নির্দিষ্ট কলাম গোনা (NULL বাদে):

-- যদি কোনো ইউজারের ইমেইল NULL থাকে, তবে তাকে কাউন্ট করবে না
SELECT COUNT(email) FROM users;

২. SUM() — যোগফল বের করা

টাকা-পয়সা বা কোয়ান্টিটি যোগ করতে লাগে।

-- মোট কত টাকার সেল হয়েছে
SELECT SUM(amount) FROM payments;

৩. AVG() — গড় মান বের করা

-- স্টুডেন্টদের গড় নাম্বার কত
SELECT AVG(score) FROM students;

৪. MIN() এবং MAX() — সর্বনিম্ন ও সর্বোচ্চ

-- সবচেয়ে সস্তা এবং দামি প্রোডাক্টের দাম
SELECT MIN(price) AS lowest_price, MAX(price) AS highest_price 
FROM products;

৪.২ GROUP BY — ক্যাটাগরি অনুযায়ী ভাগ করা

SUM বা COUNT তো পুরো টেবিলের ওপর কাজ করে। কিন্তু যদি বলা হয় "শহর অনুযায়ী" ইউজার সংখ্যা কত? তখন আমাদের ডেটাকে গ্রুপ বা ভাগ করতে হয়।

সিনট্যাক্স:

SELECT column_name, COUNT(*) 
FROM table_name 
GROUP BY column_name;

উদাহরণ (শহর অনুযায়ী ইউজার):

SELECT city, COUNT(*) AS total_users
FROM users
GROUP BY city;

আউটপুট দেখতে যেমন হবে:

citytotal_users
Dhaka120
Rajshahi40
Chittagong80

💡 মনে রাখবেন: SELECT-এ আপনি যেই কলামের নাম লিখবেন (যেমন city), GROUP BY-তে অবশ্যই সেই কলামের নাম থাকতে হবে।

৪.৩ Multiple Column GROUP BY

কখনও কখনও এক লেভেলে গ্রুপ করলে চলে না। যেমন: "কোন দেশের কোন শহরে কত ইউজার?"

SELECT country, city, COUNT(*) AS total_users
FROM users
GROUP BY country, city;

এখানে প্রথমে country অনুযায়ী ভাগ হবে, তারপর সেই দেশের ভেতরে city অনুযায়ী ভাগ হবে।

৪.৪ WHERE vs HAVING (Most Important Interview Question)

জুনিয়র এবং মিড-লেভেল ইন্টারভিউতে এটি প্রায়ই জিজ্ঞেস করা হয়। দুটোর কাজই ফিল্টার করা, কিন্তু পার্থক্য কোথায়?

বৈশিষ্ট্যWHEREHAVING
কখন কাজ করে?গ্রুপিং হওয়ার আগেগ্রুপিং হওয়ার পরে
কিসের ওপর কাজ করে?প্রতিটি আলাদা রো-এর (Row) ওপরগ্রুপ করা রেজাল্ট বা Aggregate Value-এর ওপর
Aggregate Function?SUM, COUNT ব্যবহার করা যায় নাSUM, COUNT ব্যবহার করা যায়

❌ Scenario 1: ভুল পদ্ধতি (Common Mistake)

-- এটি এরর দেবে! কারণ গ্রুপিংয়ের আগে total_users বলতে কিছু নেই।
SELECT city, COUNT(*) AS total_users 
FROM users 
GROUP BY city 
HAVING city = 'Dhaka'; 
-- যদিও এটি MySQL এর কিছু ভার্সনে রান করতে পারে, কিন্তু লজিক্যালি এটি WHERE হওয়া উচিত।

✅ Scenario 2: সঠিক পদ্ধতি (WHERE)

যদি আমরা শুধু ঢাকার ডেটা নিয়ে কাজ করতে চাই, তাহলে আগে ফিল্টার করাই ভালো।

SELECT city, COUNT(*) AS total_users 
FROM users 
WHERE city = 'Dhaka'  -- আগে ফিল্টার করলাম
GROUP BY city;        -- তারপর গ্রুপ করলাম

✅ Scenario 3: সঠিক পদ্ধতি (HAVING)

যদি বলা হয়, "যেই শহরগুলোতে ১০০ এর বেশি ইউজার আছে, শুধু তাদের দেখাও"

SELECT city, COUNT(*) AS total_users
FROM users
GROUP BY city
HAVING total_users > 100;

এখানে WHERE total_users > 100 দিলে কাজ করত না, কারণ total_users ক্যালকুলেট হয় গ্রুপিংয়ের পর।

৪.৫ Real-World Reporting Queries

একজন সিনিয়র ডেভেলপারকে প্রায়ই ড্যাশবোর্ডের জন্য কুয়েরি লিখতে হয়।

১. টপ ৫ বেস্ট সেলিং প্রোডাক্ট:

SELECT product_id, SUM(quantity) AS total_sold
FROM order_items
GROUP BY product_id
ORDER BY total_sold DESC
LIMIT 5;

২. মাসিক আয় (Monthly Revenue):

SELECT DATE_FORMAT(created_at, '%Y-%m') AS month, 
       SUM(amount) AS revenue
FROM payments
GROUP BY DATE_FORMAT(created_at, '%Y-%m');

৩. প্রতিদিন কতগুলো সাইন-আপ হয়েছে:

SELECT DATE(created_at) AS day, COUNT(*) AS total_signup
FROM users
GROUP BY DATE(created_at)
ORDER BY day DESC;

৪. ডেলিভারি ম্যানদের পারফরম্যান্স:

SELECT delivery_man_id, COUNT(*) AS delivered_count
FROM orders
WHERE status = 'delivered'
GROUP BY delivery_man_id
ORDER BY delivered_count DESC;

৪.৬ GROUP BY Performance Optimization (Senior Level Tips)

বড় ডেটাবেসে (মিলিয়ন ডেটা) GROUP BY খুব স্লো হতে পারে। এখানে সিনিয়রদের ট্রিকস:

১. প্রি-ফিল্টারিং (Pre-filtering):

যতটা সম্ভব WHERE দিয়ে ডেটা কমিয়ে ফেলুন গ্রুপিংয়ের আগে। ১ মিলিয়ন রো গ্রুপ করার চেয়ে ১০০০ রো গ্রুপ করা অনেক ফাস্ট।

২. ইনডেক্স ব্যবহার করা:

যেই কলাম দিয়ে GROUP BY করছেন (যেমন city), সেটি যদি ইনডেক্স করা থাকে, তাহলে কুয়েরি সুপার ফাস্ট হবে। কারণ ইনডেক্সে ডেটা আগে থেকেই সাজানো থাকে।

৩. ফাংশন পরিহার করা:

-- ❌ Slow (Index কাজ করে না)
GROUP BY YEAR(created_at);

-- ✅ Fast (যদি created_year নামে আলাদা কলাম থাকে)
GROUP BY created_year;

ব্যাখ্যা: কোনো কলামের ওপর ফাংশন চালালে MySQL ইনডেক্স ব্যবহার করতে পারে না (একে non-sargable query বলে)। বড় টেবিলের ক্ষেত্রে রিপোর্টিংয়ের জন্য আলাদা কলাম রাখা বুদ্ধিমানের কাজ।

🎯 অধ্যায় ৪ এর সারাংশ (Summary)

এই অধ্যায়ে আপনি শিখলেন:

  • Aggregate Functions: COUNT, SUM, AVG দিয়ে হিসাব-নিকাশ করা।
  • GROUP BY: ডেটাকে ক্যাটাগরি অনুযায়ী ভাগ করে রিপোর্ট তৈরি করা।
  • WHERE vs HAVING: WHERE র ডেটা ফিল্টার করে, HAVING প্রসেসড/গ্রুপড ডেটা ফিল্টার করে।
  • Reporting: ড্যাশবোর্ডের জন্য মাসিক/দৈনিক রিপোর্ট তৈরির কুয়েরি।
  • Optimization: পারফরম্যান্স বাড়ানোর জন্য WHERE এর সঠিক ব্যবহার এবং ফাংশন এড়িয়ে চলা।

✨ আমরা এখন বেসিক থেকে ইন্টারমিডিয়েট লেভেলে চলে এসেছি। পরবর্তী অধ্যায়ে আমরা শিখব SQL-এর সবচেয়ে শক্তিশালী এবং রোমাঞ্চকর টপিক—JOINS (Inner, Left, Right, Full)

প্রস্তুত তো? 🚀

🔒 কপিরাইট সুরক্ষিত কন্টেন্ট 🔒

কপি, স্ক্রিনশট, প্রিন্ট করা সম্পূর্ণ নিষিদ্ধ।