SQL Joins, Grouping and Aggregation Demystified: A Complete Practical Guide

Table of contents
- Understanding SQL Joins β With Examples
- π Table: students
- π Table: courses
- β 1. INNER JOIN (Only Matching Records)
- β 2. LEFT JOIN (All from Left + Matching from Right)
- β 3. RIGHT JOIN (All from Right + Matching from Left)
- β 4. FULL OUTER JOIN (All from Both Tables)
- β 5. CROSS JOIN (Cartesian Product)
- β 6. SELF JOIN (Join a table to itself)
- What is a NATURAL JOIN in SQL?
- SQL Aggregation and GROUP BY

Understanding SQL Joins β With Examples
Joins are used in SQL to combine rows from two or more tables based on a related column between them.
Weβll use two sample tables throughout:
π Table: students
student_id | name |
1 | Alice |
2 | Bob |
3 | Charlie |
4 | David |
π Table: courses
course_id | student_id | course_name |
101 | 1 | Math |
102 | 2 | Science |
103 | 1 | Physics |
104 | 5 | Chemistry |
β 1. INNER JOIN (Only Matching Records)
Returns only the rows where there is a match in both tables.
SELECT s.name, c.course_name
FROM students s
INNER JOIN courses c ON s.student_id = c.student_id;
π§Ύ Result:
name | course_name |
Alice | Math |
Bob | Science |
Alice | Physics |
student_id = 5
incourses
doesnβt match any student, so itβs excluded.
β 2. LEFT JOIN (All from Left + Matching from Right)
Returns all rows from the left table, and matched rows from the right. Non-matches return NULL
.
SELECT s.name, c.course_name
FROM students s
LEFT JOIN courses c ON s.student_id = c.student_id;
π§Ύ Result:
name | course_name |
Alice | Math |
Alice | Physics |
Bob | Science |
Charlie | NULL |
David | NULL |
Students with no course still appear.
β 3. RIGHT JOIN (All from Right + Matching from Left)
Returns all rows from the right table, and matched rows from the left. Non-matches return NULL
.
SELECT s.name, c.course_name
FROM students s
RIGHT JOIN courses c ON s.student_id = c.student_id;
π§Ύ Result:
name | course_name |
Alice | Math |
Bob | Science |
Alice | Physics |
NULL | Chemistry |
The course with
student_id = 5
has no matching student.
β 4. FULL OUTER JOIN (All from Both Tables)
Returns all records when there is a match in either left or right table. Non-matching rows return NULL
.
SELECT s.name, c.course_name
FROM students s
FULL OUTER JOIN courses c ON s.student_id = c.student_id;
π§Ύ Result:
name | course_name |
Alice | Math |
Bob | Science |
Alice | Physics |
Charlie | NULL |
David | NULL |
NULL | Chemistry |
All rows from both tables, including non-matches.
β 5. CROSS JOIN (Cartesian Product)
Returns all combinations of rows β each row from the first table is paired with all rows from the second.
SELECT s.name, c.course_name
FROM students s
CROSS JOIN courses c;
π§Ύ If students
has 4 rows and courses
has 4 rows β Output: 16 rows
β οΈ Use with caution β grows quickly with data size!
β 6. SELF JOIN (Join a table to itself)
Used when rows in a table are related to other rows in the same table.
Example: Employee hierarchy
CREATE TABLE employees (
emp_id INT PRIMARY KEY,
name VARCHAR(50),
manager_id INT
);
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.emp_id;
π§Ύ Result:
employee | manager |
John | Alice |
Alice | NULL |
Bob | Alice |
π§ Quick Summary Table
Join Type | Includes Non-Matching Rows From | Use Case |
INNER JOIN | β None | Only exact matches |
LEFT JOIN | β Left table only | When you want all left records |
RIGHT JOIN | β Right table only | When you want all right records |
FULL OUTER JOIN | β Both tables | Keep everything |
CROSS JOIN | β None (All combinations) | Combinatorics, testing, pivoting |
SELF JOIN | π Same table | Tree/hierarchy-based tables |
What is a NATURAL JOIN in SQL?
A NATURAL JOIN
is a type of join that automatically joins two tables based on all columns with the same name in both tables β without explicitly specifying the join condition.
β Syntax:
SELECT *
FROM table1
NATURAL JOIN table2;
β οΈ Important Points:
It finds columns with the same name in both tables and joins based on those.
You donβt need to specify
ON
orUSING
.Itβs often equivalent to:
SELECT *
FROM table1
JOIN table2 USING (common_column_name);
SQL Aggregation and GROUP BY
π What is GROUP BY
?
The GROUP BY
clause is used to group rows that have the same values in specified columns and perform aggregate functions like SUM
, COUNT
, AVG
, MAX
, and MIN
on each group.
π’ Common Aggregate Functions in SQL
Function | Description |
COUNT() | Counts the number of rows |
SUM() | Adds up values in a column |
AVG() | Calculates the average |
MIN() | Gets the minimum value |
MAX() | Gets the maximum value |
π§ͺ Sample Table: orders
order_id | customer_id | amount | product |
1 | 101 | 500 | Mobile |
2 | 102 | 1000 | Laptop |
3 | 101 | 150 | Charger |
4 | 103 | 2000 | TV |
5 | 102 | 700 | Headphones |
β
1. Using GROUP BY
with SUM()
-- Total amount spent by each customer
SELECT customer_id, SUM(amount) AS total_spent
FROM orders
GROUP BY customer_id;
π§Ύ Output:
customer_id | total_spent |
101 | 650 |
102 | 1700 |
103 | 2000 |
β
2. Using GROUP BY
with COUNT()
-- Number of orders per customer
SELECT customer_id, COUNT(*) AS order_count
FROM orders
GROUP BY customer_id;
π§Ύ Output:
customer_id | order_count |
101 | 2 |
102 | 2 |
103 | 1 |
β
3. Using GROUP BY
with AVG()
-- Average order amount per customer
SELECT customer_id, AVG(amount) AS avg_order
FROM orders
GROUP BY customer_id;
π§Ύ Output:
customer_id | avg_order |
101 | 325 |
102 | 850 |
103 | 2000 |
β
4. Using GROUP BY
with MIN()
and MAX()
-- Min and max order amount per customer
SELECT customer_id, MIN(amount) AS min_order, MAX(amount) AS max_order
FROM orders
GROUP BY customer_id;
π§Ύ Output:
customer_id | min_order | max_order |
101 | 150 | 500 |
102 | 700 | 1000 |
103 | 2000 | 2000 |
β 5. Grouping by Multiple Columns
-- Total amount per customer per product
SELECT customer_id, product, SUM(amount) AS total
FROM orders
GROUP BY customer_id, product;
π§Ύ Output:
customer_id | product | total |
101 | Mobile | 500 |
101 | Charger | 150 |
102 | Laptop | 1000 |
102 | Headphones | 700 |
103 | TV | 2000 |
β
6. Using HAVING
with GROUP BY
HAVING
is like WHERE
, but used after grouping to filter group-level results.
-- Customers who spent more than 1000 total
SELECT customer_id, SUM(amount) AS total_spent
FROM orders
GROUP BY customer_id
HAVING SUM(amount) > 1000;
π§Ύ Output:
customer_id | total_spent |
102 | 1700 |
103 | 2000 |
π Difference Between WHERE
and HAVING
Clause | Applies To | Example Use |
WHERE | Rows (before grouping) | WHERE amount > 500 |
HAVING | Groups (after grouping) | HAVING SUM(amount) > 1000 |
π§ Tips for Using Aggregations
Every column in
SELECT
(that is not aggregated) must be inGROUP BY
.You can use
ORDER BY
afterGROUP BY
to sort results.You can nest aggregate functions (e.g.,
ROUND(AVG(...))
).
Subscribe to my newsletter
Read articles from Yash Sakhareliya directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
