I have two tables, Orders and OrderDetails. I would like to create an SQL query that retrieves the OrderID, Unit Cost and Process Date for orders with the latest processed T-Shirts for each user.
I have created a very simplified version of my tables in this SQL fiddle.
From my google searches, most of which ended up here on SO, I have managed to figure out SQL to get the associated unit cost for the latest processed order of T-shirts per order, but I'm not sure how to further condense it from here to be for each user and still maintain the correct unit cost and process dates. I know I can't use MAX and GROUP BY and I can't work out how to do a LEFT OUTER JOIN (like how I did the first part in the SQL fiddle) when the UserID and ProcessDate reside in different tables.
For the dataset in the SQL fiddle, the output I would be looking for would be
| UserID | OrderID | UnitCost | ProcessDate |
|-------- |--------- |---------- |------------- |
| 1 | 2 | 3 | 2018-03-01 |
| 2 | 3 | 1 | 2018-04-01 |
| 3 | 5 | 3 | 2018-06-01 |
Above query output is perfectly fine just the case is correlated subquery gets executed everytime for each row which is quite a overhead and in case of large data there will be performance issue.
I have changed the subquery into join which may be helpful.
SELECT O.UserID
, O.OrderID
, OD.UnitCost
, OD.ProcessDate
FROM Orders O
left
JOIN OrderDetails OD
ON O.OrderID = OD.OrderID
left
join
( SELECT o2.userid
, max(od2.processdate) newdate
FROM Orders o2
JOIN OrderDetails od2
ON o2.OrderId = od2.OrderId
WHERE od2.Product = 'T-Shirts'
group
by o2.userid
) temp
on O.userid = temp.userid
WHERE OD.Product = 'T-Shirts'
and OD.processdate = temp.newdate
ORDER
BY OD.OrderDetailID;
Hope it helps.
You can select the "latest" order for each user using a correlated subquery:
SELECT O.UserID, O.OrderID, OD.UnitCost, OD.ProcessDate
FROM OrderDetails OD LEFT JOIN
Orders O
ON O.OrderID = OD.OrderID
WHERE OD.Product = 'T-Shirts' AND
o.OrderID = (SELECT MAX(o2.OrderId)
FROM Orders o2
WHERE o2.UserID = o.UserID
)
ORDER BY OD.OrderDetailID;
This assumes that the latest order is the one with the largest OrderId.
EDIT:
Actually, the above happens to work on your data, because the largest order id for each user has t-shirts. However, you explicitly say that you want the latest order with t-shirts, so that requires a slight modification:
SELECT O.UserID, O.OrderID, OD.UnitCost, OD.ProcessDate
FROM Orders O LEFT JOIN
OrderDetails OD
ON O.OrderID = OD.OrderID
WHERE OD.Product = 'T-Shirts' AND
o.OrderID = (SELECT MAX(o2.OrderId)
FROM Orders o2 JOIN
OrderDetails od2
ON o2.OrderId = od2.OrderId
WHERE od2.Product = 'T-Shirts' AND
o2.UserID = o.UserID
)
ORDER BY OD.OrderDetailID;
Or, if you want to go by process date:
SELECT O.UserID, O.OrderID, OD.UnitCost, OD.ProcessDate
FROM Orders O LEFT JOIN
OrderDetails OD
ON O.OrderID = OD.OrderID
WHERE OD.Product = 'T-Shirts' AND
od.ProcessDate = (SELECT od2.ProcessDate
FROM Orders o2 JOIN
OrderDetails od2
ON o2.OrderId = od2.OrderId
WHERE od2.Product = 'T-Shirts' AND
o2.UserID = o.UserID
ORDER BY od2.ProcessDate DESC
LIMIT 1
)
ORDER BY OD.OrderDetailID;
Related
First of all, apologies if this seems like a very dumb question, I have just started working with mySQL.
I have 2 tables:
1- Customer_courier_chat
2- Orders
Basically I want to do a query to count the amount of messages from a customer and the amount of messages from a courier. When I do an individual query to look for it, it works well but when I do both in the same query it returns different values.
The following query provides good results:
SELECT o.*, count(j.from_id) AS messages_courier
FROM test.orders o
INNER JOIN test.customer_courier_chat_messages j ON j.from_id = j.courier_id and o.order_id = j.order_id
Group BY o.order_id;
Results of the previous query messgaes from courier
The following one also provides good results:
SELECT o.*, count(k.from_id) AS messages_customer
FROM test.orders o
INNER JOIN test.customer_courier_chat_messages k ON k.from_id = k.customer_id and o.order_id = k.order_id
Group BY o.order_id;
Results of the previous query messages from customer
The main problem comes when I try to do both in the same query as the output is not correct.
SELECT o.*, count(j.courier_id) messages_courier, count(k.from_id) AS messages_customer
FROM test.orders o
INNER JOIN test.customer_courier_chat_messages j ON j.from_id = j.courier_id and o.order_id = j.order_id
INNER JOIN test.customer_courier_chat_messages k ON k.from_id = k.courier_id and o.order_id = k.order_id
Group BY o.order_id;
Results of previous query not working
Based on the results of the 2 invidiual queries, the previous one should provide:
order_id |city_code |messages_courier |messages_customer|
59528555 | ES | 2 | 5 |
11223344 | FR | 3 | 4 |
But, it actually provides:
order_id |city_code |messages_courier |messages_customer|
59528555 | ES | 4 | 4 |
11223344 | FR | 9 | 9 |
Am I missing something?
Thank you in advanced.
Kindly regards,
J
You can do it with conditional aggregation:
SELECT o.order_id, o.city_code,
SUM(m.from_id = m.courier_id) AS messages_courier,
SUM(m.from_id = m.customer_id) AS messages_customer
FROM test.orders o INNER JOIN test.customer_courier_chat_messages m
ON o.order_id = m.order_id
GROUP BY o.order_id, o.city_code;
You are first joining the table then using the count. You have to first pick get the count then use the join -
SELECT o.*, j.cnt messages_courier, k.cnt AS messages_customer
FROM test.orders o
INNER JOIN (SELECT order_id, COUNT(*) CNT
FROM test.customer_courier_chat_messages
WHERE from_id = courier_id
GROUP BY order_id) j ON o.order_id = j.order_id
INNER JOIN (SELECT order_id, COUNT(*) CNT
FROM test.customer_courier_chat_messages
WHERE from_id = customer_id
GROUP BY order_id) k ON o.order_id = k.order_id;
I have a query on 3 tables, settlement, order and passenger.
A settlement has orders, and orders has passengers.
TABLE: Settlemnt
id
TABLE: Order
id settlement_id
TABLE: passenger
id orders_id
If settlement has no orders (and then no passengers), it returns nothing.
What I want, is to return the empty settlement even if it has no orders.
It is the count(passengers) that ruins it for me and returns no settlement if empty.
This is the simplified query:
select s.id, o.id, count(distinct p.id)
from settlement s
left join orders o ON o.settlement_id = s.id
left join passenger p on p.orders_id = o.id
where s.date = '2016-02-02';
group by o.id
How can I make it return settlements with no orders?
Works for me...
SELECT s.id s_id
, o.id o_id
, p.id p_id
FROM settlement s
LEFT
JOIN orders o
ON o.sid = s.id
LEFT
JOIN passengers p
ON p.oid = o.id;
+------+------+------+
| s_id | o_id | p_id |
+------+------+------+
| 1 | NULL | NULL |
| 2 | 1 | 1 |
+------+------+------+
http://sqlfiddle.com/#!9/9a01b/4
The major problem with your query appears to be that you GROUP BY the o.id. Any settlement that has no order will have an o.id of NULL, hence all settlements with no orders will be lumped together into one row. Which s.id that is returned for this o.id is not defined (and effectively random).
Change it to GROUP BY both s.id and o.id
SELECT s.id,
o.id,
COUNT(DISTINCT p.id)
FROM settlement s
LEFT JOIN orders o ON o.settlement_id = s.id
LEFT JOIN passenger p ON p.orders_id = o.id
WHERE s.date = '2016-02-02';
GROUP BY s.id,
o.id
I want to find the last payment (or NULL if n/a) made for which specified product_id. Below is a representation of the tables I'm working with (simplified version).
+----------+
|Products |
|----------+
|product_id|
+----------+
+---------------+
|Orders |
+---------------+
|order_id |
|order_timestamp|
|order_status |
+---------------+
+-----------------+
|ProductsOrdersMap|
+-----------------+
|product_id |
|order_id |
+-----------------+
After JOINs, MAXs, GROUP BYs, LEFT JOINs, multiple INNER JOINs to get the greatest-n-per-group, I still can't get to the right result. Most of the times, products with multiple orders are returning multiple rows. The best results I got so far were (I was searching specific products):
product_id order_id order_timestamp order_status
8 NULL NULL NULL
9 NULL NULL NULL
10 NULL NULL NULL
12 NULL NULL NULL
13 NULL NULL NULL
14 11 2013-08-13 07:22:01 finished
15 11 2013-08-13 07:22:01 finished
15 12 2013-08-14 00:00:00 finished
32 11 2013-08-13 07:22:01 finished
83 9 2013-08-13 07:04:02 finished
83 10 2013-08-13 07:11:42 finished
Edit: After PP. anwser, I ended up with the following query:
SELECT p.product_id, o.order_id, MAX(order_timestamp) AS order_timestamp, order_status
FROM Products p LEFT JOIN (ProductsOrdersMap m, Orders o)
ON (p.product_id = m.product_id AND m.order_id = o.order_id)
WHERE p.product_id IN (8,9,10,12,13,14,15,32,83)
GROUP BY p.product_id
Which returns
product_id order_id order_timestamp order_status
8 NULL NULL NULL
9 NULL NULL NULL
10 NULL NULL NULL
12 NULL NULL NULL
13 NULL NULL NULL
14 11 2013-08-13 07:22:01 finished
15 11 2013-08-13 07:22:01 finished
32 11 2013-08-13 07:22:01 finished
83 9 2013-08-13 07:04:02 finished
At first glance, it seems correct but only the products IDs and the timestamps are right. Comparing the two queries above, you can see that, for products 15 and 83, order_id is wrong (order_status might be wrong as well).
This query should return the specified resultset (this is only desk checked, not tested)
to return ALL product_id
SELECT p.product_id
, m.order_d
, m.order_timestamp
, m.order_status
FROM products p
LEFT
JOIN ( SELECT kl.product_id
, MAX(ko.order_timestamp) AS latest_timestamp
FROM orderproductsmap kl
JOIN orders ko
ON ko.order_id = kl.order_id
GROUP
BY kl.product_id
) l
ON l.product_id = p.product_id
LEFT
JOIN ( SELECT ml.product_id
, mo.order_id
, mo.order_timestamp
, mo.order_status
FROM orderproductsmap ml
JOIN orders mo
ON mo.order_id = ml.order_id
) m
ON m.product_id = l.product_id
AND m.order_timestamp = l.latest_timestamp
GROUP
BY p.product_id
The inline view "l" gets us the latest "order_timestamp" for each "product_id". This is joined to inline view "m" to get us the whole row for the order that has the latest timestamp.
If there happens to be more than one order with the same latest "order_timestamp" (i.e. order_timestamp is not guaranteed to be unique for a given product_id) then the outermost GROUP BY ensures that only one of those order rows is returned.
If only particular product_id values need to be returned, add a WHERE clause in the outermost query. For performance, that same predicate can be repeated in the inline views.
to return only SPECIFIC product_id we add three WHERE clauses:
SELECT p.product_id
, m.order_d
, m.order_timestamp
, m.order_status
FROM products p
LEFT
JOIN ( SELECT kl.product_id
, MAX(ko.order_timestamp) AS latest_timestamp
FROM orderproductsmap kl
JOIN orders ko
ON ko.order_id = kl.order_id
WHERE kl.product_id IN (8,9,10,12,13,14,15,32,83)
GROUP
BY kl.product_id
) l
ON l.product_id = p.product_id
LEFT
JOIN ( SELECT ml.product_id
, mo.order_id
, mo.order_timestamp
, mo.order_status
FROM orderproductsmap ml
JOIN orders mo
ON mo.order_id = ml.order_id
WHERE ml.product_id IN (8,9,10,12,13,14,15,32,83)
) m
ON m.product_id = l.product_id
AND m.order_timestamp = l.latest_timestamp
WHERE p.product_id IN (8,9,10,12,13,14,15,32,83)
GROUP
BY p.product_id
Only the WHERE clause on the outermost query is required. The other two are added just to improve performance by limiting the size of each of the derived tables.
SELECT
P.product_id
,MAX(order_timestamp)
FROM
Products P
,Orders O
,ProductsOrdersMap M
WHERE
P.product_id = M.product_id
AND O.order_id = M.order_id
GROUP BY
P.product_id
To return all products, even those without orders, a LEFT JOIN is definitely the way to go. The answer from #PP above uses "old-style" inner joins and is equivalent to this:
SELECT
P.product_id
,MAX(order_timestamp)
FROM Products P
INNER JOIN ProductsOrdersMap M ON P.product_id = M.product_id
INNER JOIN Orders O ON O.order_id = M.order_id
GROUP BY
P.product_id
Starting with this syntax it's a lot easier to get to the LEFT JOIN - just replace INNER with LEFT:
SELECT
P.product_id
,MAX(order_timestamp)
FROM Products P
LEFT JOIN ProductsOrdersMap M ON P.product_id = M.product_id
LEFT JOIN Orders O ON O.order_id = M.order_id
GROUP BY
P.product_id
Addendum: Renato needed something more than just reworking the other answer as a LEFT JOIN because the order_id and order_status have to come along with the maximum timestamp. The easiest approach is to start with a list of product ID's and order ID's where the order has the maximum timestamp by order_id:
SELECT
p2.product_id,
o2.order_id
FROM Products p2
INNER JOIN ProductsOrdersMap m ON p2.product_id = m.product_id
INNER JOIN Orders o2 ON m.order_id = o2.order_id
WHERE (o2.order_id, o2.order_timestamp) IN (
SELECT order_id, MAX(order_timestamp)
FROM Orders
GROUP BY order_id)
Then, instead of using ProductsOrdersMap to resolve products to orders, use the results from the query above:
SELECT
p.product_id,
o.order_id,
o.TS,
o.order_status
FROM Products p
LEFT JOIN (
SELECT
p2.product_id,
o2.order_id
FROM Products p2
INNER JOIN ProductsOrdersMap m ON p2.product_id = m.product_id
INNER JOIN Orders o2 ON m.order_id = o2.order_id
WHERE (o2.order_id, o2.order_timestamp) IN (
SELECT order_id, MAX(order_timestamp)
FROM Orders
GROUP BY order_id)
) MaxTS ON p.product_id = MaxTS.product_id
LEFT JOIN Orders o ON MaxTS.order_id = o.order_id
i need little help in writing the MYSQL Query.
i want to retreive the data from 3 tables, but i want to retreive the data from 3rd table only if the count() value is equals to 1.
please see the below query.
SELECT count(orderdetails.orderId) as total,gadgets.*,orders.* FROM orders
JOIN orderdetails ON orders.orderId = orderdetails.orderId
CASE total WHEN 1 THEN (JOIN gadgets ON gadgets.gadgetId = orders.gadgetId)
GROUP BY orders.orderId
ORDER BY orders.orderId DESC;
mysql always gives me an error, and i couldnt find any solution over internet.
Just add a Simple Condition in Join, and it would work (Of course you have make it Left Join).
SELECT count(orderdetails.orderId) as total,gadgets.*,orders.* FROM orders
JOIN orderdetails ON orders.orderId = orderdetails.orderId
LEFT JOIN gadgets ON gadgets.gadgetId = orders.gadgetId
and total=1 --Simple Logic
GROUP BY orders.orderId
ORDER BY orders.orderId DESC;
SELECT
g.*, o.*
FROM
orders AS o
JOIN
( SELECT orderId
FROM orderdetails
GROUP BY orderId
HAVING COUNT(*) = 1
) AS od
ON o.orderId = od.orderId
JOIN gadgets AS g
ON g.gadgetId = o.gadgetId
ORDER BY
o.orderId DESC ;
You can join the table and get only results having total = 1
SELECT count(orderdetails.orderId) as total,gadgets.*,orders.* FROM orders
JOIN orderdetails ON orders.orderId = orderdetails.orderId
JOIN gadgets ON gadgets.gadgetId = orders.gadgetId
GROUP BY orders.orderId
HAVING total = 1
ORDER BY orders.orderId DESC;
HTH
I have a query like this
SELECT o.product_id,
p.name,
count(o.product_id) AS total_products_sold,
(SELECT count(o.id)
FROM ebay.`order` o) AS total_orders
FROM ebay.`order` o
INNER JOIN product p ON o.product_id = p.id
GROUP BY o.product_id
The total_orders is rerun when executed for each which not i want. I
Question:
I want the total_orders combines with every result set from the outer query.
I tried this but it only return 1 row
SELECT tps.product_id,
tps.name,
tps.total_products_sold,
count(oo.id) AS total_orders
FROM ebay.`order` oo
INNER JOIN
( SELECT o.id,
o.product_id,
p.name,
count(o.product_id) AS total_products_sold
FROM ebay.`order` o
INNER JOIN product p ON o.product_id = p.id
GROUP BY o.product_id ) AS tps ON oo.product_id = tps.product_id
Any better solution ?
Thanks.
You can use with rollup which will give you the total without changing the actual query
It wont give you the result in column of every row but you will get the result of total orders in the last row.
SELECT
o.product_id,
p.name,
count(distinct o.id) AS totalorder
FROM
ebay.`order` o
INNER JOIN
product p
ON
o.product_id = p.id
GROUP BY
o.product_id
WITH ROLLUP
For example
+-----------+------+------------+
| product_id| name | totalorder |
+-----------+------+------------+
| 2000 | A | 10 |
| 2001 | B | 20 |
| NULL | NULL | 30 | <--- Last row is having the Total order
+-----------+------+------------+
WITH ROLLUP
SELECT tps.product_id,
tps.name,
tps.total_products_sold,
s.total_orders
FROM ebay.`order` oo
INNER JOIN
(
SELECT o.id,
o.product_id,
p.name,
count(o.product_id) AS total_products_sold
FROM ebay.`order` o
INNER JOIN product p
ON o.product_id = p.id
GROUP BY o.product_id
) AS tps ON oo.product_id = tps.product_id
CROSS JOIN
(
SELECT count(id) total_orders
FROM ebay.`order`
) s