Справка по операторам SQL - выберите последний заказ для каждого клиента - PullRequest
8 голосов
/ 01 декабря 2008

Скажем, у меня есть 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, который, вероятно, не вызывал путаницы среди других.

Ответы [ 8 ]

8 голосов
/ 01 декабря 2008

Я не думаю, что вы хотите использовать 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)
6 голосов
/ 01 декабря 2008

Хотя я вижу, что вы уже приняли ответ, я думаю, что он немного более интуитивен:

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  Id = b.Id
                         );

Мне бы пришлось выполнить что-то вроде этого через план выполнения, чтобы увидеть разницу в исполнении, но там, где функция TOP выполняется после факта, и что использование "order by" может быть дорогим, я считаю, что использование max (EntryTime) будет лучшим способом запустить это.

4 голосов
/ 01 декабря 2008

Что-то вроде этого должно сделать это:

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, поэтому MAX(OrderID) используется в подзапросе Y, чтобы обеспечить его выполнение только один раз для каждого клиента. 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
2 голосов
/ 01 декабря 2008

Вы можете использовать оконную функцию.

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 включает в себя как дату заказа, так и идентификатор записи, если в одну и ту же дату есть несколько заказов.

Как правило, оконные функции намного быстрее, чем любые операции поиска, использующие MAX () для большого количества записей.

0 голосов
/ 05 марта 2018

Этот запрос намного быстрее принятого ответа:

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
0 голосов
/ 01 декабря 2008

Один подход, который я еще не видел выше:

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

Это (как и другие решения, которые я считаю) предполагает, что никакие два заказа для одного и того же клиента не могут иметь одинаковое время входа. Если это проблема, то вам придется сделать выбор относительно того, что определяет, какой из них является «последним». Если это проблема, оставьте комментарий, и я могу расширить запрос, если это необходимо, чтобы учесть это.

Общий подход запроса состоит в том, чтобы найти заказ для клиента, если нет другого заказа для того же клиента с более поздней датой. Тогда это последний порядок по определению. Этот подход часто дает лучшую производительность, чем использование производных таблиц или подзапросов.

0 голосов
/ 01 декабря 2008

Что-то вроде:

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
0 голосов
/ 01 декабря 2008
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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...