sql >> Database teknologi >  >> RDS >> Mysql

Få poster med højeste/mindste pr. gruppe

Så du ønsker at få rækken med det højeste OrderField per gruppe? Jeg ville gøre det på denne måde:

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)
 

(REDIGERING af Tomas: Hvis der er flere poster med det samme OrderField inden for den samme gruppe, og du har brug for præcis én af dem, kan du forlænge betingelsen:

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
 

slutningen af ​​redigeringen.)

Med andre ord, returner rækken t1 for hvilken ingen anden række t2 eksisterer med det samme GroupId og et større OrderField . Når t2.* er NULL, betyder det, at venstre ydre join ikke fandt et sådant match, og derfor t1 har den største værdi af OrderField i gruppen.

Ingen rækker, ingen underforespørgsler. Dette skulle køre hurtigt og optimere adgangen til t2 med "Using index", hvis du har et sammensat indeks på (GroupId, OrderField) .

Angående ydeevne, se mit svar til Hentning af den sidste post i hver gruppe . Jeg prøvede en underforespørgselsmetode og joinmetoden ved hjælp af Stack Overflow-datadumpen. Forskellen er bemærkelsesværdig:joinmetoden kørte 278 gange hurtigere i min test.

Det er vigtigt, at du har det rigtige indeks for at få de bedste resultater!

Med hensyn til din metode, der bruger @Rank-variablen, vil den ikke fungere, som du har skrevet den, fordi værdierne af @Rank ikke nulstilles, efter at forespørgslen har behandlet den første tabel. Jeg vil vise dig et eksempel.

Jeg indsatte nogle dummy-data med et ekstra felt, der er null undtagen på den række, vi ved, er den største pr. gruppe:

select * from `Table`;

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

Vi kan vise, at rangeringen stiger til tre for den første gruppe og seks for den anden gruppe, og den indre forespørgsel returnerer disse korrekt:

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

Kør nu forespørgslen uden join-betingelse for at tvinge et kartesisk produkt af alle rækker, og vi henter også alle kolonner:

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

Vi kan se fra ovenstående, at den maksimale rang pr. gruppe er korrekt, men så fortsætter @Rank med at stige, efterhånden som den behandler den anden afledte tabel, til 7 og højere. Så rækkerne fra den anden afledte tabel vil aldrig overlappe med rækkerne fra den først afledte tabel overhovedet.

Du skal tilføje en anden afledt tabel for at tvinge @Rank til at nulstilles mellem behandlingen af ​​de to tabeller (og håber, at optimeringsværktøjet ikke ændrer rækkefølgen, hvori det evaluerer tabeller, ellers kan du bruge STRAIGHT_JOIN for at forhindre det):

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

Men optimeringen af ​​denne forespørgsel er forfærdelig. Den kan ikke bruge nogen indekser, den opretter to midlertidige tabeller, sorterer dem på den hårde måde og bruger endda en join-buffer, fordi den heller ikke kan bruge et indeks, når den forbinder midlertidige tabeller. Dette er et eksempel på output fra 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 | +----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+

Hvorimod min løsning med den venstre udvendige samling optimerer meget bedre. Den bruger ingen midlertidig tabel og rapporterer endda "Using index" hvilket betyder, at den kan løse joinforbindelsen ved kun at bruge indekset uden at røre ved dataene.

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

Du vil sikkert læse folk, der kommer med påstande på deres blogs om, at "joins gør SQL langsom", men det er noget sludder. Dårlig optimering gør SQL langsom.



  1. Konverter tekstværdi i SQL Server fra UTF8 til ISO 8859-1

  2. Kører MariaDB i en hybrid cloud-opsætning

  3. Hvordan gemmer man BLOB som fil i PL/SQL?

  4. OFFSET vs. ROW_NUMBER()