Что происходит в этом запросе MYSQL с левым соединением и группой (в неправильном столбце)? - PullRequest
4 голосов
/ 02 декабря 2011

У меня есть следующие таблицы:

create temporary table Items (item_id int, item_name varchar(10));
create temporary table ItemRating (item_id int, rating int);

Со следующими данными:

insert into Items (item_id, item_name) values (1,'Item 1'),(2,'Item 2'),(3,'Item 3'),(4,'Item 4'),(5,'Item 5');
insert into ItemRating values (1,9),(1,6),(3,10);

И я запускаю следующий запрос:

select i.item_id, i.item_name, avg(ir.rating) from Items i left join ItemRating ir ON ir.item_id = i.item_id group by ir.item_id;

Вот результат, который я получаю:

+---------+-----------+----------------+
| item_id | item_name | avg(ir.rating) |
+---------+-----------+----------------+
|       2 | Item 2    |           NULL |
|       1 | Item 1    |         7.5000 |
|       3 | Item 3    |        10.0000 |
+---------+-----------+----------------+

Теперь я полностью понимаю, что запрос написан неправильно, я хочу сделать группу на i.item_id. Но я не понимаю поведение. Почему MYSQL отображает item_id 2 в результатах, а не 4 или 5? Я действительно ожидал бы увидеть только пункты 1 и 3, потому что они единственные с соответствующей записью в ItemRating.

Итак, кто-нибудь может объяснить мне, что здесь делает MYSQL?

Ответы [ 3 ]

3 голосов
/ 02 декабря 2011

Вот что происходит. Рассмотрим запрос по частям и то, что MySQL обрабатывает в процессе работы.

Сначала вы выбираете из пунктов (select i.item_id, i.item_name, avg(ir.rating) from Items i):

+---------+-----------+
| item_id | item_name |
+---------+-----------+
|       1 | Item 1    |
|       2 | Item 2    |
|       3 | Item 3    |
|       4 | Item 4    |
|       5 | Item 5    |
+---------+-----------+

Тогда вам остается присоединиться к рейтингам (left join ItemRating ir ON ir.item_id = i.item_id). Обратите внимание, что Элемент 1 появляется в двух строках после объединения, потому что именно так JOIN определен для работы - он возвращает одну строку для каждого совпадения условия соединения (и LEFT в основном означает «вернуть каждую строку в первой таблице хотя бы один раз, даже если в этой строке нет совпадений с условиями соединения»).

+---------+-----------+-----------+------------+
| item_id | item_name | ir.rating | ir.item_id |
+---------+-----------+-----------+------------+
|       1 | Item 1    |         9 |          1 |
|       1 | Item 1    |         6 |          1 |
|       2 | Item 2    |      NULL |       NULL |
|       3 | Item 3    |        10 |          3 |
|       4 | Item 4    |      NULL |       NULL |
|       5 | Item 5    |      NULL |       NULL |
+---------+-----------+-----------+------------+

Наконец, вы группируете по рейтингу (group by ir.item_id). Это вернет одну строку для каждого уникального ir.item_id. Существует три уникальных ir.item_ids (как вы можете видеть в последнем столбце): 1, NULL и 3. Для каждого из них возвращается одна строка и усредняется рейтинг.

Итак, для 1 имеем:

+---------+-----------+-----------+------------+
| item_id | item_name | ir.rating | ir.item_id |
+---------+-----------+-----------+------------+
|       1 | Item 1    |         9 |          1 |
|       1 | Item 1    |         6 |          1 |
+---------+-----------+-----------+------------+

Который сворачивается в:

+---------+-----------+----------------+------------+
| item_id | item_name | avg(ir.rating) | ir.item_id |
+---------+-----------+----------------+------------+
|       1 | Item 1    |            7.5 |          1 |
+---------+-----------+----------------+------------+

Для NULL имеем:

+---------+-----------+-----------+------------+
| item_id | item_name | ir.rating | ir.item_id |
+---------+-----------+-----------+------------+
|       2 | Item 2    |      NULL |       NULL |
|       4 | Item 4    |      NULL |       NULL |
|       5 | Item 5    |      NULL |       NULL |
+---------+-----------+-----------+------------+

Который сворачивается в:

+---------+-----------+----------------+------------+
| item_id | item_name | avg(ir.rating) | ir.item_id |
+---------+-----------+----------------+------------+
|        2| Item 2    |           NULL |       NULL |
+---------+-----------+----------------+------------+

Для 3 имеем:

+---------+-----------+-----------+------------+
| item_id | item_name | ir.rating | ir.item_id |
+---------+-----------+-----------+------------+
|       3 | Item 3    |        10 |          3 |
+---------+-----------+-----------+------------+

Который сворачивается в:

+---------+-----------+----------------+------------+
| item_id | item_name | avg(ir.rating) | ir.item_id |
+---------+-----------+----------------+------------+
|       3 | Item 3    |             10 |          3 |
+---------+-----------+----------------+------------+

Объединение трех свернутых результатов дает:

+---------+-----------+----------------+------------+
| item_id | item_name | avg(ir.rating) | ir.item_id |
+---------+-----------+----------------+------------+
|       1 | Item 1    |            7.5 |          1 |
|       3 | Item 3    |             10 |          3 |
|       2 | Item 2    |           NULL |       NULL |
+---------+-----------+----------------+------------+

Что у тебя есть.

Одна сложная часть - способ, которым свернуты строки NULL. Напомним, это были нулевые строки:

+---------+-----------+-----------+------------+
| item_id | item_name | ir.rating | ir.item_id |
+---------+-----------+-----------+------------+
|       2 | Item 2    |      NULL |       NULL |
|       4 | Item 4    |      NULL |       NULL |
|       5 | Item 5    |      NULL |       NULL |
+---------+-----------+-----------+------------+

Когда вы группируете, большинство систем баз данных даже не позволяют выбирать столбцы, которые не являются частью группы. MySQL является исключением. Так как вы группируете только по ir.rating, это единственный, который больше всего позволит вам выбрать, потому что не существует четкого способа свернуть три строки неагрегированным способом. MySQL просто выбирает первое, с которым сталкивается, и использует значения в этой строке в качестве свернутого значения. Итак (2,4,5) => (2) и (пункт 2, пункт 4, пункт 5) => пункт 2 и (NULL, NULL, NULL) => NULL. Вот почему вы видите только строку 2 (на самом деле вы видите три свернутые строки, которые выглядят как строка 2).

Чтобы действительно увидеть это в действии и понять, рассмотрите этот запрос:

select group_concat(i.item_id), group_concat(i.item_name), avg(ir.rating) from Items i left join ItemRating ir ON ir.item_id = i.item_id group by ir.item_id;

Это похоже на исходный запрос, за исключением того, что все три выбранных столбца теперь имеют функции группирования. Я использую GROUP_CONCAT, который просто объединяет строки для формирования свернутой версии (это было бы допустимо в других системах SQL, кроме MySQL). Это возвращает это:

+-------------------------+---------------------------+----------------+
| group_concat(i.item_id) | group_concat(i.item_name) | avg(ir.rating) |
+-------------------------+---------------------------+----------------+
| 2,4,5                   | Item 2,Item 4,Item 5      |           NULL |
| 1,1                     | Item 1,Item 1             |         7.5000 |
| 3                       | Item 3                    |        10.0000 |
+-------------------------+---------------------------+----------------+
2 голосов
/ 02 декабря 2011

Вот ваш набор результатов после объединения и перед группой на

+---------+-----------+----------------+-----------+
| i.item_id | i.item_name | ir.rating | ir.item_id |
+---------+-----------+----------------+-----------+
|       1   | Item 1      |         9 | 1          |
|       1   | Item 1      |         6 | 1          |
|       2   | Item 2      |      null | null       |
|       3   | Item 3      |        10 | 3          |
|       4   | Item 4      |      null | null       |
|       5   | Item 5      |      null | null       |
+---------+-----------+----------------+-----------+

Вы группируете по столбцу ir.item_id, который имеет только 3 различных значения ... 1,3 и ноль.

Очевидно, что он берет первое имя_позиции, хотя я сомневаюсь, что он задокументировал, что он делает, поэтому на него нельзя положиться Суть в том, что должно выдавать ошибку.

Что вам действительно нужно, так это группировать по i.item_id, i.item_name

0 голосов
/ 02 декабря 2011

Левое объединение приносит все значения, но вы группируете по item_id из таблицы ItemRating, так что вы получаете только 3 значения

...