Rufen Sie die Zeile mit dem Max-Wert für eine Spalte ab

574

Tabelle:

UserId, Value, Date.

Ich möchte die UserId, den Wert für das Maximum (Datum) für jede UserId erhalten. Das heißt, der Wert für jede Benutzer-ID mit dem neuesten Datum. Gibt es eine Möglichkeit, dies einfach in SQL zu tun? (Vorzugsweise Oracle)

Update: Entschuldigung für etwaige Unklarheiten: Ich muss ALLE UserIds erhalten. Für jede Benutzer-ID jedoch nur die Zeile, in der dieser Benutzer das neueste Datum hat.

Umang
quelle
21
Was ist, wenn mehrere Zeilen den maximalen Datumswert für eine bestimmte Benutzer-ID haben?
David Aldridge
Was sind die Schlüsselfelder der Tabelle?
Vamosrafa
Einige Lösungen unten verglichen: sqlfiddle.com/#!4/6d4e81/1
Used_By_Already
1
@ DavidAldridge, Diese Spalte ist wahrscheinlich eindeutig.
Pacerier

Antworten:

397

Dadurch werden alle Zeilen abgerufen, für die der Spaltenwert my_date dem Maximalwert von my_date für diese Benutzer-ID entspricht. Dadurch werden möglicherweise mehrere Zeilen für die Benutzer-ID abgerufen, wobei das maximale Datum in mehreren Zeilen liegt.

select userid,
       my_date,
       ...
from
(
select userid,
       my_date,
       ...
       max(my_date) over (partition by userid) max_my_date
from   users
)
where my_date = max_my_date

"Analytische Funktionen rocken"

Edit: In Bezug auf den ersten Kommentar ...

"Die Verwendung von analytischen Abfragen und einer Selbstverknüpfung macht den Zweck von analytischen Abfragen zunichte."

In diesem Code gibt es keine Selbstverknüpfung. Stattdessen wird ein Prädikat auf das Ergebnis der Inline-Ansicht gesetzt, das die Analysefunktion enthält - eine ganz andere Angelegenheit und eine völlig übliche Praxis.

"Das Standardfenster in Oracle reicht von der ersten bis zur aktuellen Zeile in der Partition."

Die Fensterklausel gilt nur bei Vorliegen der Order-by-Klausel. Ohne order by-Klausel wird standardmäßig keine windowing-Klausel angewendet, und es kann keine explizit angegeben werden.

Der Code funktioniert.

David Aldridge
quelle
38
Bei Anwendung auf eine Tabelle mit 8,8 Millionen Zeilen dauerte diese Abfrage die Hälfte der Zeit der Abfragen in einigen anderen Antworten mit hoher Abstimmung.
Derek Mahar
4
Möchte jemand einen Link zu dem MySQL-Äquivalent dazu posten, falls es einen gibt?
duftend
2
Könnte dies nicht Duplikate zurückgeben? Z.B. wenn zwei Zeilen dieselbe Benutzer-ID und dasselbe Datum haben (was zufällig das Maximum ist).
Jastr
2
@jastr Ich denke, das wurde in der Frage bestätigt
David Aldridge
3
Anstelle von können MAX(...) OVER (...)Sie auch ROW_NUMBER() OVER (...)(für die Top-n-pro-Gruppe) oder RANK() OVER (...)(für die größte-n-pro-Gruppe) verwenden.
MT0
441

Ich sehe, dass viele Leute Unterabfragen oder herstellerspezifische Funktionen verwenden, um dies zu tun, aber ich mache diese Art von Abfrage oft ohne Unterabfragen auf folgende Weise. Es verwendet einfaches Standard-SQL, sodass es in jeder RDBMS-Marke funktionieren sollte.

SELECT t1.*
FROM mytable t1
  LEFT OUTER JOIN mytable t2
    ON (t1.UserId = t2.UserId AND t1."Date" < t2."Date")
WHERE t2.UserId IS NULL;

Mit anderen Worten: Rufen Sie die Zeile ab, von der t1keine andere Zeile mit demselben UserIdund einem größeren Datum vorhanden ist.

