Bird
Raised Fist0
PostgreSQLquery~15 mins

HAVING for filtering groups in PostgreSQL - Deep Dive

Choose your learning style10 modes available
Overview - HAVING for filtering groups
What is it?
HAVING is a command in SQL used to filter groups of rows after they have been grouped together. It works with the GROUP BY clause to allow conditions on aggregated data, like sums or counts. Unlike WHERE, which filters individual rows before grouping, HAVING filters the groups themselves. This helps find groups that meet specific criteria.
Why it matters
Without HAVING, you could only filter rows before grouping, making it impossible to select groups based on summary information like totals or averages. This would limit your ability to analyze data in meaningful ways, such as finding customers with more than five orders or products with total sales above a threshold. HAVING lets you ask questions about groups, not just single rows.
Where it fits
Before learning HAVING, you should understand basic SQL SELECT queries, filtering with WHERE, and grouping data with GROUP BY. After HAVING, you can explore advanced aggregation functions, window functions, and complex reporting queries that combine multiple filters and groupings.
Mental Model
Core Idea
HAVING filters groups created by GROUP BY based on conditions applied to aggregated data.
Think of it like...
Imagine sorting your mail into piles by sender (GROUP BY). HAVING is like deciding to keep only the piles where the total number of letters is more than five. You first group, then decide which groups to keep based on their size.
SELECT columns
  FROM table
  WHERE row_conditions
  GROUP BY grouping_columns
  HAVING group_conditions

Flow:
[Rows] --WHERE--> [Filtered Rows] --GROUP BY--> [Groups] --HAVING--> [Filtered Groups] --SELECT--> [Result]
Build-Up - 7 Steps
1
FoundationUnderstanding GROUP BY basics
🤔
Concept: Learn how GROUP BY collects rows into groups based on column values.
GROUP BY takes rows that share the same value in specified columns and bundles them into groups. For example, grouping sales by product ID collects all sales of each product together. Aggregation functions like COUNT or SUM then summarize each group.
Result
Rows are organized into groups, each representing a unique value or combination of values from the grouping columns.
Understanding grouping is essential because HAVING works only on these groups, not on individual rows.
2
FoundationFiltering rows with WHERE clause
🤔
Concept: WHERE filters individual rows before grouping happens.
The WHERE clause removes rows that don't meet certain conditions before any grouping or aggregation. For example, WHERE price > 100 filters out cheap items before grouping sales by product.
Result
Only rows passing the WHERE condition are included in groups.
Knowing WHERE filters rows first helps clarify why HAVING is needed to filter groups after aggregation.
3
IntermediateIntroducing HAVING for group filtering
🤔Before reading on: do you think WHERE can filter groups after aggregation or only individual rows? Commit to your answer.
Concept: HAVING filters groups after aggregation, unlike WHERE which filters rows before grouping.
After GROUP BY creates groups, HAVING applies conditions to these groups using aggregate functions. For example, HAVING COUNT(*) > 5 keeps only groups with more than five rows. This lets you filter based on group summaries.
Result
Only groups meeting the HAVING condition appear in the final result.
Understanding HAVING's role after grouping unlocks powerful queries that analyze group-level data.
4
IntermediateUsing aggregate functions in HAVING
🤔Before reading on: can HAVING use non-aggregated columns or only aggregates? Commit to your answer.
Concept: HAVING conditions usually involve aggregate functions like COUNT, SUM, AVG, MIN, MAX.
You can write HAVING clauses like HAVING SUM(sales) > 1000 or HAVING AVG(rating) >= 4. These conditions test the summary values of each group. Non-aggregated columns in HAVING must appear in GROUP BY.
Result
Groups are filtered based on their aggregated values, enabling complex data analysis.
Knowing HAVING works with aggregates clarifies how to write meaningful group filters.
5
IntermediateDifference between WHERE and HAVING
🤔
Concept: WHERE filters rows before grouping; HAVING filters groups after aggregation.
If you want to exclude rows before grouping, use WHERE. If you want to exclude groups after aggregation, use HAVING. For example, WHERE price > 10 filters rows; HAVING COUNT(*) > 5 filters groups with more than five rows.
Result
Queries behave differently depending on which clause you use, affecting final results.
Distinguishing WHERE and HAVING prevents common mistakes in filtering data.
6
AdvancedCombining HAVING with multiple conditions
🤔Before reading on: do you think HAVING supports multiple conditions combined with AND/OR? Commit to your answer.
Concept: HAVING supports complex conditions using AND, OR, and parentheses.
You can write HAVING clauses like HAVING SUM(sales) > 1000 AND COUNT(*) > 5 to filter groups meeting multiple criteria. Use parentheses to control logic, e.g., HAVING (SUM(sales) > 1000 OR AVG(rating) > 4) AND COUNT(*) > 3.
Result
Groups are filtered precisely based on combined aggregate conditions.
Mastering complex HAVING conditions enables sophisticated group filtering.
7
ExpertPerformance considerations with HAVING
🤔Before reading on: do you think HAVING filters data before or after aggregation in query execution? Commit to your answer.
Concept: HAVING filters groups after aggregation, which can impact query performance if not used carefully.
Since HAVING runs after grouping and aggregation, it processes all groups before filtering. This can be slow on large datasets. Using WHERE to reduce rows before grouping improves speed. Indexes on grouping columns also help. Understanding query plans reveals how HAVING affects execution.
Result
Efficient queries balance WHERE and HAVING to minimize data processed.
Knowing HAVING's place in execution helps write faster, scalable queries.
Under the Hood
When a SQL query with GROUP BY and HAVING runs, the database first applies WHERE to filter rows. Then it groups the remaining rows by specified columns. Next, it calculates aggregate functions for each group. Finally, HAVING filters these groups based on aggregate conditions. Only groups passing HAVING are returned. This sequence ensures HAVING works on summaries, not raw rows.
Why designed this way?
SQL was designed to separate row-level filtering (WHERE) from group-level filtering (HAVING) to keep query logic clear and efficient. Early SQL versions lacked HAVING, making group filtering hard. HAVING was added to allow conditions on aggregates without complex subqueries. This design balances expressiveness and performance.
Query Execution Flow:

