SQL JOIN, GROUP BY, DATES - sql

I have 3 tables and I have trouble with the quantity. I will apply my solution down. SUM and GROUP BY are not working.
PRODUCT_INFORMATION
PRODUCT_ID
PRODUCT_NAME
PRODUCT_DESCRIPTION
PCATEGORY_ID
PRODUCT_STATUS
WARRANTY_PERIOD
ORDERS
ORDER_ID
ORDER_DATE
ORDER_MODE
CUSTOMER_ID
ORDER_STATUS
ORDER_TOTAL
ORDER_ITEMS
ORDER_ID
LINE_ITEM_ID
PRODUCT_ID
UNIT_PRICE
QUANTITY
• whose name (PRODUCT_NAME) does not contain the characters ‘_’ and ‘<’.
• the total number of products sold (QUANTITY column of table oe.ORDER_ITEMS) of a given type to be greater than 200.
The list should contain the following columns:
• PRODUCT_NAME (by oe.PRODUCT_INFORMATION)
• TOTAL_QUANTITY - the total number of products sold of a given type
SELECT p.PRODUCT_NAME, sum(oi.QUANTITY), p.WARRANTY_PERIOD AS WARRANTY, o.ORDER_MODE
FROM PRODUCT_INFORMATION p
LEFT JOIN ORDER_ITEMS oi ON p.PRODUCT_ID=oi.PRODUCT_ID
LEFT JOIN ORDERS o ON o.ORDER_ID = oi.ORDER_ID
WHERE (p.PRODUCT_NAME NOT LIKE '%<%'
AND p.PRODUCT_NAME NOT LIKE '%\_%' ESCAPE '\')
AND oi.QUANTITY>200
group by p.PRODUCT_NAME;

What does "... are not working" mean? Did you get any error? If so, which one?
What is obvious, is that GROUP BY clause has to contain all non-aggregated columns. As problem doesn't require them to be displayed, I removed them from the select column list (both p.WARRANTY_PERIOD and o.ORDER_MODE).
Furthermore, you should move the "quantity > 200" condition from WHERE into the HAVING clause.
Finally, in order to avoid characters to be escaped, I switched from NOT LIKE to INSTR.
Something like this:
SELECT p.product_name,
SUM (oi.quantity) sum_quantity
FROM product_information p
LEFT JOIN order_items oi ON p.product_id = oi.product_id
LEFT JOIN orders o ON o.order_id = oi.order_id
WHERE INSTR (p.product_name, '<') = 0
AND INSTR (p.product_name, '_') = 0
GROUP BY p.product_name
HAVING SUM (oi.quantity) > 200;

Your query selects columns which are not in the GROUP BY clause, which is usually not a problem with MySQL, but would be with any other vendor.
Since you don't require the two last columns in your output, I suggest you just get rid of them.
Also, MySQL supports regexes, which are more suited to your problem than LIKE in my opinion.
SELECT p.PRODUCT_NAME, SUM(oi.QUANTITY)
FROM PRODUCT_INFORMATION p
LEFT JOIN ORDER_ITEMS oi ON p.PRODUCT_ID=oi.PRODUCT_ID
LEFT JOIN ORDERS o ON o.ORDER_ID = oi.ORDER_ID
WHERE p.PRODUCT_NAME REGEXP '[_<]' = 0
GROUP BY p.PRODUCT_NAME
HAVING SUM(oi.QUANTITY) > 200;
Edit : As pointed out by #Littlefoot, the quantity condition should be tested after the grouping and not before, and should therefore be in the HAVING clause rather than in the WHERE clause.

Related

using subquery in order to join columns from two tables

i started learning SQL and there is something i dont understand
i want to take the columns product_id and product_name from Production.products
and join it with the quantity column from the Production.stocks table
but instead of using join i want to use a subquery.
this is the code i wrote so far:
and i don't understand why it isn't working :(
SELECT P.product_id, P.product_name,(
SELECT S.quantity
FROM Production.stocks AS S
WHERE S.product_id = P.product_id)
FROM Production.products as P;
First, let's clear up the fact that it is not recommended to use a subquery at all. Do it only for your own research reasons; if you have performance or code clarity in mind, go with the simple join.
When you make a subquery on the SELECT clause by enclosing it in parenthesis, you are forcing the result to be one single value. If not, you get the error you receive.
Usually, subqueries are used in the FROM clause, where they should be given a name and then represent a table. Like this:
SELECT P.product_id, P.product_name,S.quantity
FROM Production.products as P
inner join
(
SELECT quantity
FROM Production.stocks
) as S on S.product_id = P.product_id
You can see from the simplicity of the subquery of how little use it is.
You can use SUM keyword for prevent error and give you total quatity.
SELECT P.product_id, P.product_name,(
SELECT SUM(S.quantity)
FROM Production.stocks AS S
WHERE S.product_id = P.product_id)
FROM Production.products as P;
I don't see a need at all for a subquery.
If the products are unique entities then surely a join onto the stocks table and doing a sum on the quantity would be more beneficial in terms of query performance
SELECT
Production.Products.Product_id,
Production.Products.product_name,
SUM(Production.Stocks.quantity) AS Quantity
FROM
Production.Products
LEFT JOIN
Production.Stocks
ON
Production.Stocks.product_id = Production.Products.product_id
GROUP BY
Production.Products.product_id,
Production.Products.product_name
If you need it to quote stock quantities by store then you need to add an addition join onto stores and add the store to the select and group by clause like so
SELECT
Production.Products.Product_id,
Production.Products.product_name,
Sales.Stores.store_name,
SUM(Production.Stocks.quantity) AS Quantity
FROM
Production.Products
LEFT JOIN
Production.Stocks
ON
Production.Stocks.product_id = Production.Products.product_id
LEFT JOIN
Sales.Stores
ON
Production.Stocks.store_id = Sales.Stores.store_id
GROUP BY
Production.Products.product_id,
Production.Products.product_name,
Sales.Stores.store_name
Hope that helps
did you mean select all data from products table and show quantity column for each product?
SELECT P.product_id, P.product_name, isnull(s.quantity, 0) as Quantity
FROM Production.products as P
left join Production.stocks AS S
on p.product_id = S.product_id
if you have one to many relation with Products and Stocks you should use subquery like this
SELECT P.product_id, P.product_name, isnull(s.quantity, 0) as Quantity
FROM Production.products as P
left join (
select product_id, sum(quantity) as Quantity
from Production.stocks
group by product_id)
as S on p.product_id = S.product_id
it will be produced aggregated sum value for quantity field

How to create a function that totals an order with several items?

I need to create a function which will return the total of an order. I've been given three tables with the following variables
Table 1 - Order
Order_ID
Date_Placed
Date_Fulfilled
Table 2 - Order Product
Order_ID
Product_ID
Product_Quantity
Table 3 - Product
Product_ID
Price
I'm struggling to put together a coherent function. Any help would be greatly appreciated.
I've already attempted to set up the function with joins between both tables, but am unable to figure out where I should be putting my equation.
BEGIN
SELECT order.order_id, SUM(product.price * order_item.quantity)
FROM `order`
JOIN `order_item` ON order.order_id = order_product.order_id
JOIN `product` ON order_product.product_id = product.product_id;
END $$
You might be surprised, but the orders table is not needed for this query. You can just aggregate off the other two tables:
SELECT oi.order_id, SUM(p.price * oi.quantity)
FROM order_item oi JOIN
product p
ON po.product_id = p.product_id
GROUP BY oi.order_id;
You'll need to take your select statement, and group it by your order.order_id. That way you'll have one row per order, with the sum total of that order.
SELECT order.order_id, SUM(product.price * order_item.quantity) as total_price
FROM `order`
JOIN `order_item` ON order.order_id = order_product.order_id
JOIN `product` ON order_product.product_id = product.product_id
GROUP BY order.order_id
this will work:
SELECT order.order_id, SUM(product.price * order_item.quantity)
FROM order o,
JOIN order_item oi,
JOIN product p where
o.order_id = oi.order_id and
oi.product_id = p.product_id
group by order_product.product_id = product.product_id;

Having an issue with the last part of my Oracle SQL query

I'm having trouble with a problem (and yes it is homework).. The question is:
Write a SELECT statement that returns one row for each customer that has orders with these columns:
The email_address from the Customers table
A count of the number of orders
The total amount for each order (Hint: First, subtract the discount amount from the price. Then, multiply by the quantity.)
Return only those rows where the customer has more than 1 order.
Sort the result set in descending sequence by the sum of the line item amounts.
My query for this part works. It is:
SELECT email_address, COUNT(o.order_id) as number_of_orders, sum((item_price-discount_amount)*quantity) As Total
FROM Customers c JOIN Orders o
ON c.customer_id = o.customer_id
JOIN order_items oi on oi.order_id =o.order_id
GROUP BY email_address
HAVING COUNT (o.order_id) > 1
ORDER BY number_of_orders DESC;`
After that, I'm supposed to modify that query so that it only counts and totals line items that have an item_price value greater than 400. I can't seem to figure it out. Can someone please point me in the right direction?
BTW, it's the My Guitar Shop database.
You will have to add a filter for item_price greater than 400 assuming item_price is part of order_items table.
SELECT email_address, COUNT(o.order_id) as number_of_orders, sum((item_price-discount_amount)*quantity) As Total
FROM Customers c JOIN Orders o
ON c.customer_id = o.customer_id
JOIN order_items oi on oi.order_id =o.order_id
where oi.item_price > 400
GROUP BY email_address
HAVING COUNT (o.order_id) > 1
ORDER BY number_of_orders DESC;
SELECT email_address, COUNT(o.order_id) as number_of_orders, sum((item_price-discount_amount)*quantity) As Total
FROM Customers c JOIN Orders o
ON c.customer_id = o.customer_id
JOIN (
SELECT order_id,
CASE WHEN item_price < 400 THEN 0 ELSE item_price END,
--other columns you need
FROM order_items ) oi on oi.order_id =o.order_id
GROUP BY email_address
HAVING COUNT (o.order_id) > 1
ORDER BY number_of_orders DESC;
This is should work.
Also, you should always use table aliases when addressing a coulmn- even if there are no name clashed. It improves readability and prevent you from making nasty mistakes.

Select all orders by one customer

I have three tables, orders, orders_details and customers. I need to select orders by one customer for the orders table so I did this
orders columns:
id
customer_id
created
vat
discount
amount
paid
orders_details columns:
id
order_id
cost
qty
product
The SQL I used
SELECT
orders.*,
SUM(orders_details.qty*orders_details.cost) as amount,
SUM(orders_details.qty) AS qty
FROM
orders,
orders_details,
customers
WHERE
orders.customer_id = customers.id
AND orders_details.order_id = orders.id
AND orders.customer_id = 1
but I am getting a wrong qty of 30 instead of 20 and the amount is wrong
If you want to aggregate per order you need a GROUP BY clause. Also you should use proper JOIN syntax, and might consider using aliases to make the query more compact.
SELECT
o.*,
SUM(od.qty * od.cost) AS amount,
SUM(od.qty) AS qty
FROM orders o
INNER JOIN orders_details od ON od.order_id = o.id
INNER JOIN customers c ON o.customer_id = c.id -- not used, might be excluded
WHERE o.customer_id =1
GROUP BY o.id
Depending on what database system you are using you might need to include all columns referenced in o.* in the GROUP BY:
GROUP BY o.id, o.customer_id, o.created, o.vat, o.discount, o.amount, o.paid
Last note: as you don't seem to use any data from the customers table you probably could exclude that table altogether.
You're missing a GROUP BY clause which I'm guessing should be on orders.id.

finding average dollar amount of an order

I am trying to find the average dollar amount of an order. I have calculated the average order Total but I need an average that takes into account the fact that not all Orders have a corresponding OrderItems.
This is a homework question and it is as follows:
What is the average $$ value of an order? To get the answer, you need
to add up all the order values and divide this by the
number of orders. There are two possible averages on this question,
because not all of the order numbers in the ORDERS table are in the
ORDERITEMS table... You will calculate and display both averages.
I have writtern the one ignoring orders with no OrderItem, but not sure of how to go about the second case.
SELECT SUM(OrderItems.qty*INVENTORY.price) / COUNT(*) AS dollarValue
FROM Orders, OrderItems, Inventory
WHERE ORDERS.orderid = OrderItems.orderid AND OrderItems.partid = Inventory.partid
Link To DB Diagram
The Avg function will not replace NULL with zero; it will exclude NULL from its calculation. If you have Order rows which have no OrderItem, you need to use Left Joins. A trick you can use in SQL Server is to nest the joins like so (note the parentheses):
Select Avg(OI.Qty * I.Price)
From Orders As O
Left Join (OrderItems As OI
Join Inventory As I
On I.PartId = OI.PartId)
On OI.OrderId = O.OrderId
This will join the Inventory table to the OrderItems table before it Left Joins that result to the Orders table. In this way, OI.Qty and I.Price with both return NULL for Orders that have no OrderItems and be excluded from the calculation. An equivalent approach to the above would be to use two Left Joins:
Select Avg(OI.Qty * I.Price)
From Orders As O
Left Join OrderItems As OI
On OI.OrderId = O.OrderId
Left Join Inventory As I
On I.PartId = OI.PartId
If you wanted to count Orders with no OrderItems as zero, then you need to covert those nulls to zero using Coalesce:
Select Avg(OI.Qty * I.Price) As Avg_ExcludingNull
, Avg( Coalesce(OI.Qty * I.Price,0) ) As Avg_NullAsZero
From Orders As O
Left Join (OrderItems As OI
Join Inventory As I
On I.PartId = OI.PartId)
On OI.OrderId = O.OrderId
SQL has an aggregate function for calculating the average: AVG()
SELECT AVG(OrderItems.qty*INVENTORY.price) AS dollarValue
FROM Orders, OrderItems, Inventory
WHERE ORDERS.orderid = OrderItems.orderid AND OrderItems.partid = Inventory.partid
While we're here, may I suggest you use the more modern JOIN syntax:
SELECT AVG(OrderItems.qty*INVENTORY.price) AS dollarValue
FROM Orders
JOIN OrderItems ON ORDERS.orderid = OrderItems.orderid
JOIN Inventory ON OrderItems.partid = Inventory.partid