sql >> Databáze >  >> RDS >> Mysql

Získejte záznamy s nejvyšším/nejmenším na skupinu

Chcete tedy získat řádek s nejvyšší hodnotou OrderField za skupinu? Udělal bych to takto:

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 od Tomáše: Pokud je ve stejné skupině více záznamů se stejným OrderField a potřebujete přesně jeden z nich, možná budete chtít podmínku rozšířit:

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

konec úprav.)

Jinými slovy, vraťte řádek t1 pro který žádný jiný řádek t2 existuje se stejným GroupId a větší OrderField . Když t2.* je NULL, znamená to, že levé vnější spojení nenalezlo žádnou takovou shodu, a proto t1 má největší hodnotu OrderField ve skupině.

Žádné hodnosti, žádné poddotazy. To by mělo běžet rychle a optimalizovat přístup k t2 pomocí "Using index", pokud máte složený index na (GroupId, OrderField) .

Pokud jde o výkon, viz moje odpověď na Načítání posledního záznamu v každé skupině . Vyzkoušel jsem metodu poddotazu a metodu spojení pomocí výpisu dat Stack Overflow. Rozdíl je pozoruhodný:metoda spojení běžela v mém testu 278krát rychleji.

Je důležité, abyste měli správný index, abyste dosáhli nejlepších výsledků!

Pokud jde o vaši metodu využívající proměnnou @Rank, nebude fungovat tak, jak jste ji napsali, protože hodnoty @Rank se po zpracování první tabulky nevynulují. Ukážu vám příklad.

Vložil jsem nějaká fiktivní data s dalším polem, které je null kromě řádku, o kterém víme, že je největší na skupinu:

select * from `Table`;

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

Můžeme ukázat, že pořadí se zvýší na tři pro první skupinu a šest pro druhou skupinu a vnitřní dotaz je vrátí správně:

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

Nyní spusťte dotaz bez podmínky spojení, abyste vynutili kartézský součin všech řádků, a také načteme všechny sloupce:

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

Z výše uvedeného můžeme vidět, že maximální hodnocení na skupinu je správné, ale @Rank se dále zvyšuje, jak zpracovává druhou odvozenou tabulku, na 7 a dále. Takže pořadí z druhé odvozené tabulky se nikdy nebude překrývat s pořadím z první odvozené tabulky.

Museli byste přidat další odvozenou tabulku, abyste vynutili @Rank resetovat se na nulu mezi zpracováním dvou tabulek (a doufat, že optimalizátor nezmění pořadí, ve kterém tabulky vyhodnocuje, jinak tomu zabráníte pomocí STRAIGHT_JOIN):

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

Ale optimalizace tohoto dotazu je hrozná. Nemůže používat žádné indexy, vytváří dvě dočasné tabulky, pracně je třídí a dokonce používá vyrovnávací paměť pro spojení, protože ani při spojování dočasných tabulek nemůže použít index. Toto je příklad výstupu z 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                  |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+

Zatímco moje řešení pomocí levého vnějšího spojení se optimalizuje mnohem lépe. Nepoužívá žádnou dočasnou tabulku a dokonce hlásí "Using index" což znamená, že dokáže vyřešit spojení pouze pomocí indexu, aniž by se dotklo dat.

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

Pravděpodobně se dočtete, jak lidé na svých blozích tvrdí, že „připojení zpomaluje SQL“, ale to je nesmysl. Špatná optimalizace zpomaluje SQL.



  1. Jak nejprve třídit podle čísel pomocí dotazu Oracle SQL?

  2. Uživatel schématu Oracle nemůže vytvořit tabulku v proceduře

  3. Jak vytvořím seznam oddělený čárkami pomocí dotazu SQL?

  4. Podpora PDO pro více dotazů (PDO_MYSQL, PDO_MYSQLND)