sql >> Base de Datos >  >> RDS >> Mysql

Obtener registros con mayor/menor por grupo

Entonces, desea obtener la fila con el OrderField más alto por grupo? Yo lo haría de esta manera:

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)

(EDITADO por Tomas: Si hay más registros con el mismo OrderField dentro del mismo grupo y necesita exactamente uno de ellos, es posible que desee extender la condición:

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

fin de la edición.)

En otras palabras, devolver la fila t1 para el cual no hay otra fila t2 existe con el mismo GroupId y un mayor OrderField . Cuando t2.* es NULL, significa que la combinación externa izquierda no encontró tal coincidencia y, por lo tanto, t1 tiene el mayor valor de OrderField en el grupo.

Sin rangos, sin subconsultas. Esto debería ejecutarse rápido y optimizar el acceso a t2 con "Uso de índice" si tiene un índice compuesto en (GroupId, OrderField) .

Con respecto al rendimiento, consulte mi respuesta a Recuperación del último registro de cada grupo . Probé un método de subconsulta y el método de unión usando el volcado de datos de desbordamiento de pila. La diferencia es notable:el método de combinación se ejecutó 278 veces más rápido en mi prueba.

¡Es importante que tenga el índice correcto para obtener los mejores resultados!

Con respecto a su método que usa la variable @Rank, no funcionará como lo escribió, porque los valores de @Rank no se restablecerán a cero después de que la consulta haya procesado la primera tabla. Te mostraré un ejemplo.

Inserté algunos datos ficticios, con un campo adicional que es nulo excepto en la fila que sabemos que es la más grande por grupo:

select * from `Table`;

+---------+------------+------+
| GroupId | OrderField | foo  |
+---------+------------+------+
|      10 |         10 | NULL |
|      10 |         20 | NULL |
|      10 |         30 | foo  |
|      20 |         40 | NULL |
|      20 |         50 | NULL |
|      20 |         60 | foo  |
+---------+------------+------+

Podemos mostrar que el rango aumenta a tres para el primer grupo y a seis para el segundo grupo, y la consulta interna los devuelve correctamente:

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 |
+---------+---------+

Ahora ejecute la consulta sin condición de unión, para forzar un producto cartesiano de todas las filas, y también recuperamos todas las columnas:

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 |
+---------+---------+---------+------------+------+------+

Podemos ver de lo anterior que el rango máximo por grupo es correcto, pero luego el @Rank continúa aumentando a medida que procesa la segunda tabla derivada, a 7 y más. Por lo tanto, los rangos de la segunda tabla derivada nunca se superpondrán con los rangos de la primera tabla derivada.

Tendría que agregar otra tabla derivada para obligar a @Rank a restablecerse a cero entre el procesamiento de las dos tablas (y esperar que el optimizador no cambie el orden en que evalúa las tablas, o bien use STRAIGHT_JOIN para evitar eso):

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 |
+---------+------------+------+------+

Pero la optimización de esta consulta es terrible. No puede usar ningún índice, crea dos tablas temporales, las ordena de la manera difícil e incluso usa un búfer de unión porque tampoco puede usar un índice al unir tablas temporales. Esta es una salida de ejemplo de 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                  |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+

Mientras que mi solución que usa la combinación externa izquierda se optimiza mucho mejor. No utiliza una tabla temporal e incluso informa "Using index" lo que significa que puede resolver la unión usando solo el índice, sin tocar los datos.

+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
| 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 |
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+

Probablemente leerá a personas que afirman en sus blogs que "las uniones hacen que SQL sea lento", pero eso no tiene sentido. La mala optimización hace que SQL sea lento.