SQL Server: sélectionnez uniquement les lignes avec MAX (DATE)

109

J'ai une table de données (la base de données est MSSQL):

    ID  OrderNO PartCode  Quantity DateEntered
    417 2144     44917    100      18-08-11
    418 7235     11762    5        18-08-11
    419 9999     60657    100      18-08-11
    420 9999     60657    90       19-08-11

Je voudrais faire une requête qui renvoie OrderNO, PartCode et Quantity, mais uniquement pour la dernière commande enregistrée.

À partir du tableau d'exemple, je voudrais récupérer les informations suivantes:

     OrderNO PartCode  Quantity     
     2144     44917    100      
     7235     11762    5        
     9999     60657    90      

Notez qu'une seule ligne a été renvoyée pour la commande 9999.

Merci!

GEMI
la source
2
À partir de votre commentaire, accédez à la réponse ROW_NUMBER (). Cela peut sembler plus long, mais c'est, d'après mon expérience, beaucoup plus rapide avec des index appropriés.
MatBailie
Merci Dems, j'apprécie vos efforts.
GEMI
1
@GEMI juste par curiosité, ne MAX(DATE)retourne pas une ligne pour la commande 9999?
Zameer Ansari
Oui, mais je voulais que chaque commande différente ne renvoie que la dernière ligne de commande.
GEMI

Réponses:

184

Si rownumber() over(...)est disponible pour vous ....

select OrderNO,
       PartCode,
       Quantity
from (select OrderNO,
             PartCode,
             Quantity,
             row_number() over(partition by OrderNO order by DateEntered desc) as rn
      from YourTable) as T
where rn = 1      
Mikael Eriksson
la source
2
Merci Mikael Eriksson, c'est une requête géniale!
GEMI
56

Le meilleur moyen est Mikael Eriksson, s'il ROW_NUMBER()est à votre disposition.

Le mieux est de se joindre à une requête, selon la réponse de Cularis.

Alternativement, le moyen le plus simple et direct est une sous-requête corrélée dans la clause WHERE.

SELECT
  *
FROM
  yourTable AS [data]
WHERE
  DateEntered = (SELECT MAX(DateEntered) FROM yourTable WHERE orderNo = [data].orderNo)

Ou...

WHERE
  ID = (SELECT TOP 1 ID FROM yourTable WHERE orderNo = [data].orderNo ORDER BY DateEntered DESC)
MatBailie
la source
29
select OrderNo,PartCode,Quantity
from dbo.Test t1
WHERE EXISTS(SELECT 1
         FROM dbo.Test t2
         WHERE t2.OrderNo = t1.OrderNo
           AND t2.PartCode = t1.PartCode
         GROUP BY t2.OrderNo,
                  t2.PartCode
         HAVING t1.DateEntered = MAX(t2.DateEntered))

C'est la plus rapide de toutes les requêtes fournies ci-dessus. Le coût de la requête est de 0,0070668.

La réponse préférée ci-dessus, par Mikael Eriksson, a un coût de requête de 0,0146625

Vous ne vous souciez peut-être pas des performances d'un si petit échantillon, mais dans les requêtes volumineuses, tout s'additionne.

Ton
la source
2
Cela s'est avéré légèrement plus rapide que les autres solutions ici sur un ensemble de données de lignes d'environ 3,5 millions, mais SSMS a suggéré un index qui réduit de moitié le temps d'exécution. Merci!
facile
Rapide et simple. Merci.
Stephen Zeng
J'ai 100k lignes et pour moi la requête de Mikael Eriksson 3 fois plus vite. C'est peut-être parce que j'ai la fonction ROUND dans la partition par clause.
Wachburn
Si vous avez un champ de date avec la même valeur (15/04/2017) pour 2 ID différents, il renverra 2 lignes ...
Portekoi
Oui Portekoi, c'est vrai, mais sans autre moyen de différencier les deux rangées, comment pouvez-vous sélectionner l'une sur l'autre? Vous pourriez mettre un TOP sur le résultat, mais comment savez-vous que ce n'est pas l'autre ligne que vous voulez?
ton
10
SELECT t1.OrderNo, t1.PartCode, t1.Quantity
FROM table AS t1
INNER JOIN (SELECT OrderNo, MAX(DateEntered) AS MaxDate
            FROM table
            GROUP BY OrderNo) AS t2
