子查詢是嵌套在另一個(gè)查詢中的select-from-where表達(dá)式。子查詢嵌套在where子句中,通常用于對(duì)集合成員資格、集合的比較以及集合的基數(shù)進(jìn)行檢查。
1、集合成員資格
SQL允許測試元組在關(guān)系中的成員資格。連接詞in測試元組是否是集合中的成員,集合是有select子句產(chǎn)生的一組值組成。連接詞not in則測試元組是否不是結(jié)合中的成員。
示例:
#找出在2018年春季和秋季同時(shí)開課的課程
#前面有提到in 可以替代集合的交運(yùn)算
#not in 就不舉例了,相信讀者也能夠?qū)懗觯╪ot in 相當(dāng)于集合的差運(yùn)算)
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
);
同時(shí),in和not in也能用于枚舉型集合。例如:
-- 找出名字不叫Smith 和wu的所有教師的信息
SELECT DISTINCT *
FROM instructor
WHERE name NOT IN ('smith','wu');


2、集合的比較
-- 找出比其中一個(gè)歷史系老師工資高的教師信息
SELECT DISTINCT T.*
FROM instructor AS T,instructor AS S
WHERE T.salary>S.salary AND S.dept_name = 'history';
對(duì)于上面的查詢,可以用另一種方式書寫,短語“至少比某一個(gè)大”在SQL中用>some表示,于是可以用下面這種更加貼切的文字表達(dá)的形式書寫:
SELECT *
FROM instructor
WHERE salary>SOME ( SELECT salary
FROM instructor
WHERE dept_name = 'history');


同樣的,也有<some,=some,<=some,>=some,和<>some的比較,其中=some等價(jià)于in,<>some不等價(jià)于not in;
當(dāng)我們要找出比所有歷史系老師工資高的教師信息時(shí),可以通過>all來實(shí)現(xiàn),例如:
SELECT *
FROM instructor
WHERE salary>ALL (SELECT salary FROM instructor WHERE dept_name = 'history');

