sql >> Database >  >> RDS >> Mysql

Haal records op met de hoogste/kleinste per groep

U wilt dus de rij met het hoogste OrderField . krijgen per groep? Ik zou het op deze manier doen:

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)

(BEWERKEN door Tomas: Als er meer records zijn met hetzelfde OrderField binnen dezelfde groep en u heeft er precies één nodig, dan kunt u de voorwaarde uitbreiden:

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

einde van bewerking.)

Met andere woorden, retourneer de rij t1 waarvoor geen andere rij t2 bestaat met dezelfde GroupId en een groter OrderField . Wanneer t2.* NULL is, betekent dit dat de linker outer join niet zo'n overeenkomst heeft gevonden, en daarom t1 heeft de grootste waarde van OrderField in de groep.

Geen rangen, geen subquery's. Dit zou snel moeten werken en de toegang tot t2 moeten optimaliseren met "Using index" als u een samengestelde index heeft op (GroupId, OrderField) .

Zie mijn antwoord op Het laatste record in elke groep ophalen wat betreft prestaties. . Ik heb een subquery-methode en de join-methode geprobeerd met behulp van de Stack Overflow-gegevensdump. Het verschil is opmerkelijk:de join-methode liep in mijn test 278 keer sneller.

Het is belangrijk dat u de juiste index heeft om de beste resultaten te krijgen!

Wat betreft uw methode met behulp van de @Rank-variabele, deze zal niet werken zoals u deze hebt geschreven, omdat de waarden van @Rank niet opnieuw op nul worden gezet nadat de query de eerste tabel heeft verwerkt. Ik zal je een voorbeeld laten zien.

Ik heb wat dummy-gegevens ingevoegd, met een extra veld dat null is, behalve in de rij waarvan we weten dat deze de grootste is per groep:

select * from `Table`;

+---------+------------+------+
| GroupId | OrderField | foo  |
+---------+------------+------+
|      10 |         10 | NULL |
|      10 |         20 | NULL |
|      10 |         30 | foo  |
|      20 |         40 | NULL |
|      20 |         50 | NULL |
|      20 |         60 | foo  |
+---------+------------+------+

We kunnen laten zien dat de rangorde stijgt tot drie voor de eerste groep en zes voor de tweede groep, en de innerlijke query retourneert deze correct:

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 |
+---------+---------+

Voer nu de query uit zonder join-voorwaarde, om een ​​Cartesiaans product van alle rijen te forceren, en we halen ook alle kolommen op:

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 |
+---------+---------+---------+------------+------+------+

We kunnen uit het bovenstaande zien dat de maximale rangorde per groep correct is, maar dan blijft de @Rank toenemen terwijl het de tweede afgeleide tabel verwerkt, tot 7 en hoger. De rangen van de tweede afgeleide tabel zullen dus nooit overlappen met de rangen van de eerste afgeleide tabel.

Je zou een andere afgeleide tabel moeten toevoegen om @Rank te forceren om tussen de verwerking van de twee tabellen op nul te zetten (en hoop dat de optimizer de volgorde waarin het tabellen evalueert niet verandert, of gebruik anders STRAIGHT_JOIN om dat te voorkomen):

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 |
+---------+------------+------+------+

Maar de optimalisatie van deze query is verschrikkelijk. Het kan geen indexen gebruiken, het maakt twee tijdelijke tabellen, sorteert ze op de harde manier en gebruikt zelfs een join-buffer omdat het ook geen index kan gebruiken bij het samenvoegen van tijdelijke tabellen. Dit is een voorbeelduitvoer van 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                  |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+

Terwijl mijn oplossing met de linker buitenste join veel beter optimaliseert. Het gebruikt geen tijdelijke tabel en rapporteert zelfs "Using index" wat betekent dat het de join kan oplossen met alleen de index, zonder de gegevens aan te raken.

+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
| 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 |
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+

Je zult waarschijnlijk mensen lezen die op hun blogs beweren dat "joins SQL traag maken", maar dat is onzin. Slechte optimalisatie maakt SQL traag.



  1. MySQL versus PostgreSQL? Welke moet ik kiezen voor mijn Django-project?

  2. Benoemde tijdzones instellen in MariaDB

  3. Ruby 'pg' gem linkt naar verkeerde kopie van libpq.5.dylib (op OSX)

  4. SQLSTATE [HY000] [2002] Een verbindingspoging is mislukt.. - Bij een poging om verbinding te maken van een lokale naar een externe server