ON (t1.OrderNo = t2.OrderNo AND t1.DateEntered = t2.MaxDate)

La requête interne sélectionne tous OrderNoavec leur date maximale. Pour obtenir les autres colonnes de la table, vous pouvez les joindre sur OrderNoet le MaxDate.

Jacob
la source
1

Pour MySql, vous pouvez faire quelque chose comme ceci:

select OrderNO, PartCode, Quantity from table a
join (select ID, MAX(DateEntered) from table group by OrderNO) b on a.ID = b.ID
bencobb
la source
Vous ne pouvez pas sélectionner l'ID dans le tableau intérieur si vous groupez par numéro de commande
Jacob
@Dems merci @ cularis oui, cela fait référence à MySql, la question ne spécifiait pas quel moteur de base de données
bencobb
Si vous postez des échantillons de code, XML ou de données, veuillez mettre en évidence ces lignes dans l'éditeur de texte et cliquer sur le bouton "exemples de code" ( { }) dans la barre d'outils de l'éditeur pour bien le formater et la mettre en évidence!
marc_s
C'est MSSQL, désolé pour ça.
GEMI
1

Et vous pouvez également utiliser cette instruction select comme requête de jointure gauche ... Exemple:

... left join (select OrderNO,
   PartCode,
   Quantity from (select OrderNO,
         PartCode,
         Quantity,
         row_number() over(partition by OrderNO order by DateEntered desc) as rn
  from YourTable) as T where rn = 1 ) RESULT on ....

J'espère que cela aidera quelqu'un qui cherche cela :)

Idzi
la source
1

rownumber () over (...) fonctionne mais je n'ai pas aimé cette solution pour 2 raisons. - Cette fonction n'est pas disponible lorsque vous utilisez une ancienne version de SQL comme SQL2000 - Dépendance à la fonction et n'est pas vraiment lisible.

Une autre solution est:

SELECT tmpall.[OrderNO] ,
       tmpall.[PartCode] ,
       tmpall.[Quantity] ,
FROM   (SELECT [OrderNO],
               [PartCode],
               [Quantity],
               [DateEntered]
        FROM   you_table) AS tmpall
       INNER JOIN (SELECT [OrderNO],
                          Max([DateEntered]) AS _max_date
                   FROM   your_table
                   GROUP  BY OrderNO ) AS tmplast
               ON tmpall.[OrderNO] = tmplast.[OrderNO]
                  AND tmpall.[DateEntered] = tmplast._max_date
Navid Golforoushan
la source
1

Si vous avez indexé ID et OrderNo, vous pouvez utiliser IN: (Je déteste le trading de la simplicité pour l'obscurité, juste pour économiser quelques cycles):

select * from myTab where ID in(select max(ID) from myTab group by OrderNo);
MortenB
la source
0

Essayez d'éviter d'utiliser JOIN

SELECT SQL_CALC_FOUND_ROWS *  FROM (SELECT  msisdn, callid, Change_color, play_file_name, date_played FROM insert_log
   WHERE play_file_name NOT IN('Prompt1','Conclusion_Prompt_1','silent')
 ORDER BY callid ASC) t1 JOIN (SELECT MAX(date_played) AS date_played FROM insert_log GROUP BY callid) t2 ON t1.date_played=t2.date_played
roi néo
la source
1
Pourquoi éviter IN? Avez-vous des arguments pour soutenir votre opinion?
Preza8
cela prendra beaucoup de temps pour exécuter votre requête. Vous pouvez lire l'article suivant xaprb.com/blog/2006/06/28/why-large-in-clauses-are-problematic
king neo
@anik Ceci est un article de 2006. Avez-vous des preuves récentes de ce que vous dites?
Félix Gagnon-Grenier
0

Cela a parfaitement fonctionné pour moi.

    select name, orderno from (
         select name, orderno, row_number() over(partition by 
           orderno order by created_date desc) as rn from orders
    ) O where rn =1;
Shubhankar Sarkar
la source
-1

Cela fonctionne pour moi. utilisez MAX (CONVERT (date, ReportDate)) pour vous assurer que vous avez une valeur de date

select max( CONVERT(date, ReportDate)) FROM [TraxHistory]
user2662006
la source