Quiz 2

06 - SQL Aggregation & Grouping

1497 words
7 min read
Python Week 1: the first filter for runtime behavior
Visual companion
Python
Type and operator map

Python Week 1: the first filter for runtime behavior

View
Revision summary

What this note is really saying

Short form

# 06 - SQL Aggregation & Grouping ## 🎯 Learning Objectives After reading this topic, you will be able to: - Use aggregate functions (COUNT, SUM, AVG, MIN, MAX) correctly - Write GROUP BY clauses to group rows for aggregation - Filter groups with the HAVING clause - Understand NULL behavior in aggregations - Explain...

06 - SQL Aggregation & Grouping

🎯 Learning Objectives

After reading this topic, you will be able to:
  • Use aggregate functions (COUNT, SUM, AVG, MIN, MAX) correctly
  • Write GROUP BY clauses to group rows for aggregation
  • Filter groups with the HAVING clause
  • Understand NULL behavior in aggregations
  • Explain 3-valued logic (TRUE, FALSE, UNKNOWN)

📋 Prerequisites

📖 Core Content

6.1 Intuition: From Individual Rows to Summaries

Sometimes you don't want individual rows — you want summaries:
  • What's the average salary in each department?
  • How many students are in each course?
  • What's the total budget across all departments? Aggregation answers these questions. Instead of returning one row per data item, it returns one row per group.
Why This Matters: In real-world analytics, you rarely look at raw data — you look at aggregated summaries. Dashboards, reports, and business intelligence all rely on aggregation.

6.2 Aggregate Functions

SQL provides five standard aggregate functions:
FunctionReturnsNotes
COUNT(*)Number of rowsCounts ALL rows including NULLs
COUNT(column)Number of non-NULL valuesIgnores NULLs
COUNT(DISTINCT column)Number of unique non-NULL valuesIgnores NULLs
SUM(column)Sum of valuesWorks only on numeric columns
AVG(column)Average of valuesWorks only on numeric columns
MIN(column)Minimum valueWorks on numeric, string, date
MAX(column)Maximum valueWorks on numeric, string, date

Basic Examples

sql
-- Total number of instructors
SELECT COUNT(*) FROM instructor;
-- Result: 12
-- Number of instructors who have a non-NULL salary
SELECT COUNT(salary) FROM instructor;
-- 12 (if all have salaries)
-- Number of distinct departments
SELECT COUNT(DISTINCT dept_name) FROM instructor;
-- Result: 7
-- Average salary
SELECT AVG(salary) FROM instructor;
-- Result: ~75500
-- Maximum salary
SELECT MAX(salary) FROM instructor;
-- Result: 95000

6.3 GROUP BY: Grouping Rows

The power of aggregation comes with GROUP BY — it partitions rows into groups and applies the aggregate to each group:
sql
SELECT dept_name, AVG(salary) AS avg_salary
FROM instructor
GROUP BY dept_name;
Execution order: FROM → WHERE → GROUP BY → SELECT → ORDER BY How it works: (Diagram) Result:
dept_nameavg_salary
Comp. Sci.83000.00
Finance85000.00
Music40000.00
Physics91000.00
History61000.00
Biology72000.00
Elec. Eng.80000.00

Critical Rule: SELECT columns must be in GROUP BY or aggregated

sql
-- ERROR: name is neither in GROUP BY nor aggregated
SELECT dept_name, name, AVG(salary)
FROM instructor
GROUP BY dept_name;
For each salary group, which name should appear? There are multiple! The DBMS won't guess.
sql
-- CORRECT
SELECT dept_name, AVG(salary)
FROM instructor
GROUP BY dept_name;
Exception: MySQL/SQLite allow this (returning the first value encountered), but PostgreSQL and standard SQL reject it. Write portable SQL by following the rule.

6.4 HAVING: Filtering Groups

WHERE filters rows before grouping. HAVING filters groups after aggregation.
sql
SELECT dept_name, AVG(salary) AS avg_salary
FROM instructor
GROUP BY dept_name
HAVING AVG(salary) > 70000;
Execution order: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
dept_nameavg_salary
Comp. Sci.83000.00
Finance85000.00
Physics91000.00
Biology72000.00
Elec. Eng.80000.00

WHERE vs. HAVING: When to Use Which

sql
-- WHERE filters rows BEFORE grouping
SELECT dept_name, AVG(salary)
FROM instructor
WHERE salary > 50000  -- excludes low salaries before averaging
GROUP BY dept_name;
-- HAVING filters groups AFTER grouping
SELECT dept_name, AVG(salary)
FROM instructor
GROUP BY dept_name
HAVING AVG(salary) > 50000;  -- excludes depts with low avg salary
Rule of thumb:
  • Filter on individual rows → WHERE
  • Filter on aggregate results → HAVING
  • You can use both in the same query

6.5 NULLs and 3-Valued Logic

NULL means "unknown" or "does not exist." It creates a 3-valued logic:
TRUEFALSEUNKNOWN

Truth Tables

AND:
ABA AND B
TRUEFALSEFALSE
TRUEUNKNOWNUNKNOWN
FALSEUNKNOWNFALSE
UNKNOWNUNKNOWNUNKNOWN
OR:
ABA OR B
TRUEUNKNOWNTRUE
FALSEUNKNOWNUNKNOWN
UNKNOWNUNKNOWNUNKNOWN
NOT:
ANOT A
TRUEFALSE
FALSETRUE
UNKNOWNUNKNOWN
Key consequence: WHERE clause treats UNKNOWN as FALSE. So WHERE salary > NULL returns no rows.

NULL in Aggregations

sql
-- All aggregate functions except COUNT(*) ignore NULLs
SELECT AVG(salary) FROM instructor;  -- average of only non-NULL salaries
SELECT COUNT(*) FROM instructor;      -- counts ALL rows
SELECT COUNT(salary) FROM instructor; -- counts only rows with non-NULL salary
If all values are NULL, what does each function return?
  • COUNT(*) → number of rows (not 0!)
  • COUNT(column) → 0
  • SUM(column) → NULL
  • AVG(column) → NULL
  • MIN(column) → NULL
  • MAX(column) → NULL

6.6 Worked Examples

Example 1: Department salary statistics

sql
SELECT dept_name,
       COUNT(*) AS num_instructors,
       AVG(salary) AS avg_salary,
       MIN(salary) AS min_salary,
       MAX(salary) AS max_salary
FROM instructor
GROUP BY dept_name;
dept_namenum_instructorsavg_salarymin_salarymax_salary
Biology1720007200072000
Comp. Sci.3830006500092000
Elec. Eng.1800008000080000
Finance2850008000090000
History2610006000062000
Music1400004000040000
Physics2910008700095000

Example 2: Departments with at least 2 instructors

sql
SELECT dept_name, COUNT(*) AS count
FROM instructor
GROUP BY dept_name
HAVING COUNT(*) >= 2;
dept_namecount
Comp. Sci.3
Finance2
History2
Physics2

Example 3: WHERE + GROUP BY + HAVING together

Find departments where the average salary of instructors earning > 60000 is above 80000:
sql
SELECT dept_name, AVG(salary) AS avg_high_salary
FROM instructor
WHERE salary > 60000       -- 1. Only consider high-salary instructors
GROUP BY dept_name          -- 2. Group by department
HAVING AVG(salary) > 80000; -- 3. Only departments with avg > 80000

📐 Key Formulas / Concepts

ConceptDescription
Aggregate functionsCOUNT, SUM, AVG, MIN, MAX — summarize multiple rows into one value
GROUP BYPartitions rows into groups; one result row per group
HAVINGFilters groups after aggregation (like WHERE for groups)
NULL in aggregationIgnored by all aggregates except COUNT(*)
3-valued logicTRUE, FALSE, UNKNOWN; UNKNOWN treated as FALSE in WHERE

