SQL Join Constraint Column on Another Joined Table - mysql

So practically I have 3 tables:
products
id
orders
id
status
order_items
id
order_id
product_id
quantity
So I want to query by products table as the main one. I want to know how many each one has been ordered, hence
SELECT products.*, SUM(order_items.quantity) AS sale_count
FROM products
LEFT JOIN order_items ON products.id = order_items.product_id
GROUP BY products.id
Everything works fine, no problem. But this query will count any order_items for each row, now I want to count only if the order status is done. But the problem is to get the status of that order_items you need to reference orders right?
SELECT products.*, SUM(order_items.quantity) AS sale_count
FROM products
LEFT JOIN order_items ON products.id = order_items.product_id
LEFT JOIN orders ON order_items.order_id = orders.id AND orders.status = 'done'
GROUP BY products.id
But this doesn't make a constraint on the order_items instead only order which has the status of done will be shown in the table else NULL.
How to approach this problem? Thank you for your kind help. I really appreciate it!!

It's not 100% clear to me what you are asking, but I think you are asking how to count orders for which the order status is done without throwing out results for which the order status is not done. If so, then the following should work:
SELECT products.*,
SUM(CASE WHEN orders.status = 'done'
THEN order_items.quantity
ELSE 0 END) AS sale_count
FROM products
LEFT JOIN order_items
ON products.id = order_items.product_id
LEFT JOIN orders
ON order_items.order_id = orders.id
GROUP BY products.id

I think you just want conditional aggregation:
SELECT p.*,
SUM(CASE WHEN o.status = 'done' THEN order_items.quantity END) AS sale_count
FROM products p LEFT JOIN
order_items oi
ON p.id = oi.product_id LEFT JOIN
orders o
ON oi.order_id = o.id
GROUP BY p.id;
I moved the 'done' condition to a case statement. You can leave it in the on clause as well, if you like. I just think this logic is more explicit about your intention.

Related

SQL Get all orders which contain exclusively one kind of item

In this Database I want to count the number of orders that contain only products of a certain category.
I know how to count all orders that also contain items of a certain category, i.e. category 1:
SELECT Count(DISTINCT orderdetails.orderid) AS "AllCat1"
FROM orderdetails
INNER JOIN orders
ON orderdetails.orderid = orders.orderid
AND orderdetails.productid IN (SELECT DISTINCT productid
FROM products
WHERE categoryid = 1)
WHERE orderdate BETWEEN "1996-12-01" AND "1996-12-31";
I am having trouble finding an elegant way to get all orders that contain only category 1 items. I tried selecting all OrderIDs and grouping them by OrderID AND CategoryID:
SELECT *
FROM orderdetails
INNER JOIN orders
ON orderdetails.orderid = orders.orderid
AND orderdate BETWEEN "1996-12-01" AND "1996-12-31"
INNER JOIN products
ON orderdetails.productid = products.productid
GROUP BY orderdetails.orderid,
categoryid;
But I have no idea how to count all OrderIDs that contain category 1 items exclusively. Is my approach right? Or is there a better way to do it (Which I am sure there is)
You can use group by and having . . . but you need two levels. To get the orders that are all in one (or a set of categories) by doing:
SELECT o.orderId
FROM orders o JOIN
orderdetails od
ON od.orderid = o.orderid JOIN
products p
ON p.productid = od.productid
WHERE o.orderdate BETWEEN '1996-12-01' AND '1996-12-31'
GROUP BY o.orderId
HAVING SUM(CASE WHEN p.categoryid IN (1) THEN 1 ELSE 0 END) = COUNT(*);
The count needs a subquery:
SELECT COUNT(*)
FROM (SELECT o.orderId
FROM orders o JOIN
orderdetails od
ON od.orderid = o.orderid JOIN
products p
ON p.productid = od.productid
WHERE o.orderdate BETWEEN '1996-12-01' AND '1996-12-31'
GROUP BY o.orderId
HAVING SUM(CASE WHEN p.categoryid IN (1) THEN 1 ELSE 0 END) = COUNT(*)
) o;
You can do filtering using HAVING clause. We basically Count the order details rows where category is 1 for an order. It should be equal to the total count of rows for that order. This would ensure that all the categories in an order is 1 only.
SELECT od.orderid
FROM orderdetails AS od
INNER JOIN orders AS o
ON od.orderid = o.orderid
AND o.orderdate BETWEEN "1996-12-01" AND "1996-12-31"
INNER JOIN products AS p
ON od.productid = p.productid
GROUP BY od.orderid
HAVING COUNT(CASE WHEN p.categoryid = 1 THEN 1 END) = COUNT(*)
It is advisable to use Aliasing in case of multi-table queries for Code clarity and readability

