mysql: get two values from subquery - mysql

I am trying to use a subquery to retrieve the oldest order for each customer. I want to select email_address, order_id, and order_date
Tables:
customers(customer_id, email_address)
orders(order_id, order_date, customer_id)
What I've tried:
I can get either the order_id or the order_date by doing
SELECT email_address,
(SELECT order_date /* or order_id */
FROM orders o
WHERE o.customer_id = c.customer_id
ORDER BY order_date LIMIT 1)
FROM customers c
GROUP BY email_address;
but if I try to do SELECT order_id, order_date in my subquery, I get the error:
Operand should contain 1 column(s)

You can solve this with a JOIN, but you need to be careful to only JOIN to the oldest values for a given customer:
SELECT c.email_address, o.order_id, o.order_date
FROM customers c
JOIN orders o ON o.customer_id = c.customer_id AND
o.order_date = (SELECT MIN(order_date) FROM orders o2 WHERE o2.customer_id = c.customer_id)

You could use a JOIN to get the result you want, or modify your query as below:
SELECT email_address,
(SELECT order_date
FROM orders o1
WHERE o1.customer_id = c.customer_id
ORDER BY order_date LIMIT 1) as `order_date`,
(SELECT order_id
FROM orders o2
WHERE o2.customer_id = c.customer_id
ORDER BY order_date LIMIT 1) as `order_id`
FROM customers c
GROUP BY email_address;

The JOIN is of your choice.
How can I select multiple columns from a subquery (in SQL Server) that should have one record (select top 1) for each record in the main query?
SELECT o.order_id, c.email_address, o.order_date
FROM customers c
INNER JOIN (
SELECT order_date, order_id, customer_id
FROM orders o
ORDER BY order_date
) as o on o.customer_id = c.customer_id
GROUP BY email_address;

Related

Orders that have not yet been paid mysql

I am stuck with the following problem: I would like to return from my query the orders which have yet to be paid with the amount of the order and my customer information.
Here is what the tables look like:
Taking the case of customer number 124. I can see that this one has placed 13 orders. But I only have 7 checks registered
And I can see that if I add the sum of the orders minus the sum of the payments I find a difference that corresponds to the last 3 orders that this customer has placed.
So the question is how do I bring out the commands 10382, 10371, 10368 with all that? Knowing that the solution must be able to be applied to all orders.
So I thought to myself maybe using the order and payment dates. What orders does my customer place between their last payment and today?
So I tried something like this
#last order date
WITH last_cmd AS
(
SELECT
customerNumber, od.orderNumber, orderDate last_orderDate,
shippedDate, SUM(quantityOrdered * priceEach) totcmd
FROM
orders o
LEFT JOIN
orderdetails od ON o.orderNumber = od.orderNumber
WHERE
status NOT LIKE 'Can%'
GROUP BY
orderDate
ORDER BY
customerNumber, orderDate DESC
),
#last payment date last_payment AS
(
SELECT
customerNumber, amount, paymentDate, checkNumber
FROM
payments
GROUP BY
paymentDate
ORDER BY
customerNumber, paymentDate DESC
)
SELECT *
FROM last_cmd lc
LEFT JOIN last_payment lp ON lc.customerNumber = lp.customerNumber
GROUP BY lc.orderNumber
HAVING MAX(paymentDate) NOT BETWEEN MAX(last_orderDate) AND NOW();
But as you can see the result is not really what is expected. Still taking customer 124 as an example, command 10382 does not appear. And when I do the comparison of other orders manually for other customers the results do not match.
Does anyone have an idea please?
Here is the query I tried before posting
WITH ALL_PAYMENTS AS (
WITH paymentsValues AS (
select customerNumber, checkNumber, YEAR(paymentDate) paymentYear, sum(amount) paymentValue
from payments
LEFT JOIN customers USING (customerNumber)
GROUP BY customerNumber, paymentYear)
SELECT
customerNumber,
checkNumber, #Cumul des commandes clients
paymentYear,
SUM(paymentValue) totalPaymentsValue
FROM
paymentsValues
GROUP BY
customerNumber,
checkNumber
WITH ROLLUP
HAVING checkNumber is null),
ALL_ORDERS AS(
WITH ordersValues AS (
select customerNumber, orderNumber, YEAR(orderDate) orderYear, sum(quantityOrdered*priceEach) AS orderValue
from orderdetails
LEFT JOIN orders USING (orderNumber)
GROUP BY orderNumber, orderYear)
SELECT
customerNumber,
orderNumber, #Cumul des commandes clients
orderYear,
SUM(orderValue) totalOrderValue
FROM
ordersValues
#where orderNumber = '10179'
GROUP BY
customerNumber,
orderNumber
WITH ROLLUP
HAVING orderNumber is null)
SELECT AO.customerNumber, totalOrderValue, totalPaymentsValue, totalOrderValue-totalPaymentsValue Diff
FROM ALL_ORDERS AO
LEFT JOIN ALL_PAYMENTS AP ON AP.customerNumber=AO.customerNumber
WHERE totalOrderValue-totalPaymentsValue > 0;
This should give a list op amount that have to be paid, per date:
WITH d as (
select distinct orderDate as `date` from orders
union
select distinct paymentDate as `date` from payments)
select
`date`,
`customerNumber`,
`ordersValue`,
`amountPayed`,
`ordersValue` - `amountPayed` NotPayed
from (
select
d.`date` as "date",
o.customerNumber as "customerNumber",
sum(quantityOrders*priceEach) ordersValue,
(select sum(amount)
from payments p
where o.customerNumber = p.customerNumber AND p.paymentDate <= d.`date`) as amountPayed
from orderdetails od
cross join d
inner join Orders o on od.orderNumber = o.orderNumber AND o.orderDate <= d.`date`
group by d.`date`, o.customerNumber
) orderListPerCustomer
WHERE `customerNumber` = 124
ORDER BY `date` DESC
;
It is impossible to add order info, because there is no order info in the payments
This query does not take into account the status of the order (which might be Cancelled)
NOTE: This code is not tested, so there could be typing errors...

