SQLite HAVING

The HAVING clause filters groups after GROUP BY runs. WHERE filters individual rows before grouping; HAVING filters the resulting groups after aggregation. You need HAVING whenever a condition depends on an aggregate value like SUM, COUNT, or AVG.

WHERE vs HAVING — The Key Difference

WHERE  → filters ROWS     (before grouping)
HAVING → filters GROUPS   (after aggregation)

Timeline of a query:
──────────────────────────────────────────────────────
1. FROM       Load all rows
2. WHERE      ← filter individual rows here
3. GROUP BY   Form groups from remaining rows
4. Aggregate  Calculate COUNT, SUM, AVG etc per group
5. HAVING     ← filter groups here
6. SELECT     Choose columns to display
7. ORDER BY   Sort results

Sample Table

sales
──────────────────────────────────────────────
id │ rep    │ region │ product  │ amount
───┼────────┼────────┼──────────┼────────
1  │ Alice  │ North  │ Laptop   │ 1200
2  │ Bob    │ South  │ Phone    │ 800
3  │ Alice  │ North  │ Tablet   │ 500
4  │ Carol  │ East   │ Laptop   │ 1100
5  │ Bob    │ South  │ Laptop   │ 1300
6  │ Carol  │ East   │ Phone    │ 750
7  │ Alice  │ West   │ Phone    │ 900
8  │ Dan    │ North  │ Tablet   │ 400
9  │ Dan    │ East   │ Phone    │ 600

Basic HAVING Example

-- Only show reps whose total sales exceed 2000
SELECT rep, SUM(amount) AS total
FROM sales
GROUP BY rep
HAVING SUM(amount) > 2000;

rep   │ total
──────┼───────
Alice │ 2600
Bob   │ 2100
Carol │ 1850  ← excluded (1850 < 2000... wait)

Let's verify:
Alice: 1200+500+900 = 2600 ✓
Bob:   800+1300     = 2100 ✓
Carol: 1100+750     = 1850 ✗ (below 2000)
Dan:   400+600      = 1000 ✗

HAVING with COUNT

-- Only reps who closed more than 2 deals
SELECT rep, COUNT(*) AS deals
FROM sales
GROUP BY rep
HAVING COUNT(*) > 2;

rep   │ deals
──────┼───────
Alice │ 3
Dan   │ 2    ← excluded (not > 2)

Alice has 3 deals (rows 1, 3, 7): included ✓
Bob has 2 deals (rows 2, 5): excluded ✗
Carol has 2 deals (rows 4, 6): excluded ✗
Dan has 2 deals (rows 8, 9): excluded ✗

HAVING with AVG

-- Reps whose average deal size is above 800
SELECT rep, ROUND(AVG(amount), 2) AS avg_deal
FROM sales
GROUP BY rep
HAVING AVG(amount) > 800;

rep   │ avg_deal
──────┼──────────
Alice │ 866.67   (2600/3)
Bob   │ 1050.00  (2100/2)
Carol │ 925.00   (1850/2)

Dan: 500.00 (1000/2) → excluded ✗

HAVING with WHERE Together

WHERE and HAVING can work in the same query. WHERE pre-filters rows before grouping; HAVING post-filters the formed groups.

-- Among North and South regions only,
-- show reps with total sales above 1000

SELECT rep, region, SUM(amount) AS total
FROM sales
WHERE region IN ('North', 'South')   ← filter rows first
GROUP BY rep
HAVING SUM(amount) > 1000;           ← filter groups after

Step-by-step:
1. WHERE keeps only North + South rows:
   Alice/North: 1200, 500 → included
   Bob/South:   800, 1300 → included
   Dan/North:   400       → included

2. GROUP BY rep:
   Alice total: 1700
   Bob total:   2100
   Dan total:   400

3. HAVING > 1000:
   Alice: 1700 ✓
   Bob:   2100 ✓
   Dan:   400  ✗

rep   │ region │ total
──────┼────────┼───────
Alice │ North  │ 1700
Bob   │ South  │ 2100

HAVING vs WHERE — When Each Applies

SCENARIO                              USE
──────────────────────────────────────────────────────────
Filter by a column value              WHERE
  e.g. only North region rows         WHERE region='North'

Filter by aggregate result            HAVING
  e.g. only groups with SUM > 1000    HAVING SUM(amount)>1000

Condition does not involve            WHERE (faster)
  an aggregate function

Condition involves COUNT,             HAVING (required)
  SUM, AVG, MIN, or MAX

HAVING Without GROUP BY

HAVING without GROUP BY treats the entire table as one group and filters that single group.

-- Only return results if total revenue exceeds 5000
SELECT SUM(amount) AS grand_total
FROM sales
HAVING SUM(amount) > 5000;

grand_total: 7550   ← shown because 7550 > 5000

-- If total were less than 5000, no rows would appear at all.

Practical Example: Sales Leaderboard

-- Top performers: more than 1 deal and average above 700
SELECT
  rep,
  COUNT(*)             AS deals,
  SUM(amount)          AS total_revenue,
  ROUND(AVG(amount),2) AS avg_deal
FROM sales
GROUP BY rep
HAVING COUNT(*) > 1 AND AVG(amount) > 700
ORDER BY total_revenue DESC;

rep   │ deals │ total_revenue │ avg_deal
──────┼───────┼───────────────┼──────────
Alice │ 3     │ 2600          │ 866.67
Bob   │ 2     │ 2100          │ 1050.00
Carol │ 2     │ 1850          │ 925.00

Key Takeaways

  • HAVING filters groups; WHERE filters individual rows
  • HAVING runs after GROUP BY and aggregation
  • Use HAVING when the condition involves COUNT, SUM, AVG, MIN, or MAX
  • WHERE and HAVING can coexist in one query — they operate at different stages
  • HAVING without GROUP BY applies to the entire table as a single group

Leave a Comment

Your email address will not be published. Required fields are marked *