第11篇:深入了解连接查询及原理

star2017 1年前 ⋅ 663 阅读

打算提升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。

  1. drop table if exists t_team;
  2. create table t_team(
  3. id int not null AUTO_INCREMENT PRIMARY KEY comment '组id',
  4. team_name varchar(32) not null default '' comment '名称'
  5. ) comment '组表';
  6. drop table if exists t_employee;
  7. create table t_employee(
  8. id int not null AUTO_INCREMENT PRIMARY KEY comment '部门id',
  9. emp_name varchar(32) not null default '' comment '员工名称',
  10. team_id int not null default 0 comment '员工所在组id'
  11. ) comment '员工表表';
  12. insert into t_team values (1,'架构组'),(2,'测试组'),(3,'java组'),(4,'前端组');
  13. insert into t_employee values (1,'路人甲Java',1),(2,'张三',2),(3,'李四',3),(4,'王五',0),(5,'赵六',0);

t_team表4条记录,如下:

  1. mysql> select * from t_team;
  2. +----+-----------+
  3. | id | team_name |
  4. +----+-----------+
  5. | 1 | 架构组 |
  6. | 2 | 测试组 |
  7. | 3 | java |
  8. | 4 | 前端组 |
  9. +----+-----------+
  10. 4 rows in set (0.00 sec)

t_employee表5条记录,如下:

  1. mysql> select * from t_employee;
  2. +----+---------------+---------+
  3. | id | emp_name | team_id |
  4. +----+---------------+---------+
  5. | 1 | 路人甲Java | 1 |
  6. | 2 | 张三 | 2 |
  7. | 3 | 李四 | 3 |
  8. | 4 | 王五 | 0 |
  9. | 5 | 赵六 | 0 |
  10. +----+---------------+---------+
  11. 5 rows in set (0.00 sec)

笛卡尔积

介绍连接查询之前,我们需要先了解一下笛卡尔积。

笛卡尔积简单点理解:有两个集合A和B,笛卡尔积表示A集合中的元素和B集合中的元素任意相互关联产生的所有可能的结果。

假如A中有m个元素,B中有n个元素,A、B笛卡尔积产生的结果有m*n个结果,相当于循环遍历两个集合中的元素,任意组合。

java伪代码表示如下:

  1. for(Object eleA : A){
  2. for(Object eleB : B){
  3. System.out.print(eleA+","+eleB);
  4. }
  5. }

过程:拿A集合中的第1行,去匹配集合B中所有的行,然后再拿集合A中的第2行,去匹配集合B中所有的行,最后结果数量为m*n。

sql中笛卡尔积语法

  1. select 字段 from 1,表2[,表N];
  2. 或者
  3. select 字段 from 1 join 2 [join N];

示例:

  1. mysql> select * from t_team,t_employee;
  2. +----+-----------+----+---------------+---------+
  3. | id | team_name | id | emp_name | team_id |
  4. +----+-----------+----+---------------+---------+
  5. | 1 | 架构组 | 1 | 路人甲Java | 1 |
  6. | 2 | 测试组 | 1 | 路人甲Java | 1 |
  7. | 3 | java | 1 | 路人甲Java | 1 |
  8. | 4 | 前端组 | 1 | 路人甲Java | 1 |
  9. | 1 | 架构组 | 2 | 张三 | 2 |
  10. | 2 | 测试组 | 2 | 张三 | 2 |
  11. | 3 | java | 2 | 张三 | 2 |
  12. | 4 | 前端组 | 2 | 张三 | 2 |
  13. | 1 | 架构组 | 3 | 李四 | 3 |
  14. | 2 | 测试组 | 3 | 李四 | 3 |
  15. | 3 | java | 3 | 李四 | 3 |
  16. | 4 | 前端组 | 3 | 李四 | 3 |
  17. | 1 | 架构组 | 4 | 王五 | 0 |
  18. | 2 | 测试组 | 4 | 王五 | 0 |
  19. | 3 | java | 4 | 王五 | 0 |
  20. | 4 | 前端组 | 4 | 王五 | 0 |
  21. | 1 | 架构组 | 5 | 赵六 | 0 |
  22. | 2 | 测试组 | 5 | 赵六 | 0 |
  23. | 3 | java | 5 | 赵六 | 0 |
  24. | 4 | 前端组 | 5 | 赵六 | 0 |
  25. +----+-----------+----+---------------+---------+
  26. 20 rows in set (0.00 sec)

