Sie möchten also die Zeile mit der höchsten OrderField
pro Gruppe erhalten? Ich würde es so machen:
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)
( BEARBEITEN von Tomas: Wenn sich innerhalb derselben Gruppe mehr Datensätze mit demselben OrderField befinden und Sie genau einen davon benötigen, können Sie die Bedingung erweitern:
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
Ende der Bearbeitung.)
Mit anderen Worten, geben Sie die Zeile zurück, t1
für die keine andere Zeile t2
mit derselben GroupId
und einer größeren vorhanden ist OrderField
. Wenn t2.*
NULL ist, bedeutet dies, dass der linke äußere Join keine solche Übereinstimmung gefunden hat und daher t1
den größten Wert OrderField
in der Gruppe hat.
Keine Ränge, keine Unterabfragen. Dies sollte schnell laufen und den Zugriff auf t2 mit "Using index" optimieren, wenn Sie einen zusammengesetzten Index aktiviert haben (GroupId, OrderField)
.
Informationen zur Leistung finden Sie in meiner Antwort zum Abrufen des letzten Datensatzes in jeder Gruppe . Ich habe eine Unterabfragemethode und die Verknüpfungsmethode mithilfe des Stapelüberlauf-Datendumps ausprobiert. Der Unterschied ist bemerkenswert: Die Join-Methode lief in meinem Test 278-mal schneller.
Es ist wichtig, dass Sie den richtigen Index haben, um die besten Ergebnisse zu erzielen!
In Bezug auf Ihre Methode, die die Variable @Rank verwendet, funktioniert sie nicht so, wie Sie sie geschrieben haben, da die Werte von @Rank nicht auf Null zurückgesetzt werden, nachdem die Abfrage die erste Tabelle verarbeitet hat. Ich zeige Ihnen ein Beispiel.
Ich habe einige Dummy-Daten eingefügt, mit einem zusätzlichen Feld, das null ist, außer in der Zeile, von der wir wissen, dass sie die größte pro Gruppe ist:
select * from `Table`;
+---------+------------+------+
| GroupId | OrderField | foo |
+---------+------------+------+
| 10 | 10 | NULL |
| 10 | 20 | NULL |
| 10 | 30 | foo |
| 20 | 40 | NULL |
| 20 | 50 | NULL |
| 20 | 60 | foo |
+---------+------------+------+
Wir können zeigen, dass der Rang für die erste Gruppe auf drei und für die zweite Gruppe auf sechs steigt und die innere Abfrage diese korrekt zurückgibt:
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 |
+---------+---------+
Führen Sie nun die Abfrage ohne Verknüpfungsbedingung aus, um ein kartesisches Produkt aller Zeilen zu erzwingen, und rufen Sie auch alle Spalten ab:
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 |
+---------+---------+---------+------------+------+------+
Wir können aus dem Obigen ersehen, dass der maximale Rang pro Gruppe korrekt ist, aber dann steigt der @Rank weiter an, wenn er die zweite abgeleitete Tabelle auf 7 und höher verarbeitet. Die Ränge aus der zweiten abgeleiteten Tabelle überschneiden sich also niemals mit den Rängen aus der ersten abgeleiteten Tabelle.
Sie müssten eine weitere abgeleitete Tabelle hinzufügen, um zu zwingen, dass @Rank zwischen der Verarbeitung der beiden Tabellen auf Null zurückgesetzt wird (und hoffen, dass das Optimierungsprogramm die Reihenfolge, in der Tabellen ausgewertet werden, nicht ändert, oder STRAIGHT_JOIN verwenden, um dies zu verhindern):
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 |
+---------+------------+------+------+
Aber die Optimierung dieser Abfrage ist schrecklich. Es kann keine Indizes verwenden, es erstellt zwei temporäre Tabellen, sortiert sie auf die harte Tour und verwendet sogar einen Verknüpfungspuffer, da es auch beim Verknüpfen von temporären Tabellen keinen Index verwenden kann. Dies ist eine Beispielausgabe von 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 |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
Während meine Lösung mit der linken äußeren Verbindung viel besser optimiert. Es verwendet keine temporäre Tabelle und sogar Berichte, "Using index"
was bedeutet, dass der Join nur mithilfe des Index aufgelöst werden kann, ohne die Daten zu berühren.
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
| 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 |
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
Sie werden wahrscheinlich Leute lesen, die in ihren Blogs behaupten, dass "Joins SQL langsam machen", aber das ist Unsinn. Eine schlechte Optimierung macht SQL langsam.