⚠️ Common Pitfalls

Pitfall 1: Using column not in GROUP BY in SELECT

The Mistake: SELECT dept_name, name, AVG(salary) FROM instructor GROUP BY dept_name Why It's Wrong: name is neither in GROUP BY nor aggregated. For each department group, there are multiple names — which one should appear? Correct: Either add name to GROUP BY (if you want per-name groups) or remove it from SELECT.

Pitfall 2: Using HAVING without GROUP BY

The Mistake: SELECT AVG(salary) FROM instructor HAVING AVG(salary) > 70000 Why It's Wrong: It's valid SQL (means: average salary > 70000? return one row), but often misused. Without GROUP BY, the whole table is one group. Better: Understand that HAVING without GROUP BY treats the entire result as a single group.

Pitfall 3: Confusing WHERE and HAVING

The Mistake: Using WHERE to filter on aggregated values:
sql
SELECT dept_name, AVG(salary) FROM instructor WHERE AVG(salary) > 70000 GROUP BY dept_name;
Why It's Wrong: WHERE operates on individual rows, but AVG(salary) is not known until after grouping. Correct: Use HAVING for aggregate conditions:
sql
SELECT dept_name, AVG(salary) FROM instructor GROUP BY dept_name HAVING AVG(salary) > 70000;

📝 Practice Questions

Q1. Write a query to find the number of instructors in each department.

Answer
sql
SELECT dept_name, COUNT(*) AS num_instructors
FROM instructor
GROUP BY dept_name;

Q2. Find the average salary of instructors in departments with at least 2 instructors.

Answer
sql
SELECT dept_name, AVG(salary) AS avg_salary
FROM instructor
GROUP BY dept_name
HAVING COUNT(*) >= 2;

Q3. What is the difference between COUNT(*) and COUNT(column)?

Answer
  • COUNT(*) counts all rows in the group (including rows with NULL values)
  • COUNT(column) counts only non-NULL values in that column
If a group has 10 rows but column salary has 2 NULLs, COUNT(*) = 10 but COUNT(salary) = 8.

Q4. What does SUM(salary) return if all salaries are NULL?

Answer
SUM(salary) returns NULL, not 0. All aggregate functions except COUNT(*) return NULL when operating on all-NULL values.
COUNT(*) would return the number of rows. COUNT(salary) would return 0.

Q5. Write a query to find departments whose total budget (sum of instructor salaries) exceeds 200000.

Answer
sql
SELECT dept_name, SUM(salary) AS total_salary
FROM instructor
GROUP BY dept_name
HAVING SUM(salary) > 200000;

Q6. Explain the difference between WHERE and HAVING in a GROUP BY query.

Answer
  • WHERE: Filters rows before grouping. Conditions can reference individual columns but NOT aggregate functions.
  • HAVING: Filters groups after aggregation. Conditions CAN reference aggregate functions.
Execution order: FROM → WHERE → GROUP BY → HAVING → SELECT

Q7. How does NULL behave in AND/OR/NOT operations?

Answer
  • NULL AND TRUE = UNKNOWN
  • NULL AND FALSE = FALSE (short-circuit!)
  • NULL OR TRUE = TRUE (short-circuit!)
  • NULL OR FALSE = UNKNOWN
  • NOT NULL = UNKNOWN
WHERE clause treats UNKNOWN as FALSE, so the row is not included.

Q8. Find the minimum and maximum salary for each department, ordered by department name.

Answer
sql
SELECT dept_name, MIN(salary) AS min_salary, MAX(salary) AS max_salary
FROM instructor
GROUP BY dept_name
ORDER BY dept_name;

🔗 Cross-References

Document outline

Keep your place and jump directly to a heading.

Table of Contents
System Normal // Awaiting Context

Intelligence Hub

Navigate the knowledge graph to generate context. The Hub adapts dynamically to surface backlinks, related notes, and metadata insights.