Recupera la riga che ha il valore massimo per una colonna
-
02-07-2019 - |
Domanda
Tabella:
UserId, Value, Date.
Voglio ottenere UserId, valore per il massimo (data) per ciascun ID utente. Cioè, il valore per ciascun ID utente che ha la data più recente. C'è un modo per farlo semplicemente in SQL? (Preferibilmente Oracle)
Aggiornamento: mi scuso per qualsiasi ambiguità: ho bisogno di ottenere TUTTI gli UserId. Ma per ciascun ID utente, solo quella riga in cui l'utente ha la data più recente.
Soluzione
Questo recupererà tutte le righe per le quali il valore della colonna my_date è uguale al valore massimo di my_date per quell'id utente. Ciò può recuperare più righe per l'id utente in cui la data massima è su più righe.
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
" Funzioni analitiche rock "
Modifica: per quanto riguarda il primo commento ...
" l'uso di query analitiche e un self-join vanifica lo scopo delle query analitiche "
Non c'è auto-join in questo codice. Esiste invece un predicato posto sul risultato della vista inline che contiene la funzione analitica - una questione molto diversa e una pratica completamente standard.
" La finestra predefinita in Oracle è dalla prima riga della partizione a quella corrente "
La clausola windowing è applicabile solo in presenza della clausola order by. Senza clausole order by, nessuna clausola windowing viene applicata per impostazione predefinita e nessuna può essere specificata in modo esplicito.
Il codice funziona.
Altri suggerimenti
Vedo che molte persone usano subquery o funzionalità specifiche del fornitore per farlo, ma spesso faccio questo tipo di query senza subquery nel modo seguente. Utilizza un normale SQL standard, quindi dovrebbe funzionare con qualsiasi marca di RDBMS.
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;
In altre parole: recupera la riga da t1
dove non esistono altre righe con lo stesso UserId
e una data maggiore.
(Ho messo l'identificatore " Date " nei delimitatori perché è una parola riservata SQL.)
Nel caso in cui t1. " Date " = t2. " Data "
, appare il raddoppio. Di solito le tabelle hanno la chiave auto_inc (seq)
, ad es. id
.
Per evitare il raddoppio può essere usato come segue:
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;
Ri commenta da @Farhan:
Ecco una spiegazione più dettagliata:
Un join esterno tenta di unire t1
con t2
. Per impostazione predefinita, vengono restituiti tutti i risultati di t1
e se esiste una corrispondenza in t2
, viene anche restituito. Se non esiste alcuna corrispondenza in t2
per una determinata riga di t1
, la query restituisce comunque la riga di t1
e utilizza NULL
come segnaposto per tutte le colonne di t2
. Ecco come funzionano i join esterni in generale.
Il trucco di questa query è progettare la condizione di corrispondenza del join in modo tale che t2
deve corrispondere allo stesso userid
e a un maggiore date
. L'idea è se esiste una riga in t2
che ha una data
maggiore, quindi la riga in t1
viene confrontata con impossibile diventa la data
più grande per quel userid
. Ma se non vi è alcuna corrispondenza, ovvero se non esiste alcuna riga in t2
con una data
maggiore della riga in t1
, sappiamo che la riga in t1
era la riga con la data
più grande per il userid
indicato.
In questi casi (quando non c'è corrispondenza), le colonne di t2
saranno NULL
- anche le colonne specificate nella condizione di join. Ecco perché utilizziamo DOVE t2.UserId IS NULL
, perché stiamo cercando i casi in cui non è stata trovata nessuna riga con una data
maggiore per il userid specificato
.
SELECT userid, MAX(value) KEEP (DENSE_RANK FIRST ORDER BY date DESC)
FROM table
GROUP BY userid
Non conosco i nomi esatti delle tue colonne, ma sarebbe qualcosa del genere:
select userid, value from users u1 where date = (select max(date) from users u2 where u1.userid = u2.userid)
Non essendo al lavoro, non ho Oracle a portata di mano, ma mi sembra di ricordare che Oracle consente di abbinare più colonne in una clausola IN, che dovrebbe almeno evitare le opzioni che utilizzano una sottoquery correlata, che è raramente una buona idea.
Qualcosa del genere, forse (non ricordo se l'elenco delle colonne deve essere tra parentesi o meno):
SELECT *
FROM MyTable
WHERE (User, Date) IN
( SELECT User, MAX(Date) FROM MyTable GROUP BY User)
EDIT: l'ho provato per davvero:
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
Quindi funziona, anche se alcune delle cose nuove di zecca menzionate altrove potrebbero essere più performanti.
So che hai richiesto Oracle, ma in SQL 2005 ora lo usiamo:
-- 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
Una clausola QUALIFY non sarebbe la più semplice e la migliore?
select userid, my_date, ...
from users
qualify rank() over (partition by userid order by my_date desc) = 1
Per il contesto, su Teradata qui viene eseguito un test di dimensioni decenti in 17 secondi con questa versione QUALIFY e in 23 secondi con la 'vista in linea' / soluzione Aldridge n. 1.
Non ho Oracle per testarlo, ma la soluzione più efficiente è usare query analitiche. Dovrebbe assomigliare a questo:
SELECT DISTINCT
UserId
, MaxValue
FROM (
SELECT UserId
, FIRST (Value) Over (
PARTITION BY UserId
ORDER BY Date DESC
) MaxValue
FROM SomeTable
)
Sospetto che tu possa sbarazzarti della query esterna e metterti distinto all'interno, ma non ne sono sicuro. Nel frattempo so che funziona.
Se vuoi conoscere le query analitiche, ti suggerisco di leggere http: //www.orafaq .com / node / 55 e http: //www.akadia. com / servizi / ora_analytic_functions.html . Ecco il breve riassunto.
Sotto le query analitiche della cappa, ordinare l'intero set di dati, quindi elaborarlo in sequenza. Durante l'elaborazione, il set di dati viene partizionato in base a determinati criteri, quindi per ogni riga viene visualizzata una finestra (il valore predefinito è il primo valore nella partizione alla riga corrente - tale valore predefinito è anche il più efficiente) e può calcolare i valori utilizzando un numero di funzioni analitiche (il cui elenco è molto simile alle funzioni aggregate).
In questo caso, ecco cosa fa la query interna. L'intero set di dati è ordinato per ID utente quindi Data DESC. Quindi lo elabora in un unico passaggio. Per ogni riga viene restituito l'Idutente e la prima Data visualizzata per tale ID utente (poiché le date sono ordinate DESC, questa è la data massima). Questo ti dà la tua risposta con righe duplicate. Quindi il DISTINCT esterno schiaccia i duplicati.
Questo non è un esempio particolarmente spettacolare di query analitiche. Per una vittoria molto più grande prendi in considerazione la possibilità di prendere una tabella delle entrate finanziarie e di calcolare per ogni utente e ricevuta, un totale parziale di ciò che hanno pagato. Le query analitiche lo risolvono in modo efficiente. Altre soluzioni sono meno efficienti. Ecco perché fanno parte dello standard SQL del 2003. (Purtroppo Postgres non li ha ancora. Grrr ...)
In Oracle 12c +
, puoi utilizzare le query Top n insieme alla funzione analitica rank
per raggiungere questo obiettivo conciso senza sottoquery:
select *
from your_table
order by rank() over (partition by user_id order by my_date desc)
fetch first 1 row with ties;
Quanto sopra restituisce tutte le righe con max my_date per utente.
Se desideri solo una riga con la data massima, sostituisci il ranking
con 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;
Con PostgreSQL 8.4 o successivo, puoi usare questo:
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
Usa ROW_NUMBER ()
per assegnare una classifica univoca in base al Date
decrescente per ciascun UserId
, quindi filtrare alla prima riga per ciascun UserId
(ovvero, 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
Ho appena dovuto scrivere un " live " esempio al lavoro :)
Questo supporta più valori per UserId nella stessa data
Colonne: UserId, Value, Date
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
)
Puoi utilizzare FIRST_VALUE invece di MAX e cercarlo nel piano esplicativo. Non ho avuto il tempo di giocarci.
Naturalmente, se cerchi in tabelle enormi, probabilmente è meglio se usi suggerimenti FULL nella tua query.
select VALUE from TABLE1 where TIME =
(select max(TIME) from TABLE1 where DATE=
(select max(DATE) from TABLE1 where CRITERIA=CRITERIA))
Penso a qualcosa del genere. (Perdonami per eventuali errori di sintassi; sono abituato a usare HQL a questo punto!)
EDIT: anche frainteso la domanda! Correzione della query ...
SELECT UserId, Value
FROM Users AS user
WHERE Date = (
SELECT MAX(Date)
FROM Users AS maxtest
WHERE maxtest.UserId = user.UserId
)
Penso che dovresti fare questa variante alla query precedente:
SELECT UserId, Value FROM Users U1 WHERE
Date = ( SELECT MAX(Date) FROM Users where UserId = U1.UserId)
(T-SQL) Prima di tutto ottenere tutti gli utenti e il loro maxdate. Unisciti alla tabella per trovare i valori corrispondenti per gli utenti sui maxdate.
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
Risultati:
userid value date
----------- ----------- --------------------------
2 3 2003-01-01 00:00:00.000
1 2 2002-01-01 00:00:00.000
La risposta qui è solo Oracle. Ecco una risposta un po 'più sofisticata in tutto l'SQL:
Chi ha il miglior risultato complessivo per i compiti (somma massima dei punti per i compiti)?
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)
E un esempio più difficile, che ha bisogno di qualche spiegazione, per il quale non ho tempo atm:
Fornisci il libro (codice ISBN e titolo) più popolare nel 2008, ovvero preso in prestito più spesso nel 2008.
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);
Spero che questo aiuti (chiunque) .. :)
Saluti, Guus
Supponendo che la data sia unica per un determinato ID utente, ecco alcuni 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
Sono in ritardo alla festa, ma il seguente hack supererà sia le subquery correlate sia qualsiasi funzione di analisi ma ha una limitazione: i valori devono essere convertiti in stringhe. Quindi funziona per date, numeri e altre stringhe. Il codice non ha un bell'aspetto ma il profilo di esecuzione è eccezionale.
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
Il motivo per cui questo codice funziona così bene è che deve solo scansionare la tabella una volta. Non richiede alcun indice e, soprattutto, non è necessario ordinare la tabella, come fanno la maggior parte delle funzioni di analisi. Gli indici ti aiuteranno se hai bisogno di filtrare il risultato per un singolo userid.
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 funziona. HTH
Penso che dovrebbe funzionare?
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
Primo tentativo Ho letto male la domanda, seguendo la risposta in alto, ecco un esempio completo con risultati corretti:
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)
Questo si occuperà anche dei duplicati (restituisce una riga per ogni ID_utente):
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
Ho appena provato questo e sembra funzionare su una tabella di registrazione
select ColumnNames, max(DateColumn) from log group by ColumnNames order by 1 desc
Questo dovrebbe essere semplice come:
SELECT UserId, Value
FROM Users u
WHERE Date = (SELECT MAX(Date) FROM Users WHERE UserID = u.UserID)
Se stai usando Postgres, puoi usare array_agg
come
SELECT userid,MAX(adate),(array_agg(value ORDER BY adate DESC))[1] as value
FROM YOURTABLE
GROUP BY userid
Non ho familiarità con Oracle. Questo è quello che mi è venuto in mente
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
Entrambe le query restituiscono gli stessi risultati della risposta accettata. Vedi SQLFiddles:
Se (UserID, Date) è univoco, ovvero nessuna data appare due volte per lo stesso utente, allora:
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;
Soluzione per MySQL che non ha concetti di partizione 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
Riferimento: http: // benincampus.blogspot.com/2013/08/select-rows-which-have-maxmin-value-in.html