t_team表4条记录,t_employee表5条记录,笛卡尔积结果输出了20行记录。

内连接

语法:

  1. select 字段 from 1 inner join 2 on 连接条件;
  2. select 字段 from 1 join 2 on 连接条件;
  3. select 字段 from 1, 2 [where 关联条件];

内连接相当于在笛卡尔积的基础上加上了连接的条件。

当没有连接条件的时候,内连接上升为笛卡尔积。

过程用java伪代码如下:

  1. for(Object eleA : A){
  2. for(Object eleB : B){
  3. if(连接条件是否为true){
  4. System.out.print(eleA+","+eleB);
  5. }
  6. }
  7. }

示例1:有连接条件

查询员工及所属部门

  1. mysql> select t1.emp_name,t2.team_name from t_employee t1 inner join t_team t2 on t1.team_id = t2.id;
  2. +---------------+-----------+
  3. | emp_name | team_name |
  4. +---------------+-----------+
  5. | 路人甲Java | 架构组 |
  6. | 张三 | 测试组 |
  7. | 李四 | java |
  8. +---------------+-----------+
  9. 3 rows in set (0.00 sec)
  10. mysql> select t1.emp_name,t2.team_name from t_employee t1 join t_team t2 on t1.team_id = t2.id;
  11. +---------------+-----------+
  12. | emp_name | team_name |
  13. +---------------+-----------+
  14. | 路人甲Java | 架构组 |
  15. | 张三 | 测试组 |
  16. | 李四 | java |
  17. +---------------+-----------+
  18. 3 rows in set (0.00 sec)
  19. mysql> select t1.emp_name,t2.team_name from t_employee t1, t_team t2 where t1.team_id = t2.id;
  20. +---------------+-----------+
  21. | emp_name | team_name |
  22. +---------------+-----------+
  23. | 路人甲Java | 架构组 |
  24. | 张三 | 测试组 |
  25. | 李四 | java |
  26. +---------------+-----------+
  27. 3 rows in set (0.00 sec)

上面相当于获取了2个表的交集,查询出了两个表都有的数据。

示例2:无连接条件

无条件内连接,上升为笛卡尔积,如下:

  1. mysql> select t1.emp_name,t2.team_name from t_employee t1 inner join t_team t2;
  2. +---------------+-----------+
  3. | emp_name | team_name |
  4. +---------------+-----------+
  5. | 路人甲Java | 架构组 |
  6. | 路人甲Java | 测试组 |
  7. | 路人甲Java | java |
  8. | 路人甲Java | 前端组 |
  9. | 张三 | 架构组 |
  10. | 张三 | 测试组 |
  11. | 张三 | java |
  12. | 张三 | 前端组 |
  13. | 李四 | 架构组 |
  14. | 李四 | 测试组 |
  15. | 李四 | java |
  16. | 李四 | 前端组 |
  17. | 王五 | 架构组 |
  18. | 王五 | 测试组 |
  19. | 王五 | java |
  20. | 王五 | 前端组 |
  21. | 赵六 | 架构组 |
  22. | 赵六 | 测试组 |
  23. | 赵六 | java |
  24. | 赵六 | 前端组 |
  25. +---------------+-----------+
  26. 20 rows in set (0.00 sec)

示例3:组合条件进行查询

查询架构组的员工,3种写法

  1. 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 = '架构组';
  2. +---------------+-----------+
  3. | emp_name | team_name |
  4. +---------------+-----------+
  5. | 路人甲Java | 架构组 |
  6. +---------------+-----------+
  7. 1 row in set (0.00 sec)
  8. 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 = '架构组';
  9. +---------------+-----------+
  10. | emp_name | team_name |
  11. +---------------+-----------+
  12. | 路人甲Java | 架构组 |
  13. +---------------+-----------+
  14. 1 row in set (0.00 sec)
  15. 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 = '架构组';
  16. +---------------+-----------+
  17. | emp_name | team_name |
  18. +---------------+-----------+
  19. | 路人甲Java | 架构组 |
  20. +---------------+-----------+
  21. 1 row in set (0.00 sec)