(Ich habe den Bezeichner "Datum" in Trennzeichen gesetzt, da es sich um ein reserviertes SQL-Wort handelt.)

Falls t1."Date" = t2."Date", erscheint eine Verdoppelung. Normalerweise haben Tabellen auto_inc(seq)Schlüssel, z id. Um eine Verdoppelung zu vermeiden, kann Folgendes verwendet werden:

SELECT t1.*
FROM mytable t1
  LEFT OUTER JOIN mytable t2
    ON t1.UserId = t2.UserId AND ((t1."Date" < t2."Date") 
         OR (t1."Date" = t2."Date" AND t1.id < t2.id))
WHERE t2.UserId IS NULL;

Kommentar von @Farhan:

Hier ist eine detailliertere Erklärung:

Eine äußere Verknüpfung versucht, eine Verknüpfung t1herzustellen t2. Standardmäßig werden alle Ergebnisse von t1zurückgegeben, und wenn eine Übereinstimmung vorliegt t2, wird diese ebenfalls zurückgegeben. Wenn t2für eine bestimmte Zeile von keine Übereinstimmung vorliegt t1, gibt die Abfrage weiterhin die Zeile von zurück t1und wird NULLals Platzhalter für alle t2Spalten von verwendet. So funktionieren äußere Verknüpfungen im Allgemeinen.

Der Trick bei dieser Abfrage besteht darin, die Übereinstimmungsbedingung des Joins so zu gestalten, dass sie t2mit derselben übereinstimmt useridund eine größere date . Die Idee ist , wenn eine Zeile in besteht , t2dass eine größere date, dann die Zeile in t1es verglichen kann nicht die größte sein , datedafür userid. Aber wenn es keine Übereinstimmung gibt - dh wenn keine Zeile t2mit einer größeren dateals der Zeile in vorhanden ist t1- wissen wir, dass die Zeile in t1die Zeile mit der größten datefür die gegebene war userid.

In diesen Fällen (wenn gibt es keine Übereinstimmung), der die Spalten t2werden NULL- auch die in der angegebenen Spalten Joinbedingung. Deshalb verwenden wir WHERE t2.UserId IS NULL, weil wir nach Fällen suchen, in denen keine Zeile mit einer größeren datefür die angegebene gefunden wurde userid.

Bill Karwin
quelle
7
Wow Bill. Dies ist die kreativste Lösung für dieses Problem, das ich gesehen habe. Es ist auch ziemlich performant auf meinem ziemlich großen Datensatz. Dies übertrifft sicher viele der anderen Lösungen, die ich gesehen habe, oder meine eigenen Versuche, dieses Dilemma zu lösen.
Justin Noel
36
Bei Anwendung auf eine Tabelle mit 8,8 Millionen Zeilen dauerte diese Abfrage fast doppelt so lange wie in der akzeptierten Antwort.
Derek Mahar
16
@Derek: Optimierungen hängen von der Marke und Version von RDBMS sowie dem Vorhandensein geeigneter Indizes, Datentypen usw. ab
Bill Karwin
7
Unter MySQL scheint diese Art der Abfrage tatsächlich zu einer Schleife über das Ergebnis einer kartesischen Verknüpfung zwischen den Tabellen zu führen, was zu einer O (n ^ 2) -Zeit führt. Die Verwendung der Unterabfragemethode reduzierte stattdessen die Abfragezeit von 2,0 s auf 0,003 s. YMMV.
Jesse
1
Gibt es eine Möglichkeit, dies an Zeilen anzupassen, bei denen das Datum das größte Datum ist, das kleiner oder gleich einem vom Benutzer angegebenen Datum ist? Wenn der Benutzer beispielsweise das Datum "23-OCT-2011" angibt und die Tabelle Zeilen für "24-OCT-2011", "22-OCT-2011", "20-OCT-2011" enthält, möchte ich dies tun Holen Sie sich "22-OCT-2011". Ich kratzte mir schon seit einiger Zeit am Kopf und las diesen Ausschnitt ...
Cory Kendall
164
SELECT userid, MAX(value) KEEP (DENSE_RANK FIRST ORDER BY date DESC)
  FROM table
  GROUP BY userid