[Input Rows]
    │
    ▼
[WHERE filters rows]
    │
    ▼
[GROUP BY groups rows]
    │
    ▼
[Aggregate functions compute]
    │
    ▼
[HAVING filters groups]
    │
    ▼
[SELECT final output]
Myth Busters - 4 Common Misconceptions
Quick: Does HAVING filter rows before grouping or groups after grouping? Commit to your answer.
Common Belief:HAVING filters individual rows just like WHERE does.
Tap to reveal reality
Reality:HAVING filters groups after aggregation, not individual rows.
Why it matters:Using HAVING to filter rows can cause unexpected results or errors because it only works on groups, leading to confusion and wrong data.
Quick: Can you use non-aggregated columns in HAVING without grouping by them? Commit to your answer.
Common Belief:You can use any column in HAVING without restrictions.
Tap to reveal reality
Reality:Non-aggregated columns in HAVING must appear in GROUP BY; otherwise, the query is invalid.
Why it matters:Ignoring this causes syntax errors or incorrect grouping, breaking queries.
Quick: Is HAVING always slower than WHERE? Commit to your answer.
Common Belief:HAVING is always slower than WHERE and should be avoided.
Tap to reveal reality
Reality:HAVING filters after aggregation, so it can be slower, but proper use with WHERE and indexes can optimize performance.
Why it matters:Avoiding HAVING altogether limits query power; understanding when to use it improves efficiency.
Quick: Does HAVING replace WHERE for all filtering needs? Commit to your answer.
Common Belief:HAVING can replace WHERE for all filtering, even on individual rows.
Tap to reveal reality
Reality:HAVING cannot replace WHERE because it only filters groups, not individual rows.
Why it matters:Misusing HAVING leads to incorrect data filtering and inefficient queries.
Expert Zone
1
HAVING can reference aliases defined in SELECT, but only in some SQL dialects like PostgreSQL, which can simplify complex queries.
2
Using HAVING without GROUP BY is allowed in some databases to filter aggregated results over the entire table, acting like a global filter.
3
Combining HAVING with window functions requires careful query structuring because window functions operate after HAVING in execution order.
When NOT to use
Avoid HAVING when filtering individual rows; use WHERE instead for better performance. For complex filtering on aggregates, consider using subqueries or CTEs (WITH clauses) to improve readability and optimization.
Production Patterns
In production, HAVING is often used to find top customers, filter products by sales thresholds, or detect anomalies in grouped data. It is combined with indexes on grouping columns and sometimes with materialized views to speed up repeated queries.
Connections
Aggregation functions
HAVING builds on aggregation functions by filtering groups based on their results.
Understanding aggregation functions deeply helps write precise HAVING conditions that analyze group summaries.
Subqueries
HAVING can sometimes be replaced or complemented by subqueries that filter aggregated data.
Knowing subqueries offers alternative ways to filter groups, useful when HAVING syntax is limited or complex.
Statistics and data summarization
HAVING filters groups based on summary statistics like sums or averages.
Recognizing HAVING as a tool for statistical filtering connects database queries to broader data analysis concepts.
Common Pitfalls
#1Using WHERE to filter aggregated results instead of HAVING.
Wrong approach:SELECT product_id, COUNT(*) FROM sales GROUP BY product_id WHERE COUNT(*) > 5;
Correct approach:SELECT product_id, COUNT(*) FROM sales GROUP BY product_id HAVING COUNT(*) > 5;
Root cause:Confusing WHERE and HAVING roles; WHERE cannot use aggregate functions.
#2Using non-aggregated columns in HAVING without grouping by them.
Wrong approach:SELECT product_id, SUM(sales) FROM sales GROUP BY product_id HAVING sales > 1000;
Correct approach:SELECT product_id, SUM(sales) FROM sales GROUP BY product_id HAVING SUM(sales) > 1000;
Root cause:Misunderstanding that HAVING conditions must use aggregates or grouped columns.
#3Trying to filter groups without GROUP BY using HAVING incorrectly.
Wrong approach:SELECT COUNT(*) FROM sales HAVING COUNT(*) > 10 GROUP BY product_id;
Correct approach:SELECT product_id, COUNT(*) FROM sales GROUP BY product_id HAVING COUNT(*) > 10;
Root cause:Incorrect order of clauses and misunderstanding that HAVING filters groups created by GROUP BY.
Key Takeaways
HAVING filters groups created by GROUP BY based on aggregate conditions, unlike WHERE which filters individual rows.
Use WHERE to reduce rows before grouping and HAVING to filter groups after aggregation for efficient queries.
HAVING conditions usually involve aggregate functions like COUNT, SUM, or AVG to test group summaries.
Misusing WHERE and HAVING leads to syntax errors or incorrect results; understanding their roles is crucial.
Performance improves by combining WHERE and HAVING wisely and using indexes on grouping columns.

