it-swarm.com.de

Holen Sie sich Datensätze mit dem höchsten / kleinsten <was auch immer> pro Gruppe

Wie geht das?

Der frühere Titel dieser Frage war "mit Rang (@Rank: = @Rank + 1) in komplexen Abfragen mit Unterabfragen - funktioniert das?", weil ich nach einer Lösung mit Rängen gesucht habe, aber jetzt sehe ich dass die von Bill gepostete Lösung viel viel besser ist.

Ursprüngliche Frage:

Ich versuche, eine Abfrage zu erstellen, die den letzten Datensatz jeder Gruppe in einer definierten Reihenfolge enthält:

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

Ausdruck @Rank := @Rank + 1 wird normalerweise für rank verwendet, aber für mich sieht es verdächtig aus, wenn es in 2 Unterabfragen verwendet wird, aber nur einmal initialisiert wird. Wird es so funktionieren?

Und zweitens funktioniert es mit einer Unterabfrage, die mehrfach ausgewertet wird? Wie eine Unterabfrage in der where- (oder having-) Klausel (eine andere Art, wie man das Obige schreibt):

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

Danke im Voraus!

84
TMS

Sie möchten also die Zeile mit dem 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)

( EDIT von Tomas: Wenn mehrere Datensätze mit demselben OrderField in derselben Gruppe vorhanden sind und Sie genau einen von ihnen benötigen, können Sie den erweitern Bedingung:

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 t1 Zurück, für die keine andere Zeile t2 Mit demselben GroupId und einem größeren OrderField existiert. Wenn t2.* NULL ist, bedeutet dies, dass der linke äußere Join keine solche Übereinstimmung gefunden hat und daher t1 Den größten Wert von OrderField in der Gruppe hat.

Keine Ränge, keine Unterabfragen. Dies sollte schnell gehen und den Zugriff auf t2 mit "Using index" optimieren, wenn Sie einen zusammengesetzten Index für (GroupId, OrderField) Haben.


In Bezug auf die Leistung siehe meine Antwort auf Abrufen des letzten Datensatzes in jeder Gruppe . Ich habe eine Unterabfragemethode und die Join-Methode mit dem Stack Overflow-Datendump 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!

Bezüglich Ihrer 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 mit einem zusätzlichen Feld eingefügt, 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. Außerdem rufen wir 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 die zweite abgeleitete Tabelle auf 7 und höher verarbeitet wird. Die Ränge der zweiten abgeleiteten Tabelle überschneiden sich also niemals mit den Rängen der ersten abgeleiteten Tabelle.

Sie müssten eine weitere abgeleitete Tabelle hinzufügen, um zu erzwingen, dass @Rank zwischen dem Verarbeiten der beiden Tabellen auf Null zurückgesetzt wird (und hoffen, dass das Optimierungsprogramm die Reihenfolge, in der die Tabellen ausgewertet werden, nicht ändert, oder verwenden Sie STRAIGHT_JOIN, 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 beim Verknüpfen von temporären Tabellen auch 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 meldet sogar "Using index", Was bedeutet, dass der Join nur über den 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 lesen, wie Leute in ihren Blogs behaupten, dass "Joins SQL langsam machen", aber das ist Unsinn. Eine schlechte Optimierung macht SQL langsam.

164
Bill Karwin