上面3中方式解说。

方式1:on中使用了组合条件。

方式2:在连接的结果之后再进行过滤,相当于先获取连接的结果,然后使用where中的条件再对连接结果进行过滤。

方式3:直接在where后面进行过滤。

总结

内连接建议使用第3种语法,简洁:

  1. select 字段 from 1, 2 [where 关联条件];

外连接

外连接涉及到2个表,分为:主表和从表,要查询的信息主要来自于哪个表,谁就是主表。

外连接查询结果为主表中所有记录。如果从表中有和它匹配的,则显示匹配的值,这部分相当于内连接查询出来的结果;如果从表中没有和它匹配的,则显示null。

最终:外连接查询结果 = 内连接的结果 + 主表中有的而内连接结果中没有的记录。

外连接分为2种:

左外链接:使用left join关键字,left join左边的是主表。

右外连接:使用right join关键字,right join右边的是主表。

左连接

语法

  1. select from 主表 left join 从表 on 连接条件;

示例1:

查询所有员工信息,并显示员工所在组,如下:

  1. mysql> SELECT
  2. t1.emp_name,
  3. t2.team_name
  4. FROM
  5. t_employee t1
  6. LEFT JOIN
  7. t_team t2
  8. ON
  9. t1.team_id = t2.id;
  10. +---------------+-----------+
  11. | emp_name | team_name |
  12. +---------------+-----------+
  13. | 路人甲Java | 架构组 |
  14. | 张三 | 测试组 |
  15. | 李四 | java |
  16. | 王五 | NULL |
  17. | 赵六 | NULL |
  18. +---------------+-----------+
  19. 5 rows in set (0.00 sec)

上面查询出了所有员工,员工team_id=0的,team_name为NULL。

示例2:

查询员工姓名、组名,返回组名不为空的记录,如下:

  1. mysql> SELECT
  2. t1.emp_name,
  3. t2.team_name
  4. FROM
  5. t_employee t1
  6. LEFT JOIN
  7. t_team t2
  8. ON
  9. t1.team_id = t2.id
  10. WHERE
  11. t2.team_name IS NOT NULL;
  12. +---------------+-----------+
  13. | emp_name | team_name |
  14. +---------------+-----------+
  15. | 路人甲Java | 架构组 |
  16. | 张三 | 测试组 |
  17. | 李四 | java |
  18. +---------------+-----------+
  19. 3 rows in set (0.00 sec)

上面先使用内连接获取连接结果,然后再使用where对连接结果进行过滤。

右连接

语法

  1. select from 从表 right join 主表 on 连接条件;

示例

我们使用右连接来实现上面左连接实现的功能,如下:

  1. mysql> SELECT
  2. t2.team_name,
  3. t1.emp_name
  4. FROM
  5. t_team t2
  6. RIGHT JOIN
  7. t_employee t1
  8. ON
  9. t1.team_id = t2.id;
  10. +-----------+---------------+
  11. | team_name | emp_name |
  12. +-----------+---------------+
  13. | 架构组 | 路人甲Java |
  14. | 测试组 | 张三 |
  15. | java | 李四 |
  16. | NULL | 王五 |
  17. | NULL | 赵六 |
  18. +-----------+---------------+
  19. 5 rows in set (0.00 sec)
  20. mysql> SELECT
  21. t2.team_name,
  22. t1.emp_name
  23. FROM
  24. t_team t2
  25. RIGHT JOIN
  26. t_employee t1
  27. ON
  28. t1.team_id = t2.id
  29. WHERE
  30. t2.team_name IS NOT NULL;
  31. +-----------+---------------+
  32. | team_name | emp_name |
  33. +-----------+---------------+
  34. | 架构组 | 路人甲Java |
  35. | 测试组 | 张三 |
  36. | java | 李四 |
  37. +-----------+---------------+
  38. 3 rows in set (0.00 sec)

理解表连接原理