Practice

(1/5)
1. What is the main purpose of the HAVING clause in a PostgreSQL query?
easy
A. To join two tables
B. To filter rows before grouping
C. To sort the result set
D. To filter groups after aggregation

Solution

  1. Step 1: Understand the role of HAVING

    The HAVING clause is used to filter groups created by GROUP BY based on aggregate conditions.
  2. Step 2: Differentiate HAVING from WHERE

    WHERE filters rows before grouping, HAVING filters groups after aggregation.
  3. Final Answer:

    To filter groups after aggregation -> Option D
  4. Quick Check:

    HAVING filters groups = D [OK]
Hint: Remember HAVING filters groups, WHERE filters rows [OK]
Common Mistakes:
  • Confusing HAVING with WHERE clause
  • Thinking HAVING sorts data
  • Using HAVING without GROUP BY
2. Which of the following is the correct syntax to filter groups having a total sales greater than 1000 using HAVING?
easy
A. SELECT store, SUM(sales) FROM sales_data HAVING SUM(sales) > 1000 GROUP BY store;
B. SELECT store, SUM(sales) FROM sales_data GROUP BY store HAVING SUM(sales) > 1000;
C. SELECT store, SUM(sales) FROM sales_data WHERE SUM(sales) > 1000 GROUP BY store;
D. SELECT store, SUM(sales) FROM sales_data GROUP BY store WHERE SUM(sales) > 1000;

Solution

  1. Step 1: Identify correct order of clauses

    GROUP BY must come before HAVING in SQL syntax.
  2. Step 2: Check aggregate usage in HAVING

    HAVING uses aggregate functions like SUM(sales) to filter groups.
  3. Final Answer:

    SELECT store, SUM(sales) FROM sales_data GROUP BY store HAVING SUM(sales) > 1000; -> Option B
  4. Quick Check:

    GROUP BY before HAVING = B [OK]
