SQL——嵌套子查詢

子查詢是嵌套在另一個(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');
instructor表
結(jié)果表

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');
兩種書寫形式的結(jié)果表
instructor原表

同樣的,也有<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');
>all結(jié)果表

同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)
);
結(jié)果表
takes表
student表
course表

注:數(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)系的外碼屬性到被參照的主碼屬性之間的箭頭表示

數(shù)據(jù)庫的模式圖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容