准备数据

  1. drop table if exists test1;
  2. create table test1(
  3. a int
  4. );
  5. drop table if exists test2;
  6. create table test2(
  7. b int
  8. );
  9. insert into test1 values (1),(2),(3);
  10. insert into test2 values (3),(4),(5);
  1. mysql> select * from test1;
  2. +------+
  3. | a |
  4. +------+
  5. | 1 |
  6. | 2 |
  7. | 3 |
  8. +------+
  9. 3 rows in set (0.00 sec)
  10. mysql> select * from test2;
  11. +------+
  12. | b |
  13. +------+
  14. | 3 |
  15. | 4 |
  16. | 5 |
  17. +------+
  18. 3 rows in set (0.00 sec)

我们来写几个连接,看看效果。

示例1:内连接

  1. mysql> select * from test1 t1,test2 t2;
  2. +------+------+
  3. | a | b |
  4. +------+------+
  5. | 1 | 3 |
  6. | 2 | 3 |
  7. | 3 | 3 |
  8. | 1 | 4 |
  9. | 2 | 4 |
  10. | 3 | 4 |
  11. | 1 | 5 |
  12. | 2 | 5 |
  13. | 3 | 5 |
  14. +------+------+
  15. 9 rows in set (0.00 sec)
  16. mysql> select * from test1 t1,test2 t2 where t1.a = t2.b;
  17. +------+------+
  18. | a | b |
  19. +------+------+
  20. | 3 | 3 |
  21. +------+------+
  22. 1 row in set (0.00 sec)

9条数据正常。

示例2:左连接

  1. mysql> select * from test1 t1 left join test2 t2 on t1.a = t2.b;
  2. +------+------+
  3. | a | b |
  4. +------+------+
  5. | 3 | 3 |
  6. | 1 | NULL |
  7. | 2 | NULL |
  8. +------+------+
  9. 3 rows in set (0.00 sec)
  10. mysql> select * from test1 t1 left join test2 t2 on t1.a>10;
  11. +------+------+
  12. | a | b |
  13. +------+------+
  14. | 1 | NULL |
  15. | 2 | NULL |
  16. | 3 | NULL |
  17. +------+------+
  18. 3 rows in set (0.00 sec)
  19. mysql> select * from test1 t1 left join test2 t2 on 1=1;
  20. +------+------+
  21. | a | b |
  22. +------+------+
  23. | 1 | 3 |
  24. | 2 | 3 |
  25. | 3 | 3 |
  26. | 1 | 4 |
  27. | 2 | 4 |
  28. | 3 | 4 |
  29. | 1 | 5 |
  30. | 2 | 5 |
  31. | 3 | 5 |
  32. +------+------+
  33. 9 rows in set (0.00 sec)

上面的左连接第一个好理解。

第2个sql连接条件t1.a>10,这个条件只关联了test1表,再看看结果,是否可以理解?不理解的继续向下看,我们用java代码来实现连接查询。

第3个sql中的连接条件1=1值为true,返回结果为笛卡尔积。

java代码实现连接查询

