嵌套查询
有一说一,modernSQL的内容是真的多,甚至于我都不想继续了,不过这些其实都还无所谓,毕竟现在也还只是停留在理论阶段,还没有开始真正的上手。
在之前我们接触到的都是一些单个查询语句的情况,但是有时候这种单层的结构不能满足我们的需求。自然而然,多层也就是嵌套查询也就出现了。这种查询方式其实很好理解。就是将一个查询语句的输出作为另外一个查询语句的一个子句的输入。在格式上表现出来的其实就是一个查询中包含着另外的查询。通常情况下,这种架构下的查询语句很难优化。而且一般来说,这种嵌套查询,可以出现在任何一个子句中,只要你这样做存在着抽象价值。
where子句嵌套
简单来看一个小demo
这个并不是一个完整的嵌套查询语句。抽象来看现在这个查询语句其实存在了一个缺陷,就是你无法抽象出来你这里的父查询语句要怎么使用这个子查询语句的输出,这时就需要一些额外的关键字的使用来解决这个问题。
你提到的这个问题非常经典,正是嵌套查询的核心挑战之一。在 SQL 中,嵌套查询的出现通常是为了处理一些复杂的需求,比如筛选符合条件的数据、关联不同的表等。但是,正如你说的,这种查询方式有时难以优化,且存在一些模糊的地方,比如如何将子查询的输出与父查询关联起来。
如何将子查询与父查询结合
为了将子查询的结果作为父查询的输入,确实需要一些关键字,主要有以下几种常见的方式:
1. IN
关键字
IN
关键字可以将子查询的结果集作为父查询条件的一部分。
示例:
1
2
3SELECT name
FROM student
WHERE sid IN (SELECT sid FROM enrolled WHERE cid = '15-445');这里,子查询
1
SELECT sid FROM enrolled WHERE cid = '15-445'
会返回一组
1
sid
,然后父查询
1
SELECT name FROM student
会使用 IN关键字,筛选出 sid在这组返回结果中的学生名字。
2. EXISTS
关键字
EXISTS
用来测试子查询是否返回任何记录。如果返回了记录,EXISTS
就会返回 TRUE
,否则返回 FALSE
。通常用在子查询存在与否的判断上。
示例:
1
2
3SELECT name
FROM student s
WHERE EXISTS (SELECT 1 FROM enrolled e WHERE e.sid = s.sid AND e.cid = '15-445');这里,EXISTS子查询会检查是否存在与 student 表中 sid匹配的记录,并且这些记录的 cid是 ‘15-445’。如果存在这样一行,父查询就会返回该学生的名字。
3. ANY
/ SOME
关键字
ANY
和 SOME
都是用来比较父查询的某个值与子查询结果集中的任意一个值。如果与子查询的结果中的任意一个值符合条件,父查询会返回相应的记录。
示例:
1
2
3SELECT name
FROM student
WHERE sid = ANY (SELECT sid FROM enrolled WHERE cid = '15-445');这里,ANY检查 student表中的 sid是否与子查询 SELECT sid FROM enrolled WHERE cid = ‘15-445’的结果集中的任意一个 sid匹配。
4. ALL
关键字
ALL
用来比较父查询的某个值与子查询结果集中的所有值。如果父查询的值与子查询结果集中的所有值都符合条件,才会返回结果。
示例:
1
2
3SELECT name
FROM student
WHERE sid > ALL (SELECT sid FROM enrolled WHERE cid = '15-445');这个查询会返回 sid 大于子查询中所有 sid的学生。
5. JOIN
子查询
有时可以将子查询与 JOIN
结合,利用联接来简化查询结构,尤其是涉及到多表查询时。
示例:
1
2
3
4SELECT s.name
FROM student s
JOIN enrolled e ON s.sid = e.sid
WHERE e.cid = '15-445';这种方式将子查询转化为一个连接操作,直接从 student和 enrolled表中联接出需要的数据。这里,e.cid = ‘15-445’即是查询条件。
以上几个就是GPT给出的几个关键字的小demo,由于我不想再去对这几个进行深入,所以暂时就先这样。
from子句嵌套
简单来看几个子句的嵌套,其实可以发现一个现象,就是这种嵌套其实是基于一个接口规范来实现的。在我看来,部分子句的输入格式应该是关系,或者说元组的集合。那么,当我们查询语句的输出是一个关系时,那是否意味着我们能够使用这些个查询语句的输出作为另一个查询语句子句的输入。这里就是这样,from子句决定的是我们整个查询语句接下来所要操作的表。而一个查询语句的输出也正是一个规范格式的表。
最直观的一个,我们直接将一个查询子句放于from子句中,这在逻辑上是一个成立的,就比如:我从xx中找出了一些人,我再在这些人当中筛选出一些人之类的。
来看一个小demo
1 | SELECT d.department_name, e.employee_count |
这里需要注意下join关键字,在这里先不考虑join关键字的深入用法。简单来说一下,join关键字就是将俩侧的表合并为一个表,至于更多的规则和限制,之后有遇到再说吧。
1 | select max(tot_salary) |
可以看到这里的demo相对于上面那个存在了一个最大的区别,就是在子查询语句之后加上了一个更名关键字as,通过这个更名关键字,我们能够对于这个子查询产生的关系进行命名。这是一种规定,也很好理解,毕竟单纯的查询语句出来的关系其实是匿名的,通过这种更名操作,能够给后续的一些操作提供便利,毕竟在一些复杂的架构中是可能使用到这种子查询产生的表的。
需要注意的是,在一些SQL实现(MySQL等)要求这种子查询都必须使用as关键字来进行子查询产生的关系的更名,即使这个关系没有被引用。还有一些实现(Oracle等)允许进行更名,但是不允许在这种更名语句中进行再更名,即对于select中的属性进行隐式的更名,就比如上面那个demo一般。因此,如果实在Oracle中,需要将select子句替换为
1 | select dept_name, sum(salary) as tot_salary |
并将 as depe_total(dept_name,tot_salary)
替换为as depe_total
标量子查询
标量子查询的原理我们前面其实已经出现过了,就是通过输入和输出接口的统一来实现对于一个字查询语句的任意插入。
官方点来说:SQL允许子查询出现在返回单个值的表达式能够出现的任何地方,只要该子查询只放返回一个包含单个属性的元组,这种子查询就是标量子查询。
这个不难,简单看一个demo过了就行
1 | select dept_name, |
不带from子句的标量
在一些时候,我们需要对于数据库的一些数据计算后将这些数据用于一定的算数运算,有些时候,这种算数运算不需要再额外去指定from子句作为输入,就比如
1 | (select count(*)from teachers)/(select count(*)from instructor); |
这是使用俩个子句来组成的另一个运算语句,在部分DBMS中,这是合法的,但是更多时候这会使得DBMS系统报错无法计算,因为这个语句缺少from语句。此时数据库系统就给我们提供了一种预定义的关系来使用,这种关系时虚拟的,但是可以在from中去使用去屏蔽掉报错,但是你很少能从中去获取一些具体有效的信息,这个关系就是 dual。
此时上面那个语句可以改为
1 | (select count(*)from teachers)/(select count(*)from instructor) |
当然,这种显式指定from语句的规则可能不是每个DBMS都要求的,具体参考对于的使用手册即可。