Lesson 8 โ€ข Intermediate

    GROUP BY & HAVING

    Group rows by category and filter groups to build powerful analytical reports โ€” revenue by department, orders per customer, and more.

    ๐ŸŽฏ What You'll Learn

    • Group rows by one or more columns
    • Apply aggregates to each group (COUNT, SUM, AVG per category)
    • Filter groups with HAVING
    • The critical difference between WHERE and HAVING

    GROUP BY Basics

    GROUP BY splits your data into groups, then applies aggregate functions to each group separately. Without GROUP BY, aggregates summarize the entire table.

    ๐Ÿ—‚๏ธ Real-World Analogy

    Imagine sorting a pile of receipts into folders by store name. Then for each folder, you calculate: total spent, number of visits, average purchase. That's exactly what GROUP BY store + aggregates does!

    โš ๏ธ The #1 GROUP BY Rule

    Every column in your SELECT must either be in GROUP BY or wrapped in an aggregate function. You can't SELECT name if you're grouping by department โ€” which "name" would SQL show for the group?

    Basic GROUP BY

    Group rows and calculate aggregates per category

    Try it Yourself ยป
    SQL
    -- Count orders per customer
    SELECT customer_id, COUNT(*) AS order_count
    FROM orders
    GROUP BY customer_id;
    
    -- Total sales by category
    SELECT category, SUM(price * quantity) AS total_sales
    FROM products
    GROUP BY category
    ORDER BY total_sales DESC;
    
    -- Average salary by department
    SELECT department, 
        ROUND(AVG(salary), 2) AS avg_salary,
        COUNT(*) AS num_employees
    FROM employees
    GROUP BY department;

    Multi-Column Grouping

    Create subgroups with multiple GROUP BY columns

    Try it Yourself ยป
    SQL
    -- Group by multiple columns
    -- Sales by category AND year
    SELECT 
        category,
        EXTRACT(YEAR FROM sale_date) AS sale_year,
        SUM(amount) AS total_sales,
        COUNT(*) AS num_transactions
    FROM sales
    GROUP BY category, EXTRACT(YEAR FROM sale_date)
    ORDER BY category, sale_year;
    
    -- Revenue by department and job title
    SELECT 
        department,
        job_title,
        COUNT(*) AS headcount,
        ROUND(AVG(salary), 0) AS avg_salary
    FROM employees
    GROUP BY department, job_title
    ORDER BY department, avg_s
    ...

    HAVING โ€” Filter Groups

    HAVING filters groups after aggregation. It's like WHERE, but for aggregated results.

    ClauseFiltersWhen
    WHEREIndividual rowsBefore grouping
    HAVINGGroupsAfter grouping & aggregation

    ๐Ÿ’ก Pro Tip

    Use WHERE whenever possible โ€” it reduces data before grouping, making queries faster. Only use HAVING when you need to filter on an aggregated value (like HAVING COUNT(*) > 5).

    HAVING Clause

    Filter groups that meet aggregate conditions

    Try it Yourself ยป
    SQL
    -- HAVING: filter GROUPS (not rows)
    -- Customers with more than 5 orders
    SELECT customer_id, COUNT(*) AS order_count
    FROM orders
    GROUP BY customer_id
    HAVING COUNT(*) > 5;
    
    -- Departments with avg salary above 70k
    SELECT department, ROUND(AVG(salary), 0) AS avg_salary
    FROM employees
    GROUP BY department
    HAVING AVG(salary) > 70000
    ORDER BY avg_salary DESC;
    
    -- Categories with total sales over 10000
    SELECT category, SUM(amount) AS total_sales
    FROM sales
    GROUP BY category
    HAVING SUM(amount) > 10000;

    The Complete SQL Query Order

    Here's how all the clauses fit together:

    SELECT columns / aggregates
    FROM table
    WHERE row_conditions        -- 1. Filter rows
    GROUP BY columns            -- 2. Group remaining rows
    HAVING group_conditions     -- 3. Filter groups
    ORDER BY columns            -- 4. Sort results
    LIMIT n;                    -- 5. Limit output

    This order is strict โ€” you'll get a syntax error if you rearrange them.

    WHERE + GROUP BY + HAVING Together

    Build complete analytical queries combining all clauses

    Try it Yourself ยป
    SQL
    -- WHERE vs HAVING: they work together!
    -- WHERE filters ROWS (before grouping)
    -- HAVING filters GROUPS (after grouping)
    SELECT 
        department,
        COUNT(*) AS active_employees,
        ROUND(AVG(salary), 0) AS avg_salary
    FROM employees
    WHERE status = 'active'           -- filter: only active employees
    GROUP BY department
    HAVING COUNT(*) >= 5              -- filter: departments with 5+ people
    ORDER BY avg_salary DESC;
    
    -- Real-world: top product categories this year
    SELECT 
        category,
        COUNT
    ...

    ๐Ÿ“˜ Quick Reference

    PatternPurpose
    GROUP BY colGroup rows by column value
    GROUP BY a, bCreate subgroups
    HAVING COUNT(*) > nKeep groups with n+ rows
    HAVING SUM(x) > yKeep groups where sum exceeds y
    WHERE + GROUP BY + HAVINGFilter rows, group, filter groups

    ๐ŸŽ‰ Lesson Complete!

    You can now group data and filter groups โ€” essential skills for data analysis and reporting. Next, you'll learn subqueries โ€” nesting one query inside another for even more powerful results!

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy Policy โ€ข Terms of Service