Mysql
 sql >> Base de données >  >> RDS >> Mysql

Obtenir les enregistrements avec le plus haut/le plus petit par groupe

Donc, vous voulez obtenir la ligne avec le OrderField le plus élevé par groupe ? Je procéderais ainsi :

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)

(MODIFICATION par Tomas : S'il y a plus d'enregistrements avec le même OrderField dans le même groupe et que vous avez besoin d'exactement l'un d'entre eux, vous pouvez étendre la condition :

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 modification.)

En d'autres termes, retournez la ligne t1 pour lequel aucune autre ligne t2 existe avec le même GroupId et un plus grand OrderField . Lorsque t2.* est NULL, cela signifie que la jointure externe gauche n'a trouvé aucune correspondance, et donc t1 a la plus grande valeur de OrderField dans le groupe.

Pas de rangs, pas de sous-requêtes. Cela devrait fonctionner rapidement et optimiser l'accès à t2 avec "Utiliser l'index" si vous avez un index composé sur (GroupId, OrderField) .

Concernant les performances, voir ma réponse à Récupérer le dernier enregistrement de chaque groupe . J'ai essayé une méthode de sous-requête et la méthode de jointure en utilisant le vidage de données Stack Overflow. La différence est remarquable :la méthode de jointure s'est exécutée 278 fois plus rapidement dans mon test.

Il est important que vous disposiez du bon index pour obtenir les meilleurs résultats !

En ce qui concerne votre méthode utilisant la variable @Rank, cela ne fonctionnera pas comme vous l'avez écrit, car les valeurs de @Rank ne seront pas remises à zéro une fois que la requête aura traité la première table. Je vais vous montrer un exemple.

J'ai inséré des données factices, avec un champ supplémentaire nul, sauf sur la ligne dont nous savons qu'elle est la plus grande par groupe :

select * from `Table`;

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

Nous pouvons montrer que le rang passe à trois pour le premier groupe et à six pour le deuxième groupe, et la requête interne les renvoie correctement :

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

Exécutez maintenant la requête sans condition de jointure, pour forcer un produit cartésien de toutes les lignes, et nous récupérons également toutes les colonnes :

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

Nous pouvons voir d'après ce qui précède que le rang maximum par groupe est correct, mais ensuite le @Rank continue d'augmenter à mesure qu'il traite la deuxième table dérivée, jusqu'à 7 et plus. Ainsi, les rangs de la deuxième table dérivée ne chevaucheront jamais du tout les rangs de la première table dérivée.

Vous devrez ajouter une autre table dérivée pour forcer @Rank à se remettre à zéro entre le traitement des deux tables (et espérer que l'optimiseur ne change pas l'ordre dans lequel il évalue les tables, ou bien utiliser STRAIGHT_JOIN pour empêcher cela) :

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

Mais l'optimisation de cette requête est terrible. Il ne peut utiliser aucun index, il crée deux tables temporaires, les trie à la dure et utilise même un tampon de jointure car il ne peut pas non plus utiliser d'index lors de la jointure de tables temporaires. Ceci est un exemple de sortie 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                  |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+

Alors que ma solution utilisant la jointure externe gauche optimise beaucoup mieux. Il n'utilise aucune table temporaire et signale même "Using index" ce qui signifie qu'il peut résoudre la jointure en utilisant uniquement l'index, sans toucher aux données.

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

Vous lirez probablement des gens qui prétendent sur leurs blogs que "les jointures ralentissent SQL", mais c'est un non-sens. Une mauvaise optimisation ralentit SQL.