How to select records where all joined records aren't a match for criteria

I've a setup with the following tables (using MySQL):
orders, which have many:
a join table order_items, which have one from the:
products table
I've written a query to select orders where all their products are of a certain type:
SELECT orders.* FROM orders
INNER JOIN order_items ON order_items.order_id = orders.id
INNER JOIN products ON products.id = order_items.product_id
WHERE products.type = 'FooProduct'
AND (
NOT EXISTS (
SELECT null
FROM products
INNER JOIN order_items ON order_items.product_id = products.id
WHERE order_items.order_id = orders.id
AND products.type != 'FooProduct'
)
)
I run similar a couple of times: firstly to get orders comprised of all FooProducts, and again to get orders with all BarProducts.
My sticking point has been generating a third query to get all other orders, i.e. where all their products' types are not exclusively FooProducts, or exclusively BarProducts (aka a mix of the two, or other product types).
So, my question is how can I get all records where all product types aren't exclusively FooProducts or exclusively BarProduct.
Here's a little example data, from which I'd like to return the orders with the IDs 3 and 4:
- orders
id
1
2
3
4
-- order_items
id order_id product_id
1 1 1
2 1 1
3 2 2
4 2 2
5 3 3
6 3 4
7 4 1
8 4 2
-- products
id type
1 'FooProduct'
2 'BarProduct'
3 'OtherProduct'
4 'YetAnotherProduct'
I've attempted this, awfully so placing as a subtext, with the following in place of the existing AND (even the syntax is way off):
NOT HAVING COUNT(order_items.*) = (
SELECT null
FROM products
INNER JOIN order_items ON order_items.product_id = products.id
WHERE order_items.order_id = orders.id
AND products.type IN ('FooProduct', 'BarProduct')
)
Instead of using Correlated subqueries, you can use Having and conditional aggregation function based filtering.
products.type IN ('FooProduct', 'BarProduct') will return 0 if a product type is none of them. We can use Sum() function on it, for further filtering.
Try the following instead:
SELECT orders.order_id
FROM orders
INNER JOIN order_items ON order_items.order_id = orders.id
INNER JOIN products ON products.id = order_items.product_id
GROUP BY orders.order_id
HAVING SUM(products.type IN ('FooProduct', 'BarProduct')) < COUNT(*)
For the case, where you are looking for orders which has only FooProduct type, you can use the following instead:
SELECT orders.order_id
FROM orders
INNER JOIN order_items ON order_items.order_id = orders.id
INNER JOIN products ON products.id = order_items.product_id
GROUP BY orders.order_id
HAVING SUM(products.type <> 'FooProduct') = 0
Another possible approach is:
SELECT orders.order_id
FROM orders
INNER JOIN order_items ON order_items.order_id = orders.id
INNER JOIN products ON products.id = order_items.product_id
GROUP BY orders.order_id
HAVING SUM(products.type = 'FooProduct') = COUNT(*)
You can use aggregation and a having clause for this:
SELECT o.*
FROM orders o INNER JOIN
order_items oi
ON oi.order_id = o.id INNER JOIN
products p
ON p.id = oi.product_id
GROUP BY o.id -- OK assuming `id` is the primary key
HAVING SUM(p.type NOT IN ('FooProduct', 'BarProduct')) > 0; -- at least one other product
Actually, that is not quite right. This gets orders that have some other product, but it doesn't pick up orders that are mixes only of foo and bar. I think this gets the others:
HAVING SUM(p.type = 'FooProduct') < COUNT(*) AND
SUM(p.type = 'BarProduct') < COUNT(*)
This is a basic solution, not so efficient but easy:
SELECT * FROM orders WHERE id NOT IN (
SELECT orders.id FROM orders
INNER JOIN order_items ON order_items.order_id = orders.id
INNER JOIN products ON products.id = order_items.product_id
WHERE products.type = 'FooProduct'
AND (
NOT EXISTS (
SELECT null
FROM products
INNER JOIN order_items ON order_items.product_id = products.id
WHERE order_items.order_id = orders.id
AND products.type != 'FooProduct'
)
)
) AND id NOT IN (
SELECT orders.id FROM orders
INNER JOIN order_items ON order_items.order_id = orders.id
INNER JOIN products ON products.id = order_items.product_id
WHERE products.type = 'BarProduct'
AND (
NOT EXISTS (
SELECT null
FROM products
INNER JOIN order_items ON order_items.product_id = products.id
WHERE order_items.order_id = orders.id
AND products.type != 'BarProduct'
)
)
)
I would suggest using count(distinct) in joined subselect like this:
SELECT orders.*
FROM orders
inner join (
SELECT orderid, max(products.type) as products_type
FROM order_items
INNER JOIN products ON products.id = order_items.product_id
GROUP BY orderid
-- distinct count of different products = 1
-- -> all order items are for the same product type
HAVING COUNT(distinct products.type ) = 1
-- alternative is:
-- min(products.type )=max(products.type )
) as tmp on tmp.orderid=orders.orderid
WHERE 1=1
-- if you want only single type product orders for some specific product
and tmp.products_type = 'FooProduct'
This is a relational division problem.
One solution to find orders where all products are of a given type is this:
SELECT *
FROM orders
INNER JOIN order_items ON order_items.order_id = orders.id
INNER JOIN products ON products.id = order_items.product_id
WHERE orders.id IN (
SELECT order_items.order_id
FROM order_items
INNER JOIN products ON products.id = order_items.product_id
GROUP BY order_items.order_id
HAVING COUNT(CASE WHEN products.type = 'FooProduct' THEN 1 END) = COUNT(*)
)
Tweak the above just a little to find orders where all products are from a list of given types is this:
HAVING COUNT(CASE WHEN products.type IN ('FooProduct', 'BarProduct') THEN 1 END) = COUNT(*)
And to find all orders where all products match all types from a given list is this:
HAVING COUNT(CASE WHEN products.type IN ('FooProduct', 'BarProduct') THEN 1 END) = COUNT(*)
AND COUNT(DISTINCT products.type) = 2
DB Fiddle with tests