Finding first order in a single year

I'm trying to determine how many new people made an order in 2018. This looks straight forward enough but there is an error with putting calculated fields in the WHERE statement.
SELECT DISTINCT COUNT(c.customer_id)
FROM Customer c
LEFT JOIN
Orders o ON c.customer_id=o.customer_id
WHERE MIN(order_date) > '2017-12-31'
AND MIN(order_date) < '2019-01-01';
You can achieve this by putting a sequence number to the orders and then selecting the first row for each customer. Although, I'm not really sure why you're performing a count of the orders when you just want to consider the first orders. Nevertheless the below should work just fine.
SELECT count(res.customer_id) FROM (
SELECT c.customer_id,
ROW_NUMBER() OVER (PARTITION BY c.customer_id ORDER BY o.order_date ASC) row_num
FROM Customer c
LEFT JOIN Orders o ON c.customer_id=o.customer_id
WHERE o.order_date > '2017-12-31'
AND o.order_date < '2019-01-01'
) res WHERE res.row_num=1
Join with a subquery that finds the customers that were new in 2018.
SELECT COUNT(DISTINCT o.customer_id)
FROM Orders o
JOIN (
SELECT DISTINCT customer_id
FROM Orders
GROUP BY customer_id
HAVING MIN(order_date) > '2017-12-31'
) o1 ON o1.customer_id = o.customer_id
WHERE o.order_date < '2019-01-01';
There's also no need to join with Customers, since the customer ID is in Orders.
And the correct way to get the distinct count is COUNT(DISTINCT o.customer_id), not DISTINCT COUNT(o.customer_id).

MySQL - Using subquery to find an average

