Get SUM in GROUP BY with JOIN using MySQL

33,783

Use:

  SELECT p.department,
         SUM(p.price) AS total_price
    FROM PRODUCTS p
    JOIN (SELECT DISTINCT 
                 r.product_id,
                 r.rating
            FROM RATINGS r) x ON x.product_id = p.id
                             AND x.rating = 5
GROUP BY p.department

Technically, this does not use a subquery - it uses a derived table/inline view.

Share:
33,783
ryanb
Author by

ryanb

Producer of Railscasts, free Ruby on Rails screencasts.

Updated on January 06, 2020

Comments

  • ryanb
    ryanb over 4 years

    I have two tables in MySQL 5.1.38.

    products
    +----+------------+-------+------------+
    | id | name       | price | department |
    +----+------------+-------+------------+
    |  1 | Fire Truck | 15.00 | Toys       |
    |  2 | Bike       | 75.00 | Toys       |
    |  3 | T-Shirt    | 18.00 | Clothes    |
    |  4 | Skirt      | 18.00 | Clothes    |
    |  5 | Pants      | 22.00 | Clothes    |
    +----+------------+-------+------------+
    
    ratings
    +------------+--------+
    | product_id | rating |
    +------------+--------+
    |          1 |      5 |
    |          2 |      5 |
    |          2 |      3 |
    |          2 |      5 |
    |          3 |      5 |
    |          4 |      5 |
    |          5 |      4 |
    +------------+--------+
    

    My goal is to get the total price of all products which have a 5 star rating in each department. Something like this.

    +------------+-------------+
    | department | total_price |
    +------------+-------------+
    | Clothes    | 36.00       |  /* T-Shirt and Skirt */
    | Toys       | 90.00       |  /* Fire Truck and Bike */
    +------------+-------------+
    

    I would like to do this without a subquery if I can. At first I tried a join with a sum().

    select department, sum(price) from products
    join ratings on product_id=products.id
    where rating=5 group by department;
    +------------+------------+
    | department | sum(price) |
    +------------+------------+
    | Clothes    |      36.00 |
    | Toys       |     165.00 |
    +------------+------------+
    

    As you can see the price for the Toys department is incorrect because there are two 5 star ratings for the Bike and therefore counting that price twice due to the join.

    I then tried adding distinct to the sum.

    select department, sum(distinct price) from products
    join ratings on product_id=products.id where rating=5
    group by department;
    +------------+---------------------+
    | department | sum(distinct price) |
    +------------+---------------------+
    | Clothes    |               18.00 |
    | Toys       |               90.00 |
    +------------+---------------------+
    

    But then the clothes department is off because two products share the same price.

    Currently my work-around involves taking something unique about the product (the id) and using that to make the price unique.

    select department, sum(distinct price + id * 100000) - sum(id * 100000) as total_price
    from products join ratings on product_id=products.id
    where rating=5 group by department;
    +------------+-------------+
    | department | total_price |
    +------------+-------------+
    | Clothes    |       36.00 |
    | Toys       |       90.00 |
    +------------+-------------+
    

    But this feels like such a silly hack. Is there a better way to do this without a subquery? Thanks!

  • ryanb
    ryanb almost 14 years
    Actually I do have an auto-incrementing id field in the real application which is much more complex. I tried to simplify everything as far as I could here, but seems I went too far by taking out ratings.id. Thanks for posting this option!
  • Drew Taylor
    Drew Taylor almost 11 years
    Thanks OMG Ponies! This perfectly solved a problem I was having today. My specific case required a LEFT JOIN on the derived table and to have the SUM inside the derived table definition, but it works great. The EXPLAIN results didn't look too horrible either, so we'll see how it scales.