Mysql
 sql >> Database >  >> RDS >> Mysql

Ottieni record con più alto/più piccolo per gruppo

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.