SQL 语句帮助 - 为每个客户选择最新订单

Posted

技术标签:

【中文标题】SQL 语句帮助 - 为每个客户选择最新订单【英文标题】:SQL Statement Help - Select latest Order for each Customer 【发布时间】:2010-09-24 18:53:00 【问题描述】:

假设我有 2 个表:客户和订单。一个客户可以有多个订单。

现在,我需要向所有客户展示他的最新订单。这意味着如果客户有多个订单,则仅显示最晚进入时间的订单。

这是我独自完成的:

SELECT a.*, b.Id
FROM Customer a INNER JOIN Order b ON b.CustomerID = a.Id
ORDER BY b.EntryTime DESC

这当然会返回具有一个或多个订单的所有客户,首先显示每个客户的最新订单,这不是我想要的。在这一点上,我的思想陷入了困境,所以我希望有人能指出我正确的方向。

出于某种原因,我认为我需要在某处使用 MAX 语法,但它现在只是逃避了我。

更新:在这里查看了几个答案(有很多!)后,我意识到我犯了一个错误:我的意思是 任何 客户的最新记录。这意味着如果他没有订单,那么我不需要列出他。

UPDATE2: 修正了我自己的 SQL 语句,这可能给其他人造成了无穷无尽的困惑。

【问题讨论】:

本例中您的客户表有一个 OrderId。对吗? 如果您的 Customer 表有一个 OrderID,那么您的问题没有意义。如果这是真的,您会说每个订单都有不同的客户。您确定您的 Order 表没有 CustomerID? @Martin:哎呀,你说得对!伙计,我今天真的疯了。 :// 【参考方案1】:

我认为您不想使用 MAX(),因为您不想对 OrderID 进行分组。您需要的是带有 SELECT TOP 1 的有序子查询。

select * 
from Customers 
    inner join Orders 
        on Customers.CustomerID = Orders.CustomerID
        and OrderID = (
            SELECT TOP 1 subOrders.OrderID 
            FROM Orders subOrders 
            WHERE subOrders.CustomerID = Orders.CustomerID 
            ORDER BY subOrders.OrderDate DESC
        )

【讨论】:

是的,我认为您的答案是迄今为止最正确的答案,唯一的错误是您没有按降序对日期进行排序。更不用说最优雅的了。 :) 谢谢! 我一开始确实是订购的,然后我把自己弄糊涂了,以为你想要最旧的订单。我现在已经更新了我的示例以确保正确。 IMO 一个简单的 max 和 group by 就足够了,我将其作为单独的答案发布在下面。但很高兴知道为什么这不起作用。查询如下: select c.customer_id, max(o.order_date) from customers c left join orders o on o.customer_id = c.customer_id group by c.customer_id; @AmrinderArora 您的查询未返回所需的结果。我对你的回答发表了评论。【参考方案2】:

虽然我看到您已经接受了一个答案,但我认为这个答案更直观:

select      a.*
           ,b.Id
 
from       customer a
   
inner join Order b
on         b.CustomerID = a.Id
  
where      b.EntryTime = ( select max(EntryTime)
                           from   Order
                           where  a.Id = b.CustomerId
                         );

a.Id = b.CustomerId,因为您希望客户 (a.Id) 的所有订单 (b) 中的最大 EntryTime

我必须通过执行计划运行类似的东西才能看到执行的差异,但是 TOP 函数是事后完成的,使用 order by 可能很昂贵,我相信使用 @987654327 @ 将是运行它的最佳方式。

【讨论】:

我试过你的查询,我发现它返回了相同的结果。好的!不过,我现在不会切换我的答案,这里还有一些我想测试的其他答案。但是,我现在会投票赞成你的答案。 :) 子查询的 where 子句肯定应该是 CustomerId = a.Id?【参考方案3】:

应该这样做:

SELECT X.*, Y.LatestOrderId
FROM Customer X
LEFT JOIN (
  SELECT A.Customer, MAX(A.OrderID) LatestOrderId
  FROM Order A
  JOIN (
    SELECT Customer, MAX(EntryTime) MaxEntryTime FROM Order GROUP BY Customer
  ) B ON A.Customer = B.Customer AND A.EntryTime = B.MaxEntryTime
  GROUP BY Customer
) Y ON X.Customer = Y.Customer

这假设同一客户的两个订单可能具有相同的 EntryTime,这就是为什么在子查询 Y 中使用 MAX(OrderID) 以确保每个客户只发生一次。使用 LEFT JOIN 是因为您声明要显示所有客户 - 如果他们没有任何订单,那么 LatestOrderId 将为 NULL

