Warm tip: This article is reproduced from stackoverflow.com, please click
greatest-n-per-group join left-join mysql sql

SQL query to get products in multiple order states

发布于 2021-01-02 10:53:03

We have a products, orders, order_items, order_status schema as shown below. The orders tables status field, takes the values; ORDERED, RECEIVING, RECEIVED and CANCELED, as defined in the order_status table.

Schema for products and orders.

The UI, that interacts with this data do have a 'product view', with rows for each product. As a user select a product, another view below (In Orders), lists the orders that the selected product was present in.

The user should be able to filter the products view, based on product order status. In particular, a button, saying 'On Order', should filter the product view to only show product records that have a record in the In Orders view, with a status of 'ORDERED'.

The following query returns multiple product rows, originating from the fact that one product exists in multiple orders.

SELECT products.*,        
orders.`id` AS orderID,
orders.`status`    
FROM products 
LEFT JOIN order_items
 ON products.`id` = order_items.`product_id`
JOIN orders
 ON orders.`id` = order_items.`order_id`;

Result from above query

We want the above result set to "coalesce" on order status. That is, the result set should have only one product record for each distinct order status. We could then filter the product view on the 'status' field.

The image below shows what result set that we would like, based on the above result. Red means it should not be part of the result.

Desired result

As observed from the image above;

  • The product with ID 18 is repeated 3 times with the same status. We only want one of those rows.
  • The product with ID 19 is present in 3 rows, two with the same status. Keep one of the two rows with the same status, and the row with status = 1.
  • The product with ID 20 is repeated twice with the same status, keep one.

How to achieve that?

Questioner
Totte Karlsson
Viewed
0
185k 2020-08-28 07:28

Use GROUP BY to collapse multiple rows into one. Use MIN(o.id) to get a well-defined order ID within each group.

SELECT p.*, MIN(o.id) AS orderID, o.status
FROM products AS p
JOIN order_items AS oi ON oi.product_id = p.id
JOIN orders AS o ON o.id = oi.order_id
GROUP BY p.id, o.status

It doesn't make sense to use LEFT JOIN in this case. You never want to group by a column that comes from a LEFT JOIN table, since all the rows with no match will be grouped together. And if you're filtering on order status, you obviously only want products that are in an order.