1. 程式人生 > 實用技巧 >mysql多表查詢

mysql多表查詢

一 多表連線查詢

#重點:外連線語法

select 欄位列表
	from 表1 inner|left|right join 表2
	on 表1.欄位 = 表2.欄位;



1 交差連線:不適用任何匹配條件,生成笛卡爾積。

mysql> select * from emp,dep;
+----+------------+--------+------+--------+------+--------+
| id | name       | sex    | age  | dep_id | id   | name   |
+----+------------+--------+------+--------+------+--------+
|  1 | egon       | male   |   18 |    200 |  200 | jishu  |
|  1 | egon       | male   |   18 |    200 |  201 | ziyuan |
|  1 | egon       | male   |   18 |    200 |  202 | sell   |
|  1 | egon       | male   |   18 |    200 |  203 | yunyin |
|  2 | alex       | female |   48 |    201 |  200 | jishu  |
|  2 | alex       | female |   48 |    201 |  201 | ziyuan |
|  2 | alex       | female |   48 |    201 |  202 | sell   |
|  2 | alex       | female |   48 |    201 |  203 | yunyin |
|  3 | wupeiqi    | male   |   38 |    201 |  200 | jishu  |
|  3 | wupeiqi    | male   |   38 |    201 |  201 | ziyuan |
|  3 | wupeiqi    | male   |   38 |    201 |  202 | sell   |
|  3 | wupeiqi    | male   |   38 |    201 |  203 | yunyin |
|  4 | yuanhao    | female |   28 |    202 |  200 | jishu  |
|  4 | yuanhao    | female |   28 |    202 |  201 | ziyuan |
|  4 | yuanhao    | female |   28 |    202 |  202 | sell   |
|  4 | yuanhao    | female |   28 |    202 |  203 | yunyin |
|  5 | liwenzhou  | male   |   18 |    200 |  200 | jishu  |
|  5 | liwenzhou  | male   |   18 |    200 |  201 | ziyuan |
|  5 | liwenzhou  | male   |   18 |    200 |  202 | sell   |
|  5 | liwenzhou  | male   |   18 |    200 |  203 | yunyin |
|  6 | jingliyang | female |   18 |    204 |  200 | jishu  |
|  6 | jingliyang | female |   18 |    204 |  201 | ziyuan |
|  6 | jingliyang | female |   18 |    204 |  202 | sell   |
|  6 | jingliyang | female |   18 |    204 |  203 | yunyin |
+----+------------+--------+------+--------+------+--------+
24 rows in set (0.00 sec)

如圖沒有約束條件,則預設以多對多的形式出現,即左右兩邊重複對應。

2 內連線:只連線匹配的行

#找到兩張表共有的部分,相當於利用條件從笛卡爾積結果中篩選出了正確結果
#dep沒有204這個部門,emp對應的部門沒有203yunyin這個部門(這個部門沒有員工)
#所以最後的虛擬表中都沒有相關資訊
mysql> select emp.* , dep.name from emp inner join dep on emp.dep_id=dep.id;
+----+-----------+--------+------+--------+--------+
| id | name      | sex    | age  | dep_id | name   |
+----+-----------+--------+------+--------+--------+
|  1 | egon      | male   |   18 |    200 | jishu  |
|  2 | alex      | female |   48 |    201 | ziyuan |
|  3 | wupeiqi   | male   |   38 |    201 | ziyuan |
|  4 | yuanhao   | female |   28 |    202 | sell   |
|  5 | liwenzhou | male   |   18 |    200 | jishu  |
+----+-----------+--------+------+--------+--------+

3 外連線之左連線:優先顯示左表全部記錄

#以左邊為準,即找出所有員工資訊,當然包括沒有部門的員工
#本質就是:在內連線的接觸上增加了左邊有右邊沒有的結果
mysql> select emp.* , dep.name from emp left join dep on emp.dep_id=dep.id;
+----+------------+--------+------+--------+--------+
| id | name       | sex    | age  | dep_id | name   |
+----+------------+--------+------+--------+--------+
|  1 | egon       | male   |   18 |    200 | jishu  |
|  5 | liwenzhou  | male   |   18 |    200 | jishu  |
|  2 | alex       | female |   48 |    201 | ziyuan |
|  3 | wupeiqi    | male   |   38 |    201 | ziyuan |
|  4 | yuanhao    | female |   28 |    202 | sell   |
|  6 | jingliyang | female |   18 |    204 | NULL   |
+----+------------+--------+------+--------+--------+

4 外連線之右連線:優先顯示右表全部記錄

