Vous voulez donc obtenir la ligne avec le plus élevé OrderField
par groupe? Je le ferais de cette façon:
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 by Tomas: s'il y a plus d'enregistrements avec le même OrderField dans le même groupe et que vous avez besoin exactement de 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 l'édition.)
En d'autres termes, renvoie la ligne t1
pour laquelle aucune autre ligne t2
n'existe avec le même GroupId
et un supérieur OrderField
. Lorsque la t2.*
valeur est NULL, cela signifie que la jointure externe gauche n'a trouvé aucune correspondance de ce type et a donc t1
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 "Utilisation de l'index" si vous avez un index composé activé (GroupId, OrderField)
.
En ce qui concerne les performances, consultez ma réponse à la récupération du 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 a fonctionné 278 fois plus vite dans mon test.
Il est important que vous ayez le bon index pour obtenir les meilleurs résultats!
En ce qui concerne votre méthode utilisant la variable @Rank, elle ne fonctionnera pas comme vous l'avez écrite, 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 qui est nul sauf sur la ligne que nous savons être 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 |
+---------+---------+
Maintenant, exécutez 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 de ce qui précède que le rang maximum par groupe est correct, mais alors le @Rank continue d'augmenter à mesure qu'il traite la deuxième table dérivée, à 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 j'espère que l'optimiseur ne change pas l'ordre dans lequel il évalue les tables, ou bien utilisez STRAIGHT_JOIN pour éviter 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 jonction 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 s'optimise beaucoup mieux. Il n'utilise aucune table temporaire et même des rapports, "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 personnes faisant des déclarations sur leurs blogs selon lesquelles «les jointures ralentissent SQL», mais cela n'a aucun sens. Une mauvaise optimisation ralentit SQL.