MySQL: Ranking from multiple tables, sub queries?

This is a MySQL question. I have three tables with the following columns:
transactions (table): transact_id, customer_id, transact_amt, product_id,
products (table): product_id, product_cost, product_name, product_category
customers (table): customer_id, joined_at, last_login_at, state, name, email
I'd like a query that finds out the most popular item in every state and the state. One of the tricky parts is that some product_name have multiple product_id. Therefore I though joining the three tables that generate an output with two columns: state and product_name. Until here that worked fine doing this:
SELECT p.product_name, c.state
FROM products p
INNER JOIN transactions t
ON p.product_id = t.product_id
INNER JOIN customers c
ON c.customer_id = t.customer_id
This selects all the products, and the states from where the customer is. The problem is that I can't find the way to rank the mos popular product per state. I tried different group by, order by and using subqueries without success. I suspect I need to do subqueries, but I can't find the way to resolve it. The expected outcome should look like this:
most_popular_product | state
Bamboo | WA
Walnut | MO
Any help will be greatly appreciated.
Thank you!
You need a subquery that gets the count of transactions for each product in each state.
SELECT p.product_name, c.state, COUNT(*) AS count
FROM products p
INNER JOIN transactions t
ON p.product_id = t.product_id
INNER JOIN customers c
ON c.customer_id = t.customer_id
GROUP BY p.product_name, c.state
Then write another query that has this as a subquery, and gets the highest count for each state.
SELECT state, MAX(count) AS maxcount
FROM (
SELECT p.product_name, c.state, COUNT(*) AS count
FROM products p
INNER JOIN transactions t
ON p.product_id = t.product_id
INNER JOIN customers c
ON c.customer_id = t.customer_id
GROUP BY p.product_name, c.state
) AS t
GROUP BY state
Finally, join them together:
SELECT t1.product_name AS most_popular_product, t1.state
FROM (
SELECT p.product_name, c.state, COUNT(*) AS count
FROM products p
INNER JOIN transactions t
ON p.product_id = t.product_id
INNER JOIN customers c
ON c.customer_id = t.customer_id
GROUP BY p.product_name, c.state
) AS t1
JOIN (
SELECT state, MAX(count) AS maxcount
FROM (
SELECT p.product_name, c.state, COUNT(*) AS count
FROM products p
INNER JOIN transactions t
ON p.product_id = t.product_id
INNER JOIN customers c
ON c.customer_id = t.customer_id
GROUP BY p.product_name, c.state
) AS t
GROUP BY state
) AS t2 ON t1.state = t2.state AND t1.count = t2.maxcount
This is basically the same pattern as SQL select only rows with max value on a column, just using the first grouped query as the table you're trying to group.