#以右表為準,即找出所有部門的資訊,包括沒有員工的部門
#本質就是:在內連線的基礎上增加右邊有左邊沒有的結果
+------+-----------+--------+------+--------+--------+
| id   | name      | sex    | age  | dep_id | name   |
+------+-----------+--------+------+--------+--------+
|    1 | egon      | male   |   18 |    200 | jishu  |
|    2 | alex      | female |   48 |    201 | ziyuan |
|    3 | wupeiqi   | male   |   38 |    201 | ziyuan |
|    4 | yuanhao   | female |   28 |    202 | sell   |
|    5 | liwenzhou | male   |   18 |    200 | jishu  |
| NULL | NULL      | NULL   | NULL |   NULL | yunyin |
+------+-----------+--------+------+--------+--------+

5 全外連線:顯示左右兩個表全部記錄

全外連線:在內連線的基礎上增加左邊有右邊沒有的和右邊有做沒有沒有的結果
#注意:mysql不支援全外連線 full join
#強調:mysql可以使用此種方式間接實現全外連線
mysql> select emp.* , dep.name from emp left join dep on emp.dep_id=dep.id
    -> union
    -> select emp.* , dep.name from emp right join dep on emp.dep_id=dep.id;
+------+------------+--------+------+--------+--------+
| id   | name       | sex    | age  | dep_id | name   |
+------+------------+--------+------+--------+--------+
|    1 | egon       | male   |   18 |    200 | jishu  |
|    5 | liwenzhou  | male   |   18 |    200 | jishu  |
|    2 | alex       | female |   48 |    201 | ziyuan |
|    3 | wupeiqi    | male   |   38 |    201 | ziyuan |
|    4 | yuanhao    | female |   28 |    202 | sell   |
|    6 | jingliyang | female |   18 |    204 | NULL   |
| NULL | NULL       | NULL   | NULL |   NULL | yunyin |
+------+------------+--------+------+--------+--------+

#注意 union與union all的區別:union會去掉相同的記錄

二 符合條件的連線查詢

#例項1:以內連線的方式查詢emp和dep表,並且emp表中的age欄位必須大於25,即找出年齡大於25以及員工所在部門。
#以員工為主
select emp.name,dep.name from emp inner join dep on emp.dep_id=dep.id where emp.age>=25;

#示例2:以內連線的方式查詢emp和dep表,並且以age欄位的升序方式顯示
select emp.*,dep.name from emp inner join dep on emp.dep_id=dep.id order by age asc;

三 子查詢

(1):子查詢是將一個查詢語句巢狀在另一個查詢語句中。

(2):內層查詢語句的查詢結果,可以為外層查詢語句提供查詢條件

(3):子查詢中可以包含:in、not in、any、all、exists和 not exists等關鍵字

(4):還可以包含比較運算子:=、!=、>、<等

1 帶in關鍵字的子查詢

#查詢平均年齡在25歲以上的部門名
select id,name from dep 
where id in 
(select dep_id from emp group by dep_id having avg(age)>25);

#檢視技術部員工姓名
select name from emp where
dep_id in
(select id from dep where name='jishu');

#檢視員工數小於等於一人的部門名
select id,name from dep
where id in 
(select dep_id from emp group by dep_id having count(id)<=1);

注意:

not in 無法處理null的值,即子查詢中如果存在null的值,not in將無法處理,如下

mysql> select * from emp;
+----+------------+--------+------+--------+
| id | name | sex | age | dep_id |
+----+------------+--------+------+--------+
| 1 | egon | male | 18 | 200 |
| 2 | alex | female | 48 | 201 |
| 3 | wupeiqi | male | 38 | 201 |
| 4 | yuanhao | female | 28 | 202 |
| 5 | liwenzhou | male | 18 | 200 |
| 6 | jingliyang | female | 18 | 204 |
| 7 | xxx | male | 19 | NULL |
+----+------------+--------+------+--------+
7 rows in set (0.00 sec)

mysql> select * from dep;
+------+--------------+
| id | name |
+------+--------------+
| 200 | 技術 |
| 201 | 人力資源 |
| 202 | 銷售 |
| 203 | 運營 |
+------+--------------+
4 rows in set (0.00 sec)

# 子查詢中存在null
mysql> select * from dep where id not in (select distinct dep_id from emp);
Empty set (0.00 sec)

# 解決方案如下
mysql> select * from dep where id not in (select distinct dep_id from emp where dep_id is not null);
+------+--------+
| id | name |
+------+--------+
| 203 | 運營 |
+------+--------+
1 row in set (0.00 sec)

mysql>

2 帶比較運算子的子查詢

