子查詢是嵌套在另一個查詢中的select-from-where表達式。子查詢嵌套在where子句中透葛,通常用于對集合成員資格、集合的比較以及集合的基數(shù)進行檢查沪悲。
1获洲、集合成員資格
SQL允許測試元組在關(guān)系中的成員資格。連接詞in測試元組是否是集合中的成員殿如,集合是有select子句產(chǎn)生的一組值組成贡珊。連接詞not in則測試元組是否不是結(jié)合中的成員。
示例:
#找出在2018年春季和秋季同時開課的課程
#前面有提到in 可以替代集合的交運算
#not in 就不舉例了涉馁,相信讀者也能夠?qū)懗觯╪ot in 相當于集合的差運算)
SELECT DISTINCT S.course_id
FROM section AS S
WHERE S.semester='spring'AND S.year=2018 AND
course_id IN (SELECT course_id
FROM section as T
WHERE T.semester = 'fall' and T.year=2018
);
同時门岔,in和not in也能用于枚舉型集合。例如:
-- 找出名字不叫Smith 和wu的所有教師的信息
SELECT DISTINCT *
FROM instructor
WHERE name NOT IN ('smith','wu');
2烤送、集合的比較
-- 找出比其中一個歷史系老師工資高的教師信息
SELECT DISTINCT T.*
FROM instructor AS T,instructor AS S
WHERE T.salary>S.salary AND S.dept_name = 'history';
對于上面的查詢,可以用另一種方式書寫寒随,短語“至少比某一個大”在SQL中用>some表示,于是可以用下面這種更加貼切的文字表達的形式書寫:
SELECT *
FROM instructor
WHERE salary>SOME ( SELECT salary
FROM instructor
WHERE dept_name = 'history');
同樣的帮坚,也有<some,=some,<=some,>=some,和<>some的比較,其中=some等價于in妻往,<>some不等價于not in;
當我們要找出比所有歷史系老師工資高的教師信息時试和,可以通過>all來實現(xiàn)讯泣,例如:
SELECT *
FROM instructor
WHERE salary>ALL (SELECT salary FROM instructor WHERE dept_name = 'history');
同some,也有<all,<=all,>=all,=all,<>all的比較阅悍,<>all 等價于not in好渠,但=all并不等價于in昨稼。
3、空關(guān)系測試
SQL還有一個特性是可測試一個子查詢的結(jié)果中是否存在元組拳锚。exists結(jié)構(gòu)在作為參數(shù)的子查詢非空時返回true值假栓。使用exists,我們還能夠使用另一種方式書寫實現(xiàn)上文實現(xiàn)“找出在2018年春季和秋季同時開課的課程 ”的查詢霍掺。
SELECT DISTINCT course_id
FROM section AS S
WHERE S.semester = 'spring' AND S.year = 2018 AND
exists(SELECT *
FROM section AS T
WHERE T.semester = 'fall' AND T.year=2018 AND S.course_id=T.course_id);
上述的示例中還反應(yīng)了SQL的另一特性匾荆,來自外層查詢的一個相關(guān)名稱(上述查詢中的S)可以用在where子句的子查詢中。使用外層查詢相關(guān)名稱的子查詢稱作相關(guān)子查詢抗楔。
在包含了子查詢的操作中棋凳,在相關(guān)名稱上可以應(yīng)用作用域規(guī)則。根據(jù)此規(guī)則连躏,在一個子查詢中只能使用該子查詢本身定義的剩岳,或者包含該子查詢的任何查詢中定義的相關(guān)名稱。emmm……類似于全局變量于局部變量的作用域關(guān)系入热。
同理拍棕,可以用not exists結(jié)構(gòu)來測試子查詢結(jié)果集中是否不存在元組。
同時勺良,我們還可以使用not exists結(jié)構(gòu)來模擬集合的包含操作:將“關(guān)系A(chǔ)包含于關(guān)系B”寫成not exists(B except A)绰播,例如:
#找出選修了biology系開設(shè)的所有課程的學生
#takes關(guān)系表是表示某一個學生所選修的課程集合
SELECT S.id,S.name
FROM student AS S
WHERE NOT exists(
(
#找出biology系開設(shè)的所有課程集合
SELECT course_id
FROM course
WHERE dept_name = 'biology'
)EXCEPT (
#找出S.id選修的所有課程集合
SELECT T.course_id
FROM takes AS T
WHERE S.id = T.id
)
);
#外層select對每個學生測試其選修的所有課程集合是否包含biology系開設(shè)的所有課程。
使用mysql的人(因為mysql沒有except運算尚困,可以參照之前SQL——集合運算)蠢箩,可以用下面句子實現(xiàn)上述效果:
select distinct S.id , S.name
from student as S ,takes as T
where S.id = T.id and course_id in (
#找出biology系開設(shè)的course_id集合
select course_id
from course
where dept_name = 'biology');
也可以使用連接的方式替換上面的方式:
SELECT S.id,S.name
FROM student AS S
WHERE S.id = SOME (
SELECT id
FROM
(
#找出biology系的course_id關(guān)系,再將這個關(guān)系通過course_id與takes連接
SELECT course_id
FROM course
WHERE dept_name = 'biology'
)AS C
JOIN takes AS T USING (course_id)
);
注:數(shù)據(jù)庫的數(shù)據(jù)純屬虛構(gòu)事甜,測試使用谬泌;
其實,之前有講到如何替換except逻谦,如果按照之前的替換方法掌实,應(yīng)該是以下的書寫形式的:
但是,這樣書寫是有錯誤的邦马。為什么呢贱鼻?因為在子查詢中join子句是無法參照(或者說引用更合適)外部表的,也就說這里會報這樣的錯誤:' Unknown column 'S.id' in 'where clause'滋将。
這也提醒我們邻悬,實現(xiàn)某個查詢功能,首先搞清楚它的實現(xiàn)邏輯随闽,這些邏輯邏輯可能有好幾種實現(xiàn)方法父丰。
SELECT S.id,S.name
FROM student AS S
WHERE NOT exists(
SELECT course_id
FROM
(
SELECT course_id
FROM course
WHERE dept_name = 'biology'
)AS C
LEFT JOIN
(
SELECT course_id
FROM takes AS T
WHERE S.id = T.id
)AS N USING (course_id)
WHERE N.course_id IS NULL
);
4、重復元組存在性測試
SQL提供一個布爾函數(shù)unique橱脸,用于測試在一個子查詢的結(jié)果中是否存在重復元組础米。如果作為參數(shù)的子查詢結(jié)果中沒有重復的元組,unique結(jié)構(gòu)將返回true值添诉。
-- 找出所有在2018年最多開設(shè)一次的課程
SELECT T.*
FROM course AS T
WHERE UNIQUE(
SELECT R.course_id
FROM section AS R
WHERE T.course_id=R.course_id AND R.year = 2018
);
不過令人蛋疼的是屁桑,mysql沒法識別出來,不過沒關(guān)系栏赴,我們可以用下列方法來等價它:
#不重復蘑斧;如果要測試重復,則將等于替換成<
SELECT T.*
FROM course AS T
WHERE 1 = (
SELECT count(R.course_id)
FROM section AS R
WHERE T.course_id=R.course_id AND R.year = 2018
);
5须眷、from子句中的子查詢
SQL允許在from子句中使用子查詢表達式竖瘾。再次采用的主要觀點是:任何select-from-where表達式返回的結(jié)果都是關(guān)系,因而可以插入到另一個select-from-where中任何關(guān)系可以出現(xiàn)的位置花颗。對于下面這個查詢句子捕传,是找出系平均工資超過15000的那些戲中的教師的平均工資
SELECT dept_name,avg(salary) as avg_salary
FROM instructor
GROUP BY dept_name
HAVING avg(salary)>15000;
如果在from插入子查詢實現(xiàn),則如下:
SELECT dept_name,avg_salary
FROM (
SELECT dept_name,avg(salary) AS avg_salary
FROM instructor
GROUP BY dept_name
)AS S
WHERE S.avg_salary>15000;
注:很多SQL實現(xiàn)都支持在from子句中嵌套子查詢扩劝,還有就是SQL實現(xiàn)要求對一個子查詢結(jié)果關(guān)系都給一個名字庸论,即使該名字從不被引用,例如mysql就是這樣(因為筆者就是用的MySQL[捂臉])棒呛。當我們使用了from子句的子查詢后聂示,having就顯得不必要,因為having子句使用的謂詞出現(xiàn)在外層查詢的where子句中簇秒,當然鱼喉,不是說不可以用。
對于下面的例子:“找出在所有系中工資總額最大的系趋观,以及總額是多少”扛禽,不使用from子句的子查詢,having子句是無能為力拆内,但用from子句中的子查詢卻能輕易實現(xiàn)旋圆。
#按照系名分組,計算每個分組的工資總額麸恍,從中挑取最大值灵巧。
SELECT max(tol_salary)
FROM (
SELECT dept_name,sum(salary) AS tol_salary
FROM instructor
GROUP BY dept_name
)AS dept_tol;
6、with子句
with子句提供定義臨時關(guān)系的方法抹沪,這個定義只對包含with子句的查詢有效刻肄。例如,找出具有最大預算的系融欧。
#我感覺有點懷疑人生敏弃,因為mysql竟然不支持with子句,不過還是寫一下SQL語句的吧
with max_budget(value) as
(select max(budget)
from department)
select budget
from department,max_budget
where department.budget = max_budget.budget;
with子句最主要的作用是是的邏輯更清晰噪馏,我們也是可以用from子句或者where子句中的嵌套子查詢實現(xiàn)with子句的麦到,只不過看起來很繁瑣绿饵,難懂。
利用嵌套子查詢實現(xiàn)上面句子是這樣的:
SELECT budget
FROM department,(
SELECT max(budget) as val
FROM department
)AS max_budget
WHERE department.budget = max_budget.val;
6瓶颠、標量子查詢
SQL允許子查詢出現(xiàn)在單個值得表達式能夠出現(xiàn)的任何地方拟赊,只要該子查詢只返回包含單個屬性的單個元組,這就叫做標量子查詢:
示例:‘列出所有的系以及它們擁有的教師’
#該嵌套子查詢的結(jié)果只有一行一列粹淋,也就是只返回包含單個屬性的單個元組吸祟。
SELECT dept_name,(
SELECT count(*)
FROM instructor
WHERE department.dept_name = instructor.dept_name
)AS num_instr
FROM department;
附件:
主碼用下劃線標注,外碼依賴用從參照關(guān)系的外碼屬性到被參照的主碼屬性之間的箭頭表示