Dave Costa
quelle
3
In meinen Tests mit einer Tabelle mit einer großen Anzahl von Zeilen dauerte diese Lösung etwa doppelt so lange wie in der akzeptierten Antwort.
Derek Mahar
7
Zeigen Sie bitte Ihren Test
Rob van Wijk
Ich bestätige, dass es viel schneller ist als andere Lösungen
Tamersalama
5
Das
Problem
@ user2067753 Nein, es wird nicht der vollständige Datensatz zurückgegeben. Sie können denselben Ausdruck MAX () .. KEEP .. für mehrere Spalten verwenden, sodass Sie alle benötigten Spalten auswählen können. Es ist jedoch unpraktisch, wenn Sie eine große Anzahl von Spalten möchten und lieber SELECT * verwenden möchten.
Dave Costa
51

Ich kenne Ihre genauen Spaltennamen nicht, aber es wäre ungefähr so:

    Wählen Sie Benutzer-ID, Wert
      von Benutzern u1
     Dabei ist Datum = (max (Datum auswählen)
                     von Benutzern u2
                    wobei u1.userid = u2.userid)
Steve K.
quelle
3
Wahrscheinlich nicht sehr effizient, Steve.
David Aldridge
7
Sie unterschätzen wahrscheinlich den Oracle-Abfrageoptimierer.
Rafał Dowgird
3
Ganz und gar nicht. Dies wird mit ziemlicher Sicherheit als vollständiger Scan mit einem verschachtelten Loop-Join implementiert, um die Daten abzurufen. Sie sprechen von logischen Io in der Größenordnung des 4-fachen der Anzahl der Zeilen in der Tabelle und sind für nicht triviale Datenmengen schrecklich.
David Aldridge
4
Zu Ihrer Information: "Nicht effizient, aber funktioniert" ist dasselbe wie "Funktioniert, funktioniert aber nicht". Wann haben wir das effiziente Designziel aufgegeben?
David Aldridge
6
+1, denn wenn Ihre Datentabellen nicht immer Millionen Zeilen lang sind, ist dies die am einfachsten zu verstehende Lösung. Wenn mehrere Entwickler aller Schwierigkeitsgrade den Code ändern, ist die Verständlichkeit wichtiger als ein Bruchteil einer Sekunde an Leistung, die nicht bemerkt werden kann.
n00b
35

Da ich nicht bei der Arbeit bin, habe ich Oracle nicht zur Hand, aber ich erinnere mich, dass Oracle das Abgleichen mehrerer Spalten in einer IN-Klausel zulässt, wodurch zumindest die Optionen vermieden werden sollten, die eine korrelierte Unterabfrage verwenden, was selten gut ist Idee.

So etwas vielleicht (kann mich nicht erinnern, ob die Spaltenliste in Klammern stehen sollte oder nicht):

SELECT * 
FROM MyTable
WHERE (User, Date) IN
  ( SELECT User, MAX(Date) FROM MyTable GROUP BY User)

EDIT: Habe es gerade wirklich ausprobiert:

SQL> create table MyTable (usr char(1), dt date);
SQL> insert into mytable values ('A','01-JAN-2009');
SQL> insert into mytable values ('B','01-JAN-2009');
SQL> insert into mytable values ('A', '31-DEC-2008');
SQL> insert into mytable values ('B', '31-DEC-2008');
SQL> select usr, dt from mytable
  2  where (usr, dt) in 
  3  ( select usr, max(dt) from mytable group by usr)
  4  /

U DT
- ---------
A 01-JAN-09
B 01-JAN-09

So funktioniert es, obwohl einige der an anderer Stelle erwähnten New-Fangly-Sachen möglicherweise performanter sind.

Mike Woodhouse
quelle
4
Dies funktioniert auch unter PostgreSQL. Und ich mag die Einfachheit und Allgemeinheit davon - die Unterabfrage sagt "Hier sind meine Kriterien", die äußere Abfrage sagt "Und hier sind die Details, die ich sehen möchte". +1.
j_random_hacker
13

Ich weiß, dass Sie nach Oracle gefragt haben, aber in SQL 2005 verwenden wir jetzt Folgendes:


-- Single Value
;WITH ByDate
AS (
SELECT UserId, Value, ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY Date DESC) RowNum
FROM UserDates
)
SELECT UserId, Value
FROM ByDate
WHERE RowNum = 1

-- Multiple values where dates match
;WITH ByDate
AS (
SELECT UserId, Value, RANK() OVER (PARTITION BY UserId ORDER BY Date DESC) Rnk
FROM UserDates
)
SELECT UserId, Value
FROM ByDate
WHERE Rnk = 1
Mancaus
quelle
7

Ich habe kein Oracle, um es zu testen, aber die effizienteste Lösung ist die Verwendung von analytischen Abfragen. Es sollte ungefähr so ​​aussehen:

SELECT DISTINCT
    UserId
  , MaxValue
FROM (
    SELECT UserId
      , FIRST (Value) Over (
          PARTITION BY UserId
          ORDER BY Date DESC
        ) MaxValue
    FROM SomeTable
  )

Ich vermute, dass Sie die äußere Abfrage loswerden und die innere deutlich machen können, aber ich bin mir nicht sicher. In der Zwischenzeit weiß ich, dass dieser funktioniert.

Wenn Sie mehr über analytische Abfragen erfahren möchten, empfehlen wir Ihnen, http://www.orafaq.com/node/55 und http://www.akadia.com/services/ora_analytic_functions.html zu lesen . Hier ist die kurze Zusammenfassung.

Unter der Haube analysieren analytische Abfragen den gesamten Datensatz und verarbeiten ihn dann nacheinander. Während Sie es verarbeiten, partitionieren Sie das Dataset nach bestimmten Kriterien und sehen dann für jede Zeile ein Fenster (standardmäßig der erste Wert in der Partition für die aktuelle Zeile - dieser Standard ist auch der effizienteste) und können Werte mit a berechnen Anzahl der Analysefunktionen (deren Liste den Aggregatfunktionen sehr ähnlich ist).

In diesem Fall ist hier, was die innere Abfrage tut. Der gesamte Datensatz wird nach Benutzer-ID und dann nach Datum DESC sortiert. Dann verarbeitet es es in einem Durchgang. Für jede Zeile geben Sie die Benutzer-ID und das erste Datum zurück, das für diese Benutzer-ID angezeigt wird (da die Daten nach DESC sortiert sind, ist dies das maximale Datum). Dies gibt Ihnen Ihre Antwort mit doppelten Zeilen. Dann quetscht das äußere DISTINCT Duplikate.

Dies ist kein besonders spektakuläres Beispiel für analytische Abfragen. Für einen viel größeren Gewinn sollten Sie eine Tabelle mit Finanzbelegen erstellen und für jeden Benutzer und jede Quittung eine laufende Summe der von ihnen bezahlten Beträge berechnen. Analytische Abfragen lösen das effizient. Andere Lösungen sind weniger effizient. Aus diesem Grund sind sie Teil des SQL-Standards von 2003. (Leider hat Postgres sie noch nicht. Grrr ...)

user11318
quelle
Sie müssen auch den Datumswert zurückgeben, um die Frage vollständig zu beantworten. Wenn dies eine weitere first_value-Klausel bedeutet, würde ich vorschlagen, dass die Lösung komplexer ist als sie sein sollte und die auf max (date) basierende Analysemethode besser liest.
David Aldridge
Die Frage Aussage sagt nichts über die Rückgabe des Datums. Sie können dies entweder tun, indem Sie ein weiteres FIRST (Datum) hinzufügen oder indem Sie einfach das Datum abfragen und die äußere Abfrage in GROUP BY ändern. Ich würde den ersten verwenden und erwarten, dass der Optimierer beide in einem Durchgang berechnet.
user11318
"Die Frage Aussage sagt nichts über die Rückgabe des Datums" ... ja, Sie haben Recht. Es tut uns leid. Das Hinzufügen weiterer FIRST_VALUE-Klauseln würde jedoch ziemlich schnell chaotisch werden. Es ist eine einzelne Fenstersortierung, aber wenn Sie 20 Spalten für diese Zeile zurückgeben mussten, haben Sie viel Code zum Durchblättern geschrieben.
David Aldridge
Mir fällt auch ein, dass diese Lösung für Daten nicht deterministisch ist, bei denen eine einzelne Benutzer-ID mehrere Zeilen mit dem maximalen Datum und unterschiedlichen WERTEN hat. Eher ein Fehler in der Frage als in der Antwort.
David Aldridge
1
Ich bin damit einverstanden, dass es schmerzlich ausführlich ist. Ist dies bei SQL jedoch nicht generell der Fall? Und Sie haben Recht, dass die Lösung nicht deterministisch ist. Es gibt mehrere Möglichkeiten, mit Bindungen umzugehen, und manchmal ist jede das, was Sie wollen.
user11318
6

