অধ্যায় 5 · টেবিল জোড়া লাগানো
LEFT, RIGHT, FULL, CROSS আর সেলফ জয়েন
- পৃষ্ঠা 17 / 22
- 15 মিনিট পড়া
ইনার জয়েন শুধু সেই সারিগুলো রাখে যারা জোড়া পেয়েছে। অথচ প্রায়ই জোড়া না-পাওয়া সারিগুলোই আসল উত্তর: যে কাস্টমাররা সাইন আপ করে কখনো অর্ডার করেননি, যে প্রোডাক্ট কেউ কেনেনি, যে ক্যাটাগরিতে কিছুই নেই। আউটার জয়েন (outer join) এই সারিগুলো রাখে আর যে দিকটা নেই সেটা NULL দিয়ে ভরে দেয়। এই পাতায় আরও আছে ভিন্ন আকারের দুটো জয়েন: CROSS JOIN, যা সবকিছুকে সবকিছুর সাথে জোড়ে, আর সেলফ জয়েন (self join), যা একটা টেবিলকে নিজের সাথেই জোড়ে।
ডেটাসেটে যেন কিছু বাদ না পড়ে, ডেটার কাজে সেটা নিশ্চিত করে এই জয়েনগুলোই। শুধু অর্ডার করা কাস্টমারদের দিয়ে ট্রেন করা চার্ন মডেল এমন কাস্টমার কখনো দেখেনি যিনি কিছু না কিনেই চলে গেছেন। ইনার জয়েন দিয়ে বানানো মাসিক বিক্রির চার্ট শূন্য বিক্রির মাসগুলো শূন্য হিসেবে না দেখিয়ে বাদই দিয়ে দেয়। LEFT JOIN আর CROSS JOIN দুটো সমস্যারই সমাধান।
যা শিখবেন
LEFT JOIN, আর না-মেলা সারি খোঁজার "LEFT JOIN … IS NULL" প্যাটার্ন- একই শর্ত
ON-এ আরWHERE-এ দিলে ফলাফল কেন আলাদা হয় RIGHT JOINআরFULL OUTER JOIN, আর MySQL-এ কীভাবে ফুল জয়েন পাবেন- সম্পূর্ণ গ্রিডের জন্য
CROSS JOIN(প্রতিটা মাস × প্রতিটা ক্যাটাগরি) - সেলফ জয়েন: কর্মী আর তাদের ম্যানেজার
LEFT JOIN: বাঁ দিকের টেবিলের প্রতিটা সারি রাখা
A LEFT JOIN B ON … ফেরত দেয় A-এর (বাঁ দিকে লেখা টেবিল) প্রতিটা সারি। A-এর কোনো সারির B-তে জোড়া থাকলে প্রতিটা জোড়ার জন্য একটা করে সারি আসে, ঠিক ইনার জয়েনের মতো। জোড়া না থাকলেও সারিটা একবার আসে, আর B-এর সব কলাম হয় NULL।
SELECT c.customer_id, c.name, o.order_id, o.status
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
WHERE c.customer_id IN (1, 4, 8)
ORDER BY c.customer_id, o.order_id;+-------------+--------------+----------+-----------+
| customer_id | name | order_id | status |
+-------------+--------------+----------+-----------+
| 1 | Nadia Rahman | 1 | delivered |
| 1 | Nadia Rahman | 3 | delivered |
| 1 | Nadia Rahman | 10 | delivered |
| 4 | Rafiq Islam | 5 | cancelled |
| 8 | Karim Uddin | NULL | NULL |
+-------------+--------------+----------+-----------+Nadia-র তিনটা অর্ডার, Rafiq-এর একটা, Karim-এর একটাও না — তবু Karim আছেন, অর্ডারের জায়গায় NULL নিয়ে। ইনার জয়েন হলে শুধু প্রথম চারটা সারি আসত। (LEFT OUTER JOIN একই জিনিস; OUTER ঐচ্ছিক।)
| জয়েন | যে সারিগুলো থাকে | না-থাকা দিক ভরা হয় |
|---|---|---|
INNER JOIN | শুধু মেলা জোড়া | — |
LEFT JOIN | মেলা জোড়া + বাঁ টেবিলের না-মেলা সারি | ডান টেবিলের কলামে NULL |
RIGHT JOIN | মেলা জোড়া + ডান টেবিলের না-মেলা সারি | বাঁ টেবিলের কলামে NULL |
FULL OUTER JOIN | মেলা জোড়া + দুই দিকের না-মেলা সারি | যে দিক নেই সেখানে NULL |
CROSS JOIN | প্রতিটা সারি প্রতিটা সারির সাথে | — (কোনো শর্ত নেই) |
না-মেলা সারি খোঁজা: LEFT JOIN … IS NULL
না-মেলা সারিতে যেহেতু ডান টেবিলের কলামগুলো NULL, শুধু সেই সারিগুলো রাখলেই উত্তর মেলে "কাদের কোনো জোড়া নেই?"। একে বলে অ্যান্টি-জয়েন (anti-join):
SELECT c.customer_id, c.name, c.email
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
WHERE o.order_id IS NULL
ORDER BY c.customer_id;+-------------+-------------+-------------------+
| customer_id | name | email |
+-------------+-------------+-------------------+
| 8 | Karim Uddin | karim@example.com |
+-------------+-------------+-------------------+"প্রথম অর্ডারটা সেরে ফেলুন" ইমেইলের জন্য মার্কেটিং টিম ঠিক এই তালিকাটাই চায়। এমন কলাম পরীক্ষা করুন যা সত্যিকারের মিলে কখনো NULL হতে পারে না — ডান টেবিলের প্রাইমারি কি বা জয়েন কলামটাই — যাতে সেখানে NULL মানে একটাই: "জোড়া নেই"। একই প্যাটার্নে পাওয়া যায় কখনো বিক্রি না হওয়া প্রোডাক্ট:
SELECT p.product_id, p.name
FROM products p
LEFT JOIN order_items oi ON oi.product_id = p.product_id
WHERE oi.product_id IS NULL
ORDER BY p.product_id;+------------+-----------------------------+
| product_id | name |
+------------+-----------------------------+
| 10 | Noise-Cancelling Headphones |
| 11 | Gift Card |
+------------+-----------------------------+LEFT JOIN-এর পর গোনা: COUNT(*) বনাম COUNT(column)
LEFT JOIN-এর পর গ্রুপ ধরে গুনলে না-মেলা সারিটাও একটা সারি। COUNT(*) সেটা গোনে; COUNT(o.order_id) NULL বাদ দেয়:
SELECT c.name, COUNT(*) AS count_star, COUNT(o.order_id) AS orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
GROUP BY c.customer_id, c.name
ORDER BY orders DESC, c.name;+-----------------+------------+--------+
| name | count_star | orders |
+-----------------+------------+--------+
| Farhana Akter | 3 | 3 |
| Nadia Rahman | 3 | 3 |
| Tanvir Ahmed | 3 | 3 |
| Imran Hossain | 2 | 2 |
| Mitu Das | 1 | 1 |
| Rafiq Islam | 1 | 1 |
| Sadia Chowdhury | 1 | 1 |
| Karim Uddin | 1 | 0 |
+-----------------+------------+--------+Karim-এর অর্ডারের সঠিক সংখ্যা ০, ১ নয়। LEFT JOIN-এর পর ডান টেবিলের একটা কলাম গুনুন। যোগফলকে COALESCE(SUM(…), 0)-এ মুড়ে দিন, কারণ কিছুই না থাকলে যোগফল হয় NULL। প্রতি কাস্টমারের ফিচার টেবিল এভাবেই বানানো হয়, যেখানে শূন্যসহ প্রত্যেক কাস্টমারের একটা সারি থাকে।
শর্ত ON-এ, নাকি WHERE-এ?
ইনার জয়েনে কোনো পার্থক্য নেই। আউটার জয়েনে উত্তরই বদলে যায়। ধরুন আমরা চাই প্রত্যেক কাস্টমার আর তার ডেলিভার হওয়া অর্ডারের সংখ্যা। স্ট্যাটাসের পরীক্ষাটা WHERE-এ দিলে:
SELECT c.name, COUNT(o.order_id) AS delivered_orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
WHERE o.status = 'delivered'
GROUP BY c.customer_id, c.name
ORDER BY c.customer_id;+-----------------+------------------+
| name | delivered_orders |
+-----------------+------------------+
| Nadia Rahman | 3 |
| Tanvir Ahmed | 2 |
| Farhana Akter | 3 |
| Sadia Chowdhury | 1 |
| Imran Hossain | 1 |
| Mitu Das | 1 |
+-----------------+------------------+Rafiq আর Karim উধাও। LEFT JOIN Karim-কে রেখেছিল o.status = NULL নিয়ে, তারপর WHERE o.status = 'delivered' তাকে ফেলে দিয়েছে, কারণ NULL = 'delivered' সত্য নয়। Rafiq-এর একমাত্র অর্ডারটা ক্যানসেল করা, তাই তিনিও বাদ। ডান টেবিলের ওপর WHERE-এর পরীক্ষা চুপচাপ LEFT JOIN-কে ইনার জয়েন বানিয়ে ফেলে। পরীক্ষাটা ON-এ সরিয়ে নিন:
SELECT c.name, COUNT(o.order_id) AS delivered_orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id AND o.status = 'delivered'
GROUP BY c.customer_id, c.name
ORDER BY c.customer_id;+-----------------+------------------+
| name | delivered_orders |
+-----------------+------------------+
| Nadia Rahman | 3 |
| Tanvir Ahmed | 2 |
| Farhana Akter | 3 |
| Rafiq Islam | 0 |
| Sadia Chowdhury | 1 |
| Imran Hossain | 1 |
| Mitu Das | 1 |
| Karim Uddin | 0 |
+-----------------+------------------+শর্ত ON-এ | শর্ত WHERE-এ | |
|---|---|---|
| কখন কাজ করে | মেলানোর সময়: ডান দিকের কোন সারি জোড়া হিসেবে গণ্য হবে তা ঠিক করে | জয়েনের পরে: জোড়া লাগানো কোন সারি টিকে থাকবে তা ঠিক করে |
| জোড়াহীন বাঁ দিকের সারি | থাকে, NULL নিয়ে | বাদ পড়ে, যদি পরীক্ষাটা ডান টেবিলের কলামে হয় |
| Rafiq (শুধু একটা ক্যানসেল অর্ডার) | থাকে, ০টা ডেলিভারড | বাদ |
| Karim (কোনো অর্ডার নেই) | থাকে, ০টা ডেলিভারড | বাদ |
মোটা দাগের নিয়ম: ডান টেবিলের যে পরীক্ষা ঠিক করে কোন জোড়াগুলো গণ্য হবে, সেটা যাবে ON-এ; বাঁ টেবিলের যে পরীক্ষা ঠিক করে কোন সারিগুলো রিপোর্টে আসবে, সেটা যাবে WHERE-এ। অ্যান্টি-জয়েনের WHERE o.order_id IS NULL ইচ্ছাকৃত ব্যতিক্রম: সেখানে আপনি জয়েনের পরেই ফিল্টার করতে চান।
RIGHT JOIN
A RIGHT JOIN B রাখে B-এর প্রতিটা সারি। এটা টেবিল অদলবদল করা LEFT JOIN, তাই categories c LEFT JOIN products p একই সারি দেয়। SQLite RIGHT আর FULL জয়েন সমর্থন করে শুধু 3.39 (২০২২) সংস্করণ থেকে; MySQL আর PostgreSQL-এ RIGHT JOIN আছে বহু দশক ধরে।
SELECT c.name AS category, p.name AS product
FROM products p
RIGHT JOIN categories c ON p.category_id = c.category_id
WHERE c.category_id IN (3, 5)
ORDER BY c.category_id, p.product_id;+-----------+-------------------------+
| category | product |
+-----------+-------------------------+
| Courses | SQL Masterclass |
| Courses | AI Engineering Bootcamp |
| Furniture | NULL |
+-----------+-------------------------+বেশিরভাগ টিম শুধু LEFT JOIN লেখে: যে টেবিলের সারি রাখতেই হবে সেটা আগে বসান, তাহলে কোয়েরিটা সেই ক্রমেই পড়া যায়। একই কোয়েরিতে LEFT আর RIGHT মেশালে বোঝা কঠিন হয়ে যায়।
FULL OUTER JOIN
ফুল জয়েন দুই দিকেরই না-মেলা সারি রাখে। প্রোডাক্ট আর ক্যাটাগরিতে দুই দিকেই একা পড়ে থাকা সারি আছে: Gift Card-এর কোনো ক্যাটাগরি নেই, আর Furniture-এ কোনো প্রোডাক্ট নেই।
SELECT p.name AS product, c.name AS category
FROM products p
FULL OUTER JOIN categories c ON p.category_id = c.category_id
ORDER BY category, product;+-----------------------------+-------------+
| product | category |
+-----------------------------+-------------+
| Gift Card | NULL |
| Laptop Stand | Accessories |
| USB-C Hub | Accessories |
| Hands-On Machine Learning | Books |
| Python Crash Course | Books |
| AI Engineering Bootcamp | Courses |
| SQL Masterclass | Courses |
| 27-inch Monitor | Electronics |
| Mechanical Keyboard | Electronics |
| Noise-Cancelling Headphones | Electronics |
| Wireless Mouse | Electronics |
| NULL | Furniture |
+-----------------------------+-------------+বারোটা সারি: দশটা মেলা জোড়া, আর প্রতি দিক থেকে একটা করে একা সারি। রিকনসিলিয়েশনের (reconciliation) হাতিয়ার এই ফুল জয়েন: দুটো তালিকা মিলিয়ে দেখা (গতকালের এক্সপোর্ট আর আজকের, পেমেন্ট টেবিল আর ব্যাংক স্টেটমেন্ট) আর কী শুধু এক দিকে আছে তা বের করা। PostgreSQL একই কোয়েরি হুবহু চালায়, তবে সে NULL সবার শেষে সাজায়, তাই সেখানে Gift Card-এর সারিটা ওপরে না এসে একদম নিচে আসে।
MySQL-এ FULL JOIN নেই
-- MySQL
SELECT p.name AS product, c.name AS category
FROM products p
FULL OUTER JOIN categories c ON p.category_id = c.category_id;Error: ERROR 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'FULL OUTER JOIN categories c ON p.category_id = c.category_id' at line 4প্রচলিত সমাধান: LEFT JOIN নিন (সব প্রোডাক্ট, মিলুক বা না মিলুক), তারপর না-মেলা সারিতে ফিল্টার করা একটা RIGHT JOIN দিয়ে ডান দিকের একা সারিগুলো যোগ করুন। UNION ALL দুটো ফলাফল একটার ওপর আরেকটা বসিয়ে দেয় (বিস্তারিত UNION, INTERSECT ও EXCEPT পাতায়):
-- MySQL
SELECT p.name AS product, c.name AS category
FROM products p
LEFT JOIN categories c ON p.category_id = c.category_id
UNION ALL
SELECT p.name AS product, c.name AS category
FROM products p
RIGHT JOIN categories c ON p.category_id = c.category_id
WHERE p.product_id IS NULL
ORDER BY category, product;+-----------------------------+-------------+
| product | category |
+-----------------------------+-------------+
| Gift Card | NULL |
| Laptop Stand | Accessories |
| USB-C Hub | Accessories |
| Hands-On Machine Learning | Books |
| Python Crash Course | Books |
| AI Engineering Bootcamp | Courses |
| SQL Masterclass | Courses |
| 27-inch Monitor | Electronics |
| Mechanical Keyboard | Electronics |
| Noise-Cancelling Headphones | Electronics |
| Wireless Mouse | Electronics |
| NULL | Furniture |
+-----------------------------+-------------+একই বারোটা সারি। দ্বিতীয় অংশের WHERE p.product_id IS NULL জরুরি: এটা না থাকলে দশটা মেলা জোড়া দুবার করে আসত।
CROSS JOIN: প্রতিটা সম্ভাব্য জোড়া
CROSS JOIN-এ কোনো শর্ত নেই: এটা এক টেবিলের প্রতিটা সারিকে অন্য টেবিলের প্রতিটা সারির সাথে জোড়ে (কার্টেসিয়ান প্রোডাক্ট)। ৩টা মাস আর ৫টা ক্যাটাগরি মানে ১৫টা সারি। মাসের একটা ছোট টেবিল বানাই:
CREATE TABLE months (month TEXT PRIMARY KEY);
INSERT INTO months (month) VALUES ('2026-01'), ('2026-02'), ('2026-03');
SELECT m.month, c.name AS category
FROM months m
CROSS JOIN categories c
ORDER BY m.month, c.category_id
LIMIT 6;
SELECT COUNT(*) AS grid_rows FROM months CROSS JOIN categories;+---------+-------------+
| month | category |
+---------+-------------+
| 2026-01 | Books |
| 2026-01 | Electronics |
| 2026-01 | Courses |
| 2026-01 | Accessories |
| 2026-01 | Furniture |
| 2026-02 | Books |
+---------+-------------+
+-----------+
| grid_rows |
+-----------+
| 15 |
+-----------+এটা কেন চাইবেন? কারণ "বিক্রি নেই" নিজেও একটা ডেটা। পূর্ণ গ্রিড দিয়ে শুরু করুন, তারপর বিক্রিগুলো তার ওপর LEFT JOIN করুন, তাহলে খালি ঘরগুলো হারিয়ে না গিয়ে ০ হয়ে আসবে। নিচে LEFT JOIN-এর শিকল ক্যাটাগরি → প্রোডাক্ট → অর্ডার আইটেম → অর্ডার পর্যন্ত যায় (অনেক টেবিল জয়েন পরের পাতায়); মাসের পরীক্ষাটা ON-এ আছে যাতে না-মেলা ঘরগুলো টিকে থাকে, আর COUNT(DISTINCT …) দেওয়া হয়েছে যাতে একই ক্যাটাগরির দুটো প্রোডাক্টওয়ালা অর্ডার একবারই গোনা হয়:
SELECT m.month, c.name AS category, COUNT(DISTINCT o.order_id) AS orders
FROM months m
CROSS JOIN categories c
LEFT JOIN products p ON p.category_id = c.category_id
LEFT JOIN order_items oi ON oi.product_id = p.product_id
LEFT JOIN orders o ON o.order_id = oi.order_id AND strftime('%Y-%m', o.order_date) = m.month
WHERE c.category_id <= 3
GROUP BY m.month, c.category_id, c.name
ORDER BY m.month, c.category_id;+---------+-------------+--------+
| month | category | orders |
+---------+-------------+--------+
| 2026-01 | Books | 1 |
| 2026-01 | Electronics | 2 |
| 2026-01 | Courses | 0 |
| 2026-02 | Books | 1 |
| 2026-02 | Electronics | 1 |
| 2026-02 | Courses | 1 |
| 2026-03 | Books | 1 |
| 2026-03 | Electronics | 1 |
| 2026-03 | Courses | 1 |
+---------+-------------+--------+জানুয়ারিতে Courses না থেকে ০ দেখাচ্ছে। টাইম-সিরিজ মডেল বা চার্টের জন্য এটা জরুরি: হারিয়ে যাওয়া সারি আর শূন্য এক জিনিস নয়, আর যে মডেলকে শুধু বিক্রি-থাকা মাসগুলো দেখানো হয়, সে শিখে ফেলে যে বিক্রি কখনো শূন্যে নামে না। CROSS JOIN জেনেবুঝে আর ছোট টেবিলে ব্যবহার করুন — ১,০০,০০০ সারির দুটো টেবিল থেকে আসে এক হাজার কোটি সারি।
সেলফ জয়েন: টেবিল নিজের সাথেই
employees-এ manager_id একই টেবিলের আরেকটা সারির দিকে নির্দেশ করে। প্রত্যেক কর্মীর পাশে ম্যানেজারের নাম দেখাতে টেবিলটা দুবার ব্যবহার করুন, দুটো অ্যালিয়াসে — একটা "কর্মী"-র ভূমিকায়, একটা "ম্যানেজার"-এর:
SELECT e.name AS employee, e.department, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.employee_id
ORDER BY e.employee_id;+-----------------+-------------+-----------------+
| employee | department | manager |
+-----------------+-------------+-----------------+
| Ayesha Siddiqua | Management | NULL |
| Hasan Mahmud | Engineering | Ayesha Siddiqua |
| Nusrat Jahan | Engineering | Hasan Mahmud |
| Zahid Hasan | Engineering | Hasan Mahmud |
| Lamia Karim | Data | Ayesha Siddiqua |
| Shuvo Roy | Data | Lamia Karim |
| Priya Sen | Data | Lamia Karim |
+-----------------+-------------+-----------------+Ayesha-র কোনো ম্যানেজার নেই (manager_id NULL), তাই LEFT JOIN তাকে NULL-সহ রাখে; ইনার জয়েন হলে CEO-ই বাদ পড়তেন। উল্টো দিক থেকে প্রত্যেকের সরাসরি অধীনস্থ কতজন, গুনে দেখুন:
SELECT m.name AS manager, COUNT(e.employee_id) AS direct_reports
FROM employees m
LEFT JOIN employees e ON e.manager_id = m.employee_id
GROUP BY m.employee_id, m.name
ORDER BY direct_reports DESC, m.name;+-----------------+----------------+
| manager | direct_reports |
+-----------------+----------------+
| Ayesha Siddiqua | 2 |
| Hasan Mahmud | 2 |
| Lamia Karim | 2 |
| Nusrat Jahan | 0 |
| Priya Sen | 0 |
| Shuvo Roy | 0 |
| Zahid Hasan | 0 |
+-----------------+----------------+যেখানেই কোনো টেবিল নিজেকে রেফার করে, সেখানেই সেলফ জয়েন: অর্গ চার্ট, প্যারেন্ট ক্যাটাগরিসহ ক্যাটাগরি, কমেন্টের রিপ্লাই, আর "জোড়া" ধরনের প্রশ্ন, যেমন একই অর্ডারে কেনা প্রোডাক্ট (পরের পাতা)। হায়ারার্কির যত গভীরেই হোক, ধাপে ধাপে নামতে লাগে রিকার্সিভ CTE (দেখুন অ্যাডভান্সড টিউটোরিয়ালের CTE আর রিকার্সিভ কোয়েরি)।
pandas-এ
merge-এও একই ধরনের জয়েন: how="left", "right", "outer" (ফুল) আর "cross"। এর indicator=True অপশন একটা _merge কলাম যোগ করে, যা বলে প্রতিটা সারি কোথা থেকে এসেছে — দুটো উৎসের ডেটা মেলানোর সময় দ্রুত রিকনসিলিয়েশন চেক:
import pandas as pd
from sqlhelp import con
customers = pd.read_sql("SELECT customer_id, name FROM customers", con)
orders = pd.read_sql("SELECT order_id, customer_id FROM orders", con)
both = customers.merge(orders, on="customer_id", how="left", indicator=True)
print(both["_merge"].value_counts())
print(both.loc[both["_merge"] == "left_only", ["customer_id", "name"]])_merge
both 14
left_only 1
right_only 0
Name: count, dtype: int64
customer_id name
14 8 Karim Uddinসাধারণ ভুল
- ডান টেবিলকে WHERE-এ ফিল্টার করা।
LEFT JOIN orders o … WHERE o.status = 'delivered'ডেলিভারড অর্ডার নেই এমন প্রত্যেক কাস্টমারকে বাদ দেয়। সমাধান: পরীক্ষাটাON-এ দিন (ON o.customer_id = c.customer_id AND o.status = 'delivered')। - LEFT JOIN-এর পর COUNT(*)। এটা NULL-ভরা সারিটাও গোনে, তাই অর্ডারহীন কাস্টমার দেখায় ১। সমাধান:
COUNT(o.order_id), আর যোগফলের জন্যCOALESCE(SUM(…), 0)। - ভুল টেবিল বাঁ দিকে। যে টেবিলের সারি রাখতেই হবে সেটা আগে যাবে।
orders LEFT JOIN customersকখনো Karim-কে দেখাতে পারবে না, কারণ শুরু করার মতো তার কোনো অর্ডারই নেই:SELECT o.order_id, c.name FROM orders o LEFT JOIN customers c ON o.customer_id = c.customer_id WHERE c.customer_id = 8;সমাধান:+----------+------+ | order_id | name | +----------+------+ +----------+------+FROM customers c LEFT JOIN orders o …। - অ্যান্টি-জয়েনে NULL হতে পারে এমন কলাম পরীক্ষা।
WHERE o.status IS NULLএখানে কাজ করে শুধু এজন্য যেstatusকলামটাNOT NULL। যে কলাম সত্যিকারের মিলেও NULL হতে পারে, সেখানে মেলা সারিও ধরা পড়বে। সমাধান: ডান টেবিলের প্রাইমারি কি বা জয়েন কলাম পরীক্ষা করুন। - MySQL-এ FULL JOIN, বা পুরোনো SQLite-এ RIGHT/FULL। MySQL সিনট্যাক্স এরর দেয়; 3.39-এর আগের SQLite বলে "RIGHT and FULL OUTER JOINs are not currently supported"। সমাধান: টেবিল অদলবদল করে LEFT JOIN বানান, আর ওপরের
UNION ALLপ্যাটার্নে FULL নকল করুন।
নিজে চেষ্টা করুন
- সহজ: প্রতিটা ক্যাটাগরি আর তার প্রোডাক্টের সংখ্যা দেখান, যেসব ক্যাটাগরিতে একটাও নেই সেগুলোসহ,
category_idক্রমে। - মাঝারি: প্রতিটা প্রোডাক্টের মোট বিক্রি হওয়া ইউনিট দেখান (কখনো বিক্রি না হলে ০)। সবচেয়ে কম চারটা দেখান, সমান হলে
product_idঅনুযায়ী। - কঠিন: যেসব অর্ডারের কোনো পেমেন্ট নেই সেগুলো খুঁজুন, কাস্টমারের নাম আর অর্ডারের স্ট্যাটাসসহ। তারপর এক বাক্যে বলুন অর্ডার ৫ আর ১৩-এর পেমেন্ট কেন নেই, আর সেটা ডেটার সমস্যা কিনা।
উত্তর
-- 1
SELECT c.name AS category, COUNT(p.product_id) AS products
FROM categories c
LEFT JOIN products p ON p.category_id = c.category_id
GROUP BY c.category_id, c.name
ORDER BY c.category_id;-- 2
SELECT p.name, COALESCE(SUM(oi.quantity), 0) AS units_sold
FROM products p
LEFT JOIN order_items oi ON oi.product_id = p.product_id
GROUP BY p.product_id, p.name
ORDER BY units_sold, p.product_id
LIMIT 4;-- 3
SELECT o.order_id, c.name, o.status
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id
LEFT JOIN payments pay ON pay.order_id = o.order_id
WHERE pay.payment_id IS NULL
ORDER BY o.order_id;অর্ডার ৫ ক্যানসেল হয়েছে আর অর্ডার ১৩ এখনো পেন্ডিং, তাই কোনোটারই এখন পেমেন্ট থাকার কথা নয়; পেমেন্ট ছাড়া কোনো ডেলিভারড অর্ডার থাকলে সেটাই হতো খতিয়ে দেখার মতো ডেটার সমস্যা।
সারসংক্ষেপ
LEFT JOINবাঁ টেবিলের প্রতিটা সারি রাখে আর না-পাওয়া জোড়া NULL দিয়ে ভরে;LEFT JOIN … WHERE right.key IS NULLজোড়াহীন সারি খুঁজে দেয়।- ডান টেবিলের শর্ত যাবে
ON-এ;WHERE-এ দিলে LEFT JOIN চুপচাপ ইনার জয়েন হয়ে যায়। - LEFT JOIN-এর পর ডান টেবিলের কলাম গুনুন আর যোগফল
COALESCE(…, 0)-এ মুড়ে দিন। RIGHT JOINহলো অদলবদল করা LEFT JOIN;FULL OUTER JOINদুই দিকের একা সারিই রাখে (SQLite 3.39+, PostgreSQL; MySQL-এ লাগেUNION ALLদিয়ে নকল)।CROSS JOINপূর্ণ গ্রিড বানায় যাতে খালি ঘর শূন্য হয়ে আসে; সেলফ জয়েন একটা টেবিলকে দুটো অ্যালিয়াসে দুবার ব্যবহার করে।
এরপর: অনেক টেবিল জয়েন, আর ডুপ্লিকেট সারির ফাঁদ তিন, চার, পাঁচটা টেবিল শিকলের মতো জুড়ে আসল রিপোর্ট বানায় — আর দেখায় কীভাবে একসাথে দুটো "অনেক" দিকের টেবিল জয়েন করলে আপনার মোট অঙ্ক চুপচাপ দ্বিগুণ হয়ে যায়, আর কীভাবে তা ঠিক করবেন।