Why this chapter matters
Team decisions often depend on rollups such as weekly totals, conversion by segment, and top categories.
What you will learn
- Group rows correctly and compute aggregates like COUNT, SUM, and AVG.
- Use HAVING to filter aggregated groups after calculation.
- Avoid mixing non-grouped columns with aggregates incorrectly.
Understand the core ideas
Aggregation turns detailed rows into summarized metrics, so correctness depends on grouping dimensions and measure definitions. In PostgreSQL, every selected column must either be grouped or aggregated, which forces you to make dimension choices explicit. That is helpful because many reporting bugs come from accidental dimensions, like grouping by plan and month when you only wanted month. Start by writing the smallest valid grouped query: dimensions in GROUP BY, one measure such as SUM(amount), and no optional fields. Then add additional measures one by one and validate each against known totals. For counts, decide whether you need raw row count, distinct entity count, or conditional count. COUNT(*) answers different business questions than COUNT(DISTINCT user_id). Analysts who state this choice in plain language before coding avoid many downstream disputes about why numbers differ between dashboards.
WHERE and HAVING operate at different stages in PostgreSQL query processing. WHERE filters base rows before grouping, while HAVING filters grouped rows after aggregates are computed. Use WHERE for row level constraints like date range or paid status, and HAVING for aggregate thresholds like revenue above 5000. Mixing these up either throws errors or silently changes metric meaning. A robust QA approach is to test a query on a single month and one plan where you can manually verify results. You can also compare the grouped output sum back to a trusted base query to ensure no rows were dropped unexpectedly. Keep row grain visible in your output labels, for example one row per month_start, plan_id. When teams document both grain and measure formula, reports become easier to maintain and audits become much faster.
Key terms
- dimension
- A grouped column that defines how rows are partitioned in summary output.
- measure
- A computed aggregate value such as
SUM(revenue)orCOUNT(*). - pre-aggregate filter
- A
WHEREcondition applied beforeGROUP BYand aggregate calculation. - post-aggregate filter
- A
HAVINGcondition evaluated after grouped metrics are computed.
Monthly revenue by plan with threshold filtering
Use invoices(invoice_id, plan_id, amount, state, paid_at) at one row per invoice. Goal output grain is one row per month and plan for paid invoices only.
- Filter to paid invoices and a known time range in
WHEREso only qualifying base rows enter aggregation. - Create a month bucket using
date_trunc('month', paid_at)::dateand includeplan_idas the second grouping dimension. - Compute
SUM(amount)asmonthly_revenueand verify that each output row now represents a unique(month_start, plan_id)pair. - Apply
HAVING SUM(amount) > 5000to keep only plan-month groups that exceed the business threshold.
SELECT date_trunc('month', paid_at)::date AS month_start, plan_id, SUM(amount) AS monthly_revenue FROM invoices WHERE state = 'paid' AND paid_at >= DATE '2026-01-01' AND paid_at < DATE '2027-01-01' GROUP BY date_trunc('month', paid_at)::date, plan_id HAVING SUM(amount) > 5000 ORDER BY month_start, plan_id; returns only high-revenue plan-month groups with explicit summary grain.A common misconception
Claim: HAVING is just another way to write WHERE, so they are interchangeable.
Correction: They are not interchangeable. WHERE filters raw rows before grouping, while HAVING filters grouped results after aggregates are computed. Choosing the wrong one changes the metric.
Lessons in this chapter
- Aggregation fundamentalsBuild grouped metrics with clear dimension columns and measure columns. Read the full guide →
- HAVING versus WHEREApply WHERE before grouping and HAVING after grouping in PostgreSQL query order.
- Distinct and conditional countsUse COUNT(DISTINCT ...) and CASE-based aggregates for cleaner KPI definitions.
- Metric QA checksValidate grouped totals against known baselines to catch logic mistakes early.
Study task
Chapter checkpoint
When filtering groups with total orders greater than 100, should you use WHERE or HAVING?
Use HAVING because the filter depends on an aggregate computed after GROUP BY.