I have written the following two queries for the below requirement. Please let me know which method is correct or both methods are wrong? Thanks a lot
There were two tables -
'Orders' with - order_id(PK), item id, quantity, order_date [Transactional Table]
'Catalog' with-item id, product group, location [Dimension Table]
They asked to write a SQL code that will return the product groups of US that has no sale in any unit(i.e all the item id from an individual product group has no sale).
1st Method:
with cte as
(
select c.*,o.order_id,
case when o.order_id is not null then 1 else 0 end sale_ind
from Catalog c
left join Orders o
on c.item_id = o.item_id
and c.location = 'US'
)
select product_group
from cte
group by product_group having sum(sale_ind) = 0
2nd Method:
select c.*
from Catalog c
where c.location='US'
and item_id not in (
select item_id
from Orders)
They asked to write a SQL code that will return the product groups of US that has no sale in any unit(i.e all the item id from an individual product group has no sale).
I would tend to go with not exists for this:
select distinct c.product_group
from catalog c
where c.location = 'US' and
not exists (select 1
from orders o
where o.item_id = c.item_id
);
That said, both your queries look okay, but the first is correct. The second is returning all catalog records not all product_groups. As for the second, I would discourage you from ever using not in with a subquery. No rows are returned if item_id returned by the subquery is ever NULL.
SELECT DISTINCT c.product_group
FROM Catalog c
LEFT OUTER JOIN Orders o
on c.item_id = o.item_id
WHERE c.location='US'
AND o.item_id is null
Left join: because you want catalog records (left side) even if there are no order records (right side). The second part of the WHERE clause filters out instances where there are orders.
You can’t use an inner join as that would return only records where the Catalog record had corresponding orders, which is not what you want
Related
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.
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`;
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.
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?
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.
If you are running MySQL 8.0, you can use row_number() for filtering. I guess the logic you want is:
select *
from (
select
p.*,
o.id as orderid,
o.status ,
row_number() over(partition by p.id, o.status order by o.id) rn
from products p
inner join order_items oi on p.id = oi.product_id
inner join orders o on o.id = oi.order_id
) t
where rn = 1
I don't think that mixing inner join and left join make sense here. Either use two inner joins (as in the above query), or two left joins if you want to retain products without any order.
I am just trying some practise exercises with MYSQL. I have a dataset where I would like to get the names of customers who ordered two or more different kinds of item and how many of each kind of item they bought.
The query below gives me a row for each name of the purchaser. However, I also want to display what types of items they bought and how many of them. Ideally I would like to have the same number of rows for each customer for how many different items they bought.
SELECT firstname, familyname, description, quantity
FROM customers c
JOIN orders o ON o.custID = c.custID
JOIN lineitems l on o.orderID = l.orderID
JOIN items i on l.itemID = i.itemID
GROUP BY firstname
HAVING count(description)
The query below does give me a row for each item, how many items that person bought, and the name of the purchaser. However, it does not filter for customers who only bought one specific item anymore.
SELECT firstname, familyname, description, quantity
FROM customers c
JOIN orders o ON o.custID = c.custID
JOIN lineitems l on o.orderID = l.orderID
JOIN items i on l.itemID = i.itemID
WHERE EXISTS(
SELECT *
FROM customers
GROUP BY firstname
HAVING count(description) >= 2)
Basically I would like to combine both approaches where there are multiple rows for specific item for each customer, while also filtering out customers who only bought one type of item.
If you are running MySQL 8.0, you can do a window count in a subquery and filter in the outer query, like:
SELECT *
FROM (
SELECT
c.firstname,
c.familyname,
i.description,
l.quantity,
COUNT(*) OVER(PARTITION BY c.custID) cnt
FROM customers c
JOIN orders o ON o.custID = c.custID
JOIN lineitems l on o.orderID = l.orderID
JOIN items i on l.itemID = i.itemID
) x
WHERE cnt > 1
Note: it is a good practice to prefix column names with the alias of the table they belong to; this makes the query more readable and avoid clashes when the same column name exists across tables. I made a few assumptions and updated the query accordingly.
I have some problem with the query issue when trying to sum up the quantity.
Table
This cart item table stored id_cart and id product
This order table stored id_cart and other id may be included such as supplier. This table is used to track order record and send notification to supplier.
Wrong result. Expected output = 1, 1, 1
SELECT id, id_product, SUM(qty)
from cart_item
left join Orderp using(id_cart)
group by id_product
http://sqlfiddle.com/#!9/07bf57/1
The issue caused by duplicate id_cart in order table as well. How can i handle this? Any solution to make it works? Thanks.
There is something wrong in your data, or in your data model
INSERT INTO OrderP(`id_order`,`id_cart`)VALUES(1, 1);
INSERT INTO OrderP(`id_order`,`id_cart`)VALUES(2, 1);
There are 2 rows for id_cart = 1, so the "natural join" will double every row when joining cart_item to orderp.
Using an inner join to a different column in orderp works better because now there is only one row in orederp for each cart_item.
SELECT id_product, sum(qty)
from cart_item ci
left join Orderp o on ci.id_cart = o.id_order
GROUP BY id_product
http://sqlfiddle.com/#!9/07bf57/13
Try the following query
SELECT
i.id_product,
p.name productname,
b.id_branch,
b.branchname,
SUM(i.qty)
from cart_item i
left join (SELECT DISTINCT id_cart,id_branch FROM Orderp) o on o.id_cart=i.id_cart
left join product p on i.id_product=p.id_product
left join catalog c on c.id_product=p.id_product and c.id_branch=o.id_branch
left join branch b on b.id_branch=o.id_branch
group by
i.id_product,
p.name,
b.id_branch,
b.branchname
The main problem in Orderp table because it containts two different orders for one cart (DISTINCT id_cart,id_branch helps here). And you need to use the second condition by id_branch for catalog (and c.id_branch=o.id_branch).
SQL Fiddle - http://sqlfiddle.com/#!9/f32d5f/16
And I think you can use everywhere INNER JOIN instead LEFT JOIN
SELECT
i.id_product,
p.name productname,
b.id_branch,
b.branchname,
SUM(i.qty)
from cart_item i
join (SELECT DISTINCT id_cart,id_branch FROM Orderp) o on o.id_cart=i.id_cart
join product p on i.id_product=p.id_product
join catalog c on c.id_product=p.id_product and c.id_branch=o.id_branch
join branch b on b.id_branch=o.id_branch
group by
i.id_product,
p.name,
b.id_branch,
b.branchname
working with mySql I would like to list all purchases that customers made on a specific cathegory of products.
So, I had 3 tables: customers (idCustomer, Name) , cathegories (idCategory, CategoryName) and orders (idOrder, idCustomer, idCathegory, Qty, Price)
But I want a listing with ALL of the customers.
Not only the one who bought that specific idCategory
I thought something like:
select sum(Orders.Qty), Customers.Name
from Orders
right join Customers on Orders.idCustomer = Customer.idCustomer
where Orders.idCategory = 'Notebooks'
group by Orders.idCategory
but this statement only lists the records for customers who exists in Orders table.
And I want all of them ( the one who didnt buy, with qty =0 )
thanks in advance
Most people find left join easier to follow than right join. The logic for left join is to keep all rows in the first table, plus additional information from the remaining tables. So, if you want all customers, then that should be the first table.
You will then have a condition on the second table. Conditions on all but the first table should be in the on clause rather than a where. The reason is simple: when there is no match, then the value will be NULL and the where condition will fail.
So, try something like this:
select sum(o.Qty) as sumqty, c.Name
from Customers c left join
Orders o
on o.idCustomer = c.idCustomer and
o.idCategory = 'Notebooks'
group by c.Name;
Finally, the group by should have a relationship to the select clause.
Try this query
select sum(Orders.Qty), Customers.Name
from Customers
right join Orders on Customer.idCustomer = Orders.idCustomer and Orders.idCategory = 'Notebooks'
group by Customers.Name
What i would like to archieve:
Getting the correct sum of the total amount of the orders that has been cancelled of user id 2002.
Some pre information:
I am having deals which that has its price in deals.price and its id in deals.ID
I then have orders with a foreign key to deals.ID
Running this SQL:
select SUM(deals.price), orders.* from orders
JOIN deals ON deals.ID = orders.deal_id
where orders.user_id = 2002
and orders.cancelled = 1
Works just fine.
Here is where i get stuck:
As an addition to deals, each deals has products with their own prices.
Table is called deal_products, deal_products.price hold the price and deal_products.product_id has the ID of it.
A order is attached to a deal product in another table called order_products, where order_products.product_id = deal_products.product_id
To sum up: I would like to do is including a if inside the above SQL.
If a order has a row in order_products, get the order_products.product_id and find the price in deal_products (price) and use this instead of deals.price when SUM()'ing.
If there is no row it should use deals.price.
How can this be archieved? To first look in another table if there is a entry, and then further look in to a third table and get a value to use?
You can use COALESCE + LEFT JOIN:
select SUM(coalesce(dp.price, d.price)), o.*
from orders o JOIN deals d ON d.ID = o.deal_id
LEFT JOIN order_products op on op.order_id = o.id
LEFT JOIN deal_products dp on op.product_id = dp.product_id
where o.user_id = 2002 and o.cancelled = 1
group by ...;
COALESCE function returns first not null operand
LEFT [OUTER] JOIN = [INNER] JOIN + all rows of the structure on the left side of the LEFT JOIN keyword, which don't match the ON clause in the right structure.