下面是一个简略版的实现

  1. package com.itsoku.sql;
  2. import org.junit.Test;
  3. import java.util.ArrayList;
  4. import java.util.Arrays;
  5. import java.util.List;
  6. import java.util.Objects;
  7. import java.util.concurrent.CopyOnWriteArrayList;
  8. import java.util.stream.Collectors;
  9. public class Test1 {
  10. public static class Table1 {
  11. int a;
  12. public int getA() {
  13. return a;
  14. }
  15. public void setA(int a) {
  16. this.a = a;
  17. }
  18. public Table1(int a) {
  19. this.a = a;
  20. }
  21. @Override
  22. public String toString() {
  23. return "Table1{" +
  24. "a=" + a +
  25. '}';
  26. }
  27. public static Table1 build(int a) {
  28. return new Table1(a);
  29. }
  30. }
  31. public static class Table2 {
  32. int b;
  33. public int getB() {
  34. return b;
  35. }
  36. public void setB(int b) {
  37. this.b = b;
  38. }
  39. public Table2(int b) {
  40. this.b = b;
  41. }
  42. public static Table2 build(int b) {
  43. return new Table2(b);
  44. }
  45. @Override
  46. public String toString() {
  47. return "Table2{" +
  48. "b=" + b +
  49. '}';
  50. }
  51. }
  52. public static class Record<R1, R2> {
  53. R1 r1;
  54. R2 r2;
  55. public R1 getR1() {
  56. return r1;
  57. }
  58. public void setR1(R1 r1) {
  59. this.r1 = r1;
  60. }
  61. public R2 getR2() {
  62. return r2;
  63. }
  64. public void setR2(R2 r2) {
  65. this.r2 = r2;
  66. }
  67. public Record(R1 r1, R2 r2) {
  68. this.r1 = r1;
  69. this.r2 = r2;
  70. }
  71. @Override
  72. public String toString() {
  73. return "Record{" +
  74. "r1=" + r1 +
  75. ", r2=" + r2 +
  76. '}';
  77. }
  78. public static <R1, R2> Record<R1, R2> build(R1 r1, R2 r2) {
  79. return new Record(r1, r2);
  80. }
  81. }
  82. public static enum JoinType {
  83. innerJoin, leftJoin
  84. }
  85. public static interface Filter<R1, R2> {
  86. boolean accept(R1 r1, R2 r2);
  87. }
  88. 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) {
  89. if (Objects.isNull(table1) || Objects.isNull(table2) || joinType == null) {
  90. return new ArrayList<>();
  91. }
  92. List<Record<R1, R2>> result = new CopyOnWriteArrayList<>();
  93. for (R1 r1 : table1) {
  94. List<Record<R1, R2>> onceJoinResult = joinOn(r1, table2, onFilter);
  95. result.addAll(onceJoinResult);
  96. }
  97. if (joinType == JoinType.leftJoin) {
  98. List<R1> r1Record = result.stream().map(Record::getR1).collect(Collectors.toList());
  99. List<Record<R1, R2>> leftAppendList = new ArrayList<>();
  100. for (R1 r1 : table1) {
  101. if (!r1Record.contains(r1)) {
  102. leftAppendList.add(Record.build(r1, null));
  103. }
  104. }
  105. result.addAll(leftAppendList);
  106. }
  107. if (Objects.nonNull(whereFilter)) {
  108. for (Record<R1, R2> record : result) {
  109. if (!whereFilter.accept(record.r1, record.r2)) {
  110. result.remove(record);
  111. }
  112. }
  113. }
  114. return result;
  115. }
  116. public static <R1, R2> List<Record<R1, R2>> joinOn(R1 r1, List<R2> table2, Filter<R1, R2> onFilter) {
  117. List<Record<R1, R2>> result = new ArrayList<>();
  118. for (R2 r2 : table2) {
  119. if (Objects.nonNull(onFilter) ? onFilter.accept(r1, r2) : true) {
  120. result.add(Record.build(r1, r2));
  121. }
  122. }
  123. return result;
  124. }
  125. @Test
  126. public void innerJoin() {
  127. List<Table1> table1 = Arrays.asList(Table1.build(1), Table1.build(2), Table1.build(3));
  128. List<Table2> table2 = Arrays.asList(Table2.build(3), Table2.build(4), Table2.build(5));
  129. join(table1, table2, JoinType.innerJoin, null, null).forEach(System.out::println);
  130. System.out.println("-----------------");
  131. join(table1, table2, JoinType.innerJoin, (r1, r2) -> r1.a == r2.b, null).forEach(System.out::println);
  132. }
  133. @Test
  134. public void leftJoin() {
  135. List<Table1> table1 = Arrays.asList(Table1.build(1), Table1.build(2), Table1.build(3));
  136. List<Table2> table2 = Arrays.asList(Table2.build(3), Table2.build(4), Table2.build(5));
  137. join(table1, table2, JoinType.leftJoin, (r1, r2) -> r1.a == r2.b, null).forEach(System.out::println);
  138. System.out.println("-----------------");
  139. join(table1, table2, JoinType.leftJoin, (r1, r2) -> r1.a > 10, null).forEach(System.out::println);
  140. }
  141. }