Select top sales products

I have three tables like this
orders(id, status, ...)
products(id, created_at, ...)
product_order(order_id, product_id, quantity)
I want to select the most sold products first then continue with latest products taking the quantity in consideration, Here's my try
SELECT products.* FROM products
LEFT JOIN product_order ON product_order.product_id = products.id
LEFT JOIN orders ON orders.id = product_order.order_id
WHERE orders.status != 'REJECTED'
GROUP BY product_order.product_id
ORDER BY COUNT(*) DESC, products.created_at
This statement returns the products that are not sold first because I am using left join and they count more than the sold ones.. also I don't know how to take the quantity in consideration
Thank you,
This should work :
SELECT p.*, sum(po.quantity) qty
FROM products p
LEFT OUTER JOIN product_order po ON po.product_id = p.id
LEFT OUTER JOIN orders o ON o.id = po.order_id
WHERE o.status != 'REJECTED'
GROUP BY po.product_id
ORDER BY qty DESC, p.created_at
If you want the most sold products you could add
AND products.quantity = SELECT max(quantity) from products
after your WHERE statement

SQL query for 3 tables

I have 3 tables as follows
item{id, name, price}
customer{id, name, tel_no}
order{id, time, customer_id}
order_item{id, item_id, price, order_id}
process{id, order_item_id, status}
I need to get order_items which are not processed for a particular customer. I tried with the following query. But it doesn't help. Please correct me some one.
SELECT *
FROM order_item`
INNER JOIN `order` ON `order`.id = order_item.order_id
WHERE `order`.customer_id=1 AND NOT EXISTS (
SELECT *
FROM process
WHERE process.order_item_id=order_item.id
)
I'm using mysql as my server
Select *
from order_item OI
INNER JOIN ORDER O on O.ID=OI.Order_Id
LEFT JOIN Process P ON P.Order_item_ID = OI.Item_ID
where O.Customer_ID = 1 and P.ID is null
LEFT JOin gives you all the ORDER_Items and only those records with a matching process record so P.ID will be null thus the item has not been processed