PyCodeItPython trace & interview prep
DashboardMastery MapSQL PracticeDailyInterviewBlogLeaderboardCommunity

Loading PyCodeIt workspace...

PyCodeIt

Free interactive learning platform for Python code tracing, SQL queries, and technical interviews. Built for bootcamp grads, computer science students, and engineers.

Python Practice

  • Learning Center
  • For loop tracing
  • List tracing
  • Dictionary tracing
  • Decorators practice
  • Python Tracing Guide
  • Python Output Questions

SQL Practice

  • SQL Fundamentals
  • Relational JOINs
  • Window Functions
  • CTEs & Set Operators
  • SQL JOINs Guide
  • Window Functions Guide

Legal

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 PyCodeIt 2026. All rights reserved.

← Education Hub|sql Guide

Mastering SQL Aggregations and Having Clauses Like a Pro

Learn COUNT, SUM, AVG, MIN, and MAX aggregations, how GROUP BY works under the hood, and the difference between WHERE and HAVING.

AA

Ameer Abdullah

Data Science Graduate · AI/ML & Data Science

June 22, 2026·8 min read

Data analysis relies heavily on summarizing datasets. SQL aggregation queries let you consolidate thousands of records into meaningful metrics. Grasping the logical order of query execution (FROM -> JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY) is essential.

The Difference Between WHERE and HAVING

One of the most frequent interview traps is using the HAVING clause when WHERE is appropriate, or vice versa:

- WHERE filters rows before aggregation occurs. It cannot reference aggregate functions like SUM() or AVG(). - HAVING filters the aggregated groups after GROUP BY evaluates. It operates strictly on group summary values.

Interactive Trace Block
-- Retrieve departments with average salary > 100k
SELECT DeptID, AVG(Salary) as AvgSalary
FROM Employee
WHERE JobTitle != 'CEO' -- Filter individual rows
GROUP BY DeptID
HAVING AVG(Salary) > 100000; -- Filter grouped rows

COUNT(*) vs COUNT(column): A Critical Distinction

Interactive Trace Block
SELECT
  COUNT(*) as TotalRows,         -- counts all rows including NULLs
  COUNT(ManagerID) as HasManager, -- counts only non-NULL ManagerID rows
  COUNT(DISTINCT DeptID) as Depts -- counts distinct non-NULL values
FROM Employee;

COUNT(*) counts every row in the group, including rows where all columns are NULL. COUNT(column) counts only rows where that specific column is not NULL. COUNT(DISTINCT column) counts only unique non-NULL values. This three-way distinction is a classic interview question that catches candidates who assume COUNT always behaves the same way.

Filtering Groups: The HAVING Clause in Depth

Interactive Trace Block
-- Departments with more than 5 employees AND average salary above 80k
SELECT DeptID,
       COUNT(*) as HeadCount,
       AVG(Salary) as AvgSalary
FROM Employee
GROUP BY DeptID
HAVING COUNT(*) > 5
   AND AVG(Salary) > 80000
ORDER BY AvgSalary DESC;

HAVING can reference any aggregate function. You can also reference the column you grouped by (DeptID), but you cannot reference columns that are not in the GROUP BY and not wrapped in an aggregate. Notice that the AVG(Salary) computed in SELECT is not reused by HAVING - HAVING computes its own aggregate independently (though most optimizers will share the computation).

GROUP BY with Multiple Columns

Interactive Trace Block
-- Revenue per region per product category
SELECT Region, Category,
       SUM(Revenue) as TotalRevenue,
       COUNT(DISTINCT OrderID) as OrderCount
FROM Sales
GROUP BY Region, Category
ORDER BY Region, TotalRevenue DESC;

When grouping by multiple columns, every unique combination of the grouped columns forms a separate group. (North, Electronics) is a different group from (South, Electronics) and from (North, Furniture). This is the foundation of pivot-table-style analytical reporting and is used constantly in business intelligence queries.

ROLLUP and CUBE: Advanced Grouping Extensions

ROLLUP(A, B) produces subtotals: all combinations of (A, B), then subtotals for (A alone), then a grand total. CUBE(A, B) produces subtotals for all possible subset combinations: (A, B), (A alone), (B alone), and grand total. These extensions are used in reporting queries where you need hierarchical subtotals. GROUPING_ID() lets you distinguish which rows are subtotals vs. detail rows.

Quick Concept Quiz

Which SQL clause is evaluated BEFORE the GROUP BY clause?

Ready to test your execution tracing skills?

Hop directly into the SQL interactive playground to start coding, grading queries, and logging XP metrics to your workspace profile.

Continue learning

python

The Complete Guide to Tracing Python Code (With Examples)

Read guide →

python

10 Python Output Questions for Interview Preparation

Read guide →

python

How to Dry Run Python Code: Step-by-Step Method

Read guide →

Guide Contents

  • The Difference Between WHERE and HAVING
  • COUNT(*) vs COUNT(column): A Critical Distinction
  • Filtering Groups: The HAVING Clause in Depth
  • GROUP BY with Multiple Columns
  • ROLLUP and CUBE: Advanced Grouping Extensions