MySQL LEFT JOIN查询结果与预期不符,求执行原理解析
MySQL LEFT JOIN 实际工作原理解惑
问题背景
我创建了两张数据表tab1和tab2,表结构及数据如下:
CREATE TABLE tab1 ( id1 int NOT NULL, field11 int NOT NULL, field12 date NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; INSERT INTO tab1 (id1, field11, field12) VALUES (1, 11, '2024-07-10'), (2, 11, '2024-07-10'), (3, 11, '2024-07-10'), (4, 12, '2024-07-11'), (5, 12, '2024-07-11'), (6, 12, '2024-07-11'); CREATE TABLE tab2 ( id2 int NOT NULL, field21 int NOT NULL, field22 date NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; INSERT INTO tab2 (id2, field21, field22) VALUES (1, 11, '2024-07-01'), (2, 11, '2024-07-01'), (3, 11, '2024-07-01'), (4, 22, '2024-07-02'), (5, 22, '2024-07-02'), (6, 22, '2024-07-02');
执行以下LEFT JOIN查询语句:
select * from tab1 t1 left join tab2 t2 on t1.field11 = t2.field21;
得到的实际结果为:
+-----+---------+------------+------+---------+------------+ | id1 | field11 | field12 | id2 | field21 | field22 | +-----+---------+------------+------+---------+------------+ | 1 | 11 | 2024-07-10 | 3 | 11 | 2024-07-01 | | 1 | 11 | 2024-07-10 | 2 | 11 | 2024-07-01 | | 1 | 11 | 2024-07-10 | 1 | 11 | 2024-07-01 | | 2 | 11 | 2024-07-10 | 3 | 11 | 2024-07-01 | | 2 | 11 | 2024-07-10 | 2 | 11 | 2024-07-01 | | 2 | 11 | 2024-07-10 | 1 | 11 | 2024-07-01 | | 3 | 11 | 2024-07-10 | 3 | 11 | 2024-07-01 | | 3 | 11 | 2024-07-10 | 2 | 11 | 2024-07-01 | | 3 | 11 | 2024-07-10 | 1 | 11 | 2024-07-01 | | 4 | 12 | 2024-07-11 | NULL | NULL | NULL | | 5 | 12 | 2024-07-11 | NULL | NULL | NULL | | 6 | 12 | 2024-07-11 | NULL | NULL | NULL | +-----+---------+------------+------+---------+------------+ 12 rows in set (0.00 sec)
但我预期的结果是:
+-----+---------+------------+------+---------+------------+ | id1 | field11 | field12 | id2 | field21 | field22 | +-----+---------+------------+------+---------+------------+ | 1 | 11 | 2024-07-10 | 1 | 11 | 2024-07-01 | | 2 | 11 | 2024-07-10 | 2 | 11 | 2024-07-01 | | 3 | 11 | 2024-07-10 | 3 | 11 | 2024-07-01 | | 4 | 12 | 2024-07-11 | NULL | NULL | NULL | | 5 | 12 | 2024-07-11 | NULL | NULL | NULL | | 6 | 12 | 2024-07-11 | NULL | NULL | NULL | +-----+---------+------------+------+---------+------------+
我的预期基于W3Schools的文档说明:
LEFT JOIN关键字返回左表(table1)的所有记录,以及右表(table2)中匹配的记录(如果有)。
同时参考左连接图示,认为结果应仅对应图中的绿色区域。请问MySQL的LEFT JOIN实际是如何工作的?
核心解答
MySQL LEFT JOIN 的实际逻辑
LEFT JOIN的核心规则是:左表中的每一行,都会与右表中所有满足ON条件的行进行关联,生成新的结果行。如果左表的某一行在右表中没有匹配的行,就用NULL填充右表的字段。
回到你的案例:
tab1中field11=11的行有3条(id1=1、2、3)tab2中field21=11的行有3条(id2=1、2、3)- 每一条左表的
field11=11的行,都会和右表的3条匹配行分别关联,所以生成3×3=9条结果行 tab1中field11=12的3条行,在tab2中没有匹配的field21=12的行,所以每条行对应一条右表字段全为NULL的结果行,共3条- 最终总结果就是9+3=12条,这是符合LEFT JOIN标准逻辑的正确结果
对你误解的澄清
W3Schools的描述本身没错,但你对“右表中匹配的记录”理解有误——这里的“匹配的记录”指的是所有满足条件的记录,而不是仅一条。左连接图示的绿色区域确实代表左表所有行加上匹配的右表行,但这里的“匹配”是左表每行对应所有符合条件的右表行,而非一对一的匹配。
如何得到你预期的结果
如果你希望左表的每行仅关联右表中field21匹配的某一条记录(比如每个field21组的第一条),可以通过子查询或窗口函数实现:
方法1:子查询取每组最小id的记录
select t1.*, t2.id2, t2.field21, t2.field22 from tab1 t1 left join ( select * from tab2 where id2 in (select min(id2) from tab2 group by field21) ) t2 on t1.field11 = t2.field21;
方法2:窗口函数分组取第一条
select t1.*, t2.id2, t2.field21, t2.field22 from tab1 t1 left join ( select *, ROW_NUMBER() over (partition by field21 order by id2) as rn from tab2 ) t2 on t1.field11 = t2.field21 and t2.rn = 1;
这两种方法都能让左表每行仅关联右表中对应field21的一条记录,得到你预期的6行结果。
内容的提问来源于stack exchange,提问作者Dennosaur
相关产品推荐
相关产品推荐

