Aggregating Data with Subqueries, HAVING, and CASE in SQL
1. Why Aggregation with Subqueries Matters
As SQL queries become more complex, you often need to:
- Aggregate data (using
COUNT,SUM,AVG) - Filter aggregated results
- Compare group-level values to overall totals
- Categorize results using conditional logic
This requires combining:
GROUP BY- Subqueries
HAVINGCASE
These tools allow layered, structured aggregation.
2. WHERE vs HAVING
Understanding the difference is critical.
2.1 WHERE
- Filters raw rows.
- Applied before aggregation.
- Cannot filter aggregated results.
Example:
SELECT warehouse_id
FROM orders
WHERE order_date >= '2024-01-01';2.2 HAVING
- Filters aggregated results.
- Applied after GROUP BY.
- Can filter using aggregate functions.
Example:
SELECT warehouse_id, COUNT(*) AS order_count
FROM orders
GROUP BY warehouse_id
HAVING COUNT(*) > 10;Key Rule:
WHERE filters rows.
HAVING filters grouped results.
3. Problem Scenario: Warehouse Order Fulfillment Percentage
We have two tables:
Orders Table
order_idcustomer_idwarehouse_idorder_dateship_date
Warehouse Table
warehouse_idwarehouse_aliasmaximum_capacitytotal_employeesstate
Goal:
Determine:
- How many orders each warehouse fulfills
- What percentage of total orders that represents
- Categorize warehouses by fulfillment share
4. Aliasing Tables
Aliasing simplifies long queries.
Example:
FROM warehouse w
LEFT JOIN orders o
ON w.warehouse_id = o.warehouse_idNow:
wrefers to warehouseorefers to orders
This improves readability.
5. Building the Aggregation Logic
Step 1: Join Tables
We use LEFT JOIN because:
- We want all warehouses.
- Even those with no matching orders.
FROM warehouse w
LEFT JOIN orders o
ON w.warehouse_id = o.warehouse_idStep 2: Group Results
GROUP BY w.warehouse_id, w.warehouse_alias, w.stateGrouping ensures:
- One row per warehouse.
Step 3: Count Orders per Warehouse
COUNT(o.order_id) AS warehouse_ordersThis gives total fulfilled orders per warehouse.
6. Subquery for Total Orders
We need total company-wide orders.
Subquery:
(SELECT COUNT(*) FROM orders) AS total_ordersThis is a scalar subquery:
- Returns one value.
- Used inside SELECT.
It executes first.
Its value is reused for each outer row.
7. Calculating Percentage
To compute fulfillment percentage:
COUNT(o.order_id) /
(SELECT COUNT(*) FROM orders)This yields a fraction.
Multiply by 100 if percentage format is desired.
8. Using CASE for Categorization
CASE allows conditional classification.
Example:
CASE
WHEN COUNT(o.order_id) /
(SELECT COUNT(*) FROM orders) <= 0.2
THEN 'Fulfilled 0-20% of Orders' WHEN COUNT(o.order_id) /
(SELECT COUNT(*) FROM orders) <= 0.6
THEN 'Fulfilled 21-60% of Orders' ELSE 'Fulfilled more than 60% of Orders'
END AS fulfillment_summaryCASE structure:
CASE
WHEN condition THEN result
WHEN condition THEN result
ELSE result
END9. Filtering with HAVING
We want to exclude warehouses under construction.
These warehouses have:
- No orders.
- COUNT(o.order_id) = 0.
So we use:
HAVING COUNT(o.order_id) > 0This filters after grouping.
10. Full Conceptual Query Structure
SELECT
w.warehouse_id,
CONCAT(w.state, ' - ', w.warehouse_alias) AS warehouse_name,
COUNT(o.order_id) AS warehouse_orders,
(SELECT COUNT(*) FROM orders) AS total_orders,
CASE
WHEN COUNT(o.order_id) /
(SELECT COUNT(*) FROM orders) <= 0.2
THEN 'Fulfilled 0-20% of Orders'
WHEN COUNT(o.order_id) /
(SELECT COUNT(*) FROM orders) <= 0.6
THEN 'Fulfilled 21-60% of Orders'
ELSE 'Fulfilled more than 60% of Orders'
END AS fulfillment_summary
FROM warehouse w
LEFT JOIN orders o
ON w.warehouse_id = o.warehouse_id
GROUP BY w.warehouse_id, w.warehouse_alias, w.state
HAVING COUNT(o.order_id) > 0;11. Execution Order
SQL processes this query logically in this order:
- FROM
- JOIN
- WHERE (if present)
- GROUP BY
- HAVING
- SELECT
- ORDER BY (if present)
Subqueries execute when encountered in SELECT or HAVING.
12. Key Concepts Demonstrated
This query integrates:
- JOIN
- GROUP BY
- Aggregate functions
- Scalar subquery
- CASE conditional logic
- HAVING filter
- Aliasing
This is an advanced SQL aggregation pattern.
13. Why This Is Powerful
This structure allows:
- Comparison of group totals to overall totals
- Dynamic percentage calculations
- Automated categorization
- Filtering aggregated outputs
- Complex performance analysis
Such patterns are common in:
- Sales reporting
- Performance dashboards
- Operations analysis
- Market share studies
14. Summary
To aggregate data with subqueries:
- Use
GROUP BYto aggregate. - Use
HAVINGto filter aggregated results. - Use subqueries to calculate global metrics.
- Use
CASEto categorize results. - Use aliases to simplify complex logic.
Subqueries combined with HAVING and CASE enable layered, scalable SQL analytics.
Discover more from Insightful Data Lab
Subscribe to get the latest posts sent to your email.
