it-swarm.com.de

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

Tabelle: 

UserId, Value, Date.

Ich möchte die UserId, den Wert für das Maximum (Datum) für jede UserId, erhalten. Dies ist der Wert für jede UserId, die das letzte Datum hat. Gibt es eine Möglichkeit, dies einfach in SQL zu tun? (Vorzugsweise Oracle)

Update: Entschuldigung für Unklarheiten: Ich muss ALLE UserIds bekommen. Aber für jede Benutzer-ID nur die Zeile, in der der Benutzer das letzte Datum hat.

521
Umang

Dadurch werden alle Zeilen abgerufen, für die der my_date-Spaltenwert 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 enthalten ist.

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 analytischer Abfragen und eines Self-Joins verhindert den Zweck von analytischen Abfragen."

In diesem Code gibt es keine Selbstverbindung. Das Ergebnis der Inline-Ansicht enthält stattdessen ein Prädikat, das die analytische Funktion enthält - eine ganz andere Angelegenheit und völlig übliche Praxis.

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

Die Windowing-Klausel gilt nur in Anwesenheit 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.

366
David Aldridge

Ich sehe, dass viele Leute Unterabfragen oder herstellerspezifische Funktionen verwenden, um dies zu tun, aber ich mache diese Art von Abfrage häufig ohne Unterabfragen auf die folgende Weise. Es verwendet normales Standard-SQL und sollte daher in jeder Marke von RDBMS funktionieren.

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 von t1 ab, wo keine andere Zeile mit derselben UserId und einem größeren Date vorhanden ist.

(Ich füge den Bezeichner "Date" in Trennzeichen ein, da es sich um ein SQL-reserviertes Word handelt.)

Falls t1."Date" = t2."Date", erscheint eine Verdoppelung. Normalerweise haben Tabellen die auto_inc(seq) Taste, z. id. Um eine Verdoppelung zu vermeiden, kann man folgendes verwenden:

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;

Re Kommentar von @Farhan:

Hier ist eine detailliertere Erklärung:

Eine äußere Verknüpfung versucht, t1 mit t2 zu verbinden. Standardmäßig werden alle Ergebnisse von t1 zurückgegeben, und falls es eine Übereinstimmung in t2 gibt, werden auch diese zurückgegeben. Wenn t2 für eine bestimmte Zeile von t1 nicht übereinstimmt, gibt die Abfrage weiterhin die Zeile von t1 zurück und verwendet NULL als Platzhalter für alle Spalten von t2. So funktionieren äußere Verknüpfungen im Allgemeinen.

Der Trick bei dieser Abfrage besteht darin, die Übereinstimmungsbedingung des Joins so zu gestalten, dass t2 mit der gleichen userid und einer größeren date übereinstimmen muss. Die Idee ist, wenn eine Zeile in t2 vorhanden ist, die eine größere date hat, dann wird die Zeile in t1 verglichen mit kann nicht die größte date für diese userid sein. Wenn es jedoch keine Übereinstimmung gibt - d. H. Wenn in t2 keine Zeile mit einer größeren date als der Zeile in t1 vorhanden ist - wissen wir, dass die Zeile in t1 die Zeile mit der größten date für die angegebene userid war.

In diesen Fällen (wenn keine Übereinstimmung besteht) sind die Spalten von t2NULL - sogar die in der Join-Bedingung angegebenen Spalten. Deshalb verwenden wir WHERE t2.UserId IS NULL, da wir nach Fällen suchen, in denen keine Zeile mit einer größeren date für die angegebene userid gefunden wurde.

418
Bill Karwin
SELECT userid, MAX(value) KEEP (DENSE_RANK FIRST ORDER BY date DESC)
  FROM table
  GROUP BY userid
150
Dave Costa

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

 Wählen Sie die Benutzer-ID aus, Wert 
 von den Benutzern u1 
 Dabei ist date = (Wählen Sie max (date) 
 von den Benutzern u2 
 aus, wobei u1.userid = u2.userid) 
47
Steve K

Da ich nicht bei der Arbeit bin, habe ich kein Oracle zur Hand, aber ich erinnere mich daran, dass es in Oracle möglich ist, mehrere Spalten in einer IN-Klausel abzugleichen, was zumindest die Optionen vermeiden sollte, 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: Ich habe es gerade 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

Es funktioniert also, obwohl einige der an anderer Stelle erwähnten neuartigen Dinge vielleicht performanter sind.

35
Mike Woodhouse

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
13
mancaus

Ich habe kein Oracle zum Testen, aber die effizienteste Lösung ist die Verwendung von Analyseabfragen. 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 Frage loswerden und die innere Frage deutlich machen können, aber ich bin mir nicht sicher. Inzwischen weiß ich, dass man hier arbeitet.

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

Unter dem Abzug sortieren analytische Abfragen das gesamte Dataset und verarbeiten es dann nacheinander. Bei der Verarbeitung partitionieren Sie das Dataset nach bestimmten Kriterien, und dann wird für jede Zeile ein Fenster angezeigt (standardmäßig der erste Wert in der Partition bis zur aktuellen Zeile - dieser Standardwert ist auch der effizienteste) und kann Werte mithilfe von berechnen Anzahl der analytischen Funktionen (deren Liste den Aggregatfunktionen sehr ähnlich ist).

In diesem Fall tut dies die innere Abfrage. Der gesamte Datensatz wird nach UserId und dann nach Datum DESC sortiert. Dann verarbeitet es es in einem Durchlauf. Für jede Zeile geben Sie die UserId und das erste Datum für diese UserId zurück (da Datumsangaben nach DESC sortiert sind, ist dies das maximale Datum). Dies gibt Ihnen Ihre Antwort mit doppelten Zeilen. Dann werden die äußeren DISTINCT-Kürbisse dupliziert.

