Fetch die Zeile, die den Maximalwert für eine Spalte hat
-
02-07-2019 - |
Frage
Table:
UserId, Value, Date.
Ich mag den Benutzer-ID, Wert für den max (Datum) für jede Benutzer-ID bekommen. Das heißt, der Wert für jede Benutzer-ID, die das letzte Datum hat. Gibt es eine Möglichkeit, diese einfach in SQL zu tun? (Vorzugsweise Oracle)
Update: Wir entschuldigen uns für jede Zweideutigkeit: Ich brauche ALLE UserIds zu bekommen. Aber für jede Benutzer-ID, nur diese Zeile, wo der Benutzer das letzte Datum hat.
Lösung
Damit wird alle Zeilen, für das Abrufen des my_date Spalt Wert auf den Maximalwert von my_date für diesen Benutzer-ID entspricht. Dies kann mehrere Zeilen für die Benutzer-ID abgerufen werden, wo das maximale Datum auf mehreren Zeilen 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 rock"
Edit: Im Hinblick auf den ersten Kommentar ...
„unter Verwendung von analytischer Abfragen und eine Selbstverknüpfung Niederlagen den Zweck der analytischen Abfragen“
Es gibt keine Selbstverknüpfung in diesem Code. Es ist vielmehr ein Prädikat auf dem Ergebnis der Inline-Ansicht platziert, die die analytische Funktion enthält -. Eine ganz andere Sache, und ganz gängige Praxis
„Das Standardfenster in Oracle ist von der ersten Zeile in der Partition des aktuellen“
Die Windowing-Klausel gilt nur in Gegenwart der Bestellung durch Klausel. Ohne ORDER BY-Klausel wird keine Windowing-Klausel standardmäßig angewendet und niemand kann explizit angegeben werden.
Der Code funktioniert.
Andere Tipps
Ich sehe viele Menschen Subqueries oder auch herstellerspezifische Funktionen nutzen, dies zu tun, aber ich habe oft diese Art von Abfrage ohne Unterabfragen in der folgenden Weise. Es verwendet einfache, SQL-Standard, so dass es in jeder Marke von RDBMS funktionieren soll.
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:. Holt die Zeilen aus t1
, wenn keine anderen Zeile existiert mit dem gleichen UserId
und ein größeren Datum
(habe ich die Kennung "Date" in Trennzeichen, da es sich um ein SQL reserviertes Wort ist.)
Im Fall, wenn t1."Date" = t2."Date"
scheint verdoppelt. Normalerweise Tabellen hat auto_inc(seq)
Schlüssel, z.B. id
.
Um zu vermeiden, Verdoppelung kann verwendet werden, folgt:
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 ausführlichere Erklärung:
Eine äußere Verknüpfung Versuche t1
mit t2
beizutreten. Standardmäßig werden alle Ergebnisse von t1
zurückgegeben und , wenn eine Übereinstimmung in t2
ist, wird es auch zurückgegeben. Wenn es keine Übereinstimmung in t2
für eine gegebene Reihe von t1
ist, dann gibt die Abfrage noch die Reihe von t1
und verwendet NULL
als Platzhalter für alle t2
Spalten. Das ist nur, wie äußere Arbeit im Allgemeinen verbindet.
Der Trick bei dieser Abfrage ist, um die Anpassungsbedingung der Join zu gestalten, dass t2
muß übereinstimmen gleiche userid
und ein mehr date
. Die Idee ist, wenn eine Zeile in t2
existiert, die eine größere date
hat, dann die Zeile in t1
es im Vergleich ist gegen nicht der größte date
für diesen userid
sein. Aber wenn es keine Übereinstimmung gibt - das heißt, wenn keine Zeile mit einer größeren t2
als die Reihe in date
in t1
existiert - wissen wir, dass die Reihe in t1
die Zeile mit dem größten date
für die gegebenen userid
war
In den Fällen (wenn es keine Übereinstimmung ist), werden die Spalten von t2
NULL
werden - auch die Spalten in der Join-Bedingung angegeben. Also, warum wir WHERE t2.UserId IS NULL
verwenden, weil wir für die Fälle, wo sind momentan keine Zeile mit einem größeren date
für die gegebene userid
gefunden wurde.
SELECT userid, MAX(value) KEEP (DENSE_RANK FIRST ORDER BY date DESC)
FROM table
GROUP BY userid
Ich weiß nicht, Ihre genauen Spaltennamen, aber es wäre so etwas wie dies:
select userid, value from users u1 where date = (select max(date) from users u2 where u1.userid = u2.userid)
Nicht bei der Arbeit zu sein, ich habe nicht Oracle zur Hand, aber ich glaube, daran zu erinnern, dass Oracle mehr Spalten gelegt werden, in einer IN-Klausel ermöglicht es, die die Optionen zumindest vermeiden sollten, die eine korrelierte Unterabfrage verwenden, das ist, selten eine gute Idee.
So etwas, vielleicht (kann mich nicht erinnern, wenn die Spaltenliste parenthesised werden soll oder nicht):
SELECT *
FROM MyTable
WHERE (User, Date) IN
( SELECT User, MAX(Date) FROM MyTable GROUP BY User)
EDIT: Gerade versucht es für real:
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, auch wenn einige der neuen fangly Sachen an anderer Stelle erwähnt kann mehr performant.
Ich weiß, dass Sie sich für Oracle gefragt, aber in SQL 2005 haben wir diese jetzt verwenden:
-- 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
Wäre es nicht eine QUALIFY Klausel sowohl einfachste und am besten?
select userid, my_date, ...
from users
qualify rank() over (partition by userid order by my_date desc) = 1
Für Kontext, auf Teradata hier einen anständige Größe Test dieser läuft in 17s mit dieser Version QUALIFY und in 23s mit dem 'Inline-View' / Aldridge Lösung # 1.
Ich habe keine Oracle zu testen, aber die effizienteste Lösung ist analytische Abfragen verwendet werden. Es soll wie folgt 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 loswerden der äußeren Abfrage erhalten und auf der inneren verschieden gesetzt, aber ich bin mir nicht sicher. In der Zwischenzeit weiß, dass ich diese funktioniert.
Wenn Sie über analytische Abfragen lernen, ich würde vorschlagen, dass das Lesen http: //www.orafaq .com / node / 55 und http: //www.akadia. com / services / ora_analytic_functions.html . Hier ist die kurze Zusammenfassung.
Unter der Haube analytische Abfragen, um die gesamte Datensatz sortieren, sie dann nacheinander verarbeiten. Wie Sie es verarbeiten partitionieren Sie den Datensatz nach bestimmten Kriterien, und dann für jede Zeile zu einem bestimmten Fenster schaut (standardmäßig auf den ersten Wert in der Partition auf die aktuelle Zeile - das Standard ist auch die effizienteste) und Werte berechnen kann ein mit Anzahl der analytischen Funktionen (die Liste, von denen sehr ähnlich, die Aggregatfunktionen).
In diesem Fall hier ist das, was die innere Abfrage der Fall ist. Die gesamte Datensatz wird von UserId dann sortiert Datum DESC. Dann verarbeitet sie es in einem Durchgang. Für jede Zeile kehren Sie den Benutzer-ID und das erste Datum für die Benutzer-ID gesehen (seit Daten DESC sortiert, das ist das höchste Datum). Dadurch erhalten Sie Ihre Antwort mit duplizierten Zeilen. Dann werden die äußeren DISTINCT squashes Duplikate.
Dies ist kein besonders spektakuläres Beispiel für analytische Abfragen. Für einen viel größeren betrachtet Sieg eine Tabelle der Finanzeinnahmen zu nehmen und für jeden Benutzer und Erhalt der Berechnung einer laufenden Summe das, was sie bezahlte. Analytic Abfragen löst effektiv das. Andere Lösungen sind weniger effizient. Deshalb ist sie ein Teil der 2003 SQL-Standard sind. (Leider Postgres hat sie noch nicht haben. Grrr ...)
Oracle 12c+
, können Sie Top n Abfragen zusammen mit analytischer Funktion rank
dies zu erreichen, sehr prägnant ohne Unterabfragen:
select *
from your_table
order by rank() over (partition by user_id order by my_date desc)
fetch first 1 row with ties;
Die oben genannten Erträge werden alle Zeilen mit max my_date pro Benutzer.
Wenn Sie nur eine Zeile mit max Datum wollen, dann ersetzen Sie die rank
mit 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;
Mit PostgreSQL 8.4 oder höher verwenden, können Sie diese 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
Verwenden Sie ROW_NUMBER()
eine eindeutige Rangfolge zuweisen auf absteigend Date
für jedes UserId
, filtriere dann zu der ersten Zeile für jeden 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;
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
Sie hatte gerade ein "live" Beispiel bei der Arbeit zu schreiben:)
Dieses unterstützt mehrere Werte für Benutzer-ID auf der Seite gleichen 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 und es in dem Plan erklären nachschlagen. Ich habe nicht die Zeit, mit ihm zu spielen haben.
Natürlich, wenn die Suche durch riesige Tabellen, ist es wahrscheinlich besser, wenn Sie FULL Hinweise in Ihrer Abfrage verwenden.
select VALUE from TABLE1 where TIME =
(select max(TIME) from TABLE1 where DATE=
(select max(DATE) from TABLE1 where CRITERIA=CRITERIA))
Ich denke, so etwas wie diese. (Verzeihen Sie mir für alle Syntaxfehler;! Ich mit HQL an dieser Stelle verwendet bin)
EDIT: Auch die Frage falsch verstanden! Korrigiert die Abfrage ...
SELECT UserId, Value
FROM Users AS user
WHERE Date = (
SELECT MAX(Date)
FROM Users AS maxtest
WHERE maxtest.UserId = user.UserId
)
i Sache sollten Sie diese Variante zur vorherige Abfrage machen:
SELECT UserId, Value FROM Users U1 WHERE
Date = ( SELECT MAX(Date) FROM Users where UserId = U1.UserId)
(T-SQL) erhalten zunächst alle Benutzer und deren MaxDate. Join mit der Tabelle die entsprechenden Werte für die Benutzer in dem 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
Die Antwort ist nur Oracle. Hier ist ein bisschen anspruchsvollere Antwort in jedem SQL:
Wer hat die beste Gesamthausaufgaben Ergebnis (maximale Summe von Hausaufgaben Punkte) hat?
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, die eine Erklärung benötigen, für die ich Zeit atm nicht:
Geben Sie das Buch (ISBN und Titel), die im Jahr 2008 am beliebtesten ist, das heißt, die im Jahr 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);
Hope, das hilft (jedermann) ..:)
Viele Grüße, Guus
Unter der Annahme Datum für eine bestimmte Benutzer-ID eindeutig zuzuordnen ist, 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
Ich bin ziemlich spät, um die Partei, sondern die folgenden Hack wird beide korrelierte Unterabfragen und jede Analysefunktion übertreffen, hat aber eine Einschränkung: Werte in Strings konvertieren müssen. So funktioniert es für Daten, Zahlen und andere Saiten. Der Code sieht nicht gut aus, aber das Ausführungsprofil ist groß.
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 funktioniert so gut ist, dass es nur einmal um die Tabelle scannen muss. Es erfordert keine Indizes und vor allem braucht es nicht um die Tabelle zu sortieren, die die meisten Analysefunktionen tun. Die Indizes werden jedoch helfen, wenn Sie das Ergebnis für eine einzelne Benutzer-ID filtern müssen.
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 das funktioniert. HTH
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
Zuerst versuche ich die Frage falsch verstanden, nach der Top-Antwort, hier ist ein komplettes Beispiel mit dem richtigen 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)
Dies wird auch Pflege von Duplikaten (Rückkehr eine Zeile für jede user_id):
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
getestet Gerade dies und es scheint, auf einer Logging-Tabelle zu arbeiten
select ColumnNames, max(DateColumn) from log group by ColumnNames order by 1 desc
Dies sollte so einfach sein wie:
SELECT UserId, Value
FROM Users u
WHERE Date = (SELECT MAX(Date) FROM Users WHERE UserID = u.UserID)
Wenn Sie Postgres verwenden, können Sie wie
verwendenarray_agg
SELECT userid,MAX(adate),(array_agg(value ORDER BY adate DESC))[1] as value
FROM YOURTABLE
GROUP BY userid
Ich bin nicht vertraut mit Oracle. Das ist, was ich kam mit
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 geben die gleichen Ergebnisse wie die akzeptierte Antwort. Siehe SQLFiddles:
Wenn (UserID, Datum) einzigartig ist, das heißt kein Datum zweimal für den gleichen Benutzer erscheint 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;
select UserId,max(Date) over (partition by UserId) value from users;
Lösung für MySQL, die nicht über Konzepte der Partition HALTEN, 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