SQL max of a count involving 3 tables - mysql

I have one table Customers with CustomerID and PhoneNumber, the second table is Orders that has CustomerId and OrderNumber and a third table OrderDetails that has OrderNumber, PriceOfOneUnit and UnitsOrdered. I need to find the PhoneNumber for the customer who placed the largest order (PriceOfOneUnit * UnitsOrdered). Doing a count(PriceOfOneUnit*UnitsOrdered) as A1 and then `Group By CustomerId Order By A1 DESC LIMIT 1 is evidently not working after joining the 3 tables. Can anyone help.

If we take you at your word, and what you want is the biggest single line-item rather than the largest order, you can find the largest line-item and then find the order to which it belongs and then the customer who placed that order. You can use a dummy aggregate function to pull back the order id from orderDetails.
EDIT:
OK, for someone just starting out, I think it can be clearer to think in terms of Venn diagrams and use what are called Inline Views and subqueries:
select customername, phone
from customer
inner join
(
select o.id, customerid from orders o
inner join
(
select od.orderid from orderdetail od
where (od.qty * od.itemprice) =
(
select max(od.qty * od.itemprice)
from orderdetail as od
)
) as biggestorder
on o.id = biggestorder.orderid
) as X
on customer.id = X.customerid
Each of the queries inside parentheses returns a set that can be joined/intersected with other sets.

Give this a try,
SELECT cus.CustomerId, cus.PhoneNumber
FROM Customers cus
INNER JOIN Orders a
ON cus.CustomerId = a.CustomerId
INNER JOIN OrderDetails b
On a.OrderNumber = b.OrderNumber
GROUP BY cus.CustomerId, cus.PhoneNumber
HAVING SUM(b.PriceOfOneUnit * b.UnitsOrdered) =
(
SELECT SUM(b.PriceOfOneUnit * b.UnitsOrdered) totalOrdersAmount
FROM Orders aa
INNER JOIN OrderDetails bb
On aa.OrderNumber = bb.OrderNumber
GROUP BY aa.CustomerId
ORDER BY totalOrdersAmount DESC
LIMIT 1
)

Related

SQL How to select original(distinct) values from table without using distinct, group by and over keywords?

Currently I'm studying and I received task to write query (join 4 tables: people, goods, orders and order details). So main table Order_details has two columns: Order_id and Good_id, in order to make possible to have many goods in one order (e.g. order with id = 1 has 3 rows in Order_details table but has different goods primary keys in each row).
So the problem is that I don't know any other possible methods(besides using group by, distinct or over()) to receive only one row of each order in Order_details table (like I would get by using for example Distinct keyword). I'm receiving completely same rows of each order (with same Order_id and Good_id) but i don't know how to get only one row of each order.
Here's my query(so basically i need to select sum of all goods in order but i don't think that it really matters in my problem) and scheme (if it'll help)
By the way I'm working with MYSQL.
SELECT
Order_details.Order_id,
Orders.Date, People.First_name,
People.Surname,
(
SELECT SUM(Goods.Price * Order_details.Quantity)
FROM Order_details, Goods
WHERE Order_details.Good_id = Goods.Good_id
AND Order_details.Order_id = Orders.Order_id
) AS Total_price
FROM Order_details, Goods, Orders, People
WHERE Order_details.Order_id = Orders.Order_id
AND Order_details.Good_id = Goods.Good_id
AND Order_details.Order_id = Orders.Order_id
AND Orders.Person_id = People.Person_id
ORDER BY Order_id ASC;
I have tried several methods, but still cant figure it out. Maybe somehow it is possible with subquery? But i'm not sure...
(I have tried method with UNION but it's not the key as well)
Remove the Goods and Order_details tables from the FROM clause and the corresponding conditions in the WHERE clause. You are not selecting anything from it anyway, except the SUM in the subselect. The Order_id can be selected from the Orders table. The join is just causing multiple rows per order.
Also please don't join with comma. Use the JOIN .. ON syntax. This makes it easier to see if the join conditions are reasonable.
SELECT
Orders.Order_id
Orders.Date,
People.First_name,
People.Surname,
(
SELECT SUM(Goods.Price * Order_details.Quantity)
FROM Order_details
JOIN Goods ON Order_details.Good_id = Goods.Good_id
WHERE Order_details.Order_id = Orders.Order_id
) AS Total_price
FROM Orders
JOIN People ON Orders.Person_id = People.Person_id
ORDER BY Orders.Order_id ASC;
you can use row_number() for this kind of thing it will assign a row number based on your criteria and then you can just pick the rows where the value is 1.
with t as (SELECT
Order_details.Order_id,
Orders.Date, People.First_name,
People.Surname,
row_number() over (
partition by order_id, good_id
order by order_id, good_id) rn,
(
SELECT SUM(Goods.Price * Order_details.Quantity)
FROM Order_details, Goods
WHERE Order_details.Good_id = Goods.Good_id
AND Order_details.Order_id = Orders.Order_id
) AS Total_price
FROM Order_details, Goods, Orders, People
WHERE Order_details.Order_id = Orders.Order_id
AND Order_details.Good_id = Goods.Good_id
AND Order_details.Order_id = Orders.Order_id
AND Orders.Person_id = People.Person_id
ORDER BY Order_id ASC)
select * from t where rn = 1

MySQL error code 1242

I'm trying to create a view of some columns from 3 different tables. One of the columns 'OrderNumber' is in 2 of the tables so I'm trying to do a UNION for them, but because I've made a subquery it returns an 1242 error and won't return more than 1 row. I just want to know how I can rewrite this query so that there are no subqueries, or is there someway to bypass it. Or perhaps I need to write multiple queries? Though I'd prefer to keep it to the one query, thanks.
CREATE VIEW CustOrderItems AS
SELECT CustFirstName,
CustLastName,
(SELECT OrderNumber
FROM Orders
UNION
SELECT OrderNumber
FROM Order_Details)
OrderDate,
ShipDate,
QuantityOrdered * QuotedPrice as ItemTotal
FROM Customers JOIN Orders JOIN Order_Details;
Substitute whatever your customer id
drop view if exists custorders;
create view custorders as
SELECT c.CustFirstName,
c.CustLastName,
o.OrderNumber order_ordernumber,
od.OrderNumber orderdetails_ordernumber,
o.OrderDate,
o.ShipDate,
od.QuantityOrdered * od.QuotedPrice as ItemTotal
FROM Customers c
JOIN Orders o on c.id = o.cust_id
JOIN Order_Details od on o.ordernumber = od.ordernumber
where c.id = ?
It's not clear what are your join criteria because the statement syntax is bad. But I assume you want to join on OrderNumber like SELECT ... FROM Customers INNER JOIN Orders ON Customers.OrderNumber = Orders.OrderNumber. In this case, if you want to use order numbers from two tables just repeat the query and make union of the two like:
SELECT ,,, FROM Customers INNER JOIN Order_Details ON Customers.OrderNumber = Order_Details.OrderNumber
UNION
SELECT FROM Customers INNER JOIN Orders ON Customers.OrderNumber = Orders.OrderNumber

Un Ordered Customer

I have two tables fist one customer and second one Orderdetails. I want list of sign ups from 4th on wards and exclude the list of customers who have bought.
SELECT *
FROM Customer
WHERE RegisteredDate BETWEEN CONVERT(DATETIME,CONVERT(VARCHAR(50),'12/04/2015',101))
AND CONVERT(DATETIME,CONVERT(VARCHAR(50),'12/07/2015',101))
AND CustomerID NOT IN(SELECT *
FROM Customer c
INNER JOIN orderdetails od ON c.CustomerId = od.CustomerID
WHERE C.RegisteredDate
BETWEEN CONVERT(DATETIME,CONVERT(VARCHAR(50),'12/04/2015',101))
AND CONVERT(DATETIME,CONVERT(VARCHAR(50),'12/07/2015',101))
AND Transactionid IS NOT NULL
)
the result is:
Msg 116, Level 16, State 1, Line 13 Only one expression can be
specified in the select list when the subquery is not introduced with
EXISTS.
Remove * (all columns) in subquery and specify select CustomerID instead of *(all columns) in subquery.
Because when using NOT IN, it is expected that the number of columns return by the subquery is only one.
As mentioned in other answer IN operator only expects one column to be returned when being used with a sub-query, you should only select the cutomerID in the sub-query but if there is a chance of it returning NULL values then use EXISTS operator something like....
SELECT *
FROM Customer c1
WHERE RegisteredDate BETWEEN CONVERT(DATETIME,'12/04/2015',101)
AND CONVERT(DATETIME,'12/07/2015',101)
AND NOT EXISTS (SELECT *
FROM Customer c
INNER JOIN orderdetails od ON c.CustomerId = od.CustomerID
WHERE C.RegisteredDate
BETWEEN CONVERT(DATETIME,'12/04/2015',101)
AND CONVERT(DATETIME,'12/07/2015',101)
AND Transactionid IS NOT NULL
AND C1.CustomerID = c.CustomerID
)
You cant use select * in a subquery, you need to specify the column which in this case is customerid.
SELECT *
FROM Customer
WHERE RegisteredDate BETWEEN CONVERT(DATETIME,CONVERT(VARCHAR(50),'12/04/2015',101))
AND CONVERT(DATETIME,CONVERT(VARCHAR(50),'12/07/2015',101))
AND CustomerID NOT IN(SELECT c.CustomerID
FROM Customer c
INNER JOIN orderdetails od ON c.CustomerId = od.CustomerID
WHERE C.RegisteredDate
BETWEEN CONVERT(DATETIME,CONVERT(VARCHAR(50),'12/04/2015',101))
AND CONVERT(DATETIME,CONVERT(VARCHAR(50),'12/07/2015',101))
AND Transactionid IS NOT NULL
)

Converting Multiple subqueries with GROUP BY to JOIN

I'm working on a simple ordering system in MySQL and I came across this snag that I'm hoping some SQL genius can help me out with.
I have a table for Orders, Payments (with a foreign key reference to the Order table), and OrderItems (also, with a foreign key reference to the Order table) and what I would like to do is get the total outstanding balance (Total and Paid) for the Order with a single query. My initial thought was to do something simple like this:
SELECT Order.*, SUM(OrderItem.Amount) AS Total, SUM(Payment.Amount) AS Paid
FROM Order
JOIN OrderItem ON OrderItem.OrderId = Order.OrderId
JOIN Payment ON Payment.OrderId = Order.OrderId
GROUP BY Order.OrderId
However, if there are multiple Payments or multiple OrderItems, it messes up Total or Paid, respectively (eg. One OrderItem record with an amount of 100 along with two Payment Records will produce a Total of 200).
In order to overcome this, I can use some subqueries in the following way:
SELECT Order.OrderId, OrderItemGrouped.Total, PaymentGrouped.Paid
FROM Order
JOIN (
SELECT OrderItem.OrderId, SUM(OrderItem.Amount) AS Total
FROM OrderItem
GROUP BY OrderItem.OrderId
) OrderItemGrouped ON OrderItemGrouped.OrderId = Order.OrderId
JOIN (
SELECT Payment.OrderId, SUM(Payment.Amount) AS Paid
FROM Payment
GROUP BY Payment.OrderId
) PaymentGrouped ON PaymentGrouped.OrderId = Order.OrderId
As you can imagine (and as an EXPLAIN on this query will show), this is not exactly an optimal query so, I'm wondering, is there any way to convert these two subqueries with GROUP BY statements into JOINs?
The following is likely to be faster with the right indexes:
select o.OrderId,
(select sum(oi.Amount)
from OrderItem oi
where oi.OrderId = o.OrderId
) as Total,
(select sum(p.Amount)
from Payment p
where oi.OrderId = o.OrderId
) as Paid
from Order o;
The right indexes are OrderItem(OrderId, Amount) and Payment(OrderId, Amount).
I don't like writing aggregation queries this way, but it can sometimes help performance in MySQL.
Some answers have already suggested using a correlated subquery, but have not really offered an explanation as to why. MySQL does not materialise correlated subqueries, but it will materialise a derived table. That is to say with a simplified version of your query as it is now:
SELECT Order.OrderId, OrderItemGrouped.Total
FROM Order
JOIN (
SELECT OrderItem.OrderId, SUM(OrderItem.Amount) AS Total
FROM OrderItem
GROUP BY OrderItem.OrderId
) OrderItemGrouped ON OrderItemGrouped.OrderId = Order.OrderId;
At the start of execution MySQL will put the results of your subquery into a temporary table, and hash this table on OrderId for faster lookups, whereas if you run:
SELECT Order.OrderId,
( SELECT SUM(OrderItem.Amount)
FROM OrderItem
WHERE OrderItem.OrderId = OrderId
) AS Total
FROM Order;
The subquery will be executed once for each row in Order. If you add something like WHERE Order.OrderId = 1, it is obviously not efficient to aggregate the entire OrderItem table, hash the result to only lookup one value, but if you are returning all orders then the inital cost of creating the hash table will make up for itself it not having to execute the subquery for every row in the Order table.
If you are selecting a lot of rows and feel the materialisation will be of benefit, you can simplifiy your JOIN query as follows:
SELECT Order.OrderId, SUM(OrderItem.Amount) AS Total, PaymentGrouped.Paid
FROM Order
INNER JOIN OrderItem
ON OrderItem.OrderID = Order.OrderID
INNER JOIN
( SELECT Payment.OrderId, SUM(Payment.Amount) AS Paid
FROM Payment
GROUP BY Payment.OrderId
) PaymentGrouped
ON PaymentGrouped.OrderId = Order.OrderId;
GROUP BY Order.OrderId, PaymentGrouped.Paid;
Then you only have one derived table.
What about something like this:
SELECT Order.OrderId, (
SELECT SUM(OrderItem.Amount)
FROM OrderItem as OrderItemGrouped
where
OrderItemGrouped.OrderId = Order.OrderId
), AS Total,
(
SELECT SUM(Payment.Amount)
FROM Payment as PaymentGrouped
where
PaymentGrouped.OrderId = Order.OrderId
) as Paid
FROM Order
PS: You win again #Gordon xD
Select o.orderid, i.total, s.paid
From orders o
Left join (select orderid, sum(amount)
From orderitem) i
On i.orderid = o.orderid
Ieft join (select orderid, sum(amount)
From payments) s
On s.orderid = o.orderid

MySQL - Select last record from second table matching with first table

I have two tables customers and orders, below is the structure.
Table - customers
id
customer_name
Table - orders
id
order_id
customer_id
customers table have customers records and orders table have orders placed by customers,
customer_id in orders table is linked to the id field of customers table.
Now one customer can have zero or one or more than one orders, i want to get the last order placed by customers only.
when i run the following query a simple invisible join, it returns all the orders by the customer
SELECT customers.customer_name,orders.order_id FROM orders,customers WHERE orders.customer_id=customers.id
I have also tried different JOIN statements but cannot get the last order by the customer, i want to get it in one SQL query for all customers.
Thank you in advance for your help.
In MySQL there is just few ways to make it work (that I now actually). The first one is sort your table as desc before the join:
SELECT c.customer_name, o.customer_id, o.order_id,o.id FROM customers c
INNER JOIN orders o
ON o.id = (SELECT id FROM orders WHERE customer_id = c.id ORDER BY id DESC LIMIT 1)
Using in real time is the only way to get it done, but if you need to make some join on not real time you can create a temporary table or a alias table sorting it to make your select, like this:
CREATE TABLE tmp_your_table AS
SELECT * FROM orders ORDER BY id DESC
So now you are able to make this join work:
SELECT c.customer_name, o.customer_id, o.order_id,o.id FROM customers c
INNER JOIN tmp_your_table o ON o.id = tmp_your_table.id
Try this query
SELECT
c.customer_name,
max(o.order_id)
FROM
customers c
INNER JOIN
orders o
ON
o.customer_id = c.id
GROUP BY
c.customer_name
You don't have any date field in the order table so assuming the latest order will be the one which has max(order_id).
Try this query
SELECT
c.customer_name,
o.order_id
FROM
customers c
INNER JOIN
orders o
ON
o.customer_id = c.id
ORDER BY
o.id desc
LIMIT 1;