Mysql
 sql >> Baza danych >  >> RDS >> Mysql

Uzyskaj rekordy z najwyższym/najmniejszym na grupę

Więc chcesz uzyskać wiersz z najwyższym OrderField na grupę? Zrobiłbym to w ten sposób:

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 by Tomasz: Jeśli istnieje więcej rekordów z tym samym OrderField w tej samej grupie i potrzebujesz dokładnie jednego z nich, możesz rozszerzyć warunek:

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

koniec edycji.)

Innymi słowy, zwróć wiersz t1 dla którego nie ma innego wiersza t2 istnieje z tym samym GroupId i większe OrderField . Kiedy t2.* ma wartość NULL, oznacza to, że lewe sprzężenie zewnętrzne nie znalazło takiego dopasowania, a zatem t1 ma największą wartość OrderField w grupie.

Bez rang, bez podzapytań. Powinno to działać szybko i zoptymalizować dostęp do t2 za pomocą „Using index”, jeśli masz indeks złożony na (GroupId, OrderField) .

Jeśli chodzi o wydajność, zobacz moją odpowiedź na Pobieranie ostatniego rekordu w każdej grupie . Próbowałem metody podzapytania i metody join przy użyciu zrzutu danych Stack Overflow. Różnica jest niezwykła:metoda łączenia działała 278 razy szybciej w moim teście.

Aby uzyskać najlepsze wyniki, ważne jest, aby mieć odpowiedni indeks!

Jeśli chodzi o twoją metodę używającą zmiennej @Rank, nie będzie działać tak, jak ją napisałeś, ponieważ wartości @Rank nie zostaną zresetowane do zera po przetworzeniu przez zapytanie pierwszej tabeli. Pokażę ci przykład.

Wstawiłem kilka fikcyjnych danych, z dodatkowym polem o wartości null, z wyjątkiem wiersza, o którym wiemy, że jest największy na grupę:

select * from `Table`;

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

Możemy pokazać, że ranga wzrasta do trzech dla pierwszej grupy i sześciu dla drugiej grupy, a wewnętrzne zapytanie zwraca je poprawnie:

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

Teraz uruchom zapytanie bez warunku złączenia, aby wymusić iloczyn kartezjański wszystkich wierszy, a także pobierzemy wszystkie kolumny:

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 powyższego widać, że maksymalna ranga na grupę jest poprawna, ale następnie @Rank nadal rośnie, gdy przetwarza drugą tabelę pochodną, ​​do 7 i wyżej. Tak więc rangi z drugiej tabeli pochodnej nigdy nie będą się pokrywać z rangami z pierwszej tabeli pochodnej.

Musisz dodać kolejną tabelę pochodną, ​​aby zmusić @Rank do zerowania między przetwarzaniem dwóch tabel (i mieć nadzieję, że optymalizator nie zmieni kolejności, w której ocenia tabele, lub użyć STRAIGHT_JOIN, aby temu zapobiec):

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 optymalizacja tego zapytania jest straszna. Nie może używać żadnych indeksów, tworzy dwie tymczasowe tabele, sortuje je w trudny sposób, a nawet używa bufora łączenia, ponieważ nie może również używać indeksu podczas łączenia tabel tymczasowych. To jest przykładowe wyjście 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                  |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+

Natomiast moje rozwiązanie wykorzystujące lewe sprzężenie zewnętrzne optymalizuje się znacznie lepiej. Nie używa tabeli tymczasowej, a nawet zgłasza "Using index" co oznacza, że ​​może rozwiązać połączenie, używając tylko indeksu, bez dotykania danych.

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

Prawdopodobnie przeczytasz ludzi, którzy na swoich blogach twierdzą, że „połączenia spowalniają SQL”, ale to nonsens. Słaba optymalizacja spowalnia SQL.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Prawidłowy sposób użycia LIKE '%{$var}%' z przygotowanymi wyciągami? [mysqli]

  2. SELECT widoku zawiera podzapytanie w klauzuli FROM

  3. Używanie aliasu w obliczeniach SQL

  4. Używanie MySQLi do WSTAWIANIA danych do bazy danych

  5. Jak uzyskać rekord z maksymalną wartością w MySQL?