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.

È stato utile?

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:

  1. Risposta accettata
  2. La mia soluzione con Postgres
  3. La mia soluzione con Oracle

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top