经常会遇到 MySQL 左联很多个表的情况,大部分情况下,只要主表外键与每个左联表都是一对一或者多对一(hasOne)的关系,那么结果记录数就不会存在问题。但是当与左联表关系是一对多的情况下,结果会出现重复记录。这种情况除了是以左联表作为目标记录,一般都会做去重处理。

通过聚合函数(比如:COUNTSUMAVGMINMAX 等)结合 group by 分组查询可以实现去重目的。

select a.id, a.name, count(b.id) as b_n
from a
left join b on a.id = b.a_id
group by a.id

上面是联查一个表的情况,分组查询结果没有问题。但当分组联查两个表,数据会存在重复统计的情况。因为 SQL 是先联查,然后按照联查结果分组的。结果集为 a(n)b(m)c(l),以 a 表为主表,对于表 b 聚合函数结果重复了 l 次,对于表 c 聚合结果重复了 m 次:

select a.id, a.name, count(b.id) as b_n, SUM(c.money) as c_total
from a
left join b on a.id = b.a_id
left join c on a.id = c.a_id
group by a.id

遇到这种情况脑子里第一时间想到的,就是使用子查询。

使用子查询的 SQL:

select a.id, a.name,
(select count(b.id) from b where b.a_id = a.id) as b_n,
(select SUM(c.money) from c where c.a_id = a.id) as c_total
from a

别说,子查询还挺好用的。

网上还有一种 先分组查询,再将结果作为联查表联查 的方法,他的查询 SQL 类似:

select a.id, a.name, b1.b_n, c1.c_total
from a
left join (select count(b.id) as b_n, b.a_id from b group by b.a_id) as b1
on a.id = b1.a_id
left join (select SUM(c.money) as c_total, c.a_id from c group by c.a_id) as c1
on a.id = c1.a_id
from a

执行有效。但本地测试(主表 1000 记录,两联查表 10000 条记录,联查表外键有创建索引)使用子查询方法和这种先分组再联查方法,后者比前者慢了近 10 倍。前者 0.003s,后者 0.03s 左右。