代码中的innerJoin()方法模拟了下面的sql:

  1. mysql> select * from test1 t1,test2 t2;
  2. +------+------+
  3. | a | b |
  4. +------+------+
  5. | 1 | 3 |
  6. | 2 | 3 |
  7. | 3 | 3 |
  8. | 1 | 4 |
  9. | 2 | 4 |
  10. | 3 | 4 |
  11. | 1 | 5 |
  12. | 2 | 5 |
  13. | 3 | 5 |
  14. +------+------+
  15. 9 rows in set (0.00 sec)
  16. mysql> select * from test1 t1,test2 t2 where t1.a = t2.b;
  17. +------+------+
  18. | a | b |
  19. +------+------+
  20. | 3 | 3 |
  21. +------+------+
  22. 1 row in set (0.00 sec)

运行一下innerJoin()输出如下:

  1. Record{r1=Table1{a=1}, r2=Table2{b=3}}
  2. Record{r1=Table1{a=1}, r2=Table2{b=4}}
  3. Record{r1=Table1{a=1}, r2=Table2{b=5}}
  4. Record{r1=Table1{a=2}, r2=Table2{b=3}}
  5. Record{r1=Table1{a=2}, r2=Table2{b=4}}
  6. Record{r1=Table1{a=2}, r2=Table2{b=5}}
  7. Record{r1=Table1{a=3}, r2=Table2{b=3}}
  8. Record{r1=Table1{a=3}, r2=Table2{b=4}}
  9. Record{r1=Table1{a=3}, r2=Table2{b=5}}
  10. -----------------
  11. Record{r1=Table1{a=3}, r2=Table2{b=3}}

对比一下sql和java的结果,输出的结果条数、数据基本上一致,唯一不同的是顺序上面不一样,顺序为何不一致,稍微介绍

代码中的leftJoin()方法模拟了下面的sql:

  1. mysql> select * from test1 t1 left join test2 t2 on t1.a = t2.b;
  2. +------+------+
  3. | a | b |
  4. +------+------+
  5. | 3 | 3 |
  6. | 1 | NULL |
  7. | 2 | NULL |
  8. +------+------+
  9. 3 rows in set (0.00 sec)
  10. mysql> select * from test1 t1 left join test2 t2 on t1.a>10;
  11. +------+------+
  12. | a | b |
  13. +------+------+
  14. | 1 | NULL |
  15. | 2 | NULL |
  16. | 3 | NULL |
  17. +------+------+
  18. 3 rows in set (0.00 sec)