Wäre eine QUALIFY-Klausel nicht sowohl einfach als auch am besten?

select userid, my_date, ...
from users
qualify rank() over (partition by userid order by my_date desc) = 1

Für Teradata wird hier ein anständiger Größentest in 17 Sekunden mit dieser QUALIFY-Version und in 23 Sekunden mit der Inline-Ansicht / Aldridge-Lösung Nr. 1 ausgeführt.

wcw
quelle
1
Dies ist meiner Meinung nach die beste Antwort. Seien Sie jedoch vorsichtig mit der rank()Funktion in Situationen, in denen es Bindungen gibt. Sie könnten mit mehr als einem enden rank=1. Besser zu verwenden, row_number()wenn Sie wirklich nur einen Datensatz zurückgeben möchten.
Cartbeforehorse
1
Beachten Sie auch, dass die QUALIFYKlausel für Teradata spezifisch ist. In Oracle müssen Sie (zumindest) Ihre Abfrage verschachteln und mithilfe einer WHEREKlausel in der Wrapping-Select-Anweisung filtern (was wahrscheinlich die Leistung beeinträchtigt, würde ich mir vorstellen).
Cartbeforehorse
5

In Oracle 12c+können Sie Top n- Abfragen zusammen mit der Analysefunktion verwenden rank, um dies ohne Unterabfragen sehr präzise zu erreichen :

select *
from your_table
order by rank() over (partition by user_id order by my_date desc)
fetch first 1 row with ties;

Das obige gibt alle Zeilen mit max my_date pro Benutzer zurück.

Wenn Sie nur eine Zeile mit maximalem Datum möchten, ersetzen Sie die rankdurch row_number:

select *
from your_table
order by row_number() over (partition by user_id order by my_date desc)
fetch first 1 row with ties; 
Gurwinder Singh
quelle
5

Verwenden Sie ROW_NUMBER()diese Option, Dateum jedem absteigend eine eindeutige Rangfolge zuzuweisen UserId, und filtern Sie dann für jede Zeile in die erste Zeile UserId(dh ROW_NUMBER= 1).

SELECT UserId, Value, Date
FROM (SELECT UserId, Value, Date,
        ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY Date DESC) rn
      FROM users) u
WHERE rn = 1;
Markusk
quelle
5

Mit PostgreSQL 8.4 oder höher können Sie Folgendes verwenden:

select user_id, user_value_1, user_value_2
  from (select user_id, user_value_1, user_value_2, row_number()
          over (partition by user_id order by user_date desc) 
        from users) as r
  where r.row_number=1
Cito
quelle
3

Ich denke, Sie sollten diese Variante zur vorherigen Abfrage machen:

SELECT UserId, Value FROM Users U1 WHERE 
Date = ( SELECT MAX(Date)    FROM Users where UserId = U1.UserId)
stefano m
quelle
3
Select  
   UserID,  
   Value,  
   Date  
From  
   Table,  
   (  
      Select  
          UserID,  
          Max(Date) as MDate  
      From  
          Table  
      Group by  
          UserID  
    ) as subQuery  
Where  
   Table.UserID = subQuery.UserID and  
   Table.Date = subQuery.mDate  
Aheho
quelle
3

Musste nur ein "Live" -Beispiel bei der Arbeit schreiben :)

Dieser unterstützt mehrere Werte für UserId am selben Datum.

Spalten: Benutzer-ID, Wert, Datum

