2008-12-01 7 views
7

Dites que j'ai 2 tables: Clients et Commandes. Un client peut avoir plusieurs commandes.Aide de l'instruction SQL - Sélectionnez la dernière commande pour chaque client

Maintenant, je dois montrer à tous les clients avec sa dernière commande. Cela signifie que si un client a plus d'une commande, affichez uniquement la commande avec la dernière heure d'entrée.

C'est jusqu'où je réussi moi-même:

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

Bien sûr retourne tous les clients avec une ou plusieurs commandes, montrant la dernière commande d'abord pour chaque client, ce qui est pas ce que je voulais. Mon esprit était coincé dans une ornière à ce stade, alors j'espère que quelqu'un peut me diriger dans la bonne direction.

Pour une raison quelconque, je pense J'ai besoin d'utiliser la syntaxe MAX quelque part, mais il m'échappe juste maintenant.

MISE À JOUR: Après avoir traversé quelques réponses ici (il y a beaucoup!), Je me suis aperçu que je fait une erreur: je voulais dire tout Client avec son dernier album. Cela signifie que s'il n'a pas d'Ordre, je n'ai pas besoin de le lister.

UPDATE2: Correction de ma propre instruction SQL, qui n'a probablement pas causé de fin de confusion aux autres.

+0

Votre table des clients dans cet exemple a un OrderId. Est-ce correct? –

+0

Oui, comme implicite dans le SQL. – alextansc

+0

Si votre table client a un OrderID votre question n'a pas de sens. Si c'était vrai, vous diriez que chaque commande a un client différent. Êtes-vous sûr que votre table Order n'a pas de CustomerID? –

Répondre

8

Je ne pense pas que vous ne voulez utiliser MAX () car vous ne voulez pas grouper le OrderID. Ce que vous avez besoin est une sous requête commandée avec un TOP SELECT 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) 
+0

Oui, je pense que votre réponse est la plus correcte jusqu'à présent, la seule erreur est que vous n'avez pas trié la date dans l'ordre décroissant. Ne pas mentionné le plus élégant. :) Merci! – alextansc

+0

J'ai fait la commande au début, puis je me suis embrouillé et j'ai pensé que vous vouliez la commande la plus ancienne. J'ai maintenant mis à jour mon exemple pour être correct. –

0
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) 
+0

Vous obtenez la commande avec le Max OrderID et non le Max EntryTime qui peut être différent. –

3

Quelque chose comme ça devrait le faire:

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 

Cela suppose que deux commandes pour le même client peut avoir le même entryTime, ce qui explique pourquoi MAX(OrderID) est utilisé dans la sous-requête Y pour vous assurer qu'il ne se produit une fois par client. Le LEFT JOIN est utilisé parce que vous avez indiqué que vous vouliez montrer tous les clients - s'ils n'ont pas d'ordres, alors le dernierOrdreOrdonnant sera NULL.

Espérons que cela aide!

-

Mise à jour :-) Cela montre seulement aux clients des commandes:

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 
+0

Cette solution est beaucoup plus rapide que celle qui a été sélectionnée par l'OP. –

0

Quelque chose comme:

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 
6

Alors que je vois que vous avez déjà accepté une réponse, je pense que celui-ci est un peu plus intuitive:

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

Je devrais exécuter quelque chose comme ça à travers un plan d'exécution pour voir la différence d'exécution, mais où la fonction TOP est faite après le fait et que l'utilisation de "order by" peut être chère, je crois que max (EntryTime) serait le meilleur moyen de l'exécuter.

+0

J'ai essayé votre requête, et je trouve qu'elle renvoie le même résultat. Agréable! Je ne vais pas changer ma réponse pour le moment, il y a quelques autres réponses que je voulais tester. Mais, je vais voter votre réponse pour l'instant. :) – alextansc

+0

Sûrement que la clause where de cette sous-requête doit être CustomerId = a.Id? –

1

Vous pouvez utiliser une fonction de fenêtre.

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 

Pour chaque client (a.id), il trie toutes les commandes et rejette tout sauf le dernier. La clause ORDER BY inclut à la fois la date de la commande et l'identifiant de l'entrée, s'il y a plusieurs commandes à la même date.

Généralement, les fonctions de fenêtre sont beaucoup plus rapides que toute recherche utilisant MAX() sur un grand nombre d'enregistrements.

0

Une approche que je ne l'ai pas vu plus haut encore:

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 

Ce (ainsi que les autres solutions, je crois) suppose que deux commandes pour le même client peut avoir le même temps d'entrée. Si cela vous préoccupe, vous devrez faire un choix quant à ce qui détermine lequel est le «dernier». Si c'est un problème poster un commentaire et je peux développer la requête si nécessaire pour tenir compte de cela.

L'approche générale de la requête est de trouver la commande pour un client où il n'y a pas d'autre commande pour le même client avec une date ultérieure. C'est alors la dernière commande par définition. Cette approche donne souvent de meilleures performances que l'utilisation de tables ou de sous-requêtes dérivées.

+0

J'ai testé cette requête, et cela fonctionne, comme les deux autres réponses jusqu'à présent. Pourriez-vous ajouter comment vous pourriez gérer 2 commandes avec exactement le même temps d'entrée? – alextansc

+0

Intéressant. Cela ne serait-il pas exponentiellement plus lent pour chaque commande supplémentaire d'un client car la base de données devra joindre chaque commande à chaque autre commande? –

0

Cette requête est beaucoup plus rapide que la réponse acceptée:

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 
Questions connexes