Mysql高手系列 – 第11篇:深入了解連接查詢及原理
- 2019 年 10 月 3 日
- 筆記
這是Mysql系列第11篇。
環境:mysql5.7.25,cmd命令中進行演示。
當我們查詢的數據來源於多張表的時候,我們需要用到連接查詢,連接查詢使用率非常高,希望大家都務必掌握。
本文內容
- 笛卡爾積
- 內連接
- 外連接
- 左連接
- 右連接
- 表連接的原理
- 使用java實現連接查詢,加深理解
準備數據
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)
笛卡爾積
介紹連接查詢之前,我們需要先了解一下笛卡爾積。
笛卡爾積簡單點理解:有兩個集合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行記錄。
內連接
語法:
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中使用了組合條件。
方式2:在連接的結果之後再進行過濾,相當於先獲取連接的結果,然後使用where中的條件再對連接結果進行過濾。
方式3:直接在where後面進行過濾。
總結
內連接建議使用第3種語法,簡潔:
select 字段 from 表1, 表2 [where 關聯條件];
外連接
外連接涉及到2個表,分為:主表和從表,要查詢的信息主要來自於哪個表,誰就是主表。
外連接查詢結果為主表中所有記錄。如果從表中有和它匹配的,則顯示匹配的值,這部分相當於內連接查詢出來的結果;如果從表中沒有和它匹配的,則顯示null。
最終:外連接查詢結果 = 內連接的結果 + 主表中有的而內連接結果中沒有的記錄。
外連接分為2種:
左外鏈接:使用left join關鍵字,left join左邊的是主表。
右外連接:使用right join關鍵字,right join右邊的是主表。
左連接
語法
select 列 from 主表 left join 從表 on 連接條件;
示例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對連接結果進行過濾。
右連接
語法
select 列 from 從表 right join 主表 on 連接條件;
示例
我們使用右連接來實現上面左連接實現的功能,如下:
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)
理解表連接原理
準備數據
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,返回結果為笛卡爾積。
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的過程。
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的結果完全一致。
擴展
表連接中還可以使用前面學過的group by
、having
、order by
、limit
。
這些關鍵字相當於在表連接的結果上在進行操作,大家下去可以練習一下,加深理解。
Mysql系列目錄
- 第1篇:mysql基礎知識
- 第2篇:詳解mysql數據類型(重點)
- 第3篇:管理員必備技能(必須掌握)
- 第4篇:DDL常見操作
- 第5篇:DML操作匯總(insert,update,delete)
- 第6篇:select查詢基礎篇
- 第7篇:玩轉select條件查詢,避免采坑
- 第8篇:詳解排序和分頁(order by & limit)
- 第9篇:分組查詢詳解(group by & having)
- 第10篇:常用的幾十個函數詳解
mysql系列大概有20多篇,喜歡的請關注一下,歡迎大家加我微信itsoku或者留言交流mysql相關技術!