没有子查询的 MySQL 组内聚合 - 建议的测试数据已更新

标签 mysql sql group-by grouping greatest-n-per-group

我在 MySQL 销售数据库中有两个表:

订单表:

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 );  

订单明细表:

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 );  

现在我需要为每个客户选择他们购买每种产品的最后价格。

最简单的方法是使用子查询:

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;  

结果:

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

现在是问题;如果我想避免子查询、临时表或 View ,我只想使用连接,怎么可能得到相同的结果;这个查询只是一个更大查询的一小部分,并且有子查询是非常低效的。

我试过这个查询:

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;

但它给出了不同的结果:

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

如您所见,LastPrice 和 LastDate 不正确。我也尝试了艾伦的建议,但结果是:

CustomerID ProductID LastPrice LastDate
1 1 4 2012-06-30 00:00:00
1 2 20 2012-06-30 00:00:00

spencer 的答案中的第一个查询结果重复的产品:

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

其他答案都使用子查询,我尽量避免。
有什么建议吗?

最佳答案

寻找"greatest-n-per-group"

这是我在 SQL 中学到的最棒的东西,我希望你也喜欢它。

好的,这是我的尝试:

SELECT o.CustomerID, od.ProductID, od.Price AS LastPrice, o.OrderDate AS LastDate  
FROM OrderDetails od
LEFT JOIN orders as o ON od.OrderID = o.ID
LEFT JOIN orders as o2 ON o.CustomerID = o2.CustomerID AND o.id < o2.id
WHERE o2.id IS NULL
ORDER BY o.CustomerID, od.ProductID;

您想按客户 + 产品了解客户最后一次购买每种产品的时间以及他们为此支付的费用。

所以我从产品开始,加入订单(首先加入),然后再次加入订单,这样我就可以将查询限制为每个客户 + 产品的单个订单(o2 匹配所有订单,但不包括最多最近的订单)。然后我们使用 o2 与最近的订单不匹配这一事实来只选择那一行。

这假设您不会在一个订单中以不同的价格两次购买相同的商品,并且较新的订单将始终具有更高的 ID。

希望这能让您足够接近,以便可以根据需要修改您的真实数据/查询 - 祝您好运!

关于没有子查询的 MySQL 组内聚合 - 建议的测试数据已更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11317059/

相关文章:

sql - 如何仅从字符串中提取字母数字字符? (SQL 谷歌 BigQuery)

sql - GROUP BY 与 ORDER BY 组合

sql - PostgreSQL 中 INNER JOIN 中字段的 SUM

mysql - Laravel 5.6 - 不支持 ALGORITHM=COPY

php - PDO 可以为多个查询重用相同的语句句柄吗?

java - Hibernate自引用实体查询时重复字段

LINQ:日期时间字段中按月和年分组

mysql - 如何在每个 XX :50 Seconds? 上安排一个 MySQL 事件

php - Laravel 5 - Elequent GROUP BY 失败

sql - dplyr sql连接