SELECT
   DISTINCT UserId,
   MAX(Date) OVER (PARTITION BY UserId ORDER BY Date DESC),
   MAX(Values) OVER (PARTITION BY UserId ORDER BY Date DESC)
FROM
(
   SELECT UserId, Date, SUM(Value) As Values
   FROM <<table_name>>
   GROUP BY UserId, Date
)

Sie können FIRST_VALUE anstelle von MAX verwenden und im EXPLAIN-Plan nachschlagen. Ich hatte keine Zeit damit zu spielen.

Wenn Sie große Tabellen durchsuchen, ist es wahrscheinlich besser, wenn Sie in Ihrer Abfrage VOLLSTÄNDIGE Hinweise verwenden.

Truper
quelle
3
select VALUE from TABLE1 where TIME = 
   (select max(TIME) from TABLE1 where DATE= 
   (select max(DATE) from TABLE1 where CRITERIA=CRITERIA))
nouky
quelle
2

Ich denke so etwas. (Verzeihen Sie mir etwaige Syntaxfehler; ich bin es gewohnt, an dieser Stelle HQL zu verwenden!)

EDIT: Auch die Frage falsch verstanden! Die Abfrage wurde korrigiert ...

SELECT UserId, Value
FROM Users AS user
WHERE Date = (
    SELECT MAX(Date)
    FROM Users AS maxtest
    WHERE maxtest.UserId = user.UserId
)
jdmichal
quelle
Erfüllt nicht die Bedingung "für jede Benutzer-ID"
David Aldridge
Wo würde es scheitern? Für jede Benutzer-ID in Benutzer wird garantiert, dass mindestens eine Zeile mit dieser Benutzer-ID zurückgegeben wird. Oder fehlt mir irgendwo ein Sonderfall?
jdmichal
2

(T-SQL) Holen Sie sich zuerst alle Benutzer und deren maximales Datum. Verbinden Sie sich mit der Tabelle, um die entsprechenden Werte für die Benutzer an den maximalen Daten zu finden.

create table users (userid int , value int , date datetime)
insert into users values (1, 1, '20010101')
insert into users values (1, 2, '20020101')
insert into users values (2, 1, '20010101')
insert into users values (2, 3, '20030101')

select T1.userid, T1.value, T1.date 
    from users T1,
    (select max(date) as maxdate, userid from users group by userid) T2    
    where T1.userid= T2.userid and T1.date = T2.maxdate

Ergebnisse:

userid      value       date                                    
----------- ----------- -------------------------- 
2           3           2003-01-01 00:00:00.000
1           2           2002-01-01 00:00:00.000
boes
quelle
2

Die Antwort hier ist nur Oracle. Hier ist eine etwas komplexere Antwort in allen SQL-Anweisungen:

Wer hat das beste Gesamtergebnis bei den Hausaufgaben (maximale Summe der Hausaufgabenpunkte)?

SELECT FIRST, LAST, SUM(POINTS) AS TOTAL
FROM STUDENTS S, RESULTS R
WHERE S.SID = R.SID AND R.CAT = 'H'
GROUP BY S.SID, FIRST, LAST
HAVING SUM(POINTS) >= ALL (SELECT SUM (POINTS)
FROM RESULTS
WHERE CAT = 'H'
GROUP BY SID)

Und ein schwierigeres Beispiel, das einer Erklärung bedarf, für das ich keine Zeit habe atm:

Geben Sie das Buch (ISBN und Titel) an, das 2008 am beliebtesten ist, dh das 2008 am häufigsten ausgeliehen wurde.

SELECT X.ISBN, X.title, X.loans
FROM (SELECT Book.ISBN, Book.title, count(Loan.dateTimeOut) AS loans
FROM CatalogEntry Book
LEFT JOIN BookOnShelf Copy
ON Book.bookId = Copy.bookId
LEFT JOIN (SELECT * FROM Loan WHERE YEAR(Loan.dateTimeOut) = 2008) Loan 
ON Copy.copyId = Loan.copyId
GROUP BY Book.title) X
HAVING loans >= ALL (SELECT count(Loan.dateTimeOut) AS loans
FROM CatalogEntry Book
LEFT JOIN BookOnShelf Copy
ON Book.bookId = Copy.bookId
LEFT JOIN (SELECT * FROM Loan WHERE YEAR(Loan.dateTimeOut) = 2008) Loan 
ON Copy.copyId = Loan.copyId
GROUP BY Book.title);

