I have two tables in MySQL sales database:
Orders table:
CREATE TABLE salestest.`orders` (
`ID` int(11) unsigned NOT NULL auto_increment,
`OrderDate` datetime NOT NULL,
`CustomerID` int(11) unsigned NOT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `ID` (`ID`),
KEY `OrderDate` (`OrderDate`),
KEY `CustomerID` (`CustomerID`)
) ENGINE=InnoDB;
INSERT INTO salestest.orders VALUES
( 1, '2012-04-15', 1 ),
( 2, '2012-05-20', 1 ),
( 3, '2012-06-30', 1 );
OrderDetails table:
CREATE TABLE salestest.`OrderDetails` (
`ID` int(11) unsigned NOT NULL auto_increment,
`OrderID` int(11) unsigned NOT NULL,
`ProductID` int(11) unsigned NOT NULL,
`Price` double NOT NULL default '0',
PRIMARY KEY (`ID`),
UNIQUE KEY `ID` (`ID`),
KEY `OrderID` (`OrderID`),
KEY `ProductID` (`ProductID`),
CONSTRAINT `OrderID_fk` FOREIGN KEY (`OrderID`) REFERENCES `orders` (`ID`)
) ENGINE=InnoDB;
INSERT INTO salestest.OrderDetails VALUES
( 1, 1, 1, 2 ),
( 2, 1, 2, 15 ),
( 3, 1, 3, 22 ),
( 4, 2, 1, 3 ),
( 5, 2, 2, 17 ),
( 6, 2, 3, 23 ),
( 7, 2, 4, 40 ),
( 8, 3, 1, 4 ),
( 9, 3, 2, 20 );
Now I need to select for each customer, the last price they purchase each product.
The easy way to do it is by using a subquery:
SELECT od2.CustomerID,od2.ProductID, od2.Price AS LastPrice, od2.OrderDate AS LastDate
FROM (SELECT o1.ID, o1.CustomerID, o1.OrderDate, od1.ProductID, od1.Price
FROM orders AS o1
LEFT JOIN OrderDetails as od1 ON o1.ID=od1.OrderID
ORDER BY OrderDate DESC) AS od2
GROUP BY CustomerID, ProductID
ORDER BY CustomerID, ProductID;
Result:
CustomerID ProductID LastPrice LastDate
1 1 4 2012-06-30 00:00:00
1 2 20 2012-06-30 00:00:00
1 3 23 2012-05-20 00:00:00
1 4 40 2012-05-20 00:00:00
Now the question; how is it possible to get the same result if I want to avoid sub-query, temp tables or a view, I only want to use joins; this query is a small part of a much larger query, and having sub-query is highly inefficient.
I tried this query:
SELECT o1.CustomerID,od1.ProductID, od1.Price AS LastPrice, o1.OrderDate AS LastDate
FROM Orders AS o1 LEFT JOIN OrderDetails as od1 ON o1.ID=od1.OrderID
GROUP BY CustomerID, ProductID
ORDER BY CustomerID, ProductID;
but it gives a different result:
CustomerID ProductID LastPrice LastDate
1 1 2 2012-04-15 00:00:00
1 2 15 2012-04-15 00:00:00
1 3 22 2012-04-15 00:00:00
1 4 40 2012-05-20 00:00:00
As you see, LastPrice & LastDate are not correct. I also tried Allen’s suggestion, but the result is:
CustomerID ProductID LastPrice LastDate
1 1 4 2012-06-30 00:00:00
1 2 20 2012-06-30 00:00:00
first query from spencer’s answer results duplicated products:
CustomerID ProductID LastPrice LastDate
1 3 22 2012-04-15 00:00:00
1 3 23 2012-05-20 00:00:00
1 4 40 2012-05-20 00:00:00
1 1 4 2012-06-30 00:00:00
1 2 20 2012-06-30 00:00:00
other answers all use sub-query, which I am trying to avoid.
any suggestions?
UPDATE:
I have been unable to reproduce the result set using only joins (without using an inline view or a correlated subquery).
I don’t think it’s possible to reliably return the specified result set wihtout using an inline view or a correlated subquery.
This returns the specified result, but with no inlineview and no subqueries. (But that’s not to say that this is going to be the “fastest” query that returns the result set.
NOT WORKING… please standby
This query joins the tables to themselves and filters out the “topmost” row for each group.
—
Conceptually, that query is the same as the following query. The following query makes use of an “inline view” (aliased as
aandb). The purpose of the inline view is really just to get CustomerID and OrderDate associated with each OrderDetail line.We’d use a common table expression (CTE) in place of the inline views, if MySQL supported them.
Finally, here’s an entirely different approach, which uses MySQL “user variables” to simulate the analytic functions that are missing from MySQL.