同some,也有<all,<=all,>=all,=all,<>all的比較,<>all 等價(jià)于not in,但=all并不等價(jià)于in。
3、空關(guān)系測試
SQL還有一個(gè)特性是可測試一個(gè)子查詢的結(jié)果中是否存在元組。exists結(jié)構(gòu)在作為參數(shù)的子查詢非空時(shí)返回true值。使用exists,我們還能夠使用另一種方式書寫實(shí)現(xiàn)上文實(shí)現(xiàn)“找出在2018年春季和秋季同時(shí)開課的課程 ”的查詢。
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的另一特性,來自外層查詢的一個(gè)相關(guān)名稱(上述查詢中的S)可以用在where子句的子查詢中。使用外層查詢相關(guān)名稱的子查詢稱作相關(guān)子查詢。
在包含了子查詢的操作中,在相關(guān)名稱上可以應(yīng)用作用域規(guī)則。根據(jù)此規(guī)則,在一個(gè)子查詢中只能使用該子查詢本身定義的,或者包含該子查詢的任何查詢中定義的相關(guān)名稱。emmm……類似于全局變量于局部變量的作用域關(guān)系。
同理,可以用not exists結(jié)構(gòu)來測試子查詢結(jié)果集中是否不存在元組。
同時(shí),我們還可以使用not exists結(jié)構(gòu)來模擬集合的包含操作:將“關(guān)系A(chǔ)包含于關(guān)系B”寫成not exists(B except A),例如:
#找出選修了biology系開設(shè)的所有課程的學(xué)生
#takes關(guān)系表是表示某一個(gè)學(xué)生所選修的課程集合
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對(duì)每個(gè)學(xué)生測試其選修的所有課程集合是否包含biology系開設(shè)的所有課程。
使用mysql的人(因?yàn)閙ysql沒有except運(yùn)算,可以參照之前SQL——集合運(yùn)算),可以用下面句子實(shí)現(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)系,再將這個(gè)關(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),測試使用;
其實(shí),之前有講到如何替換except,如果按照之前的替換方法,應(yīng)該是以下的書寫形式的:
但是,這樣書寫是有錯(cuò)誤的。為什么呢?因?yàn)樵谧硬樵冎衘oin子句是無法參照(或者說引用更合適)外部表的,也就說這里會(huì)報(bào)這樣的錯(cuò)誤:' Unknown column 'S.id' in 'where clause'。
這也提醒我們,實(shí)現(xiàn)某個(gè)查詢功能,首先搞清楚它的實(shí)現(xiàn)邏輯,這些邏輯邏輯可能有好幾種實(shí)現(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、重復(fù)元組存在性測試
SQL提供一個(gè)布爾函數(shù)unique,用于測試在一個(gè)子查詢的結(jié)果中是否存在重復(fù)元組。如果作為參數(shù)的子查詢結(jié)果中沒有重復(fù)的元組,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沒法識(shí)別出來,不過沒關(guān)系,我們可以用下列方法來等價(jià)它:
#不重復(fù);如果要測試重復(fù),則將等于替換成<
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子句中使用子查詢表達(dá)式。再次采用的主要觀點(diǎn)是:任何select-from-where表達(dá)式返回的結(jié)果都是關(guān)系,因而可以插入到另一個(gè)select-from-where中任何關(guān)系可以出現(xiàn)的位置。對(duì)于下面這個(gè)查詢句子,是找出系平均工資超過15000的那些戲中的教師的平均工資
SELECT dept_name,avg(salary) as avg_salary
FROM instructor
GROUP BY dept_name
HAVING avg(salary)>15000;
如果在from插入子查詢實(shí)現(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實(shí)現(xiàn)都支持在from子句中嵌套子查詢,還有就是SQL實(shí)現(xiàn)要求對(duì)一個(gè)子查詢結(jié)果關(guān)系都給一個(gè)名字,即使該名字從不被引用,例如mysql就是這樣(因?yàn)楣P者就是用的MySQL[捂臉])。當(dāng)我們使用了from子句的子查詢后,having就顯得不必要,因?yàn)閔aving子句使用的謂詞出現(xiàn)在外層查詢的where子句中,當(dāng)然,不是說不可以用。
對(duì)于下面的例子:“找出在所有系中工資總額最大的系,以及總額是多少”,不使用from子句的子查詢,having子句是無能為力,但用from子句中的子查詢卻能輕易實(shí)現(xiàn)。
#按照系名分組,計(jì)算每個(gè)分組的工資總額,從中挑取最大值。
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子句提供定義臨時(shí)關(guān)系的方法,這個(gè)定義只對(duì)包含with子句的查詢有效。例如,找出具有最大預(yù)算的系。
#我感覺有點(diǎn)懷疑人生,因?yàn)閙ysql竟然不支持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子句中的嵌套子查詢實(shí)現(xiàn)with子句的,只不過看起來很繁瑣,難懂。
利用嵌套子查詢實(shí)現(xiàn)上面句子是這樣的:
SELECT budget
FROM department,(
SELECT max(budget) as val
FROM department
)AS max_budget
WHERE department.budget = max_budget.val;
6、標(biāo)量子查詢
SQL允許子查詢出現(xiàn)在單個(gè)值得表達(dá)式能夠出現(xiàn)的任何地方,只要該子查詢只返回包含單個(gè)屬性的單個(gè)元組,這就叫做標(biāo)量子查詢:
示例:‘列出所有的系以及它們擁有的教師’
#該嵌套子查詢的結(jié)果只有一行一列,也就是只返回包含單個(gè)屬性的單個(gè)元組。
SELECT dept_name,(
SELECT count(*)
FROM instructor
WHERE department.dept_name = instructor.dept_name
)AS num_instr
FROM department;
附件:
主碼用下劃線標(biāo)注,外碼依賴用從參照關(guān)系的外碼屬性到被參照的主碼屬性之間的箭頭表示
