Comment faire ça?
L'ancien titre de cette question était "en utilisant le rang (@Rank: = @Rank + 1) dans une requête complexe avec des sous-requêtes - cela fonctionnera-t-il? " Parce que je cherchais une solution utilisant des rangs, mais maintenant je vois que la solution publiée par Bill est beaucoup mieux.
Question originale:
J'essaie de composer une requête qui prendrait le dernier enregistrement de chaque groupe selon un ordre défini:
SET @Rank=0;
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 := @Rank + 1 AS Rank
from Table
order by OrderField
) as s
on t.GroupId = s.GroupId and t.MaxRank = s.Rank
order by OrderField
L'expression @Rank := @Rank + 1
est normalement utilisée pour le rang, mais pour moi, elle semble suspecte lorsqu'elle est utilisée dans 2 sous-requêtes, mais initialisée une seule fois. Cela fonctionnera-t-il de cette façon?
Et deuxièmement, fonctionnera-t-il avec une sous-requête évaluée plusieurs fois? Comme la sous-requête dans la clause where (ou ayant) (une autre façon d'écrire ce qui précède):
SET @Rank=0;
select Table.*, @Rank := @Rank + 1 AS Rank
from Table
having Rank = (select max(Rank) AS MaxRank
from (select GroupId, @Rank := @Rank + 1 AS Rank
from Table as t0
order by OrderField
) as t
where t.GroupId = table.GroupId
)
order by OrderField
Merci d'avance!
Réponses:
Vous voulez donc obtenir la ligne avec le plus élevé
OrderField
par groupe? Je le ferais de cette façon:( 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:
fin de l'édition.)
En d'autres termes, renvoie la ligne
t1
pour laquelle aucune autre lignet2
n'existe avec le mêmeGroupId
et un supérieurOrderField
. Lorsque lat2.*
valeur est NULL, cela signifie que la jointure externe gauche n'a trouvé aucune correspondance de ce type et a donct1
la plus grande valeur deOrderField
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:
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:
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:
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):
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
: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.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.
la source
@Rank1
et@Rank2
, un pour chaque sous-requête? Cela réglerait-il le problème? Serait-ce plus rapide que votre solution?@Rank1
et@Rank2
ne ferait aucune différence.... AND t1.foo = t2.foo
pour obtenir plus tard les résultats corrects pourWHERE ... AND foo='bar'