天天看點

MySQL聯表查詢【詳解】MySQL連接配接查詢及原理

文章目錄

  • MySQL連接配接查詢及原理
    • 1、本文内容
    • 2、準備資料
    • 3、笛卡爾積
      • sql中笛卡爾積文法
    • 4、内連接配接
      • 示例1:有連接配接條件
      • 示例2:無連接配接條件
      • 示例3:組合條件進行查詢
      • 總結
    • 5、外連接配接
      • 左連接配接
      • 右連接配接
    • 6、了解表連接配接原理
      • 示例1:内連接配接
      • 示例2:左連接配接
    • 7、java代碼實作連接配接查詢
    • 8、java代碼改進版本
    • 8、擴充

MySQL連接配接查詢及原理

1、本文内容

笛卡爾積

内連接配接

外連接配接

左連接配接

右連接配接

表連接配接的原理

使用java實作連接配接查詢,加深了解

2、準備資料

2張表:

t_team:組表。

t_employee:員工表,内部有個team_id引用組表的id。

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條記錄,如下:

mysql> select * from t_team;
+----+-----------+
| id | team_name |
+----+-----------+
|  1 | 架構組   |
|  2 | 測試組   |
|  3 | java組   |
|  4 | 前端組   |
+----+-----------+
4 rows in set (0.00 sec)
           

t_employee表5條記錄,如下:

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)
           

3、笛卡爾積

介紹連接配接查詢之前,我們需要先了解一下笛卡爾積。

笛卡爾積簡單點了解:有兩個集合A和B,笛卡爾積表示A集合中的元素和B集合中的元素任意互相關聯産生的所有可能的結果。

假如A中有m個元素,B中有n個元素,A、B笛卡爾積産生的結果有m*n個結果,相當于循環周遊兩個集合中的元素,任意組合。

java僞代碼表示如下:

for(Object eleA : A){
	for(Object eleB : B){
		System.out.print(eleA+","+eleB);
	}
}
           

過程:拿A集合中的第1行,去比對集合B中所有的行,然後再拿集合A中的第2行,去比對集合B中所有的行,最後結果數量為m*n。

sql中笛卡爾積文法

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行記錄。

4、内連接配接

文法:

select 字段 from 表1 inner join 表2 on 連接配接條件;
或
select 字段 from 表1 join 表2 on 連接配接條件;
或
select 字段 from 表1, 表2 [where 關聯條件];
           

内連接配接相當于在笛卡爾積的基礎上加上了連接配接的條件。

當沒有連接配接條件的時候,内連接配接上升為笛卡爾積。

過程用java僞代碼如下:

for(Object eleA : A){
	for(Object eleB : B){
		if(連接配接條件是否為true){
			System.out.print(eleA+","+eleB);
		}
	}
}
           

示例1:有連接配接條件

查詢員工及所屬部門

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:無連接配接條件

無條件内連接配接,上升為笛卡爾積,如下:

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種寫法

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中使用了組合條件。(select 字段 from 表1 inner join 表2 on 連接配接條件 and 條件)
  • 方式2:在連接配接的結果之後再進行過濾,相當于先擷取連接配接的結果,然後使用where中的條件再對連接配接結果進行過濾。(select 字段 from 表1 inner join 表2 on 連接配接條件 where 關聯條件)
  • 方式3:直接在where後面進行過濾。(select 字段 from 表1,表2 where 關聯條件 and 條件)

總結

内連接配接建議使用第3種文法,簡潔:(但用的多的還是: 表1inner join表2 on 連接配接條件)

5、外連接配接

外連接配接涉及到2個表,分為:主表和從表,要查詢的資訊主要來自于哪個表,誰就是主表。

外連接配接查詢結果為主表中所有記錄。如果從表中有和它比對的,則顯示比對的值,這部分相當于内連接配接查詢出來的結果;如果從表中沒有和它比對的,則顯示null。

最終:外連接配接查詢結果 = 内連接配接的結果 + 主表中有的而内連接配接結果中沒有的記錄。

外連接配接分為2種:

  • 左外連結:使用left join關鍵字,left join左邊的是主表。
  • 右外連接配接:使用right join關鍵字,right join右邊的是主表。

左連接配接

文法:

示例1:

查詢所有員工資訊,并顯示員工所在組,如下:

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:

查詢員工姓名、組名,傳回組名不為空的記錄,如下:

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對連接配接結果進行過濾。

右連接配接

文法:

示例:

我們使用右連接配接來實作上面左連接配接實作的功能,如下:

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)
           

6、了解表連接配接原理

準備資料:

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);
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:内連接配接

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:左連接配接

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,傳回結果為笛卡爾積。

7、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:

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()輸出如下:

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:

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(),結果如下:

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的過程。

8、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(),輸出:

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的結果完全一緻。

8、擴充

表連接配接中還可以使用前面學過的group by、having、order by、limit。

這些關鍵字相當于在表連接配接的結果上在進行操作,可以練習一下。

繼續閱讀