mysql中關於exists的講解
我認為exists語法是mysql中一個很強大的工具,可以簡單地實現某些復雜的數據處理。
下面我談談與exists有關的三個方面。
all 與 any
首先,看到了exists,難免還會想到all和any,它們比exists容易理解一些。all 和 any都能讓一行數據與多行數據進行比較,這是它們的主要功能。
create table T(X int);
insert into T(X) values(1),(2),(3),(4);
# eg.1
select * from T where X > all( select * from T where X < 3 ); #輸出3,4
# eg.2
select * from T where X > any( select * from T where X > 1 ); #輸出3,4
先看eg.1, 顯然select * from T where X < 3結果是1,2;而all要求存在X大於集合{1,2}內的任意元素,即3,4。
同理,對於eg.2,select * from T where X > 1結果是2,3,4;any的要求是存在X大於集合{2,3,4}內的某個元素即可,即3,4。
划分表
在說exists之前,再看看一個比較特別的語句,關於表(table)的“划分”用法。
eg.1
# fruitTable
Id Name Class Count Date
1 蘋果 水果 10 2011-7-1
1 桔子 水果 20 2011-7-2
1 香蕉 水果 15 2011-7-3
2 白菜 蔬菜 12 2011-7-1
2 青菜 蔬菜 19 2011-7-2
現在要求進行篩選,條件是Id唯一,Date選最近的一次
這種篩選條件潛藏着對於表的划分要求。以fruitTable為例,需要划分為2個子表,Id為1的為一個子表、Id為2的為另一個子表,再從各自子表里面選出時間最大的那個元組。
先看看下面一個錯誤的解法
SELECT DISTINCT Id, Name, Class, Count, Date FROM fruitTable t1
WHERE (Date IN
(SELECT MAX(Date) FROM fruitTable t2 GROUP BY Id));
# 結果
1 桔子 水果 20 2011-7-2
1 香蕉 水果 15 2011-7-3
2 青菜 蔬菜 19 2011-7-2
這周解法在邏輯上有漏洞。它將不同Id的最大時間混在了一起,沒有真正地划分表格。
再來看看正確的解法
划分表格的思路是正確的,但問題是怎么划分,如果另外創建2個新的table,那這樣顯然太麻煩了,於是有了下面這種寫法。
SELECT DISTINCT Id, Name, Class, Count, Date FROM fruitTable t1
WHERE (Date =
(SELECT MAX(Date) FROM fruitTable t2 WHERE t2.Id=t1.Id));
注意WHERE t2.Id=t1.Id 很巧妙地 對表t2 基於t2.Id=t1.Id這個標准 進行了划分。可以推導一下,比如遍歷表t1,先是第1個元組: 1 蘋果 水果 10 2011-7-1, 可以知道t1.Id=1, 帶入第2個select: (SELECT MAX(Date) FROM fruitTable t2 WHERE t2.Id=1) , 觀察這個select語句的篩選條件WHERE t2.Id=1, 發現它的范圍限定在了Id為1的元組內,聚集函數MAX(Date)返回Id為1的所有元組中Date最大的值(2011-7-3)。
因此對於表t1, 當t1.Id=1時,只有Date=2011-7-3的元組才會被選出來;而當tl.Id=2時,第2個select又變為SELECT MAX(Date) FROM fruitTable t2 WHERE t2.Id=2, 返會所有Id=2的元組中Date的最大值(2011-7-2)。
可以發現,表t2是受t1.Id控制的,根據t1.Id的不同而被划分為不同的子表,這就是表的划分,並且不需要另外創建新的表。
exists
先說說exists的基本用法
create table R(
X int, Y varchar(5), Z varchar(5)
);
create table S(
Y varchar(5), Z varchar(5), Q int
);
insert into R(X,Y,Z) values(
1,'a','A'
),(
1,'b','B'
),(
1,'a','B'
),(
1,'c','C'
),(
2,'a','B'
),(
2,'b','B'
),(
2,'c','A'
),(
3,'z','Z'
);
insert into S(Y,Z,Q) values(
'b','B',1
),(
'a','B',2
);
-----------------------------
select * from R where exists( select * from S where S.Y='b' and R.Y=S.Y );
# 結果
'1', 'b', 'B'
'2', 'b', 'B'
對於exists可以先簡單地理解為if判斷。
比如語句select * from R where exists( select * from S where S.Y='b' and R.Y=S.Y );就可以理解為 從表R中篩選出滿足條件 S.Y='b' and R.Y=S.Y (select * from S where S.Y='b' and R.Y=S.Y) 的元組。
這個性質可以看出2個特性
- 首先exists()括號內的表不會影響最終返回的結果。比如上面的例子,返回的結果始終是關於表R的元組,和表S沒有任何關系
- 對於exists()語句,關鍵的是括號內的where子句。對於exists( select * from S where S.Y='b' and R.Y=S.Y ) 這種語句,可以直接當作 if( S.Y== 'b' and R.Y ==S.Y )。當然也不是說select不重要,比如exists( select 1 from S where S.Y='b' and R.Y=S.Y )是永遠為真的條件。
理清上面2點,我們就更能意識到exists非常像是一個關於條件判斷的語句。
下面例子類似
# 選了張三老師課的學生
select distinct sc.sid from sc
where exists (
select * from course c,teacher t
where sc.cid = c.cid and c.tid = t.tid and t.tname = "張三");
但僅僅只有exists還不夠,因為很多其它語句也能實現這個功能,真正強大的是not exists。
對於存在exists只是一個元組與某個局部作比較,因為只要存在即可。而對於不存在,卻是一個元組和整體做比較,因為要確定不存在,就必須遍歷所有。
在這方面來說,not exists比exists更強大。
找最值
SELECT DISTINCT Id, Name, Class, Count, Date FROM fruitTable t1
WHERE (Date =
(SELECT MAX(Date) FROM fruitTable t2 WHERE t2.Id=t1.Id));
#用not exists
SELECT DISTINCT Id, Name, Class, Count, Date FROM fruitTable t1
WHERE NOT EXISTS(
SELECT * FROM fruitTable t2 WHERE t2.Id=t1.Id and t2.Date > t1.Date );
這里not exists同樣可以看作not if,關鍵是明白哪部分條件被否定(not)。根據之前的理論,這里條件明顯是t2.Id=t1.Id and t2.Date > t1.Date , 而t2.Id=t1.Id不能作為否定的對象,因為這是必然存在的(自己想想,t1和t2內容一樣),用來限定表t2的范圍(即之前說的划分子表),再看t2.Date > t1.Date,這才是否定的部分,即對於t2中Id為t1.Id的所有元組的Date都不大於t1.Date,而此時的t1.Date也即最大值。
嵌套not exists
還有更復雜的情況,多層not exists嵌套使用。比如實現關系代數里的除法運算。
# 表R,S的定義上面已經給出 下面計算 R除以S
select distinct R1.x from R R1 where not exists (
select * from S where not exists (
select * from R R2 where R1.X=R2.X and R2.Y=S.Y and R2.Z=S.Z ));
一個not exists只表示不存在,需要遍歷所有元組才能做出判斷
2個not exists嵌套,表示每一個都存在,同樣需要遍歷所有元組才能確定,同時還是“肯定”
這里有3個select,2個not exists。
最里面的not exists是用來否定R2.Y=S.Y and R2.Z=S.Z (因為R1.X=R2.X一定成立,這個是用來划分子表的), 最外層的not exists就用來表示不存在這個意思,你會發現最后這個句子表達的意思就是關系代數里面除法的定義。
使用聯合來解決exists問題
因為MySQL每次的操作都是基於行的,當涉及到表與表之間類似集合的關系時,處理起來比較麻煩。比如下面這個問題。
insert into R(X,Y) values(
1,'a'
),(
1,'b'
),(
1,'B'
),(
1,'C'
),(
2,'A'
),(
2,'c'
),(
3,'z'
);
insert into S(Y,Q) values(
'b',1
),(
'B',2
);
#問題:表R內,對於X值相同的行組成一組(或叫集合)。在這樣的每組元素中,要求R(Y)中不能出現與S(Y)相同的值,求這樣的組的X值有哪些。
#這種問題是關於集合之間的關系,不同於 一行與一個集合之間的關系。
#下面運用之前講的not exists來求解
select distinct X from R R1 where not exists (
select * from R R2 where R2.X=R1.X and R2.Y in (select distinct Y from S));
下面來介紹另外一種方法,聯合。
仔細觀察可以發現R和S之間是有關系的,因此可以將它們進行自然連接,這樣就直接得到了所有R(Y)=S(Y)的值。
select distinct X from R where X not in (select distinct X from R,S where R.Y=S.Y);
但是對於代碼可讀性來說,in和exists比派生表聯合優雅