希望这会有所帮助!

--

更新 :-) 这仅显示有订单的客户:

SELECT A.Customer, MAX(A.OrderID) LatestOrderId
FROM Order A
JOIN (
  SELECT Customer, MAX(EntryTime) MaxEntryTime FROM Order GROUP BY Customer
) B ON A.Customer = B.Customer AND A.EntryTime = B.MaxEntryTime
GROUP BY Customer

【讨论】:

此解决方案比 OP 选择的解决方案快得多。 这是更好的解决方案,因为它预先计算所有必要的数据,而不是单独相关的记录。【参考方案4】:

您可以使用窗口函数。

SELECT *
  FROM (SELECT a.*, b.*,
               ROW_NUMBER () OVER (PARTITION BY a.ID ORDER BY b.orderdate DESC,
                b.ID DESC) rn
          FROM customer a, ORDER b
         WHERE a.ID = b.custid)
 WHERE rn = 1

对于每个客户 (a.id),它会对所有订单进行排序并丢弃除最新订单之外的所有订单。 ORDER BY 子句包括订单日期和条目 id,以防同一日期有多个订单。

通常,窗口函数比在大量记录上使用 MAX() 进行的任何查找都快得多。

【讨论】:

【参考方案5】:

这个查询比接受的答案快得多:

SELECT c.id as customer_id, 
    (SELECT co.id FROM customer_order co WHERE 
    co.customer_id=c.id 
    ORDER BY some_date_column DESC limit 1) as last_order_id
    FROM customer c

【讨论】:

这确实更快。我有 50k+ 的记录,与运行 15 分钟但仍未完成的接受答案相比,此查询在大约 8 秒内完成。【参考方案6】:
SELECT Cust.*, Ord.*
FROM Customers cust INNER JOIN Orders ord ON cust.ID = ord.CustID
WHERE ord.OrderID = 
    (SELECT MAX(OrderID) FROM Orders WHERE Orders.CustID = cust.ID)

【讨论】:

您得到的订单是 Max OrderID 而不是 Max EntryTime,这可能会有所不同。【参考方案7】:

类似:

SELECT
  a.*
FROM
  Customer a
    INNER JOIN Order b
      ON a.OrderID = b.Id
        INNER JOIN (SELECT Id, max(EntryTime) as EntryTime FROM Order b GROUP BY Id) met
          ON
            b.EntryTime = met.EntryTime and b.Id = met.Id

【讨论】:

【参考方案8】:

我还没有在上面看到的一种方法:

SELECT
     C.*,
     O1.ID
FROM
     dbo.Customers C
INNER JOIN dbo.Orders O1 ON
     O1.CustomerID = C.ID
LEFT OUTER JOIN dbo.Orders O2 ON
     O2.CustomerID = C.ID AND
     O2.EntryTime > O1.EntryTime
WHERE
     O2.ID IS NULL

这(以及我相信的其他解决方案)假设同一客户的两个订单不可能有完全相同的进入时间。如果这是一个问题,那么您将不得不选择决定哪个是“最新”的。如果这是一个问题,请发表评论,如果需要,我可以扩展查询。

查询的一般方法是查找一个客户的订单,其中没有同一客户的另一个订单在较晚的日期。根据定义,它是最新的订单。这种方法通常比使用派生表或子查询提供更好的性能。

【讨论】:

我已经测试了这个查询,并且它可以工作,就像到目前为止的其他两个答案一样。您是否介意添加如何处理 2 个输入时间完全相同的订单? 有趣。由于数据库需要将每个订单连接到每个其他订单,因此对于客户拥有的每个额外订单,这是否会以指数方式变慢?【参考方案9】:

一个简单的最大值和“分组依据”就足够了。

select c.customer_id, max(o.order_date)
from customers c
inner join orders o on o.customer_id = c.customer_id
group by c.customer_id;

不需要子选择,这会减慢速度。

【讨论】:

这不会返回问题所要求的列。他们要求提供订单的 ID,而不是下订单的日期。他们还想排除没有订单的客户。 @MartinBrown 谢谢。排除问题很容易通过将左连接更改为内连接来解决,但我同意,订单的 ID 需要不同的方式。我会考虑那部分。谢谢。

以上是关于SQL 语句帮助 - 为每个客户选择最新订单的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server:从多个客户的多个订单中选择最大的订单总数,并且每个订单上有多个项目

将第一个 SQL 选择语句结果用于第二个选择语句

客户和订单 Sql 语句

为每个客户选择第一个和第 n 个订单

如何将选择语句连接在一起

在 SQL 中选择最大订单号