Angenommen, ich habe diese Tabelle tab
(Geige verfügbar).
| g | a | b | v |
---------------------
| 1 | 3 | 5 | foo |
| 1 | 4 | 7 | bar |
| 1 | 2 | 9 | baz |
| 2 | 1 | 1 | dog |
| 2 | 5 | 2 | cat |
| 2 | 5 | 3 | horse |
| 2 | 3 | 8 | pig |
Ich gruppiere Zeilen nach g
und für jede Gruppe möchte ich einen Wert aus der Spalte v
. Ich will aber nicht irgendein Wert, aber ich möchte den Wert aus der Zeile mit maximal a
und von all diesen die mit dem Maximum b
. Mit anderen Worten, mein Ergebnis sollte sein
| 1 | bar |
| 2 | horse |
Aktuelle Lösung
Ich kenne eine Abfrage, um dies zu erreichen:
SELECT grps.g,
(SELECT v FROM tab
WHERE g = grps.g
ORDER BY a DESC, b DESC
LIMIT 1) AS r
FROM (SELECT DISTINCT g FROM tab) grps
Frage
Aber ich betrachte diese Frage eher hässlich. Meistens, weil es a verwendet abhängige Unterabfrage, was sich wie ein echter Performance-Killer anfühlt. Ich frage mich also, ob es eine einfachere Lösung für dieses Problem gibt.
Erwartete Antworten
Die wahrscheinlichste Antwort, die ich auf diese Frage erwartewäre eine Art Add-On oder Patch für MySQL (oder MariaDB), das eine Funktion dafür bietet. Aber ich werde auch andere nützliche Inspirationen begrüßen. Alles, was ohne eine abhängige Unterabfrage funktioniert, würde als Antwort gelten.
Wenn Ihre Lösung nur für eine einzelne Bestellspalte funktioniert, d. H. Nicht unterscheiden kann cat
und horse
Fühlen Sie sich frei, diese Antwort so vorzuschlagen, wie ich es für die meisten Anwendungsfälle noch nützlich finde. Zum Beispiel, 100*a+b
Dies wäre eine wahrscheinliche Möglichkeit, die obigen Daten nach beiden Spalten zu ordnen, während nur ein einziger Ausdruck verwendet wird.
Ich habe ein paar ziemlich hackige Lösungen im Sinn und könnte sie nach einer Weile hinzufügen, aber ich werde zuerst schauen und sehen, ob zuerst einige nette neue hinzukommen.
Benchmark-Ergebnisse
Da ist es ziemlich schwer die verschiedenen zu vergleichenAntworten, indem ich sie mir nur ansehe, habe ich einige Benchmarks für sie ausgeführt. Diese wurden auf meinem eigenen Desktop mit MySQL 5.1 ausgeführt. Die Zahlen sind mit keinem anderen System vergleichbar, nur miteinander. Sie sollten wahrscheinlich Ihre eigenen Tests mit Ihren realen Daten durchführen, wenn die Leistung für Ihre Anwendung entscheidend ist. Wenn neue Antworten eingehen, füge ich sie möglicherweise meinem Skript hinzu und führe alle Tests erneut aus.
- 100.000 Artikel, 1.000 Gruppen zur Auswahl, InnoDb:
- 0,166s für MvG (aus Frage)
- 0,520s für RichardTheKiwi
- 2.199s für xdazz
- 19.24s für Dems (sequentielle Unterabfragen)
- 48,72 s für acatt
- 100.000 Artikel, 50.000 Gruppen zur Auswahl, InnoDb:
- 0,356s für xdazz
- 0,640s für RichardTheKiwi
- 0,764s für MvG (aus Frage)
- 51,50s für acatt
- zu lang zum Dems (sequentielle Unterabfragen)
- 100.000 Artikel, 100 Gruppen zur Auswahl, InnoDb:
- 0,163s für MvG (aus Frage)
- 0,523s für RichardTheKiwi
- 2,072s für Dems (sequentielle Unterabfragen)
- 17.78s für xdazz
- 49,85s für acatt
Es scheint also, dass meine eigene Lösung bisher nicht alles istso schlimm, auch mit der abhängigen Unterabfrage. Überraschenderweise schneidet die Lösung von acatt, die ebenfalls eine abhängige Unterabfrage verwendet und die ich daher in etwa gleich betrachtet hätte, viel schlechter ab. Wahrscheinlich etwas, mit dem der MySQL-Optimierer nicht fertig werden kann. Die von RichardTheKiwi vorgeschlagene Lösung scheint auch eine gute Gesamtleistung zu haben. Die beiden anderen Lösungen hängen stark von der Struktur der Daten ab. Bei vielen Gruppen kleiner Gruppen übertrifft der xdazz-Ansatz alle anderen. wohingegen die Lösung von Dems für wenige große Gruppen am besten (wenn auch immer noch nicht außergewöhnlich gut) ist.
Antworten:
4 für die Antwort № 1SELECT g, a, b, v
FROM (
SELECT *,
@rn := IF(g = @g, @rn + 1, 1) rn,
@g := g
FROM (select @g := null, @rn := 0) x,
tab
ORDER BY g, a desc, b desc, v
) X
WHERE rn = 1;
Einzelpass. Alle anderen Lösungen sehen für mich wie O (n ^ 2) aus.
5 für die Antwort № 2
Auf diese Weise wird keine Unterabfrage verwendet.
SELECT t1.g, t1.v
FROM tab t1
LEFT JOIN tab t2 ON t1.g = t2.g AND (t1.a < t2.a OR (t1.a = t2.a AND t1.b < t2.b))
WHERE t2.g IS NULL
Erläuterung:</ strong>
Der LEFT JOIN basiert auf der Basis, dass wenn t1.a seinen Maximalwert erreicht hat, es kein s2.a mit einem größeren Wert gibt und die s2-Zeilenwerte NULL sind.
1 für die Antwort № 3
Dies kann mithilfe einer korrelierten Abfrage gelöst werden:
SELECT g, v
FROM tab t
WHERE NOT EXISTS (
SELECT 1
FROM tab
WHERE g = t.g
AND a > t.a
OR (a = t.a AND b > t.b)
)
1 für die Antwort № 4
Viele RDBMS haben Konstrukte, die für dieses Problem besonders geeignet sind. MySQL isn "t einer von ihnen.
Dies führt Sie zu drei grundlegenden Ansätzen.
Überprüfen Sie jeden Datensatz mithilfe von EXISTS und einer korrelierten Unterabfrage in einer EXISTS-Klausel, um festzustellen, ob es sich um einen gewünschten handelt. (@acatts Antwort, aber ich verstehe, dass MySQL dies nicht immer sehr gut optimiert. Stellen Sie sicher, dass Sie einen zusammengesetzten Index haben
(g,a,b)
bevor angenommen wird, dass MySQL dies nicht sehr gut macht.)Machen Sie ein halbes kartesisches Produkt, um den gleichen Scheck vollständig auszufüllen. Jeder Datensatz, der nicht beitritt, ist ein Zieldatensatz. Wenn jede Gruppe ("g") groß ist, kann dies die Leistung schnell beeinträchtigen (Wenn es 10 Datensätze für jeden eindeutigen Wert von gibt
g
Dies ergibt ~ 50 Datensätze und verwirft 49. Bei einer Gruppengröße von 100 ergibt es ~ 5000 Datensätze und verwirft 4999), aber es ist ideal für kleine Gruppengrößen. (Antwort von @xdazz.)Oder verwenden Sie mehrere Unterabfragen, um den MAX (a) und dann den MAX (b) zu bestimmen ...
Mehrere aufeinanderfolgende Unterabfragen ...
SELECT
yourTable.*
FROM
(SELECT g, MAX(a) AS a FROM yourTable GROUP BY g ) AS searchA
INNER JOIN
(SELECT g, a, MAX(b) AS b FROM yourTable GROUP BY g, a) AS searchB
ON searchA.g = searchB.g
AND searchA.a = searchB.a
INNER JOIN
yourTable
ON yourTable.g = searchB.g
AND yourTable.a = searchB.a
AND yourTable.b = searchB.b
Abhängig davon, wie MySQL die zweite Unterabfrage optimiert, ist diese möglicherweise leistungsfähiger als die anderen Optionen. Es ist jedoch das längste (und möglicherweise am wenigsten wartbar) Code für die gegebene Aufgabe.
Annahme eines zusammengesetzten Index für alle drei Suchfelder (g, a, b)
Ich würde davon ausgehen, dass es am besten für große Gruppen geeignet ist g
. Das sollte aber getestet werden.
Für kleine Gruppengrößen von g
Ich würde mit der Antwort von @xdazz gehen.
BEARBEITEN
Es gibt auch einen Brute-Force-Ansatz.
- Erstellen Sie eine identische Tabelle, jedoch mit einer AUTO_INCREMENT-Spalte als ID.
- Fügen Sie Ihre Tabelle in diesen Klon ein, geordnet nach g, a, b.
- Die IDs können dann mit gefunden werden
SELECT g, MAX(id)
. - Dieses Ergebnis kann dann zum Nachschlagen des verwendet werden
v
Werte, die Sie brauchen.
Dies ist wahrscheinlich nicht der beste Ansatz. Wenn dies der Fall ist, ist dies praktisch eine Voraussetzung für die Fähigkeit des Optimierers von MySQL, diese Art von Problem zu lösen.
Das heißt, jeder Motor hat seine Schwachstellen. Also persönlich versuche ich alles, bis ich denken Ich verstehe, wie sich das RDBMS verhält und kann meine Wahl treffen :)
BEARBEITEN
Beispiel mit ROW_NUMBER()
. (Oracle, SQL Server, PostGreSQL usw.)
SELECT
*
FROM
(
SELECT
ROW_NUMBER() OVER (PARTITION BY g ORDER BY a DESC, b DESC) AS sequence_id,
*
FROM
yourTable
)
AS data
WHERE
sequence_id = 1