[英]What's happening in this MYSQL query with a left join and a group by (on the wrong column)?
我有以下表格:
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在做什么吗?
这是正在发生的事情。 逐个考虑查询以及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
,它只是连接字符串以形成折叠版本(除了MySQL之外,这在其他SQL系统中也是有效的)。 这返回了:
+-------------------------+---------------------------+----------------+
| 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 |
+-------------------------+---------------------------+----------------+
这是您在加入之后和分组之前的结果集
+---------+-----------+----------------+-----------+
| 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和null。
显然它采取了第一个item_name虽然我怀疑它记录它正在做什么所以这不能依赖。 底线是应该抛出错误。
你真正想要的是i.item_id,i.item_name分组
左连接带来了所有值,但是您在item_id上对ItemRating表进行分组,因此您只能获得3个值
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.