Quindi vuoi ottenere la riga con il OrderField
più alto per gruppo? Lo farei in questo modo:
SELECT t1.*
FROM `Table` AS t1
LEFT OUTER JOIN `Table` AS t2
ON t1.GroupId = t2.GroupId AND t1.OrderField < t2.OrderField
WHERE t2.GroupId IS NULL
ORDER BY t1.OrderField; // not needed! (note by Tomas)
(EDIT di Tomas: Se sono presenti più record con lo stesso OrderField all'interno dello stesso gruppo e ne hai bisogno esattamente uno, potresti voler estendere la condizione:
SELECT t1.*
FROM `Table` AS t1
LEFT OUTER JOIN `Table` AS t2
ON t1.GroupId = t2.GroupId
AND (t1.OrderField < t2.OrderField
OR (t1.OrderField = t2.OrderField AND t1.Id < t2.Id))
WHERE t2.GroupId IS NULL
fine della modifica.)
In altre parole, restituisci la riga t1
per cui nessun'altra riga t2
esiste con lo stesso GroupId
e un OrderField
maggiore . Quando t2.*
è NULL, significa che il join esterno sinistro non ha trovato tale corrispondenza e quindi t1
ha il valore maggiore di OrderField
nel gruppo.
Nessun rango, nessuna sottoquery. Questo dovrebbe funzionare velocemente e ottimizzare l'accesso a t2 con "Using index" se hai un indice composto su (GroupId, OrderField)
.
Per quanto riguarda le prestazioni, vedi la mia risposta a Recupero dell'ultimo record in ogni gruppo . Ho provato un metodo di sottoquery e il metodo di join utilizzando il dump dei dati di Stack Overflow. La differenza è notevole:il metodo di unione è stato eseguito 278 volte più velocemente nel mio test.
È importante avere l'indice giusto per ottenere i migliori risultati!
Per quanto riguarda il tuo metodo che utilizza la variabile @Rank, non funzionerà come l'hai scritto, perché i valori di @Rank non verranno reimpostati su zero dopo che la query ha elaborato la prima tabella. Ti mostro un esempio.
Ho inserito dei dati fittizi, con un campo in più che è nullo tranne che sulla riga che sappiamo essere la più grande per gruppo:
select * from `Table`;
+---------+------------+------+
| GroupId | OrderField | foo |
+---------+------------+------+
| 10 | 10 | NULL |
| 10 | 20 | NULL |
| 10 | 30 | foo |
| 20 | 40 | NULL |
| 20 | 50 | NULL |
| 20 | 60 | foo |
+---------+------------+------+
Possiamo mostrare che il rango aumenta a tre per il primo gruppo e sei per il secondo gruppo e la query interna li restituisce correttamente:
select GroupId, max(Rank) AS MaxRank
from (
select GroupId, @Rank := @Rank + 1 AS Rank
from `Table`
order by OrderField) as t
group by GroupId
+---------+---------+
| GroupId | MaxRank |
+---------+---------+
| 10 | 3 |
| 20 | 6 |
+---------+---------+
Ora esegui la query senza condizioni di join, per forzare un prodotto cartesiano di tutte le righe e recuperiamo anche tutte le colonne:
select s.*, t.*
from (select GroupId, max(Rank) AS MaxRank
from (select GroupId, @Rank := @Rank + 1 AS Rank
from `Table`
order by OrderField
) as t
group by GroupId) as t
join (
select *, @Rank := @Rank + 1 AS Rank
from `Table`
order by OrderField
) as s
-- on t.GroupId = s.GroupId and t.MaxRank = s.Rank
order by OrderField;
+---------+---------+---------+------------+------+------+
| GroupId | MaxRank | GroupId | OrderField | foo | Rank |
+---------+---------+---------+------------+------+------+
| 10 | 3 | 10 | 10 | NULL | 7 |
| 20 | 6 | 10 | 10 | NULL | 7 |
| 10 | 3 | 10 | 20 | NULL | 8 |
| 20 | 6 | 10 | 20 | NULL | 8 |
| 20 | 6 | 10 | 30 | foo | 9 |
| 10 | 3 | 10 | 30 | foo | 9 |
| 10 | 3 | 20 | 40 | NULL | 10 |
| 20 | 6 | 20 | 40 | NULL | 10 |
| 10 | 3 | 20 | 50 | NULL | 11 |
| 20 | 6 | 20 | 50 | NULL | 11 |
| 20 | 6 | 20 | 60 | foo | 12 |
| 10 | 3 | 20 | 60 | foo | 12 |
+---------+---------+---------+------------+------+------+
Possiamo vedere da quanto sopra che il rango massimo per gruppo è corretto, ma poi il @Rank continua ad aumentare mentre elabora la seconda tabella derivata, a 7 e oltre. Quindi i ranghi della seconda tabella derivata non si sovrapporranno mai ai ranghi della prima tabella derivata.
Dovresti aggiungere un'altra tabella derivata per forzare @Rank a reimpostare a zero tra l'elaborazione delle due tabelle (e sperare che l'ottimizzatore non modifichi l'ordine in cui valuta le tabelle, oppure usa STRAIGHT_JOIN per impedirlo):
select s.*
from (select GroupId, max(Rank) AS MaxRank
from (select GroupId, @Rank := @Rank + 1 AS Rank
from `Table`
order by OrderField
) as t
group by GroupId) as t
join (select @Rank := 0) r -- RESET @Rank TO ZERO HERE
join (
select *, @Rank := @Rank + 1 AS Rank
from `Table`
order by OrderField
) as s
on t.GroupId = s.GroupId and t.MaxRank = s.Rank
order by OrderField;
+---------+------------+------+------+
| GroupId | OrderField | foo | Rank |
+---------+------------+------+------+
| 10 | 30 | foo | 3 |
| 20 | 60 | foo | 6 |
+---------+------------+------+------+
Ma l'ottimizzazione di questa query è terribile. Non può utilizzare alcun indice, crea due tabelle temporanee, le ordina nel modo più difficile e utilizza persino un buffer di join perché non può nemmeno utilizzare un indice quando si unisce a tabelle temporanee. Questo è un esempio di output da EXPLAIN
:
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
| 1 | PRIMARY | <derived4> | system | NULL | NULL | NULL | NULL | 1 | Using temporary; Using filesort |
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 2 | |
| 1 | PRIMARY | <derived5> | ALL | NULL | NULL | NULL | NULL | 6 | Using where; Using join buffer |
| 5 | DERIVED | Table | ALL | NULL | NULL | NULL | NULL | 6 | Using filesort |
| 4 | DERIVED | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
| 2 | DERIVED | <derived3> | ALL | NULL | NULL | NULL | NULL | 6 | Using temporary; Using filesort |
| 3 | DERIVED | Table | ALL | NULL | NULL | NULL | NULL | 6 | Using filesort |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
Considerando che la mia soluzione che utilizza il join esterno sinistro si ottimizza molto meglio. Non utilizza tabelle temporanee e riporta persino "Using index"
il che significa che può risolvere il join utilizzando solo l'indice, senza toccare i dati.
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
| 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 6 | Using filesort |
| 1 | SIMPLE | t2 | ref | GroupId | GroupId | 5 | test.t1.GroupId | 1 | Using where; Using index |
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
Probabilmente leggerai le persone che affermano sui loro blog che "i join rendono SQL lento", ma non ha senso. Una scarsa ottimizzazione rende SQL lento.