I am having trouble with the syntax of this query. I am trying to return the largest total for one company from each country.
The tables look like:
Orders
Columns
OrderID
CustomerID
EmployeeID
OrderDate
RequiredDate
OrderDetails
Columns
OrderID
ProductID
UnitPrice
Quantity
Products
Columns
ProductID
ProductName
QuantityPerUnit
UnitPrice
Customers
Columns
CustomerID
CompanyName
ContactName
Country
I have tried the following:
SELECT
T1.Country,
CompanyName,
T1.OrderSum
FROM
(SELECT
C.Country,
C.CompanyName,
SUM(UnitPrice * Quantity) AS OrderSum
FROM Customers C
JOIN Orders O
ON C.CustomerID = O.CustomerID
JOIN OrderDetails D
ON D.OrderID = O.OrderID
GROUP BY C.Country) T1
JOIN
-- TOP PAYMENT TOTALS BY COUNTRY
(SELECT COUNTRY,
MAX(OrderSum) AS OrderSum
FROM
-- PAYMENT TOTALS BY CUSTOMER
(SELECT C.Country,
C.CompanyName,
SUM(UnitPrice * Quantity) AS OrderSum
FROM Customers C
JOIN Orders O1
ON O1.CustomerID = C.CustomerID
JOIN OrderDetails D1
ON D1.OrderID = O1.OrderID
GROUP BY C.COUNTRY, C.CompanyName) T2
GROUP BY COUNTRY) T3
ON T1.COUNTRY = T3.COUNTRY
AND T1.OrderSum = T3.OrderSum
ORDER BY Country;
This query only returns three countries:
Ireland Hungry Owl All-Night Grocers 57317.3900
Norway Sant Gourmet 5735.1500
Poland Wolski Zajazd 3531.9500
But, this query I tried, returns all countries, but I am not sure if it is correct because I did not include the 'max' value as I did in the previous query:
SELECT
T1.Country,
CompanyName,
T1.OrderSum
FROM
(SELECT
C.Country,
C.CompanyName,
SUM(UnitPrice * Quantity) AS OrderSum
FROM Customers C
JOIN Orders O
ON C.CustomerID = O.CustomerID
JOIN OrderDetails D
ON D.OrderID = O.OrderID
GROUP BY C.CompanyName) T1
GROUP By Country
ORDER BY Country;
I am also unsure if I am calculating the order totals correctly, which may be the mistake on my part. But I am trying to find the company from each country that has the largest order total.
Sorry for all the text.
The following query would list only the companies with the largest order total per country:
SELECT A.Country, A.CompanyName, A.OrderSum
FROM (
SELECT
C.Country,
C.CompanyName,
SUM(D.UnitPrice * D.Quantity) AS OrderSum
FROM Customers C
JOIN Orders O ON C.CustomerID = O.CustomerID
JOIN OrderDetails D ON D.OrderID = O.OrderID
GROUP BY
C.Country, C.CustomerID
) A
JOIN (
SELECT
S.Country, MAX(S.OrderSum) as MaxSum
FROM (
SELECT
C.Country,
C.CompanyName,
SUM(D.UnitPrice * D.Quantity) AS OrderSum
FROM Customers C
JOIN Orders O ON C.CustomerID = O.CustomerID
JOIN OrderDetails D ON D.OrderID = O.OrderID
GROUP BY
C.Country, C.CustomerID
) S
GROUP BY
S.Country
) B ON A.Country = B.Country
WHERE
A.Country = B.Country AND
A.OrderSum = B.MaxSum
ORDER BY
A.Country, A.CompanyName
;
[UPDATE]
Note that the above SQL follows the way OrderSum is calculated in your listed queries. Given that table Products has QuantityPerUnit and UnitPrice, I suspect that your OrderSum should be multiplied by QuantityPerUnit as well – in which case you'll need to revise the math for OrderSum.

JOIN VS SUBQUERY

