Java充电社
专辑
博文
联系我
本人继续续收门徒,亲手指导
MySQL教程
-> 深入了解连接查询及原理
1、MySQL的一些基础知识
2、MySQL中数据类型介绍
3、MySQL管理员常用的一些命令
4、DDL常见操作汇总
5、DML常见操作
6、select查下基础篇
7、select条件查询
8、排序和分页(order by 、limit)
9、分组查询(group by、having)
10、常用函数汇总
11、深入了解连接查询及原理
12、子查询(本篇非常重要,高手必备)
13、细说NULL导致的神坑,让人防不胜防
14、事务详解
15、视图
16、变量
17、存储过程 & 自定义函数详解
18、流程控制语句介绍
19、游标详解
20、异常捕获及处理详解
21、什么是索引?
22、MySQL索引原理详解
23、MySQL索引管理
24、如何正确的使用索引?
25、sql中的where条件在数据库中提取与应用浅析
26、聊聊如何使用MySQL实现分布式锁
27、MySQL如何确保数据不丢失的?有几点我们可以借鉴
28、MySQL系列测试库脚本
29、win10安装mysql5.7.20解压版
上一篇:常用函数汇总
下一篇:子查询(本篇非常重要,高手必备)
<div style="display:none"></div> 打算提升sql技能的,可以加我微信itsoku,带你成为sql高手。 这是Mysql系列第11篇。 环境:mysql5.7.25,cmd命令中进行演示。 **当我们查询的数据来源于多张表的时候,我们需要用到连接查询,连接查询使用率非常高,希望大家都务必掌握。** ## 本文内容 1. 笛卡尔积 2. 内连接 3. 外连接 4. 左连接 5. 右连接 6. 表连接的原理 7. 使用java实现连接查询,加深理解 ## 准备数据 > 2张表: > > t_team:组表。 > > t_employee:员工表,内部有个team_id引用组表的id。 ```java drop table if exists t_team; create table t_team( id int not null AUTO_INCREMENT PRIMARY KEY comment '组id', team_name varchar(32) not null default '' comment '名称' ) comment '组表'; drop table if exists t_employee; create table t_employee( id int not null AUTO_INCREMENT PRIMARY KEY comment '部门id', emp_name varchar(32) not null default '' comment '员工名称', team_id int not null default 0 comment '员工所在组id' ) comment '员工表表'; insert into t_team values (1,'架构组'),(2,'测试组'),(3,'java组'),(4,'前端组'); insert into t_employee values (1,'路人甲Java',1),(2,'张三',2),(3,'李四',3),(4,'王五',0),(5,'赵六',0); ``` `t_team`表4条记录,如下: ```java mysql> select * from t_team; +----+-----------+ | id | team_name | +----+-----------+ | 1 | 架构组 | | 2 | 测试组 | | 3 | java组 | | 4 | 前端组 | +----+-----------+ 4 rows in set (0.00 sec) ``` `t_employee`表5条记录,如下: ```java mysql> select * from t_employee; +----+---------------+---------+ | id | emp_name | team_id | +----+---------------+---------+ | 1 | 路人甲Java | 1 | | 2 | 张三 | 2 | | 3 | 李四 | 3 | | 4 | 王五 | 0 | | 5 | 赵六 | 0 | +----+---------------+---------+ 5 rows in set (0.00 sec) ``` ## 笛卡尔积 介绍连接查询之前,我们需要先了解一下笛卡尔积。 笛卡尔积简单点理解:有两个集合A和B,笛卡尔积表示A集合中的元素和B集合中的元素任意相互关联产生的所有可能的结果。 假如A中有m个元素,B中有n个元素,A、B笛卡尔积产生的结果有m*n个结果,相当于循环遍历两个集合中的元素,任意组合。 java伪代码表示如下: ```java for(Object eleA : A){ for(Object eleB : B){ System.out.print(eleA+","+eleB); } } ``` > 过程:拿A集合中的第1行,去匹配集合B中所有的行,然后再拿集合A中的第2行,去匹配集合B中所有的行,最后结果数量为m*n。 ### sql中笛卡尔积语法 ```java select 字段 from 表1,表2[,表N]; 或者 select 字段 from 表1 join 表2 [join 表N]; ``` 示例: ``` mysql> select * from t_team,t_employee; +----+-----------+----+---------------+---------+ | id | team_name | id | emp_name | team_id | +----+-----------+----+---------------+---------+ | 1 | 架构组 | 1 | 路人甲Java | 1 | | 2 | 测试组 | 1 | 路人甲Java | 1 | | 3 | java组 | 1 | 路人甲Java | 1 | | 4 | 前端组 | 1 | 路人甲Java | 1 | | 1 | 架构组 | 2 | 张三 | 2 | | 2 | 测试组 | 2 | 张三 | 2 | | 3 | java组 | 2 | 张三 | 2 | | 4 | 前端组 | 2 | 张三 | 2 | | 1 | 架构组 | 3 | 李四 | 3 | | 2 | 测试组 | 3 | 李四 | 3 | | 3 | java组 | 3 | 李四 | 3 | | 4 | 前端组 | 3 | 李四 | 3 | | 1 | 架构组 | 4 | 王五 | 0 | | 2 | 测试组 | 4 | 王五 | 0 | | 3 | java组 | 4 | 王五 | 0 | | 4 | 前端组 | 4 | 王五 | 0 | | 1 | 架构组 | 5 | 赵六 | 0 | | 2 | 测试组 | 5 | 赵六 | 0 | | 3 | java组 | 5 | 赵六 | 0 | | 4 | 前端组 | 5 | 赵六 | 0 | +----+-----------+----+---------------+---------+ 20 rows in set (0.00 sec) ``` t_team表4条记录,t_employee表5条记录,笛卡尔积结果输出了20行记录。 ## 内连接 **语法:** ```java select 字段 from 表1 inner join 表2 on 连接条件; 或 select 字段 from 表1 join 表2 on 连接条件; 或 select 字段 from 表1, 表2 [where 关联条件]; ``` > 内连接相当于在笛卡尔积的基础上加上了连接的条件。 > > 当没有连接条件的时候,内连接上升为笛卡尔积。 过程用java伪代码如下: ```java for(Object eleA : A){ for(Object eleB : B){ if(连接条件是否为true){ System.out.print(eleA+","+eleB); } } } ``` ### 示例1:有连接条件 > 查询员工及所属部门 ```java mysql> select t1.emp_name,t2.team_name from t_employee t1 inner join t_team t2 on t1.team_id = t2.id; +---------------+-----------+ | emp_name | team_name | +---------------+-----------+ | 路人甲Java | 架构组 | | 张三 | 测试组 | | 李四 | java组 | +---------------+-----------+ 3 rows in set (0.00 sec) mysql> select t1.emp_name,t2.team_name from t_employee t1 join t_team t2 on t1.team_id = t2.id; +---------------+-----------+ | emp_name | team_name | +---------------+-----------+ | 路人甲Java | 架构组 | | 张三 | 测试组 | | 李四 | java组 | +---------------+-----------+ 3 rows in set (0.00 sec) mysql> select t1.emp_name,t2.team_name from t_employee t1, t_team t2 where t1.team_id = t2.id; +---------------+-----------+ | emp_name | team_name | +---------------+-----------+ | 路人甲Java | 架构组 | | 张三 | 测试组 | | 李四 | java组 | +---------------+-----------+ 3 rows in set (0.00 sec) ``` > 上面相当于获取了2个表的交集,查询出了两个表都有的数据。 ### 示例2:无连接条件 > 无条件内连接,上升为笛卡尔积,如下: ```java mysql> select t1.emp_name,t2.team_name from t_employee t1 inner join t_team t2; +---------------+-----------+ | emp_name | team_name | +---------------+-----------+ | 路人甲Java | 架构组 | | 路人甲Java | 测试组 | | 路人甲Java | java组 | | 路人甲Java | 前端组 | | 张三 | 架构组 | | 张三 | 测试组 | | 张三 | java组 | | 张三 | 前端组 | | 李四 | 架构组 | | 李四 | 测试组 | | 李四 | java组 | | 李四 | 前端组 | | 王五 | 架构组 | | 王五 | 测试组 | | 王五 | java组 | | 王五 | 前端组 | | 赵六 | 架构组 | | 赵六 | 测试组 | | 赵六 | java组 | | 赵六 | 前端组 | +---------------+-----------+ 20 rows in set (0.00 sec) ``` ### 示例3:组合条件进行查询 > 查询架构组的员工,3种写法 ```java mysql> select t1.emp_name,t2.team_name from t_employee t1 inner join t_team t2 on t1.team_id = t2.id and t2.team_name = '架构组'; +---------------+-----------+ | emp_name | team_name | +---------------+-----------+ | 路人甲Java | 架构组 | +---------------+-----------+ 1 row in set (0.00 sec) mysql> select t1.emp_name,t2.team_name from t_employee t1 inner join t_team t2 on t1.team_id = t2.id where t2.team_name = '架构组'; +---------------+-----------+ | emp_name | team_name | +---------------+-----------+ | 路人甲Java | 架构组 | +---------------+-----------+ 1 row in set (0.00 sec) mysql> select t1.emp_name,t2.team_name from t_employee t1, t_team t2 where t1.team_id = t2.id and t2.team_name = '架构组'; +---------------+-----------+ | emp_name | team_name | +---------------+-----------+ | 路人甲Java | 架构组 | +---------------+-----------+ 1 row in set (0.00 sec) ``` 上面3中方式解说。 方式1:on中使用了组合条件。 方式2:在连接的结果之后再进行过滤,相当于先获取连接的结果,然后使用where中的条件再对连接结果进行过滤。 方式3:直接在where后面进行过滤。 ### 总结 内连接建议使用第3种语法,简洁: ```java select 字段 from 表1, 表2 [where 关联条件]; ``` ## 外连接 外连接涉及到2个表,分为:主表和从表,要查询的信息主要来自于哪个表,谁就是主表。 外连接查询结果为主表中所有记录。如果从表中有和它匹配的,则显示匹配的值,这部分相当于内连接查询出来的结果;如果从表中没有和它匹配的,则显示null。 **最终:外连接查询结果 = 内连接的结果 + 主表中有的而内连接结果中没有的记录。** **外连接分为2种:** **左外链接:使用left join关键字,left join左边的是主表。** **右外连接:使用right join关键字,right join右边的是主表。** ## 左连接 ### 语法 ```java select 列 from 主表 left join 从表 on 连接条件; ``` ### 示例1: 查询所有员工信息,并显示员工所在组,如下: ```java mysql> SELECT t1.emp_name, t2.team_name FROM t_employee t1 LEFT JOIN t_team t2 ON t1.team_id = t2.id; +---------------+-----------+ | emp_name | team_name | +---------------+-----------+ | 路人甲Java | 架构组 | | 张三 | 测试组 | | 李四 | java组 | | 王五 | NULL | | 赵六 | NULL | +---------------+-----------+ 5 rows in set (0.00 sec) ``` 上面查询出了所有员工,员工team_id=0的,team_name为NULL。 ### 示例2: 查询员工姓名、组名,返回组名不为空的记录,如下: ```java mysql> SELECT t1.emp_name, t2.team_name FROM t_employee t1 LEFT JOIN t_team t2 ON t1.team_id = t2.id WHERE t2.team_name IS NOT NULL; +---------------+-----------+ | emp_name | team_name | +---------------+-----------+ | 路人甲Java | 架构组 | | 张三 | 测试组 | | 李四 | java组 | +---------------+-----------+ 3 rows in set (0.00 sec) ``` 上面先使用内连接获取连接结果,然后再使用where对连接结果进行过滤。 ## 右连接 ### 语法 ```java select 列 from 从表 right join 主表 on 连接条件; ``` ### 示例 我们使用右连接来实现上面左连接实现的功能,如下: ```java mysql> SELECT t2.team_name, t1.emp_name FROM t_team t2 RIGHT JOIN t_employee t1 ON t1.team_id = t2.id; +-----------+---------------+ | team_name | emp_name | +-----------+---------------+ | 架构组 | 路人甲Java | | 测试组 | 张三 | | java组 | 李四 | | NULL | 王五 | | NULL | 赵六 | +-----------+---------------+ 5 rows in set (0.00 sec) mysql> SELECT t2.team_name, t1.emp_name FROM t_team t2 RIGHT JOIN t_employee t1 ON t1.team_id = t2.id WHERE t2.team_name IS NOT NULL; +-----------+---------------+ | team_name | emp_name | +-----------+---------------+ | 架构组 | 路人甲Java | | 测试组 | 张三 | | java组 | 李四 | +-----------+---------------+ 3 rows in set (0.00 sec) ``` ## 理解表连接原理 **准备数据** ```java drop table if exists test1; create table test1( a int ); drop table if exists test2; create table test2( b int ); insert into test1 values (1),(2),(3); insert into test2 values (3),(4),(5); ``` ```java mysql> select * from test1; +------+ | a | +------+ | 1 | | 2 | | 3 | +------+ 3 rows in set (0.00 sec) mysql> select * from test2; +------+ | b | +------+ | 3 | | 4 | | 5 | +------+ 3 rows in set (0.00 sec) ``` 我们来写几个连接,看看效果。 ### 示例1:内连接 ```java mysql> select * from test1 t1,test2 t2; +------+------+ | a | b | +------+------+ | 1 | 3 | | 2 | 3 | | 3 | 3 | | 1 | 4 | | 2 | 4 | | 3 | 4 | | 1 | 5 | | 2 | 5 | | 3 | 5 | +------+------+ 9 rows in set (0.00 sec) mysql> select * from test1 t1,test2 t2 where t1.a = t2.b; +------+------+ | a | b | +------+------+ | 3 | 3 | +------+------+ 1 row in set (0.00 sec) ``` 9条数据正常。 ### 示例2:左连接 ```java mysql> select * from test1 t1 left join test2 t2 on t1.a = t2.b; +------+------+ | a | b | +------+------+ | 3 | 3 | | 1 | NULL | | 2 | NULL | +------+------+ 3 rows in set (0.00 sec) mysql> select * from test1 t1 left join test2 t2 on t1.a>10; +------+------+ | a | b | +------+------+ | 1 | NULL | | 2 | NULL | | 3 | NULL | +------+------+ 3 rows in set (0.00 sec) mysql> select * from test1 t1 left join test2 t2 on 1=1; +------+------+ | a | b | +------+------+ | 1 | 3 | | 2 | 3 | | 3 | 3 | | 1 | 4 | | 2 | 4 | | 3 | 4 | | 1 | 5 | | 2 | 5 | | 3 | 5 | +------+------+ 9 rows in set (0.00 sec) ``` 上面的左连接第一个好理解。 第2个sql连接条件`t1.a>10`,这个条件只关联了test1表,再看看结果,是否可以理解?不理解的继续向下看,我们用java代码来实现连接查询。 第3个sql中的连接条件1=1值为true,返回结果为笛卡尔积。 ## java代码实现连接查询 > 下面是一个简略版的实现 ```java package com.itsoku.sql; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; public class Test1 { public static class Table1 { int a; public int getA() { return a; } public void setA(int a) { this.a = a; } public Table1(int a) { this.a = a; } @Override public String toString() { return "Table1{" + "a=" + a + '}'; } public static Table1 build(int a) { return new Table1(a); } } public static class Table2 { int b; public int getB() { return b; } public void setB(int b) { this.b = b; } public Table2(int b) { this.b = b; } public static Table2 build(int b) { return new Table2(b); } @Override public String toString() { return "Table2{" + "b=" + b + '}'; } } public static class Record<R1, R2> { R1 r1; R2 r2; public R1 getR1() { return r1; } public void setR1(R1 r1) { this.r1 = r1; } public R2 getR2() { return r2; } public void setR2(R2 r2) { this.r2 = r2; } public Record(R1 r1, R2 r2) { this.r1 = r1; this.r2 = r2; } @Override public String toString() { return "Record{" + "r1=" + r1 + ", r2=" + r2 + '}'; } public static <R1, R2> Record<R1, R2> build(R1 r1, R2 r2) { return new Record(r1, r2); } } public static enum JoinType { innerJoin, leftJoin } public static interface Filter<R1, R2> { boolean accept(R1 r1, R2 r2); } public static <R1, R2> List<Record<R1, R2>> join(List<R1> table1, List<R2> table2, JoinType joinType, Filter<R1, R2> onFilter, Filter<R1, R2> whereFilter) { if (Objects.isNull(table1) || Objects.isNull(table2) || joinType == null) { return new ArrayList<>(); } List<Record<R1, R2>> result = new CopyOnWriteArrayList<>(); for (R1 r1 : table1) { List<Record<R1, R2>> onceJoinResult = joinOn(r1, table2, onFilter); result.addAll(onceJoinResult); } if (joinType == JoinType.leftJoin) { List<R1> r1Record = result.stream().map(Record::getR1).collect(Collectors.toList()); List<Record<R1, R2>> leftAppendList = new ArrayList<>(); for (R1 r1 : table1) { if (!r1Record.contains(r1)) { leftAppendList.add(Record.build(r1, null)); } } result.addAll(leftAppendList); } if (Objects.nonNull(whereFilter)) { for (Record<R1, R2> record : result) { if (!whereFilter.accept(record.r1, record.r2)) { result.remove(record); } } } return result; } public static <R1, R2> List<Record<R1, R2>> joinOn(R1 r1, List<R2> table2, Filter<R1, R2> onFilter) { List<Record<R1, R2>> result = new ArrayList<>(); for (R2 r2 : table2) { if (Objects.nonNull(onFilter) ? onFilter.accept(r1, r2) : true) { result.add(Record.build(r1, r2)); } } return result; } @Test public void innerJoin() { List<Table1> table1 = Arrays.asList(Table1.build(1), Table1.build(2), Table1.build(3)); List<Table2> table2 = Arrays.asList(Table2.build(3), Table2.build(4), Table2.build(5)); join(table1, table2, JoinType.innerJoin, null, null).forEach(System.out::println); System.out.println("-----------------"); join(table1, table2, JoinType.innerJoin, (r1, r2) -> r1.a == r2.b, null).forEach(System.out::println); } @Test public void leftJoin() { List<Table1> table1 = Arrays.asList(Table1.build(1), Table1.build(2), Table1.build(3)); List<Table2> table2 = Arrays.asList(Table2.build(3), Table2.build(4), Table2.build(5)); join(table1, table2, JoinType.leftJoin, (r1, r2) -> r1.a == r2.b, null).forEach(System.out::println); System.out.println("-----------------"); join(table1, table2, JoinType.leftJoin, (r1, r2) -> r1.a > 10, null).forEach(System.out::println); } } ``` **代码中的`innerJoin()`方法模拟了下面的sql:** ```java mysql> select * from test1 t1,test2 t2; +------+------+ | a | b | +------+------+ | 1 | 3 | | 2 | 3 | | 3 | 3 | | 1 | 4 | | 2 | 4 | | 3 | 4 | | 1 | 5 | | 2 | 5 | | 3 | 5 | +------+------+ 9 rows in set (0.00 sec) mysql> select * from test1 t1,test2 t2 where t1.a = t2.b; +------+------+ | a | b | +------+------+ | 3 | 3 | +------+------+ 1 row in set (0.00 sec) ``` 运行一下`innerJoin()`输出如下: ```java Record{r1=Table1{a=1}, r2=Table2{b=3}} Record{r1=Table1{a=1}, r2=Table2{b=4}} Record{r1=Table1{a=1}, r2=Table2{b=5}} Record{r1=Table1{a=2}, r2=Table2{b=3}} Record{r1=Table1{a=2}, r2=Table2{b=4}} Record{r1=Table1{a=2}, r2=Table2{b=5}} Record{r1=Table1{a=3}, r2=Table2{b=3}} Record{r1=Table1{a=3}, r2=Table2{b=4}} Record{r1=Table1{a=3}, r2=Table2{b=5}} ----------------- Record{r1=Table1{a=3}, r2=Table2{b=3}} ``` 对比一下sql和java的结果,输出的结果条数、数据基本上一致,唯一不同的是顺序上面不一样,**顺序为何不一致,稍微介绍**。 **代码中的`leftJoin()`方法模拟了下面的sql:** ```java mysql> select * from test1 t1 left join test2 t2 on t1.a = t2.b; +------+------+ | a | b | +------+------+ | 3 | 3 | | 1 | NULL | | 2 | NULL | +------+------+ 3 rows in set (0.00 sec) mysql> select * from test1 t1 left join test2 t2 on t1.a>10; +------+------+ | a | b | +------+------+ | 1 | NULL | | 2 | NULL | | 3 | NULL | +------+------+ 3 rows in set (0.00 sec) ``` 运行`leftJoin()`,结果如下: ```java Record{r1=Table1{a=3}, r2=Table2{b=3}} Record{r1=Table1{a=1}, r2=null} Record{r1=Table1{a=2}, r2=null} ----------------- Record{r1=Table1{a=1}, r2=null} Record{r1=Table1{a=2}, r2=null} Record{r1=Table1{a=3}, r2=null} ``` 效果和sql的效果完全一致,可以对上。 **现在我们来讨论java输出的顺序为何和sql不一致?** > 上面java代码中两个表的连接查询使用了嵌套循环,外循环每执行一次,内循环的表都会全部遍历一次,如果放到mysql中,就相当于内表(被驱动表)全部扫描了一次(一次全表io读取操作),主表(外循环)如果有n条数据,那么从表就需要全表扫描n次,表的数据是存储在磁盘中,每次全表扫描都需要做io操作,io操作是最耗时间的,如果mysql按照上面的java方式实现,那效率肯定很低。 **那mysql是如何优化的呢?** > msql内部使用了一个内存缓存空间,就叫他`join_buffer`吧,先把外循环的数据放到`join_buffer`中,然后对从表进行遍历,从表中取一条数据和`join_buffer`的数据进行比较,然后从表中再取第2条和`join_buffer`数据进行比较,直到从表遍历完成,使用这方方式来减少从表的io扫描次数,当`join_buffer`足够大的时候,大到可以存放主表所有数据,那么从表只需要全表扫描一次(即只需要一次全表io读取操作)。 > > mysql中这种方式叫做`Block Nested Loop`。 **java代码改进一下,来实现join_buffer的过程。** ## java代码改进版本 ```java package com.itsoku.sql; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; import com.itsoku.sql.Test1.*; public class Test2 { public static int joinBufferSize = 10000; public static List<?> joinBufferList = new ArrayList<>(); public static <R1, R2> List<Record<R1, R2>> join(List<R1> table1, List<R2> table2, JoinType joinType, Filter<R1, R2> onFilter, Filter<R1, R2> whereFilter) { if (Objects.isNull(table1) || Objects.isNull(table2) || joinType == null) { return new ArrayList<>(); } List<Test1.Record<R1, R2>> result = new CopyOnWriteArrayList<>(); int table1Size = table1.size(); int fromIndex = 0, toIndex = joinBufferSize; toIndex = Integer.min(table1Size, toIndex); while (fromIndex < table1Size && toIndex <= table1Size) { joinBufferList = table1.subList(fromIndex, toIndex); fromIndex = toIndex; toIndex += joinBufferSize; toIndex = Integer.min(table1Size, toIndex); List<Record<R1, R2>> blockNestedLoopResult = blockNestedLoop((List<R1>) joinBufferList, table2, onFilter); result.addAll(blockNestedLoopResult); } if (joinType == JoinType.leftJoin) { List<R1> r1Record = result.stream().map(Record::getR1).collect(Collectors.toList()); List<Record<R1, R2>> leftAppendList = new ArrayList<>(); for (R1 r1 : table1) { if (!r1Record.contains(r1)) { leftAppendList.add(Record.build(r1, null)); } } result.addAll(leftAppendList); } if (Objects.nonNull(whereFilter)) { for (Record<R1, R2> record : result) { if (!whereFilter.accept(record.r1, record.r2)) { result.remove(record); } } } return result; } public static <R1, R2> List<Record<R1, R2>> blockNestedLoop(List<R1> joinBufferList, List<R2> table2, Filter<R1, R2> onFilter) { List<Record<R1, R2>> result = new ArrayList<>(); for (R2 r2 : table2) { for (R1 r1 : joinBufferList) { if (Objects.nonNull(onFilter) ? onFilter.accept(r1, r2) : true) { result.add(Record.build(r1, r2)); } } } return result; } @Test public void innerJoin() { List<Table1> table1 = Arrays.asList(Table1.build(1), Table1.build(2), Table1.build(3)); List<Table2> table2 = Arrays.asList(Table2.build(3), Table2.build(4), Table2.build(5)); join(table1, table2, JoinType.innerJoin, null, null).forEach(System.out::println); System.out.println("-----------------"); join(table1, table2, JoinType.innerJoin, (r1, r2) -> r1.a == r2.b, null).forEach(System.out::println); } @Test public void leftJoin() { List<Table1> table1 = Arrays.asList(Table1.build(1), Table1.build(2), Table1.build(3)); List<Table2> table2 = Arrays.asList(Table2.build(3), Table2.build(4), Table2.build(5)); join(table1, table2, JoinType.leftJoin, (r1, r2) -> r1.a == r2.b, null).forEach(System.out::println); System.out.println("-----------------"); join(table1, table2, JoinType.leftJoin, (r1, r2) -> r1.a > 10, null).forEach(System.out::println); } } ``` 执行`innerJoin()`,输出: ``` Record{r1=Table1{a=1}, r2=Table2{b=3}} Record{r1=Table1{a=2}, r2=Table2{b=3}} Record{r1=Table1{a=3}, r2=Table2{b=3}} Record{r1=Table1{a=1}, r2=Table2{b=4}} Record{r1=Table1{a=2}, r2=Table2{b=4}} Record{r1=Table1{a=3}, r2=Table2{b=4}} Record{r1=Table1{a=1}, r2=Table2{b=5}} Record{r1=Table1{a=2}, r2=Table2{b=5}} Record{r1=Table1{a=3}, r2=Table2{b=5}} ----------------- Record{r1=Table1{a=3}, r2=Table2{b=3}} ``` 执行`leftJoin()`,输出: ```java Record{r1=Table1{a=3}, r2=Table2{b=3}} Record{r1=Table1{a=1}, r2=null} Record{r1=Table1{a=2}, r2=null} ----------------- Record{r1=Table1{a=1}, r2=null} Record{r1=Table1{a=2}, r2=null} Record{r1=Table1{a=3}, r2=null} ``` 结果和sql的结果完全一致。 ## 扩展 表连接中还可以使用前面学过的`group by`、`having`、`order by`、`limit`。 这些关键字相当于在表连接的结果上在进行操作,大家下去可以练习一下,加深理解。 <a style="display:none" target="_blank" href="https://mp.weixin.qq.com/s/_S1DD2JADnXvpexxaBwLLg" style="color:red; font-size:20px; font-weight:bold">继续收门徒,亲手带,月薪 4W 以下的可以来找我</a> ## 最新资料 1. <a href="https://mp.weixin.qq.com/s?__biz=MzkzOTI3Nzc0Mg==&mid=2247484964&idx=2&sn=c81bce2f26015ee0f9632ddc6c67df03&scene=21#wechat_redirect" target="_blank">尚硅谷 Java 学科全套教程(总 207.77GB)</a> 2. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484192&idx=1&sn=505f2faaa4cc911f553850667749bcbb&scene=21#wechat_redirect" target="_blank">2021 最新版 Java 微服务学习线路图 + 视频</a> 3. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484573&idx=1&sn=7f3d83892186c16c57bc0b99f03f1ffd&scene=21#wechat_redirect" target="_blank">阿里技术大佬整理的《Spring 学习笔记.pdf》</a> 4. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484544&idx=2&sn=c1dfe907cfaa5b9ae8e66fc247ccbe84&scene=21#wechat_redirect" target="_blank">阿里大佬的《MySQL 学习笔记高清.pdf》</a> 5. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485167&idx=1&sn=48d75c8e93e748235a3547f34921dfb7&scene=21#wechat_redirect" target="_blank">2021 版 java 高并发常见面试题汇总.pdf</a> 6. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485664&idx=1&sn=435f9f515a8f881642820d7790ad20ce&scene=21#wechat_redirect" target="_blank">Idea 快捷键大全.pdf</a> ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/1/2883e86e-3eff-404a-8943-0066e5e2b454.png)
#custom-toc-container