#比較運算子:=、!=、>、>=、<、<=、<>
#查詢大於所有人平均年齡的員工名與年齡
select name ,age from emp where age>(select avg(age) from emp);
+---------+------+
| name    | age  |
+---------+------+
| alex    |   48 |
| wupeiqi |   38 |
+---------+------+

#查詢大於部門內平均年齡的員工名、年齡
select t1.name,t1.age,t1.dep_id from emp as t1 
inner join 
(select dep_id,avg(age) as avg_age from emp group by dep_id) as t2
on t1.dep_id = t2.dep_id
where t1.age > t2.avg_age; 
+------+------+--------+
| name | age  | dep_id |
+------+------+--------+
| alex |   48 |    201 |
+------+------+--------+

select name,age,dep_id from emp group by dep_id having age>avg(age);

3 帶ANY關鍵字的子查詢

# any 和 in運算子不同之處1
any 必須和其他的比較運算子共同使用,而且any必須將比較運算子放在 any 關鍵字之前,所比較的值需要匹配子查詢的任意一個值。
例如:使用 in 和 使用 any 運算子得到的結果是一致的
SELECT * FROM emp WHERE salary = ANY
(SELECT MAX(salary) FROM emp GROUP BY post);

SELECT * FROM emp WHERE salary IN
(SELECT MAX(salary) FROM emp GROUP BY post);

結論:也就是說“=any”等價於 in 運算子,而"<>any"則等價於 not in 運算子

# ANY和 IN 運算子不同之處2
ANY 運算子不能與固定的集合相匹配,比如下面的 SQL 語句是錯誤的
SELECT * FROM emp WHERE salary < ANY(10000,12000,20000);

4 帶ALL關鍵字的子查詢

# all 同 any 類似,只不過 all 表示的是所有, any 表示任一
查詢出那些薪資比所有部門的平均薪資都高的員工(比all語句中最高薪資要高即可)
SELECT name FROM emp WHERE salary> ALL
(SELECT AVG(salary) FROM emp GROUP BY post);


同樣將all換上any,則表示比任何一個員工平均薪資高即可滿足條件(比any語句中的最低薪資高即可)
SELECT name FROM emp WHERE salary> ANY
(SELECT AVG(salary) FROM emp GROUP BY post);

查詢出那些薪資比所有部門的平均薪資都低的員工(比all語句中最低薪資要低即可)
SELECT name FROM emp WHERE salary< ALL
(SELECT AVG(salary) FROM emp GROUP BY post);

同樣將all換上any,則表示比任何一個員工平均薪資低即可滿足條件(比any語句中的最高薪資低即可)
SELECT name FROM emp WHERE salary< ANY
(SELECT AVG(salary) FROM emp GROUP BY post);


5 帶EXISTS關鍵字的子查詢

EXISTS關字鍵字表示存在。在使用EXISTS關鍵字時,內層查詢語句不返回查詢的記錄。
而是返回一個真假值。True或False
當返回True時,外層查詢語句將進行查詢;當返回值為False時,外層查詢語句不進行查詢

#department表中存在dept_id=203,Ture
mysql> select * from employee
    ->     where exists
    ->         (select id from department where id=200);
+----+------------+--------+------+--------+
| id | name       | sex    | age  | dep_id |
+----+------------+--------+------+--------+
|  1 | egon       | male   |   18 |    200 |
|  2 | alex       | female |   48 |    201 |
|  3 | wupeiqi    | male   |   38 |    201 |
|  4 | yuanhao    | female |   28 |    202 |
|  5 | liwenzhou  | male   |   18 |    200 |
|  6 | jingliyang | female |   18 |    204 |
+----+------------+--------+------+--------+

#department表中存在dept_id=205,False
mysql> select * from employee
    ->     where exists
    ->         (select id from department where id=204);
Empty set (0.00 sec)

5.1 in與exists

當 in 和 exists 在查詢效率上比較時,in 查詢的效率快於 exists 的查詢效率

#exists
exists後面一般都是子查詢,後面的子查詢被稱作相關子查詢(即與主語句相關),當子查詢返回行數時,exists條件返回true,否則返回false,exists是不返回列表的值的,exists只在乎括號內的資料能不能查找出來,是否存在這樣的記錄

#例
查詢出那些班級裡有學生的班級(只要學生列表中有一個學生存在該班級的id即可)
SELECT * FROM class where EXISTS
(SELECT * FROM student where class.cid=class_id);

#exists的執行原理:
1.依次執行外部查詢:即select * from class
2.然後為外部查詢返回每一行分別執行一次子查詢
通俗的講,在外部class表中拿一個欄位下的記錄class.cid到內部student表中與class_id下的每一個記錄比對,比對成功則返回結果true,對比失敗返回false(就是class.cid配對了student表class_id下的所有的記錄都失敗了)
3.子查詢如果返回行,則exists條件成立,條件成立則輸出外部查詢取出的那條記錄


# in
in後跟的都是子查詢,in()後面的子查詢 是返回結果集的
# 例
查詢和所有女生年齡相同的男生
select * from student where sex='男' and age in(select age from stu where sex='女')

# in的執行原理為:
in()的執行次序和exists()不一樣,in()的子查詢會先產生結果集,
然後主查詢再去結果集裡去找符合要求的欄位列表去.符合要求的輸出,反之則不輸出.

相比之下exists每一次欄位的訪問都要執行一遍exists語句中的指令,
而in則是先執行完in裡面的指令集併產生硬性的結果,然後再進行主查詢的訪問。

erists:
主查詢執行呼叫一個欄位下的記錄去訪問======>執行exists中的語句相應欄位下的記錄與主查詢的記錄進行配比直到成功或是指令執行完畢才結束=========>返回結果給子查詢,判斷該欄位TRUE或FALSE(即該記錄會不會出現在表中)

in:
先執行in中的語句產生一個結果集======>執行主查詢,主查詢呼叫與結果集相對應欄位下的一個記錄去與結果集進行配比=========>成功則返回,那該記錄成功寫入欄位下,失敗則相反。


5.2 not in 與 not exists

not exists 查詢的效率遠遠高於not in查詢效率

#not in
為了證明not in成立,即找不動,需要一條一條的查詢表,符合要求才返回子查詢的結果,不符合的就繼續查詢下一條記錄,直到把表中的記錄查詢完,只能查詢全部記錄才能證明,並沒有用到索引。


#not exists
如果主查詢表中的記錄少,子查詢中的記錄多,並有索引。
例如:查詢那些班級中沒有學生的班級
SELECT * FROM class where not EXISTS
(SELECT * FROM student where class.cid=class_id);

not exists的執行順序是:
在表中查詢,是根據索引查詢的,如果存在就返回true,如果不存在就返回false,不會每條記錄都去查詢。
只要有一個匹對失敗,則條件成立。

前戲

create database dbtest;

use dbtset;

create table student(
    id int primary key auto_increment,
    name varchar(16)
);

create table course(
    id int primary key auto_increment,
    name varchar(16),
    comment varchar(20)
);

create table student2course(
    id int primary key auto_increment,
    sid int,
    cid int,
    foreign key(sid) references student(id),
    foreign key(cid) references course(id)
);


insert into student(name) values
("egon"),
("lili"),
("jack"),
("tom");

insert into course(name,comment) values
("資料庫","資料倉庫"),
("數學","根本學不會"),
("英語","鳥語花香");


insert into student2course(sid,cid) values
(1,1),
(1,2),
(1,3),
(2,1),
(2,2),
(3,2);

示例

# 1、查詢選修了所有課程的學生id、name:(即該學生根本就不存在一門他沒有選的課程。)

#不存在一門都沒有選修的即是選修了所有門
SELECT * FROM student WHERE  not EXISTS
(SELECT * FROM course WHERE NOT EXISTS  #結果為一門都沒有選修到返回上層並被取反即是都選修到
(SELECT * FROM student2course WHERE sid=student.id and cid=course.id)); 
#底層結果為真,上層有一行為假,上層每一行為假,則上上層有一行為真

# 2、查詢沒有選擇所有課程的學生,即沒有全選的學生。(存在這樣的一個學生,他至少有一門課沒有選)
SELECT * FROM student WHERE   EXISTS
(SELECT * FROM course WHERE NOT EXISTS  
(SELECT * FROM student2course WHERE sid=student.id and cid=course.id)); 

# 3、查詢一門課也沒有選的學生。(不存這樣的一個學生,他至少選修一門課程)
	SELECT * FROM student WHERE   not EXISTS
	(SELECT * FROM course WHERE  EXISTS  
	(SELECT * FROM student2course WHERE sid=student.id and cid=course.id)); 


# 4、查詢至少選修了一門課程的學生。
SELECT * FROM student WHERE   EXISTS
(SELECT * FROM course WHERE  EXISTS  
(SELECT * FROM student2course WHERE sid=student.id and cid=course.id)); 


#注意:
exists(): 有一個匹對成功,條件就成立即主查詢的欄位在子查詢中有相對應的
not exists():有一個匹對失敗,條件就成立即主查詢的欄位在子查詢沒有相對應的