5

我在 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

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

我试过这个查询:

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

斯宾塞的回答结果中的第一个查询重复的产品:

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

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

4

4 回答 4

1

寻找“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。

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

于 2012-07-03T18:51:50.417 回答
0

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

SELECT o1.CustomerID, d1.ProductID, d1.Price, o1.Orderdate
  FROM orders o1
  JOIN OrderDetails d1 ON d1.OrderID = o1.ID
  LEFT      
  JOIN orders o2 
    ON o1.CustomerID = o2.CustomerID
       AND o1.ID <> o2.ID
       AND (o1.OrderDate < o2.OrderDate
           OR (o1.OrderDate = o2.OrderDate AND o1.ID < o2.ID)
           )
  LEFT
  JOIN OrderDetails d2
    ON d2.OrderID = o2.ID
       AND d2.ProductID = d1.ProductId
       AND (o1.OrderDate < o2.OrderDate
           OR (o1.OrderDate = o2.OrderDate AND o1.ID < o2.ID)
           OR (o1.OrderDate = o2.OrderDate AND o1.ID = o2.ID AND d1.ID < d2.ID )
           )
 WHERE d2.ID IS NULL 

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 a and b). The purpose of the inline view is really just to get CustomerID and OrderDate associated with each OrderDetail line.

 SELECT a.CustomerID, a.ProductID, a.Price, a.Orderdate
   FROM (SELECT o1.CustomerID, d1.ProductID, d1.Price, o1.OrderDate, d1.OrderID, d1.ID
           FROM orders o1
           JOIN OrderDetails d1 ON d1.OrderID = o1.ID
        ) a
   LEFT      
   JOIN (SELECT o2.CustomerID, d2.ProductID, d2.Price, o2.OrderDate, d2.OrderID, d2.ID
           FROM orders o2
           JOIN OrderDetails d2 ON d2.OrderID = o2.ID
        ) b
     ON a.CustomerID = b.CustomerID
        AND a.ProductID = b.ProductId
        AND a.OrderID <> b.OrderID
        AND a.ID <> b.ID
        AND (a.OrderDate < b.OrderDate 
             OR (a.OrderDate = b.OrderDate AND a.OrderID < b.OrderID)
             OR (a.OrderDate = b.OrderDate AND a.OrderID = b.OrderID AND a.ID < b.ID))
  WHERE b.ID IS NULL 

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.

SELECT q.CustomerID
     , q.ProductID
     , q.Price
     , q.OrderDate
  FROM (SELECT IF(p.CustomerID = @last_customerid,IF(p.ProductID = @last_productid,0,1),1) AS break
             , @last_customerid := p.CustomerID AS CustomerID
             , @last_productid := p.ProductID AS ProductID
             , p.Price
             , p.OrderDate
         FROM (SELECT @last_customerid := NULL, @last_productid := NULL) i
         JOIN ( SELECT o.CustomerID, d.ProductID, o.OrderDate, d.Price 
                  FROM orders o
                  JOIN OrderDetails d ON d.OrderID = o.ID
                 ORDER BY o.CustomerID, d.ProductID, o.OrderDate DESC
              ) p
       ) q
  WHERE q.break = 1

于 2012-07-03T19:04:27.630 回答
0

您可能会发现这更有效:

select opl.CustomerID,
    opl.ProductID,
    opl.LastDate,
    od.Price
from (
    select o.CustomerID,
        od.ProductID,
        max(o.OrderDate) as LastDate
    from Orders o
    inner join OrderDetails od on o.ID = od.OrderID
    group by o.CustomerID, od.ProductID
) opl
inner join Orders o on opl.CustomerID = o.CustomerID
    and opl.LastDate = o.OrderDate
inner join OrderDetails od on o.ID = od.OrderID
于 2012-07-03T18:52:24.497 回答
0
select B.*,A.Price from
(select CustomerID,ProductID,OrderDate,Price from Orders o join OrderDetails od on o.ID=od.OrderID) A 
join 
(select CustomerID,ProductID,max(OrderDate) as LastOrderDate 
from Orders o 
join OrderDetails od on o.ID=od.OrderID
group by CustomerID,ProductID) B 
on A.CustomerID=B.CustomerID and A.ProductID=B.ProductID and A.OrderDate=B.LastOrderDate
于 2012-07-03T18:49:11.023 回答