You already know how to fetch data with SELECT and filter it with WHERE. Now let's make our results **organised** (sorted the way we want) and **summarised** (grouped into meaningful categories) ā the exact skills real analysts use every single day! šāØ
> [!TIP]
> **How to use these notes:** Focus especially on **ORDER BY with multiple columns**, **GROUP BY + HAVING**, and the **Golden Rule of GROUP BY** ā these three topics appear in almost every board exam! Type every query yourself. šÆ
---
## 7.1 š Introduction
When you run `SELECT * FROM Students;`, MySQL shows rows in whatever order they happen to be stored ā usually the order they were inserted. But what if you want:
- Marks sorted from **highest to lowest**?
- Students grouped by **stream**, showing each stream's average?
```mermaid
graph LR
RAW["š Unsorted, Scattered Data"]
ORDER["š¢ ORDER BY\nSort the rows"]
GROUP["š¦ GROUP BY\nSummarise into categories"]
REPORT["š Meaningful Report!"]
RAW --> ORDER --> REPORT
RAW --> GROUP --> REPORT
style ORDER fill:#2196F3,color:#fff
style GROUP fill:#9C27B0,color:#fff
style REPORT fill:#4CAF50,color:#fff
```
> **Analogy:** Imagine a teacher has 100 answer sheets in a random pile. `ORDER BY` is like **arranging them by marks**. `GROUP BY` is like **sorting them into separate stacks by Section** and then calculating each stack's average. Both make messy data USEFUL!
**Our Sample Table ā used in every example below:**
**Table: Students**
| RollNo | Name | Stream | Marks | City |
| :--- | :--- | :--- | :--- | :--- |
| 101 | Arjun | Science | 85 | Delhi |
| 102 | Zara | Commerce | 92 | Mumbai |
| 103 | Vihaan | Science | 78 | Delhi |
| 104 | Ananya | Humanities | 88 | Pune |
| 105 | Rohan | Commerce | 65 | Mumbai |
| 106 | Ishaan | Science | 92 | Delhi |
---
## 7.2 š¢ Ordering Records in Result ā ORDER BY Clause
### 7.2.1 Recalling SQL SELECT ORDER BY Clause š¶
`ORDER BY` sorts your query's result ā either **Ascending (AāZ, 0ā9)** or **Descending (ZāA, 9ā0)**.
```sql
-- order_by_basic.sql
SELECT Name, Marks FROM Students
ORDER BY Marks DESC;
```
**Output:**
```
Name Marks
Zara 92
Ishaan 92
Ananya 88
Arjun 85
Vihaan 78
Rohan 65
```
| Keyword | Meaning | Is it Default? |
| :--- | :--- | :--- |
| `ASC` | Smallest ā Largest (AāZ) | ā
Yes ā used automatically if you write nothing |
| `DESC` | Largest ā Smallest (ZāA) | ā No ā must be typed explicitly |
```sql
-- default_asc_demo.sql
SELECT Name, Marks FROM Students ORDER BY Marks;
-- Same as writing: ORDER BY Marks ASC;
```
> [!IMPORTANT]
> **Board Exam Tip**
> "What is the default sorting order in SQL if ASC/DESC is not mentioned?" ā **1-mark** question!
> Answer: **Ascending order (ASC)** is the default.
---
### 7.2.2 Ordering Data on Multiple Columns šÆ
You can sort by MORE than one column ā the second column is used to break TIES in the first column.
```sql
-- order_by_multiple.sql
SELECT Name, City, Marks FROM Students
ORDER BY City ASC, Marks DESC;
```
**Output:**
```
Name City Marks
Ishaan Delhi 92 ā Delhi group, sorted by Marks (highest first)
Arjun Delhi 85
Vihaan Delhi 78
Zara Mumbai 92 ā Mumbai group starts, sorted by Marks
Rohan Mumbai 65
Ananya Pune 88 ā Pune group
```
```mermaid
graph TD
STEP1["Step 1: Sort by City\n(Delhi, Mumbai, Pune)"]
STEP2["Step 2: WITHIN each city,\nsort by Marks DESC"]
STEP1 --> STEP2
style STEP1 fill:#2196F3,color:#fff
style STEP2 fill:#4CAF50,color:#fff
```
> **How to read this:** First, MySQL sorts everyone by City (alphabetically). THEN, for students who share the SAME city (like the 3 Delhi students), it sorts them by Marks ā highest first!
> [!IMPORTANT]
> **Board Exam Tip**
> "Write a query to display students sorted by City, and within each city by Marks in descending order." ā **2-mark** question, extremely common!
> Answer: `SELECT * FROM Students ORDER BY City ASC, Marks DESC;`
---
### 7.2.3 Ordering Data on the Basis of an Expression š§®
You can even sort by a CALCULATED value, not just an existing column!
```sql
-- order_by_expression.sql
SELECT Name, Marks, Marks * 0.95 AS AdjustedMarks
FROM Students
ORDER BY Marks * 0.95 DESC;
```
**Output:**
```
Name Marks AdjustedMarks
Zara 92 87.4
Ishaan 92 87.4
Ananya 88 83.6
Arjun 85 80.75
Vihaan 78 74.1
Rohan 65 61.75
```
> You can even use the ALIAS name instead of repeating the whole expression:
> `ORDER BY AdjustedMarks DESC` ā works exactly the same way, but cleaner!
> [!NOTE]
> **Sorting doesn't need a "real" column! š§ **
> MySQL can sort based on ANY calculation ā even one that doesn't exist as a stored column. This is very useful for reports like "sort by discounted price" without needing to permanently store that discounted price anywhere!
---
## 7.3 š Aggregate Functions
**Aggregate Functions** work on a whole GROUP of rows and squeeze them down into ONE single summary value.
```mermaid
graph LR
MANY["š Many Rows\n85, 92, 78, 88, 65, 92"]
AGG["š Aggregate Function"]
ONE["1ļøā£ ONE Result\n(e.g., Average = 83.33)"]
MANY --> AGG --> ONE
style AGG fill:#FF9800,color:#fff
style ONE fill:#4CAF50,color:#fff
```
**The Five Core Aggregate Functions:**
::: grid
::: card ā | SUM() | Adds up all values | `SUM(Marks)` ā 500
::: card š | AVG() | Finds the average | `AVG(Marks)` ā 83.33
::: card š | MAX() | Finds the largest value | `MAX(Marks)` ā 92
::: card š | MIN() | Finds the smallest value | `MIN(Marks)` ā 65
::: card š¢ | COUNT() | Counts rows/values | `COUNT(*)` ā 6
:::
```sql
-- aggregate_functions_demo.sql
SELECT SUM(Marks) FROM Students; -- Output: 500
SELECT AVG(Marks) FROM Students; -- Output: 83.33
SELECT MAX(Marks) FROM Students; -- Output: 92
SELECT MIN(Marks) FROM Students; -- Output: 65
SELECT COUNT(*) FROM Students; -- Output: 6
```
**COUNT(*) vs COUNT(column) ā The Critical Difference!**
```sql
-- count_star_vs_column.sql
SELECT COUNT(*) FROM Students; -- Counts ALL rows (even if some columns have NULL)
SELECT COUNT(City) FROM Students; -- Counts only NON-NULL values in the City column
```
| Function | What It Counts |
| :--- | :--- |
| `COUNT(*)` | Every row in the table, including rows with NULL values |
| `COUNT(column)` | Only the rows where THAT SPECIFIC column is NOT NULL |
> [!WARNING]
> **Important Rule!**
> All aggregate functions **IGNORE NULL values** during their calculation ā except `COUNT(*)`, which counts every row regardless of NULLs!
> [!IMPORTANT]
> **Board Exam Tip**
> "What is the difference between COUNT(*) and COUNT(column_name)?" ā **2-mark** question, asked EVERY year!
> Answer: `COUNT(*)` counts the total number of rows, including those with NULL values. `COUNT(column_name)` counts only rows where that specific column has a non-NULL value.
---
## 7.4 š§© Types of SQL Functions
Two families of SQL functions ā knowing the difference is a favourite exam question!
| Feature | Single-Row Functions | Aggregate (Multi-Row) Functions |
| :--- | :--- | :--- |
| **Input** | ONE row at a time | A GROUP of rows at once |
| **Output** | ONE result PER row | ONE result for the WHOLE group |
| **Examples** | `UPPER()`, `ROUND()`, `SUBSTR()` | `SUM()`, `AVG()`, `COUNT()`, `MAX()`, `MIN()` |
```mermaid
graph TD
F["SQL FUNCTIONS"]
SINGLE["š¢ Single-Row\n(Ch 6: String/Numeric/Date)\n5 rows in ā 5 results out"]
MULTI["š Aggregate\n(This Chapter!)\n5 rows in ā 1 result out"]
F --> SINGLE
F --> MULTI
style SINGLE fill:#2196F3,color:#fff
style MULTI fill:#9C27B0,color:#fff
```
```sql
-- single_row_vs_aggregate.sql
-- Single-row: gives 6 results (one per student)
SELECT UPPER(Name) FROM Students;
-- Aggregate: gives 1 result (total for everyone)
SELECT SUM(Marks) FROM Students;
```
> [!IMPORTANT]
> **Board Exam Tip**
> "Classify the following as Single-Row or Aggregate functions: UPPER(), SUM(), ROUND(), COUNT(), SUBSTR()." ā **2-mark** question!
> **Single-Row:** UPPER(), ROUND(), SUBSTR() | **Aggregate:** SUM(), COUNT()
---
## 7.5 š¦ Grouping Result ā GROUP BY
`GROUP BY` splits your data into CATEGORIES, so you can apply aggregate functions **separately to each category** instead of the whole table at once.
```mermaid
graph TD
ALL["š All 6 Students"]
GRP["GROUP BY Stream"]
SCI["š¬ Science Group\nArjun, Vihaan, Ishaan"]
COM["š¼ Commerce Group\nZara, Rohan"]
HUM["š Humanities Group\nAnanya"]
ALL --> GRP
GRP --> SCI
GRP --> COM
GRP --> HUM
style GRP fill:#FF9800,color:#fff
style SCI fill:#4CAF50,color:#fff
style COM fill:#2196F3,color:#fff
style HUM fill:#9C27B0,color:#fff
```
**Basic GROUP BY:**
```sql
-- group_by_basic.sql
SELECT Stream, AVG(Marks) FROM Students
GROUP BY Stream;
```
**Output:**
```
Stream AVG(Marks)
Science 85.0
Commerce 78.5
Humanities 88.0
```
**How the average was calculated:**
```
Science: (85 + 78 + 92) / 3 = 85.0
Commerce: (92 + 65) / 2 = 78.5
Humanities: 88 / 1 = 88.0
```
> [!IMPORTANT]
> **Board Exam Tip**
> "Write a query to display the average marks for each stream." ā **2-mark** question, asked constantly!
> Answer: `SELECT Stream, AVG(Marks) FROM Students GROUP BY Stream;`
---
### 7.5.1 Nested Groups ā Grouping on Multiple Columns šŖ
You can group by MORE than one column ā creating finer sub-groups (like a two-level filing cabinet!).
```sql
-- nested_groups.sql
SELECT City, Stream, COUNT(*) AS Total
FROM Students
GROUP BY City, Stream;
```
**Output:**
```
City Stream Total
Delhi Science 3
Mumbai Commerce 2
Pune Humanities 1
```
```mermaid
graph TD
ALL["š All Students"]
C1["Delhi"]
C2["Mumbai"]
C3["Pune"]
S1["Science: 3"]
S2["Commerce: 2"]
S3["Humanities: 1"]
ALL --> C1 --> S1
ALL --> C2 --> S2
ALL --> C3 --> S3
style C1 fill:#4CAF50,color:#fff
style C2 fill:#2196F3,color:#fff
style C3 fill:#9C27B0,color:#fff
```
> First, MySQL groups by **City**. THEN, within each city, it further groups by **Stream**. Since every city in our table happens to have only ONE stream, each combination gets its own row!
---
### 7.5.2 Placing Conditions on Groups ā HAVING Clause šÆ
`WHERE` filters individual ROWS **before** grouping. `HAVING` filters GROUPS **after** grouping. This is one of the MOST tested concepts in the entire syllabus!
```sql
-- having_demo.sql
SELECT Stream, MAX(Marks) FROM Students
GROUP BY Stream
HAVING MAX(Marks) > 90;
```
**Output:**
```
Stream MAX(Marks)
Science 92
Commerce 92
```
*(Humanities is HIDDEN ā its max mark is only 88, which fails the `> 90` condition!)*
```mermaid
graph LR
RAW["All Rows"]
W["WHERE\n(filters ROWS,\nbefore grouping)"]
G["GROUP BY\n(creates groups)"]
AGG["Aggregate Functions\nrun on each group"]
H["HAVING\n(filters GROUPS,\nafter grouping)"]
RESULT["Final Result"]
RAW --> W --> G --> AGG --> H --> RESULT
style W fill:#FF9800,color:#fff
style H fill:#F44336,color:#fff
```
**WHERE vs HAVING ā The Ultimate Comparison:**
| Feature | WHERE | HAVING |
| :--- | :--- | :--- |
| **Filters** | Individual ROWS | GROUPS (after GROUP BY) |
| **Runs** | BEFORE grouping | AFTER grouping |
| **Can use aggregate functions?** | ā No! (`WHERE AVG(Marks) > 80` is ILLEGAL) | ā
Yes! (`HAVING AVG(Marks) > 80` is correct) |
**Using WHERE and HAVING TOGETHER ā The Full Combo!**
```sql
-- combined_where_having.sql
-- "Show average marks per stream, only for Delhi students,
-- only showing streams where average is above 80"
SELECT Stream, AVG(Marks)
FROM Students
WHERE City = 'Delhi'
GROUP BY Stream
HAVING AVG(Marks) > 80
ORDER BY AVG(Marks) DESC;
```
> [!WARNING]
> **The #1 WHERE/HAVING Mistake!**
> Writing `WHERE AVG(Marks) > 80` is a SYNTAX ERROR! Aggregate functions like AVG(), SUM(), COUNT() can NEVER be used inside a WHERE clause ā you MUST use HAVING for any condition involving them!
> [!IMPORTANT]
> **Board Exam Tip**
> "Differentiate between WHERE and HAVING clause." ā **2-mark** question, asked EVERY single year without fail!
> Answer: **WHERE** filters individual rows BEFORE grouping and cannot use aggregate functions. **HAVING** filters entire GROUPS AFTER GROUP BY is applied, and CAN use aggregate functions.
---
### 7.5.3 Non-Group Expressions with GROUP BY ā ļø
**THE most important rule in this whole chapter!** When using GROUP BY, your SELECT list can ONLY contain:
1. Columns that ARE in the GROUP BY clause, OR
2. Aggregate functions
```sql
-- illegal_query.sql ā THIS WILL CAUSE AN ERROR!
SELECT Name, Stream, AVG(Marks)
FROM Students
GROUP BY Stream;
```
**Why is this wrong?** Science has THREE students: Arjun, Vihaan, Ishaan. When MySQL groups them into ONE "Science" row, which Name should it display? Arjun's? Vihaan's? Ishaan's? **It's impossible to know ā so MySQL refuses to run this query!**
```sql
-- correct_query.sql ā
THIS WORKS FINE!
SELECT Stream, AVG(Marks)
FROM Students
GROUP BY Stream;
```
```mermaid
graph TD
Q["SELECT Name, Stream, AVG(Marks)\nGROUP BY Stream;"]
PROBLEM["ā 'Name' is NOT in GROUP BY\nand NOT an aggregate function"]
CONFUSE["MySQL: 'Which Name do I show\nfor the Science group?'"]
ERROR["š« ERROR!"]
Q --> PROBLEM --> CONFUSE --> ERROR
style ERROR fill:#F44336,color:#fff
```
> [!WARNING]
> **The Golden Rule of GROUP BY!**
> Every column you SELECT must be EITHER (a) inside the GROUP BY clause, OR (b) wrapped inside an aggregate function like SUM(), AVG(), COUNT(). Breaking this rule is the #1 GROUP BY mistake students make in exams!
> [!IMPORTANT]
> **Board Exam Tip**
> "Explain why the following query gives an error: `SELECT Name, Stream, COUNT(*) FROM Students GROUP BY Stream;`" ā **2-mark** question!
> Answer: `Name` is not included in the GROUP BY clause and is not wrapped in an aggregate function. Since each Stream group contains multiple different Names, MySQL cannot determine a single Name to display for that group ā this causes an error.
---
## š The Complete Order of Execution ā SF-WG-HO!
```mermaid
graph LR
S["1ļøā£ SELECT\nColumns to show"]
F["2ļøā£ FROM\nWhich table"]
W["3ļøā£ WHERE\nFilter rows"]
G["4ļøā£ GROUP BY\nCreate groups"]
H["5ļøā£ HAVING\nFilter groups"]
O["6ļøā£ ORDER BY\nSort final result"]
S --> F --> W --> G --> H --> O
style W fill:#FF9800,color:#fff
style H fill:#F44336,color:#fff
style O fill:#4CAF50,color:#fff
```
**Memorise this order: SELECT ā FROM ā WHERE ā GROUP BY ā HAVING ā ORDER BY**
> [!TIP]
> **Memory Trick: "S-F-W-G-H-O" šµ**
> **S**illy **F**rogs **W**alk **G**iant **H**orses **O**utside
> (Or make up your own funny sentence ā whatever helps you remember the ORDER!)
---
## ā ļø Common Errors and Misconceptions
| Mistake | What Goes Wrong | Correct Understanding |
| :--- | :--- | :--- |
| ā `WHERE AVG(Marks) > 80` | Syntax Error! | ā
Use `HAVING AVG(Marks) > 80` instead |
| ā `SELECT Name, Stream, COUNT(*) ... GROUP BY Stream` | Error ā Name not in GROUP BY | ā
Only include GROUP BY columns or aggregate functions in SELECT |
| ā Thinking ORDER BY sorts BEFORE the query runs | It's the LAST step (see SF-WG-HO) | ā
ORDER BY always happens at the very END |
| ā Forgetting default sort is ASC | Assumes no keyword = random order | ā
No keyword = Ascending (ASC) by default |
| ā `COUNT(*)` and `COUNT(column)` always give same answer | Different if NULLs exist! | ā
COUNT(*) counts all rows; COUNT(column) skips NULLs in that column |
| ā HAVING used without GROUP BY | Rarely meaningful alone | ā
HAVING is designed to work WITH GROUP BY |
---
## š Quick Revision ā Exam Ready!
**ORDER BY ā One-Line Summary:**
- Default = ASC (ascending)
- Multiple columns: `ORDER BY col1, col2 DESC` ā col1 sorts first, col2 breaks ties
- Can sort by a calculated expression too, not just plain columns
**Aggregate Functions ā Quick Table:**
| Function | Purpose |
| :--- | :--- |
| `SUM()` | Total |
| `AVG()` | Average |
| `MAX()` | Largest |
| `MIN()` | Smallest |
| `COUNT(*)` | All rows |
| `COUNT(col)` | Non-NULL values in that column |
**GROUP BY ā One-Line Summary:**
- Groups rows sharing the same value(s)
- Multiple columns = nested/finer groups
- **HAVING** filters GROUPS (after); **WHERE** filters ROWS (before)
- **Golden Rule:** Every SELECT column must be in GROUP BY OR wrapped in an aggregate function
**The Execution Order:**
```
SELECT ā FROM ā WHERE ā GROUP BY ā HAVING ā ORDER BY
```
---
## šÆ Sample Board Exam Questions
### Q1: Very Short Answer [1 mark each]
a) What is the default sorting order in ORDER BY?
**ā Ascending (ASC)**
b) Which clause is used to filter groups (not rows)?
**ā HAVING**
c) What does `COUNT(*)` return for a table with 10 rows (some with NULLs)?
**ā 10 ā it counts ALL rows regardless of NULL values**
d) Name any two aggregate functions.
**ā Any two of: SUM(), AVG(), MAX(), MIN(), COUNT()**
e) Can HAVING be used without GROUP BY? (Conceptually)
**ā It CAN be written, but it's designed to work together with GROUP BY ā without it, the whole table is treated as ONE single group.**
---
### Q2: Short Answer [2 marks]
**Q: Differentiate between WHERE and HAVING clause with an example.**
**WHERE** filters individual rows BEFORE grouping and cannot use aggregate functions:
`SELECT * FROM Students WHERE City = 'Delhi';`
**HAVING** filters entire groups AFTER GROUP BY and CAN use aggregate functions:
`SELECT Stream, AVG(Marks) FROM Students GROUP BY Stream HAVING AVG(Marks) > 80;`
---
### Q3: Query Writing [3 marks]
**Q: Write SQL queries for the following:**
a) Display all students sorted by Marks in descending order.
```sql
SELECT * FROM Students ORDER BY Marks DESC;
```
b) Display the number of students in each stream.
```sql
SELECT Stream, COUNT(*) FROM Students GROUP BY Stream;
```
c) Display streams having more than 2 students.
```sql
SELECT Stream, COUNT(*) FROM Students
GROUP BY Stream HAVING COUNT(*) > 2;
```
---
### Q4: Output Based [3 marks]
**Q: Based on the Students table, what is the output of the following query?**
```sql
SELECT City, COUNT(*) AS Total FROM Students
GROUP BY City
HAVING COUNT(*) > 1;
```
**Output:**
```
City Total
Delhi 3
Mumbai 2
```
*(Pune has only 1 student, so it's excluded by HAVING COUNT(*) > 1)*
---
### Q5: Error Spotting [2 marks]
**Q: Find the error in the following query and correct it:**
```sql
SELECT Name, Stream, AVG(Marks) FROM Students GROUP BY Stream;
```
**Error:** `Name` is not in the GROUP BY clause and is not an aggregate function ā MySQL cannot decide which Name to show for a group with multiple students.
**Corrected:**
```sql
SELECT Stream, AVG(Marks) FROM Students GROUP BY Stream;
```
---
## āļø Practice Problems
1. Write a query to display students sorted by Stream (A-Z), and within each stream, by Marks (highest first).
2. Write a query to find the total marks scored by all students combined.
3. Write a query to display each city along with the maximum marks scored by any student in that city.
4. Write a query to display streams where the average marks are greater than 80, sorted by average marks descending.
5. What is the difference between `COUNT(*)` and `COUNT(City)`? Would they give different results if some City values were NULL?
6. Write a query to display the number of students in each City-Stream combination.
7. Explain why `SELECT RollNo, City, COUNT(*) FROM Students GROUP BY City;` produces an error. Rewrite it correctly.
8. Write a query showing only cities having more than 1 student, along with their average marks.
9. Predict the output: `SELECT Stream, MIN(Marks), MAX(Marks) FROM Students GROUP BY Stream;`
10. Write one complete query combining WHERE, GROUP BY, HAVING, and ORDER BY ā to show streams (excluding 'Humanities') having more than 1 student, sorted by count descending.
Back to List
Calculating...
UNIT 2 : CH 7
Dec 22, 2025
š§© Querying Using SQL
Learning Support
Need Help With This Chapter?
Save key topics for exam revision, ask questions to teachers, or submit content corrections.
Verified Doubts & Teacher Answers
Loading resolved questions for this note...