Hoffe das hilft (jedem) .. :)

Grüße, Guus

Guus
quelle
Die akzeptierte Antwort lautet nicht "nur Oracle" - es ist Standard-SQL (von vielen DBMS unterstützt)
a_horse_with_no_name
2

Angenommen, das Datum ist für eine bestimmte Benutzer-ID eindeutig. Hier einige TSQL:

SELECT 
    UserTest.UserID, UserTest.Value
FROM UserTest
INNER JOIN
(
    SELECT UserID, MAX(Date) MaxDate
    FROM UserTest
    GROUP BY UserID
) Dates
ON UserTest.UserID = Dates.UserID
AND UserTest.Date = Dates.MaxDate 
marc
quelle
2

Ich bin ziemlich spät zur Party, aber der folgende Hack übertrifft sowohl korrelierte Unterabfragen als auch alle Analysefunktionen, hat jedoch eine Einschränkung: Werte müssen in Zeichenfolgen konvertiert werden. Es funktioniert also für Datumsangaben, Zahlen und andere Zeichenfolgen. Der Code sieht nicht gut aus, aber das Ausführungsprofil ist großartig.

select
    userid,
    to_number(substr(max(to_char(date,'yyyymmdd') || to_char(value)), 9)) as value,
    max(date) as date
from 
    users
group by
    userid

Der Grund, warum dieser Code so gut funktioniert, ist, dass er die Tabelle nur einmal scannen muss. Es sind keine Indizes erforderlich, und vor allem muss die Tabelle nicht sortiert werden, wie dies bei den meisten Analysefunktionen der Fall ist. Indizes sind jedoch hilfreich, wenn Sie das Ergebnis nach einer einzelnen Benutzer-ID filtern müssen.

aLevelOfIndirection
quelle
Es ist ein guter Ausführungsplan im Vergleich zu den meisten anderen, aber all diese Tricks auf mehr als ein paar Felder anzuwenden, wäre mühsam und könnte dagegen wirken. Aber sehr interessant - danke. siehe sqlfiddle.com/#!4/2749b5/23
Used_By_Already
Sie haben Recht, es kann langweilig werden, weshalb dies nur dann erfolgen sollte, wenn die Ausführung der Abfrage dies erfordert. Dies ist häufig bei ETL-Skripten der Fall.
aLevelOfIndirection
das ist sehr nett. habe mit LISTAGG etwas Ähnliches gemacht, sieht aber hässlich aus. postgres hat eine bessere Alternative mit array_agg. siehe meine Antwort :)
Bruno Calza
1
select userid, value, date
  from thetable t1 ,
       ( select t2.userid, max(t2.date) date2 
           from thetable t2 
          group by t2.userid ) t3
 where t3.userid t1.userid and
       t3.date2 = t1.date

IMHO funktioniert das. HTH

Zsolt Botykai
quelle
1

Ich denke das sollte funktionieren?

Select
T1.UserId,
(Select Top 1 T2.Value From Table T2 Where T2.UserId = T1.UserId Order By Date Desc) As 'Value'
From
Table T1
Group By
T1.UserId
Order By
T1.UserId
GateKiller
quelle
1

Beim ersten Versuch habe ich die Frage falsch verstanden. Nach der Top-Antwort finden Sie hier ein vollständiges Beispiel mit korrekten Ergebnissen:

CREATE TABLE table_name (id int, the_value varchar(2), the_date datetime);

INSERT INTO table_name (id,the_value,the_date) VALUES(1 ,'a','1/1/2000');
INSERT INTO table_name (id,the_value,the_date) VALUES(1 ,'b','2/2/2002');
INSERT INTO table_name (id,the_value,the_date) VALUES(2 ,'c','1/1/2000');
INSERT INTO table_name (id,the_value,the_date) VALUES(2 ,'d','3/3/2003');
INSERT INTO table_name (id,the_value,the_date) VALUES(2 ,'e','3/3/2003');