Hint: HAVING always follows GROUP BY clause [OK]
Common Mistakes:
  • Placing HAVING before GROUP BY
  • Using WHERE with aggregate functions
  • Incorrect clause order
3. Given the table orders with columns customer_id and amount, what will this query return?
SELECT customer_id, COUNT(*) FROM orders GROUP BY customer_id HAVING COUNT(*) > 2;
medium
A. All customers with more than 2 orders
B. All customers with exactly 2 orders
C. All customers with less than 2 orders
D. Syntax error due to HAVING clause

Solution

  1. Step 1: Understand the GROUP BY and COUNT(*)

    The query groups rows by customer_id and counts orders per customer.
  2. Step 2: Apply HAVING filter

    HAVING COUNT(*) > 2 keeps only customers with more than 2 orders.
  3. Final Answer:

    All customers with more than 2 orders -> Option A
  4. Quick Check:

    HAVING COUNT(*) > 2 filters groups with >2 rows [OK]
Hint: HAVING filters groups by aggregate results [OK]
Common Mistakes:
  • Confusing > 2 with = 2
  • Thinking HAVING causes syntax error
  • Ignoring GROUP BY effect
4. Identify the error in this query:
SELECT department, AVG(salary) FROM employees HAVING AVG(salary) > 50000 GROUP BY department;
medium
A. HAVING clause used before GROUP BY
B. AVG() cannot be used in HAVING
C. Missing WHERE clause before HAVING
D. No error, query is correct

Solution

  1. Step 1: Check clause order

    SQL requires GROUP BY before HAVING clause.
  2. Step 2: Identify error in query

    HAVING appears before GROUP BY, which is invalid syntax.
  3. Final Answer:

    HAVING clause used before GROUP BY -> Option A
  4. Quick Check:

    GROUP BY must precede HAVING [OK]
Hint: GROUP BY comes before HAVING always [OK]
Common Mistakes:
  • Placing HAVING before GROUP BY
  • Assuming AVG() not allowed in HAVING
  • Confusing WHERE and HAVING
5. You have a sales table with columns region, salesperson, and amount. Write a query to find regions where the total sales exceed 10,000 but only include salespeople who individually sold more than 3,000. Which query correctly applies HAVING to achieve this?
hard
A. SELECT region, salesperson, SUM(amount) FROM sales GROUP BY region, salesperson HAVING SUM(amount) > 3000 AND SUM(amount) > 10000;
B. SELECT region, SUM(amount) FROM sales GROUP BY region HAVING SUM(amount) > 10000 AND salesperson IN (SELECT salesperson FROM sales GROUP BY salesperson HAVING SUM(amount) > 3000);
C. SELECT region, salesperson, SUM(amount) FROM sales GROUP BY region, salesperson HAVING SUM(amount) > 3000 AND region IN (SELECT region FROM sales GROUP BY region HAVING SUM(amount) > 10000);
D. SELECT region, salesperson, SUM(amount) FROM sales WHERE SUM(amount) > 3000 GROUP BY region, salesperson HAVING SUM(amount) > 10000;

Solution

  1. Step 1: Understand filtering conditions

    We want regions with total sales > 10,000 and salespeople with sales > 3,000.
  2. Step 2: Analyze correct logic

    Group by region and salesperson, filter salespeople with >3000 sales using HAVING SUM(amount) > 3000, and filter regions with total sales > 10000 using a subquery in HAVING: region IN (SELECT region FROM sales GROUP BY region HAVING SUM(amount) > 10000).
  3. Step 3: Check HAVING usage

    This correctly applies HAVING on both salesperson and region aggregates using subquery.
  4. Final Answer:

    SELECT region, salesperson, SUM(amount) FROM sales GROUP BY region, salesperson HAVING SUM(amount) > 3000 AND region IN (SELECT region FROM sales GROUP BY region HAVING SUM(amount) > 10000); -> Option C
  5. Quick Check:

    Use HAVING with subqueries for multi-level group filters [OK]
Hint: Use HAVING with subqueries for multi-level group filters [OK]
Common Mistakes:
  • Using WHERE with aggregate functions
  • Incorrect clause order
  • Combining unrelated HAVING conditions without subqueries
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard