SQL——嵌套子查询

子查询是嵌套在另一个查询中的select-from-where表达式。子查询嵌套在where子句中,通常用于对集合成员资格、集合的比较以及集合的基数进行检查。

1、集合成员资格

SQL允许测试元组在关系中的成员资格。连接词in测试元组是否是集合中的成员,集合是有select子句产生的一组值组成。连接词not in则测试元组是否不是结合中的成员。

示例:

#找出在2018年春季和秋季同时开课的课程 
#前面有提到in 可以替代集合的交运算
#not in 就不举例了,相信读者也能够写出(not 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');
instructor表
结果表

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');
两种书写形式的结果表
instructor原表

同样的,也有<some,=some,<=some,>=some,和<>some的比较,其中=some等价于in,<>some不等价于not in;

当我们要找出比所有历史系老师工资高的教师信息时,可以通过>all来实现,例如:

SELECT *
FROM instructor
WHERE salary>ALL (SELECT salary FROM instructor WHERE dept_name = 'history');
>all结果表

同some,也有<all,<=all,>=all,=all,<>all的比较,<>all 等价于not in,但=all并不等价于in。

3、空关系测试

SQL还有一个特性是可测试一个子查询的结果中是否存在元组。exists结构在作为参数的子查询非空时返回true值。使用exists,我们还能够使用另一种方式书写实现上文实现“找出在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);

上述的示例中还反应了SQL的另一特性,来自外层查询的一个相关名称(上述查询中的S)可以用在where子句的子查询中。使用外层查询相关名称的子查询称作相关子查询

在包含了子查询的操作中,在相关名称上可以应用作用域规则。根据此规则,在一个子查询中只能使用该子查询本身定义的,或者包含该子查询的任何查询中定义的相关名称。emmm……类似于全局变量于局部变量的作用域关系。

同理,可以用not exists结构来测试子查询结果集中是否不存在元组。

同时,我们还可以使用not exists结构来模拟集合的包含操作:将“关系A包含于关系B”写成not exists(B except A),例如:

#找出选修了biology系开设的所有课程的学生
#takes关系表是表示某一个学生所选修的课程集合

SELECT S.id,S.name
FROM student AS S 
WHERE NOT exists(
    (
      #找出biology系开设的所有课程集合
      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系开设的所有课程。

使用mysql的人(因为mysql没有except运算,可以参照之前SQL——集合运算),可以用下面句子实现上述效果:

select distinct S.id , S.name
from student as S ,takes as T
where S.id = T.id and course_id in (
#找出biology系开设的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关系,再将这个关系通过course_id与takes连接
    SELECT course_id
    FROM course
    WHERE dept_name = 'biology'
  )AS C
  JOIN takes AS T USING (course_id)
);
结果表
takes表
student表
course表

注:数据库的数据纯属虚构,测试使用;

其实,之前有讲到如何替换except,如果按照之前的替换方法,应该是以下的书写形式的:
但是,这样书写是有错误的。为什么呢?因为在子查询中join子句是无法参照(或者说引用更合适)外部表的,也就说这里会报这样的错误:' Unknown column 'S.id' in 'where clause'

这也提醒我们,实现某个查询功能,首先搞清楚它的实现逻辑,这些逻辑逻辑可能有好几种实现方法。

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提供一个布尔函数unique,用于测试在一个子查询的结果中是否存在重复元组。如果作为参数的子查询结果中没有重复的元组,unique结构将返回true值。

-- 找出所有在2018年最多开设一次的课程
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没法识别出来,不过没关系,我们可以用下列方法来等价它:

#不重复;如果要测试重复,则将等于替换成<
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表达式返回的结果都是关系,因而可以插入到另一个select-from-where中任何关系可以出现的位置。对于下面这个查询句子,是找出系平均工资超过15000的那些戏中的教师的平均工资

SELECT dept_name,avg(salary) as avg_salary
FROM instructor
GROUP BY dept_name
HAVING avg(salary)>15000;

如果在from插入子查询实现,则如下:

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实现都支持在from子句中嵌套子查询,还有就是SQL实现要求对一个子查询结果关系都给一个名字,即使该名字从不被引用,例如mysql就是这样(因为笔者就是用的MySQL[捂脸])。当我们使用了from子句的子查询后,having就显得不必要,因为having子句使用的谓词出现在外层查询的where子句中,当然,不是说不可以用。

对于下面的例子:“找出在所有系中工资总额最大的系,以及总额是多少”,不使用from子句的子查询,having子句是无能为力,但用from子句中的子查询却能轻易实现。

#按照系名分组,计算每个分组的工资总额,从中挑取最大值。
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子句提供定义临时关系的方法,这个定义只对包含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子句中的嵌套子查询实现with子句的,只不过看起来很繁琐,难懂。
利用嵌套子查询实现上面句子是这样的:

SELECT budget
FROM department,(
  SELECT max(budget) as val
  FROM department
  )AS max_budget
WHERE department.budget = max_budget.val;

6、标量子查询

SQL允许子查询出现在单个值得表达式能够出现的任何地方,只要该子查询只返回包含单个属性的单个元组,这就叫做标量子查询:
示例:‘列出所有的系以及它们拥有的教师’

#该嵌套子查询的结果只有一行一列,也就是只返回包含单个属性的单个元组。
SELECT dept_name,(
    SELECT count(*)
    FROM instructor
    WHERE department.dept_name = instructor.dept_name
)AS num_instr
FROM department;

附件
主码用下划线标注,外码依赖用从参照关系的外码属性到被参照的主码属性之间的箭头表示

数据库的模式图

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容