Mysql
 sql >> база данни >  >> RDS >> Mysql

Вземете записи с най-висок/най-малък <каквото> на група

Така че искате да получите реда с най-високото OrderField на група? Бих го направил по следния начин:

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)

(РЕДАКТИРАНЕ от Томаш: Ако има повече записи със същото OrderField в рамките на същата група и имате нужда от точно един от тях, може да искате да разширите условието:

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

край на редакцията.)

С други думи, върнете реда t1 за който няма друг ред t2 съществува със същия GroupId и по-голямо OrderField . Когато t2.* е NULL, това означава, че лявото външно съединение не е намерило такова съвпадение и следователно t1 има най-голямата стойност на OrderField в групата.

Без рангове, без подзаявки. Това трябва да работи бързо и да оптимизира достъпа до t2 с „Използване на индекс“, ако имате съставен индекс на (GroupId, OrderField) .

По отношение на производителността вижте моя отговор на Извличане на последния запис във всяка група . Опитах метод на подзаявка и метода на присъединяване, използвайки дъмп на данни за препълване на стека. Разликата е забележителна:методът на присъединяване работи 278 пъти по-бързо в моя тест.

Важно е да имате правилния индекс, за да получите най-добри резултати!

По отношение на вашия метод, използващ променливата @Rank, той няма да работи така, както сте го написали, защото стойностите на @Rank няма да се нулират, след като заявката обработи първата таблица. Ще ви покажа пример.

Вмъкнах някои фиктивни данни с допълнително поле, което е нулево, освен в реда, за който знаем, че е най-голямото за група:

select * from `Table`;

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

Можем да покажем, че рангът се увеличава до три за първата група и шест за втората група, а вътрешната заявка връща тези правилно:

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

Сега стартирайте заявката без условие за присъединяване, за да принудите декартово произведение на всички редове и също така извличаме всички колони:

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

От горното можем да видим, че максималният ранг на група е правилен, но след това @Rank продължава да се увеличава, докато обработва втората извлечена таблица, до 7 и по-високи. Така че ранговете от втората извлечена таблица изобщо няма да се припокриват с ранговете от първата извлечена таблица.

Ще трябва да добавите друга извлечена таблица, за да принудите @Rank да се нулира между обработката на двете таблици (и да се надяваме, че оптимизаторът няма да промени реда, в който оценява таблиците, или да използва 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 |
+---------+------------+------+------+

Но оптимизацията на тази заявка е ужасна. Той не може да използва никакви индекси, създава две временни таблици, сортира ги по трудния начин и дори използва буфер за присъединяване, защото не може да използва индекс и при присъединяване на временни таблици. Това е примерен изход от 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                  |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+

Докато моето решение, използващо лявото външно съединение, оптимизира много по-добре. Той не използва временна таблица и дори отчита "Using index" което означава, че може да разреши присъединяването, използвайки само индекса, без да докосва данните.

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

Вероятно ще прочетете хора, които твърдят в блоговете си, че „съединяванията правят SQL бавен“, но това са глупости. Лошата оптимизация прави 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. Множество ранга в една таблица

  2. Как да извика съхранена процедура на MySQL от PHP код?

  3. Как да намерите и замените текст в MySQL база данни с помощта на SQL

  4. Свързване чрез предходен еквивалент за MySQL

  5. MySQL премахва нечислови знаци за сравнение