SQL Server: WYBIERZ tylko wiersze z MAX (DATE)

109

Mam tabelę danych (db to 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

Chciałbym zadać zapytanie, które zwraca OrderNO, PartCode i Quantity, ale tylko dla ostatniego zarejestrowanego zamówienia.

Z tabeli przykładowej chciałbym odzyskać następujące informacje:

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

Zwróć uwagę, że tylko jeden wiersz został zwrócony dla zamówienia 9999.

Dzięki!

GEMI
źródło
2
W swoim komentarzu przejdź do ROW_NUMBER (). Może wyglądać na dłuższą, ale z mojego doświadczenia wynika, że ​​jest o wiele szybsza przy odpowiednich indeksach.
MatBailie,
Dzięki Dems, doceniam twój wysiłek.
GEMI
1
@GEMI tylko z ciekawości, nie MAX(DATE)zwraca ani jednej linii dla zamówienia 9999?
Zameer Ansari,
Tak, ale chciałem, aby każde inne zamówienie zwracało tylko ostatnią linię zamówienia.
GEMI,

Odpowiedzi:

185

Jeśli rownumber() over(...)jest dostępny dla Ciebie ....

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
źródło
2
Dzięki Mikael Eriksson, to niesamowite zapytanie!
GEMI
57

Najlepszym sposobem jest Mikael Eriksson, jeśli ROW_NUMBER()jest dostępny.

Następną najlepszą rzeczą jest przyłączenie się do zapytania, zgodnie z odpowiedzią Cularis.

Alternatywnie, najprostszym i najprostszym sposobem jest skorelowane pod-zapytanie w klauzuli WHERE.

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

Lub...

WHERE
  ID = (SELECT TOP 1 ID FROM yourTable WHERE orderNo = [data].orderNo ORDER BY DateEntered DESC)
MatBailie
źródło
30
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))

Jest to najszybsze ze wszystkich zapytań podanych powyżej. Koszt zapytania wyniósł 0,0070668.

Preferowana odpowiedź powyżej, autorstwa Mikaela Erikssona, ma koszt zapytania wynoszący 0,0146625

Możesz nie przejmować się wydajnością dla tak małej próbki, ale w dużych zapytaniach wszystko się sumuje.

ton
źródło
2
Okazało się to dla mnie nieznacznie szybsze niż inne rozwiązania tutaj na zbiorze danych ~ 3,5 mln wierszy, jednak SSMS zasugerował indeks, który skrócił czas wykonania o połowę. Dzięki!
easuter
Szybko i prosto. Dzięki.
Stephen Zeng
Mam 100 tys. Wierszy i dla mnie zapytanie Mikaela Erikssona 3 razy szybciej. Może dlatego, że mam funkcję ROUND w klauzuli partycji po.
Wachburn
Jeśli masz pole daty o tej samej wartości (15.04.2017) dla 2 różnych identyfikatorów, zwróci ono 2 wiersze ...
Portekoi
Tak, Portekoi, to prawda, ale bez innego sposobu rozróżnienia tych dwóch rzędów, jak można wybrać jeden z nich? Możesz umieścić TOP na wyniku, ale skąd wiesz, że nie jest to drugi wiersz, który chcesz?
dźwięk
11
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)

Zapytanie wewnętrzne wybiera wszystkie OrderNoz ich maksymalną datą. Aby uzyskać inne kolumny tabeli, możesz dołączyć do nich OrderNoi MaxDate.

Jakub
źródło
1

W przypadku MySql możesz wykonać następujące czynności:

select OrderNO, PartCode, Quantity from table a
join (select ID, MAX(DateEntered) from table group by OrderNO) b on a.ID = b.ID
bencobb
źródło
Nie możesz wybrać ID w tabeli wewnętrznej, jeśli grupujesz według numeru zamówienia
Jacob
@Dems thanks @ cularis tak, to dotyczy MySql, w pytaniu nie sprecyzowano jaki silnik bazy danych
bencobb
Jeśli kod pocztowy, próbki XML lub danych, należy zaznaczyć te linie w edytorze tekstowym i kliknij na przycisk „Kod próbki” ( { }) na pasku narzędzi edytora, aby ładnie format i składnia go podświetlić!
marc_s
To jest MSSQL, przepraszam za to.
GEMI
1

Możesz również użyć tej instrukcji select jako zapytania z lewym złączeniem ... Przykład:

... 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 ....

Mam nadzieję, że pomoże to komuś, kto tego szuka :)

idzi
źródło
1

rownumber () over (...) działa, ale nie podobało mi się to rozwiązanie z 2 powodów. - Ta funkcja nie jest dostępna, gdy używasz starszej wersji SQL, takiej jak SQL2000 - Zależność od funkcji i nie jest tak naprawdę czytelna.

Innym rozwiązaniem jest:

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
źródło
1

Jeśli masz indeksowany identyfikator i OrderNo, możesz użyć IN: (nienawidzę prostoty handlu ze względu na niejasność, tylko po to, aby zapisać kilka cykli):

select * from myTab where ID in(select max(ID) from myTab group by OrderNo);
MortenB
źródło
0

Staraj się unikać IN używać 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
King Neo
źródło
1
Dlaczego unikać IN? Czy masz jakieś argumenty na poparcie swojej opinii?
Preza8
wykonanie zapytania zajmie dużo czasu. Możesz przeczytać następujący artykuł xaprb.com/blog/2006/06/28/why-large-in-clauses-are-problematic
king neo
@anik To jest artykuł z 2006 roku. Czy masz jakieś niedawne dowody tego, co mówisz?
Félix Gagnon-Grenier
0

To zadziałało dla mnie doskonale.

    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
źródło
-1

To działa dla mnie. użyj MAX (CONVERT (data, ReportDate)), aby upewnić się, że masz wartość daty

select max( CONVERT(date, ReportDate)) FROM [TraxHistory]
user2662006
źródło