- -

  select id, the_value
      from table_name u1
      where the_date = (select max(the_date)
                     from table_name u2
                     where u1.id = u2.id)

- -

id          the_value
----------- ---------
2           d
2           e
1           b

(3 row(s) affected)
KyleLanser
quelle
1

Dies kümmert sich auch um Duplikate (geben Sie eine Zeile für jede Benutzer-ID zurück):

SELECT *
FROM (
  SELECT u.*, FIRST_VALUE(u.rowid) OVER(PARTITION BY u.user_id ORDER BY u.date DESC) AS last_rowid
  FROM users u
) u2
WHERE u2.rowid = u2.last_rowid
na43251
quelle
1

Gerade getestet und es scheint auf einer Protokollierungstabelle zu funktionieren

select ColumnNames, max(DateColumn) from log  group by ColumnNames order by 1 desc
Mauro
quelle
1

Dies sollte so einfach sein wie:

SELECT UserId, Value
FROM Users u
WHERE Date = (SELECT MAX(Date) FROM Users WHERE UserID = u.UserID)
Valerion
quelle
1

Lösung für MySQL ohne Partitionskonzepte KEEP, DENSE_RANK.

select userid,
       my_date,
       ...
from
(
select @sno:= case when @pid<>userid then 0
                    else @sno+1
    end as serialnumber, 
    @pid:=userid,
       my_Date,
       ...
from   users order by userid, my_date
) a
where a.serialnumber=0

Referenz: http://benincampus.blogspot.com/2013/08/select-rows-which-have-maxmin-value-in.html

Ben Lin
quelle
Dies funktioniert nicht "auch auf anderen DBs ". Dies funktioniert nur unter MySQL und möglicherweise unter SQL Server, da es ein ähnliches Variablenkonzept hat. Es wird definitiv nicht auf Oracle, Postgres, DB2, Derby, H2, HSQLDB, Vertica, Greenplum funktionieren. Zusätzlich ist die akzeptierte Antwort Standard-ANSI-SQL (das nur von MySQL nicht unterstützt wird)
a_horse_with_no_name
Pferd, ich denke du hast recht. Ich habe keine Kenntnisse über andere DBs oder ANSI. Meine Lösung ist in der Lage, das Problem in MySQL zu lösen, das ANSI SQL nicht ordnungsgemäß unterstützt, um es auf standardmäßige Weise zu lösen.
Ben Lin
1

Wenn Sie Postgres verwenden, können Sie array_agglike verwenden

SELECT userid,MAX(adate),(array_agg(value ORDER BY adate DESC))[1] as value
FROM YOURTABLE
GROUP BY userid

Ich bin mit Oracle nicht vertraut. Das habe ich mir ausgedacht

SELECT 
  userid,
  MAX(adate),
  SUBSTR(
    (LISTAGG(value, ',') WITHIN GROUP (ORDER BY adate DESC)),
    0,
    INSTR((LISTAGG(value, ',') WITHIN GROUP (ORDER BY adate DESC)), ',')-1
  ) as value 
FROM YOURTABLE
GROUP BY userid 

Beide Abfragen liefern die gleichen Ergebnisse wie die akzeptierte Antwort. Siehe SQLFiddles:

  1. Akzeptierte Antwort
  2. Meine Lösung mit Postgres
  3. Meine Lösung mit Oracle
Bruno Calza
quelle
0

Wenn (Benutzer-ID, Datum) eindeutig ist, dh kein Datum zweimal für denselben Benutzer angezeigt wird, dann:

select TheTable.UserID, TheTable.Value
from TheTable inner join (select UserID, max([Date]) MaxDate
                          from TheTable
                          group by UserID) UserMaxDate
     on TheTable.UserID = UserMaxDate.UserID
        TheTable.[Date] = UserMaxDate.MaxDate;
finnw
quelle
Ich glaube, dass Sie auch durch die UserID beitreten müssen
Tom H
0
select   UserId,max(Date) over (partition by UserId) value from users;
Amitābha
quelle
2
Dadurch werden alle Zeilen zurückgegeben, nicht nur eine Zeile pro Benutzer.
Jon Heller