使用GROUP BY将多个子查询转换为JOIN

时间:2014-02-19 16:01:08

标签: mysql sql subquery

我正在研究一个简单的MySQL订购系统,我遇到了这个障碍,我希望一些SQL天才可以帮助我。

我有一个表,用于订单,付款(带有对Order表的外键引用)和OrderItems(也有一个对Order表的外键引用),我想要做的是获得总计未完成的使用单个查询的订单余额(总计和付费)。我最初的想法是做一些像这样简单的事情:

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

但是,如果有多个付款或多个OrderItems,它会分别混淆Total或Paid(例如,一个OrderItem记录,金额为100,另外两个付款记录将产生总计200)。

为了克服这个问题,我可以通过以下方式使用一些子查询:

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

你可以想象(并且这个查询会显示EXPLAIN),这不是一个最佳查询,所以,我想知道,有没有办法用{{1转换这两个子查询}}语句到GROUP BY s?

4 个答案:

答案 0 :(得分:3)

使用正确的索引可能会更快:

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;

正确的索引是OrderItem(OrderId, Amount)Payment(OrderId, Amount)

我不喜欢用这种方式编写聚合查询,但它有时可以帮助MySQL中的性能。

答案 1 :(得分:1)

有些答案已经建议使用相关的子查询,但是没有真正解释为什么。 MySQL没有实现相关的子查询,但它将实现派生表。也就是说现在使用查询的简化版本:

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;

在执行开始时,MySQL会将子查询的结果放入临时表中,并在OrderId上散列此表以便更快地查找,而如果运行:

SELECT  Order.OrderId,
        (   SELECT SUM(OrderItem.Amount)
            FROM OrderItem
            WHERE OrderItem.OrderId = OrderId
        ) AS Total
FROM    Order;

对于Order中的每一行,子查询将被执行一次。如果添加WHERE Order.OrderId = 1之类的东西,聚合整个OrderItem表显然效率不高,将结果散列为仅查找一个值,但如果要返回所有订单,则创建哈希表的初始成本将使为了它自己,它不必为Order表中的每一行执行子查询。

如果您选择了很多行并认为实现有益,您可以按如下方式简化您的JOIN查询:

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;

然后你只有一个派生表。

答案 2 :(得分:0)

这样的事情:

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:你再次获胜@Gordon xD

答案 3 :(得分:0)

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