/ / Wählen Sie einen Wert aus einer Gruppe basierend auf der Reihenfolge aus anderen Spalten aus - mysql, sql, mariadb

Wählen Sie einen Wert aus einer Gruppe basierend auf der Reihenfolge aus anderen Spalten aus - MySQL, SQL, Mariadb

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 gund 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 aund 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 horseFü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.

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 № 1
SELECT 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 gDies 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 gIch 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