I need to do this but with a subquery, not a join. My problem is, how can I use a subquery to display another column? I could grab the info from there, but I'll be missing the order_date column from the orders table. Can I use a subquery to display it?
SELECT CONCAT(c.customer_first_name, ' ' , c.customer_last_name) AS customer_name, MAX(o.order_date) AS recent_order_date
FROM customers AS c
JOIN orders AS o
ON c.customer_id = o.customer_id
GROUP BY customer_name
ORDER BY MAX(o.order_date) DESC
It's not at all clear what resultset you are trying to return, but it looks an awful like the like the ubiquitous "latest row" problem.
The normative pattern for the solution to that problem is to use a JOIN to the inline view. If there's not a unique constraint, you run the possibility of returning more than one matching row.
To get the latest order (the row in the orders table with the maximum order_date for each customer, assuming that the (customer_id, order_date) tuple is unique, you can do something like this:
SELECT o.*
FROM ( SELECT n.customer_id
, MAX(n.order_date) AS latest_order_date
FROM orders n
GROUP BY n.customer_id
) m
JOIN orders o
ON o.customer_id = m.customer_id
AND o.order_date = m.latest_order_date
If you want to also retrieve columns from the customers table based on the customer_id returned from orders, you'd use a JOIN (not a subquery)
SELECT CONCAT(c.customer_first_name,' ',c.customer_last_name) AS customer_name
, c.whatever
, o.order_date AS recent_order_date
, o.whatever
FROM ( SELECT n.customer_id
, MAX(n.order_date) AS latest_order_date
FROM orders n
GROUP BY n.customer_id
) m
JOIN orders o
ON o.customer_id = m.customer_id
AND o.order_date = m.latest_order_date
JOIN customers c
ON c.customer_id = o.customer_id
ORDER BY o.order_date DESC, o.customer_id DESC
As I mentioned before, if a given customer can have two orders with the exact same value for order_date, there's potential to return more than one order for each customer_id.
To rectify that, we can return a unique key from the inline view, and use that in the join predicate to guarantee only a single row returned from orders.
(NOTE: this approach is specific to MySQL, with this syntax, other RDBMS will throw an error that essentially says "the GROUP BY must include all non-aggregates". But MySQL allows it.)
SELECT CONCAT(c.customer_first_name,' ',c.customer_last_name) AS customer_name
, c.whatever
, o.order_date AS recent_order_date
, o.whatever
FROM ( SELECT n.customer_id
, MAX(n.order_date) AS latest_order_date
, n.order_id
FROM orders n
GROUP BY n.customer_id
) m
JOIN orders o
AND o.customer_id = m.customer_id
AND o.order_date = m.latest_order_date
AND o.order_id = n.order_id
JOIN customers c
ON c.customer_id = o.customer_id
ORDER BY o.order_date DESC, o.customer_id DESC
I am not really sure i understand your question, but i think this works... (not tested though...)
SELECT
(
SELECT
CONCAT(c.customer_first_name, ' ' , c.customer_last_name)
FROM
customers c
WHERE
c.customer_id = o.customer_id
LIMIT 1
) AS customer_name,
MAX(o.order_date) AS recent_order_date
FROM
orders o
GROUP BY
customer_name
ORDER BY
MAX(o.order_date) DESC

Can I execute a COUNT() before GROUP BY

I am working on an mySQL assignment for school and I am stuck on a question. I am still new to mySQL. COUNT(o.customer_id) is not working the way I want. I want it to count the number of orders but it is counting all items. i.e. Customer 1 has 2 orders but it is returning 3 because one order has two items. I have three tables one with customers, another with orders than another with each item on each order. Ive posed my query below. Any help would be great.
SELECT email_address, COUNT(o.order_id) AS num_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 ot
ON o.order_id = ot.order_id
GROUP BY o.customer_id
HAVING num_of_orders > 1
ORDER BY total DESC;
As simple as use Distinct reserved word:
SELECT email_address, COUNT(distinct o.order_id) AS num_of_orders
Looks like you want to count the DISTINCT number of orders. Add a DISTINCT into the COUNT. Although MySQL allows you to use the SELECT expression in the HAVING clause, it's not good practice to do so.
SELECT email_address, COUNT(DISTINCT o.order_id) AS num_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 ot
ON o.order_id = ot.order_id
GROUP BY o.customer_id
HAVING COUNT(DISTINCT o.order_id) > 1
ORDER BY total DESC;
Just take out the join to items. All it is doing is duplicating rows when there are multiple items.
SELECT email_address, COUNT(o.order_id) AS num_of_orders,
SUM(((item_price - discount_amount) * quantity)) AS total
FROM customers c JOIN orders o
ON c.customer_id = o.customer_id
GROUP BY o.customer_id
HAVING COUNT(o.order_id) > 1
ORDER BY total DESC;