Dies ist kein besonders spektakuläres Beispiel für analytische Abfragen. Wenn Sie einen viel größeren Gewinn erzielen möchten, sollten Sie eine Tabelle mit den Finanzquittungen erstellen und für jeden Benutzer und die Quittung eine laufende Summe dessen berechnen, was sie bezahlt haben. Analytische Abfragen lösen das effizient. Andere Lösungen sind weniger effizient. Deshalb sind sie Teil des SQL-Standards von 2003. (Leider hat Postgres sie noch nicht. Grrr ...)

6
user11318

Wäre eine QUALIFY-Klausel nicht einfach und gleichzeitig am besten?

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

In Bezug auf Teradata läuft hier ein anständiger Größentest dieser Version in 17s mit dieser QUALIFY-Version und in 23s mit der Inline-Ansicht/Aldridge-Lösung # 1.

6
wcw

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
5
Cito

InOracle 12c+können Sie Top n -Abfragen zusammen mit der Analysefunktion rank verwenden, um dies sehr knapp ohne Unterabfragen 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 haben möchten, ersetzen Sie rank durch 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; 
5
Gurwinder Singh
select VALUE from TABLE1 where TIME = 
   (select max(TIME) from TABLE1 where DATE= 
   (select max(DATE) from TABLE1 where CRITERIA=CRITERIA))
3
nouky

Verwenden Sie ROW_NUMBER() , um der absteigenden Date für jede UserId ein eindeutiges Ranking zuzuweisen, und filtern Sie dann die erste Zeile für jede UserId (d. H. 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;
3
markusk

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

Dieses unterstützt mehrere Werte für UserId am same Datum.

Spalten: UserId, 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 Erklärungsplan nachschlagen. Ich hatte keine Zeit, damit zu spielen.

Wenn Sie große Tabellen durchsuchen, ist es natürlich besser, wenn Sie in Ihrer Abfrage FULL-Hinweise verwenden.

3
Truper
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  
3
Aheho

Die Antwort hier ist nur Oracle. Hier ist eine etwas anspruchsvollere Antwort in allen SQL:

Wer hat das beste Gesamtergebnis der 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 eine Erklärung braucht, für das ich keine Zeit habe:

Geben Sie das Buch (ISBN und Titel) an, das 2008 am populärsten ist, d. H. Welches 2008 am häufigsten ausgeliehen wird.

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 (jedermann) .. :)

Grüße, Guus

2
Guus

Ich denke so etwas. (Vergib mir irgendwelche Syntaxfehler; ich bin es gewohnt, HQL an diesem Punkt 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
)
2
jdmichal

(T-SQL) Holen Sie sich zunächst alle Benutzer und deren maxdate. Verbinden Sie sich mit der Tabelle, um die entsprechenden Werte für die Benutzer in den maxdates 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
2
boes

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)
2
stefano m

Angenommen, Date ist für eine bestimmte Benutzer-ID eindeutig, hier einige TSQL-Werte:

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 
2
marc

Ich bin ziemlich spät dran, aber der folgende Hack übertrifft sowohl korrelierte Unterabfragen als auch alle Analysefunktionen, hat jedoch eine Einschränkung: Werte müssen in Strings konvertiert werden. Es funktioniert also für Datumsangaben, Zahlen und andere Zeichenketten. 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 helfen jedoch, wenn Sie das Ergebnis nach einer einzelnen Benutzer-ID filtern müssen.

2

Wenn Sie Postgres verwenden, können Sie array_agg like verwenden

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

Ich kenne Oracle nicht. Das ist was ich mir ausgedacht habe

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
1
Bruno Calza

Dies sollte so einfach sein wie:

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

Zuerst versuche ich, die Frage falsch zu lesen, gefolgt von der besten Antwort, 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)
1
KyleLanser

Dies kümmert sich auch um Duplikate (gibt für jede user_id eine Zeile 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
1
na43251

Ich habe es gerade getestet und es scheint an einem Logingtisch zu funktionieren

select ColumnNames, max(DateColumn) from log  group by ColumnNames order by 1 desc
1
Mauro
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 

1
Zsolt Botykai

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
1
GateKiller

check this link wenn Ihre Fragen dieser Seite ähnlich erscheinen, dann würde ich Ihnen die folgende Abfrage vorschlagen, die die Lösung für diesen Link gibt

select distinct sno,item_name,max(start_date) over(partition by sno),max(end_date) over(partition by sno),max(creation_date) over(partition by sno), max(last_modified_date) over(partition by sno) from uniq_select_records order by sno,item_name asc;

werden genaue Ergebnisse in Bezug auf diesen Link gegeben

0
Smart003
SELECT a.userid,a.values1,b.mm 
FROM table_name a,(SELECT userid,Max(date1)AS mm FROM table_name GROUP BY userid) b
WHERE a.userid=b.userid AND a.DATE1=b.mm;
0
praveen

Wenn (Benutzer-ID, Datum) eindeutig ist, d. H. Für denselben Benutzer kein Datum zweimal erscheint, dann gilt Folgendes:

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;
0
finnw

Verwenden Sie den Code:

select T.UserId,T.dt from (select UserId,max(dt) 
over (partition by UserId) as dt from t_users)T where T.dt=dt;

Dadurch werden die Ergebnisse unabhängig von doppelten Werten für UserId abgerufen. Wenn Ihre Benutzer-ID eindeutig ist, wird sie einfacher:

select UserId,max(dt) from t_users group by UserId;
0
Natty
select   UserId,max(Date) over (partition by UserId) value from users;
0
Amitābha

Lösung für MySQL ohne Konzepte der Partition 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

0
Ben Lin