运行leftJoin(),结果如下:

  1. Record{r1=Table1{a=3}, r2=Table2{b=3}}
  2. Record{r1=Table1{a=1}, r2=null}
  3. Record{r1=Table1{a=2}, r2=null}
  4. -----------------
  5. Record{r1=Table1{a=1}, r2=null}
  6. Record{r1=Table1{a=2}, r2=null}
  7. 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代码改进版本

  1. package com.itsoku.sql;
  2. import org.junit.Test;
  3. import java.util.ArrayList;
  4. import java.util.Arrays;
  5. import java.util.List;
  6. import java.util.Objects;
  7. import java.util.concurrent.CopyOnWriteArrayList;
  8. import java.util.stream.Collectors;
  9. import com.itsoku.sql.Test1.*;
  10. public class Test2 {
  11. public static int joinBufferSize = 10000;
  12. public static List<?> joinBufferList = new ArrayList<>();
  13. 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) {
  14. if (Objects.isNull(table1) || Objects.isNull(table2) || joinType == null) {
  15. return new ArrayList<>();
  16. }
  17. List<Test1.Record<R1, R2>> result = new CopyOnWriteArrayList<>();
  18. int table1Size = table1.size();
  19. int fromIndex = 0, toIndex = joinBufferSize;
  20. toIndex = Integer.min(table1Size, toIndex);
  21. while (fromIndex < table1Size && toIndex <= table1Size) {
  22. joinBufferList = table1.subList(fromIndex, toIndex);
  23. fromIndex = toIndex;
  24. toIndex += joinBufferSize;
  25. toIndex = Integer.min(table1Size, toIndex);
  26. List<Record<R1, R2>> blockNestedLoopResult = blockNestedLoop((List<R1>) joinBufferList, table2, onFilter);
  27. result.addAll(blockNestedLoopResult);
  28. }
  29. if (joinType == JoinType.leftJoin) {
  30. List<R1> r1Record = result.stream().map(Record::getR1).collect(Collectors.toList());
  31. List<Record<R1, R2>> leftAppendList = new ArrayList<>();
  32. for (R1 r1 : table1) {
  33. if (!r1Record.contains(r1)) {
  34. leftAppendList.add(Record.build(r1, null));
  35. }
  36. }
  37. result.addAll(leftAppendList);
  38. }
  39. if (Objects.nonNull(whereFilter)) {
  40. for (Record<R1, R2> record : result) {
  41. if (!whereFilter.accept(record.r1, record.r2)) {
  42. result.remove(record);
  43. }
  44. }
  45. }
  46. return result;
  47. }
  48. public static <R1, R2> List<Record<R1, R2>> blockNestedLoop(List<R1> joinBufferList, List<R2> table2, Filter<R1, R2> onFilter) {
  49. List<Record<R1, R2>> result = new ArrayList<>();
  50. for (R2 r2 : table2) {
  51. for (R1 r1 : joinBufferList) {
  52. if (Objects.nonNull(onFilter) ? onFilter.accept(r1, r2) : true) {
  53. result.add(Record.build(r1, r2));
  54. }
  55. }
  56. }
  57. return result;
  58. }
  59. @Test
  60. public void innerJoin() {
  61. List<Table1> table1 = Arrays.asList(Table1.build(1), Table1.build(2), Table1.build(3));
  62. List<Table2> table2 = Arrays.asList(Table2.build(3), Table2.build(4), Table2.build(5));
  63. join(table1, table2, JoinType.innerJoin, null, null).forEach(System.out::println);
  64. System.out.println("-----------------");
  65. join(table1, table2, JoinType.innerJoin, (r1, r2) -> r1.a == r2.b, null).forEach(System.out::println);
  66. }
  67. @Test
  68. public void leftJoin() {
  69. List<Table1> table1 = Arrays.asList(Table1.build(1), Table1.build(2), Table1.build(3));
  70. List<Table2> table2 = Arrays.asList(Table2.build(3), Table2.build(4), Table2.build(5));
  71. join(table1, table2, JoinType.leftJoin, (r1, r2) -> r1.a == r2.b, null).forEach(System.out::println);
  72. System.out.println("-----------------");
  73. join(table1, table2, JoinType.leftJoin, (r1, r2) -> r1.a > 10, null).forEach(System.out::println);
  74. }
  75. }

执行innerJoin(),输出:

  1. Record{r1=Table1{a=1}, r2=Table2{b=3}}
  2. Record{r1=Table1{a=2}, r2=Table2{b=3}}
  3. Record{r1=Table1{a=3}, r2=Table2{b=3}}
  4. Record{r1=Table1{a=1}, r2=Table2{b=4}}
  5. Record{r1=Table1{a=2}, r2=Table2{b=4}}
  6. Record{r1=Table1{a=3}, r2=Table2{b=4}}
  7. Record{r1=Table1{a=1}, r2=Table2{b=5}}
  8. Record{r1=Table1{a=2}, r2=Table2{b=5}}
  9. Record{r1=Table1{a=3}, r2=Table2{b=5}}
  10. -----------------
  11. Record{r1=Table1{a=3}, r2=Table2{b=3}}

执行leftJoin(),输出:

  1. Record{r1=Table1{a=3}, r2=Table2{b=3}}
  2. Record{r1=Table1{a=1}, r2=null}
  3. Record{r1=Table1{a=2}, r2=null}
  4. -----------------
  5. Record{r1=Table1{a=1}, r2=null}
  6. Record{r1=Table1{a=2}, r2=null}
  7. Record{r1=Table1{a=3}, r2=null}

结果和sql的结果完全一致。

扩展

表连接中还可以使用前面学过的group byhavingorder bylimit

这些关键字相当于在表连接的结果上在进行操作,大家下去可以练习一下,加深理解。

最新资料

更多内容请访问:IT源点

相关文章推荐

全部评论: 0

    我有话说: