mysql中關於exists的深入講解
- 2020 年 4 月 11 日
- 筆記
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。
找最值
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 ));
這裡有3個select,2個not exists。
最裏面的not exists是用來否定R2.Y=S.Y and R2.Z=S.Z
(因為R1.X=R2.X一定成立,這個是用來劃分子表的), 最外層的not exists就用來表示不存在這個意思,你會發現最後這個句子表達的意思就是關係代數裏面除法的定義。