2009-05-29 3 views
45

Ho la seguente tabella:Selezionare i 3 dischi più recenti in cui i valori di una colonna sono distinte

id  time  text  otheridentifier 
    ------------------------------------------- 
    1  6   apple  4 
    2  7   orange 4 
    3  8   banana 3 
    4  9   pear  3 
    5  10  grape  2 

Quello che voglio fare è selezionare i 3 dischi più recenti (dal tempo decrescente), il cui otheridentifier s sono distinti. Quindi, in questo caso, il risultato sarebbe id 's: 5, 4, e 2.

id = 3 sarebbe saltato perché c'è un disco più recente con lo stesso otheridentifier campo.

Qui è quello che ho cercato di fare:

SELECT * FROM `table` GROUP BY (`otheridentifier`) ORDER BY `time` DESC LIMIT 3 

Tuttavia, finisco per ottenere file di id = 5, , e invece di 5, 4, 2 come previsto.

Qualcuno può dirmi perché questa query non restituirà ciò che mi aspettavo? Ho provato a modificare l'ORDER BY in ASC, ma questo semplicemente riorganizza le righe restituite a 1, 3, 5.

risposta

34

Non restituisce quello che ci si aspetta perché il raggruppamento avviene prima di ordinare, come riflesso dalla posizione delle clausole nel Istruzione SQL. Sfortunatamente, dovrai sforzarti per ottenere le file che desideri. Prova questo:

SELECT * 
FROM `table` 
WHERE `id` = (
    SELECT `id` 
    FROM `table` as `alt` 
    WHERE `alt`.`otheridentifier` = `table`.`otheridentifier` 
    ORDER BY `time` DESC 
    LIMIT 1 
) 
ORDER BY `time` DESC 
LIMIT 3 
+1

mi sono ricordato il tempo che ho passato ore a fissare sQL come questo e risulta MySQL 4.0 non supporta le query nidificate; p – Unreality

+1

@Unreality: Fortu nativamente la maggior parte delle soluzioni che coinvolgono le subquery possono essere espresse come join se necessario. :) – Rytmis

+1

Sì, ma quelli con ORDER/LIMIT sono _non_ facilmente espressi da JOIN ... –

2
SELECT * FROM table t1 
WHERE t1.time = 
    (SELECT MAX(time) FROM table t2 
    WHERE t2.otheridentifier = t1.otheridentifier) 
+0

Come sceglierebbe l'ultima riga per l'altro identificatore? – Andomar

+0

@Andomar: non dovrei provare a rispondere alle domande quando non sono completamente sveglio. Modificato un po 'i nomi delle colonne - guarda se ora ha più senso. :) – Rytmis

18

si poteva partecipare al tavolo su se stessa per filtrare l'ultima voce per otheridentifier, e poi prendere le prime 3 file di che:

SELECT last.* 
FROM `table` last 
LEFT JOIN `table` prev 
    ON prev.`otheridentifier` = last.`otheridentifier` 
    AND prev.`time` < last.`time` 
WHERE prev.`id` is null 
ORDER BY last.`time` DESC 
LIMIT 3 
+0

Andomar, puoi spiegare come MySQL sta usando la tabella 'prev' nella tua query? Ho provato ad usarlo su una query locale e sto ricevendo un errore che dice che "database.prev' non esiste. –

+0

Fiddle con i dati dell'OP e la tua richiesta. Non sono sicuro del motivo per cui questa risposta è stata upvoted 6 volte - non funziona qui: http://sqlfiddle.com/#!2/ace0b/1 –

+1

@acoder: hai ragione, c'era un piccolo errore nella query: il mancava il nome della tabella dopo "left join". Ho aggiornato la risposta. – Andomar

2

Andomar's answer è probabilmente meglio a che non usa una subquery.

Un approccio alternativo:

select * 
from `table` t1 
where t1.`time` in (
        select max(s2.`time`) 
        from  `table` t2 
        group by t2.otheridentifier 
        ) 
+0

Penso di vedere un problema qui se i valori temporali non sono univoci - questo potrebbe restituire righe che non dovrebbe. Supponiamo che ci sia un valore temporale che è il massimo per un altro identificatore, ma è, diciamo, il secondo più grande per un altro identificatore. Questa query non restituirebbe entrambi gli altri identificatori? Posso essere del tutto fuori però, sono ancora un po 'stanco. :) – Rytmis

+0

@Rytmis: Sì, e anche la mia query, e la tua :) hehe – Andomar

+0

@Andomar: Hmm, sei sicuro della mia query? Perché l'ho appena testato aggiungendo una riga (6, 7, 'fragola', 3) - il valore temporale 7 è il più grande nel gruppo che ha altro identificatore 4, ma il secondo più grande in quello che ha altro identificatore 3. Mio la query restituisce ancora solo le righe che l'OP voleva. Il mio test case è sbagliato? :) – Rytmis

1

che dire

SELECT *, max(time) FROM `table` group by otheridentifier 
+0

Modifica - questo funziona sui dati dell'OP. Ho avuto alcuni extra nella mia query. Funziona bene con i dati dell'OP. http://sqlfiddle.com/#!2/ace0b/2 –

+1

Non sembra funzionare afaict. Nel tuo sqlfiddle, dovrebbe mostrare l'arancio e la pera - il loro valore temporale è più alto. – mahemoff

+0

@mahemoff Questo non funziona correttamente come hai detto tu. – GusDeCooL

4

ho avuto un requisito analogo, ma ho avuto criteri di selezione più avanzati. Utilizzando alcune delle altre risposte non ho potuto ottenere esattamente quello che mi serviva, ma ho trovato si può ancora fare un GROUP BY e ORDER BY dopo così:

SELECT t.* FROM (SELECT * FROM table ORDER BY time DESC) t 
GROUP BY t.otheridentifier 
+0

funziona bene per me !! – wiLLiamcastrO

2

È possibile utilizzare questa query per ottenere risposta corretta:

SELECT * FROM 
     (SELECT * FROM `table` order by time DESC) 
      t group by otheridentifier 
0

questo anche:

SELECT * FROM 
OrigTable T INNER JOIN 
( 
SELECT otheridentifier,max(time) AS duration 
FROM T 
GROUP BY otheridentifier) S 
ON S.duration = T.time AND S.otheridentifier = T.otheridentifier.