sql >> Databasteknik >  >> RDS >> Mysql

Få poster med högsta/minsta per grupp

Så du vill få raden med det högsta OrderField per grupp? Jag skulle göra så här:

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 av Tomas: Om det finns fler poster med samma OrderField inom samma grupp och du behöver exakt en av dem, kanske du vill utöka villkoret:

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

slutet av redigeringen.)

Med andra ord, returnera raden t1 för vilken ingen annan rad t2 finns med samma GroupId och ett större OrderField . När t2.* är NULL betyder det att den vänstra yttre kopplingen inte hittade någon sådan matchning, och därför t1 har det största värdet av OrderField i gruppen.

Inga rangord, inga underfrågor. Detta bör gå snabbt och optimera åtkomsten till t2 med "Using index" om du har ett sammansatt index på (GroupId, OrderField) .

Angående prestanda, se mitt svar på Hämta den sista posten i varje grupp . Jag försökte en subquery-metod och join-metoden med Stack Overflow-datadumpen. Skillnaden är anmärkningsvärd:joinmetoden gick 278 gånger snabbare i mitt test.

Det är viktigt att du har rätt index för att få bästa resultat!

När det gäller din metod som använder @Rank-variabeln, kommer den inte att fungera som du har skrivit den, eftersom värdena för @Rank inte återställs till noll efter att frågan har bearbetat den första tabellen. Jag ska visa dig ett exempel.

Jag infogade lite dummydata, med ett extra fält som är null förutom på raden som vi vet är störst per grupp:

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 visa att rangordningen ökar till tre för den första gruppen och sex för den andra gruppen, och den inre frågan returnerar dessa 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 frågan utan kopplingsvillkor, för att tvinga fram en kartesisk produkt av alla rader, och vi hämtar även alla kolumner:

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 från ovan att maxrankningen per grupp är korrekt, men sedan fortsätter @Rank att öka när den bearbetar den andra härledda tabellen, till 7 och högre. Så rankningarna från den andra härledda tabellen kommer aldrig att överlappa med rankningarna från den första härledda tabellen alls.

Du måste lägga till en annan härledd tabell för att tvinga @Rank att återställas till noll mellan bearbetningen av de två tabellerna (och hoppas att optimeraren inte ändrar ordningen i vilken den utvärderar tabeller, eller använd STRAIGHT_JOIN för att förhindra 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 av denna fråga är fruktansvärd. Den kan inte använda några index, den skapar två temporära tabeller, sorterar dem på den hårda vägen och använder till och med en kopplingsbuffert eftersom den inte heller kan använda ett index när den går med i tillfälliga tabeller. Detta är exempel på utdata från 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                  |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+

Medan min lösning med den vänstra yttre skarven optimerar mycket bättre. Den använder ingen temptabell och rapporterar till och med "Using index" vilket innebär att det kan lösa sammanfogningen med endast indexet, utan att röra data.

+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
| 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 kommer förmodligen att läsa folk som påstår på sina bloggar att "anslutningar gör SQL långsam", men det är nonsens. Dålig optimering gör SQL långsam.



  1. Hur ställer man in primärnyckel för automatisk ökning i PostgreSQL?

  2. FEL:Ladda lokal data är inaktiverad - detta måste vara aktiverat på både klient- och serversidan

  3. MariaDB DEFAULT() Förklarat

  4. Omvandlar godtyckligt många rader till kolumner i PostgreSQL