精选专栏链接 🔗


  • MySQL技术笔记专栏
  • Redis技术笔记专栏
  • 大模型搭建专栏
  • Python学习笔记专栏
  • 深度学习算法专栏

欢迎订阅,点赞+关注,每日精进1%,与百万开发者共攀技术珠峰

更多内容持续更新中!希望能给大家带来帮助~ 😀😀😀


详解MySQL子查询

  • 1,什么是子查询
  • 2,从具体需求理解子查询
  • 3,子查询的分类
  • 4,单行子查询实战
    • 4.1,普通的单行子查询
    • 4.2,HAVING子句中的单行子查询
    • 4.3,CASE中的单行子查询
  • 5,多行子查询实战
    • 5.1,多行子查询实战
    • 5.2,多行子查询的空值问题
  • 6,相关子查询和不相关子查询
    • 6.1,相关子查询的执行流程
    • 6.2,不相关子查询与相关子查询对比
    • 6.3,在ORDER BY 中使用关联子查询
    • 6.4,案例进阶
    • 6.5,EXISTS 与 NOT EXISTS关键字
  • 7,子查询和自连接效率


1,什么是子查询

子查询指一个查询语句嵌套在另一个查询语句内部的查询。 SQL 中子查询的使用大大增强了 SELECT 查询的能力,因为很多时候查询需要从结果集中获取数据,或者需要从同一个表中先计算得出一个数据结果,然后与这个数据结果(可能是某个标量,也可能是某个集合)进行比较。


2,从具体需求理解子查询

需求:查询employees表中谁的工资比 Abel 高

在这里插入图片描述
针对此需求,有多种实现方式可供选择。

实现方式一 :使用多个SQL语句

先查询员工Abel的工资:

SELECT salary
FROM employees
WHERE last_name = 'Abel';

运行结果如下:

在这里插入图片描述
根据查询结果进行筛选:

SELECT last_name,salary
FROM employees
WHERE salary > 11000;

运行结果如下:

在这里插入图片描述

此实现方式与数据库进行了两次交互,效率较低。


实现方式二:使用自连接

SQL语句如下:

SELECT e2.last_name,e2.salary
FROM employees e1,employees e2
WHERE e2.`salary` > e1.`salary` 
AND e1.last_name = 'Abel';

运行结果如下:

在这里插入图片描述

自连接的实现方式通过联表将符合连接条件的记录拼接在一起进行查询。只需与数据库进行一次交互,效率高于方式一。


实现方式三:使用子查询

方式一中是先查询出 Abel 的工资为11000,然后在第二条SQL语句中通过WHERE salary > 11000 进行过滤,找到符合条件的信息。如果我们不把11000写死,而是用一段SQL语句实现,那么这就是一个简单的子查询。其中:

  • 子查询语句要包含在括号内;
  • 外面的SELECT语句称为主查询或外查询,内部的子查询SELECT语句称为子查询或内查询
  • 子查询在主查询之前一次执行完成,子查询的结果被主查询使用 ;

SQL示例如下:

SELECT last_name,salary
FROM employees
WHERE salary > (SELECT salaryFROM employeesWHERE last_name = 'Abel');

运行结果如下:

在这里插入图片描述


3,子查询的分类

  • 根据从子查询中返回的结果的条目数可以分为:单行子查询多行子查询
  • 根据子查询是否被执行多次可以分为相关子查询不相关子查询

下面我们会结合具体需求,详细讲解这几类子查询。


4,单行子查询实战

单行子查询只返回一个结果数据供主查询使用。

单行子查询常结合如下比较操作符:

操作符含义
=等于
>大于
>=大于等于
<小于
<=小于等于
<>不等于

4.1,普通的单行子查询

需求一:查询工资大于149号员工工资的员工的信息

SQL语句如下:

SELECT employee_id,last_name,salary
FROM employees
WHERE salary > (SELECT salaryFROM employeesWHERE employee_id = 149);

需求二:返回job_id与141号员工相同,salary比143号员工多的员工姓名,job_id和工资

SQL语句如下:

SELECT last_name,job_id,salary
FROM employees
WHERE job_id = (SELECT job_idFROM employeesWHERE employee_id = 141)
AND salary > (SELECT salaryFROM employeesWHERE employee_id = 143);

运行结果如下:

在这里插入图片描述

需求三:返回公司工资最少的员工的last_name、job_id和salary

SQL语句如下:

SELECT last_name, job_id, salary
FROM   employees
# 即找出工资等于最少工资的所有员工的信息
WHERE  salary = (SELECT MIN(salary)FROM   employees);

运行结果如下:

在这里插入图片描述

需求四:查询与141号员工的manager_id和department_id相同的其他员工的employee_id,manager_id,department_id。

实现方式一:

SELECT employee_id,manager_id,department_id
FROM employees
WHERE manager_id = (SELECT manager_idFROM employeesWHERE employee_id = 141)
AND department_id = (SELECT department_idFROM employeesWHERE employee_id = 141)
AND employee_id <> 141;

实现方式二(了解):成对查询

SELECT employee_id,manager_id,department_id
FROM employees
WHERE (manager_id,department_id) = (SELECT manager_id,department_idFROM employeesWHERE employee_id = 141)
AND employee_id <> 141;

以上两种实现方式返回的运行结果相同,如下图所示:

在这里插入图片描述


4.2,HAVING子句中的单行子查询

需求:查询最低工资大于110号部门最低工资的部门id和其最低工资

SELECT department_id,MIN(salary)
FROM employees
WHERE department_id IS NOT NULL  # 过滤掉department_id为NULL的部门
GROUP BY department_id
HAVING MIN(salary) > (SELECT MIN(salary)FROM employeesWHERE department_id = 110);

这是一个HAVING子句中使用单行子查询的例子。


4.3,CASE中的单行子查询

需求:查询员工的employee_id,last_name和一个新字段location。其中,若员工department_id与location_id为1800的department_id相同,则location为’Canada’,其余则为’USA’。

SELECT employee_id,last_name,CASE department_id WHEN (SELECT department_id FROM departments WHERE location_id = 1800) THEN 'Canada'ELSE 'USA' END "location"
FROM employees;

运行结果如下:

在这里插入图片描述


5,多行子查询实战

子查询返回多行数据即为多行子查询,也可称为集合比较子查询。

多行子查询使用的时候常结合多行比较操作符,如下:

操作符含义
IN等于列表中的任意一个
ANY需要和单行比较操作符一起使用,和子查询返回的某一个值比较
ALL需要和单行比较操作符一起使用,和子查询返回的所有值比较
SOME实际上是ANY的别名,作用相同,一般常使用ANY

5.1,多行子查询实战

需求一:找出所有工资等于任意部门最低工资的员工(无论其所属部门)

SQL语句如下:

SELECT employee_id, last_name
FROM   employees
WHERE  salary IN(SELECT   MIN(salary)FROM     employeesGROUP BY department_id); 

运行结果如下:

在这里插入图片描述


需求二:返回其它job_id中比job_id为‘IT_PROG’部门任意一个工资低的员工的员工号、姓名、job_id 以及salary

SQL代码如下:

SELECT employee_id,last_name,job_id,salary
FROM employees
WHERE job_id <> 'IT_PROG'
AND salary < ANY (SELECT salaryFROM employeesWHERE job_id = 'IT_PROG');

运行结果如下:

在这里插入图片描述


需求三:返回其它job_id中比job_id为 ‘IT_PROG’ 部门所有工资低的员工的员工号、姓名、job_id 以及salary

SQL代码如下:

SELECT employee_id,last_name,job_id,salary
FROM employees
WHERE job_id <> 'IT_PROG'
AND salary < ALL (SELECT salaryFROM employeesWHERE job_id = 'IT_PROG');

运行结果如下:

在这里插入图片描述


需求四:查询平均工资最低的部门 id

实现方式一: 先求各部门的最低平均工资,然后看哪个部门的最低平均工资等于此最低平均工资

求最低平均工资时的错误SQL示例:

SELECT  MIN(AVG(salary))
FROM employees
GROUP BY department_id; 

运行报错,错误原因:MySQL中不支持聚合函数的嵌套使用。

针对此需求,我们可以发散思维:可以将查询出的各个部门的平均工资结果形成一张中间表t1,平均工资是其中的一个字段,然后对此字段再次做聚合操作(求Min)。

求最低平均工资的正确SQL示例如下:

SELECT MIN(avg_sal)
FROM (SELECT AVG(salary) avg_salFROM employeesGROUP BY department_id
) t_dept_avg_sal  # 注意此处需要给表起别名,否则报错

此时 avg_sal 相当于 t_dept_avg_sal 表的一个字段,巧妙地避开了聚合函数的嵌套。

因此,实现此需求的完整SQL代码如下:

SELECT department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary) = (SELECT MIN(avg_sal)FROM (SELECT AVG(salary) avg_salFROM employeesGROUP BY department_id) t_dept_avg_sal  # 注意此处需要给表起别名,否则报错
)

运行结果如下:

在这里插入图片描述
实现方式二: 看作多行查询,借助ALL操作符实现

SELECT department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary) <= ALL(	SELECT AVG(salary) avg_salFROM employeesGROUP BY department_id) 

运行结果如下:

在这里插入图片描述


5.2,多行子查询的空值问题

多行子查询的空值问题使开发中尤其需要注意的问题。子查询返回的结果如果有NULL值时需要特别注意!!!

接下来我们结合具体场景分析空值问题。

需求一:查出employees表中所有的管理者的last_name

在这里插入图片描述

SQL语句如下:

SELECT last_name
FROM employees
WHERE employee_id IN (SELECT manager_idFROM employees);

运行结果如下:

在这里插入图片描述

需求二:查出employees表中所有的非管理者的last_name

运行如下SQL语句:

SELECT last_name
FROM employees
WHERE employee_id NOT IN (SELECT manager_idFROM employees);

发现查出的结果为空:

在这里插入图片描述

错误分析:原因是子查询返回的结果存在NULL值,则会导致最终返回空。

正确的SQL语句是:

SELECT last_name
FROM employees
WHERE employee_id NOT IN (SELECT manager_idFROM employeesWHERE manager_id IS NOT NULL);

运行结果如下:

在这里插入图片描述


6,相关子查询和不相关子查询

根据子查询是否被执行多次可以分为相关子查询不相关子查询。我们前面讲到的场景都是不相关子查询,因此本节我们重点理解相关子查询。


6.1,相关子查询的执行流程

如果子查询的执行依赖于外部查询,通常情况下都是因为子查询中的表用到了外部的表,并进行了条件关联,因此每执行一次外部查询,子查询都要重新计算一次,这样的子查询就称之为相关子查询

相关子查询按照一行接一行的顺序执行,主查询的每一行都执行一次子查询。


6.2,不相关子查询与相关子查询对比

需求一:查询员工中工资大于公司平均工资的员工的last_name,salary和其department_id

SQL语句如下:

SELECT last_name,salary,department_id
FROM employees
WHERE salary > (SELECT AVG(salary)FROM employees);

子查询执行一次,所以此案例为不相关子查询

需求二:查询员工中工资大于本部门平均工资的员工的last_name,salary和其department_id

SQL语句如下:

SELECT last_name,salary,department_id
FROM employees e1
WHERE salary > (SELECT AVG(salary)FROM employees e2WHERE department_id = e1.`department_id`);

显然 每执行一次外部查询,子查询都要重新计算一次 ,所以此案例为相关子查询

此外,需求二还有另外一种实现方式:在FROM中声明子查询

SELECT e.last_name,e.salary,e.department_id
FROM employees e,(SELECT department_id,AVG(salary) avg_salFROM employeesGROUP BY department_id) t_dept_avg_sal
WHERE e.department_id = t_dept_avg_sal.department_id
AND e.salary > t_dept_avg_sal.avg_sal

第二种实现方式不属于相关子查询,但是也可以达到同样的效果。


6.3,在ORDER BY 中使用关联子查询

需求:查询员工的id,salary,按照department_name 升序排序

SELECT employee_id,salary
FROM employees e
ORDER BY (SELECT department_nameFROM departments dWHERE e.`department_id` = d.`department_id`) ASC;

6.4,案例进阶

需求:若employees表中employee_id与job_history表(岗位变动信息表)中employee_id相同的数目不小于2,输出这些相同id的员工的employee_id,last_name和其job_id

SQL语句如下:

SELECT employee_id,last_name,job_id
FROM employees e
WHERE 2 <= (SELECT COUNT(*)FROM job_history jWHERE e.`employee_id` = j.`employee_id`)

运行结果如下:

在这里插入图片描述


6.5,EXISTS 与 NOT EXISTS关键字

关联子查询通常也会和 EXISTS关键字一起来使用,用来检查在子查询中是否存在满足条件的行。

  • 如果在子查询中不存在满足条件的行:
    • 条件返回 FALSE
    • 继续在子查询中查找
  • 如果在子查询中存在满足条件的行:
    • 不在子查询中继续查找
    • 条件返回 TRUE

NOT EXISTS关键字表示如果不存在某种条件,则返回TRUE,否则返回FALSE。


需求一:查询公司管理者的employee_id,last_name,job_id,department_id信息

实现方式一:自连接

# 去重保证每个管理者只出现一次
SELECT DISTINCT mgr.employee_id,mgr.last_name,mgr.job_id,mgr.department_id
FROM employees emp JOIN employees mgr
ON emp.manager_id = mgr.employee_id;

实现方式二 :子查询

SELECT employee_id,last_name,job_id,department_id
FROM employees
WHERE employee_id IN (  # 先查出所有管理者的manager_idSELECT DISTINCT manager_idFROM employees);

实现方式三:使用EXISTS

SELECT employee_id,last_name,job_id,department_id
FROM employees e1
# 逐个记录执行,没有符合条件的就返回FALSE,有符合条件的就返回TRUE
WHERE EXISTS (SELECT *FROM employees e2WHERE e1.`employee_id` = e2.`manager_id`);

需求二:查询departments表中,不存在于employees表中的部门的department_id和department_name

SQL代码如下:

SELECT department_id,department_name
FROM departments d
# 对于传入子查询的每一个记录,都去employees 看有无对应的记录,有则返回TRUE
WHERE NOT EXISTS (SELECT *FROM employees eWHERE d.`department_id` = e.`department_id`);

需求三 :在employees中增加一个department_name字段,数据为员工对应的部门名称

SQL语句如下:

UPDATE employees e
SET department_name =  (SELECT department_name FROM   departments dWHERE  e.department_id = d.department_id);

需求四:删除表employees中,其与emp_history表皆有的数据

DELETE FROM employees e
WHERE employee_id in  (SELECT employee_idFROM   emp_history WHERE  employee_id = e.employee_id);

7,子查询和自连接效率

回顾需求: 谁的工资比Abel的高?

#方式1:自连接
SELECT e2.last_name,e2.salary
FROM employees e1,employees e2
WHERE e1.last_name = 'Abel'
AND e1.`salary` < e2.`salary`
#方式2:子查询
SELECT last_name,salary
FROM employees
WHERE salary > (SELECT salaryFROM employeesWHERE last_name = 'Abel');

问题: 以上两种方式有好坏之分吗?

解答: 自连接方式好!

题目中可以使用子查询,也可以使用自连接。一般情况建议使用自连接,因为在许多 DBMS 的处理过程中,对于自连接的处理速度要比子查询快得多。

可以这样理解:子查询实际上是通过未知表进行查询后的条件判断,而自连接是通过已知的自身数据表进行条件判断,因此在大部分 DBMS 中都对自连接处理进行了优化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/95388.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/95388.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/95388.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode 206. 反转链表 迭代/递归

原题链接&#xff1a;Leetcode 206. 反转链表 解法一&#xff1a;迭代 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* …

C++11新特性 | 欢迎来到现代C++的世界!

左值与右值 左值与右值的概念 可以被取地址的值为左值&#xff08;left value&#xff0c;简称lvalue&#xff09;&#xff0c;否则为右值&#xff08;right value&#xff0c;简称rvalue&#xff09;。 常见的左值、右值例子&#xff1a; // >>>>>>> 左值…

因为对象装箱拆箱导致的空指针异常

今天业务突然提了个事件单&#xff0c;客户添加承租人一直报错&#xff0c;但是很奇怪&#xff0c;报错信息是空的二话不说&#xff0c;先跟API组要了接口和参数&#xff0c;然后看日志然鹅&#xff0c;这个接口并没有打印日志&#xff0c;只有一个e.printStackTrace()然后静下…

Rust 在 Windows 环境下交叉编译其他操作系统可执行文件的详细指南

前言&#xff1a;为什么需要交叉编译&#xff1f; 想象一下这样的场景&#xff1a;你在 Windows 电脑上开发了一个 Rust 程序&#xff0c;希望它能在 Linux 服务器上运行&#xff0c;或者在朋友的 macOS 电脑上测试 —— 总不能为了编译不同系统的版本&#xff0c;专门买一台 …

Ubuntu系统配置镜像源

要修改的文件 /etc/apt/sources.list操作步骤 1. 备份原文件 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak2. 编辑文件 sudo nano /etc/apt/sources.list3. 清空文件内容&#xff0c;填入以下内容 先获取Ubuntu版本代号&#xff1a; lsb_release -cs然后根据版本填…

【Spring Cloud Alibaba】Sentinel(一)

【Spring Cloud Alibaba】Sentinel&#xff08;一&#xff09;1. 简介2. 服务雪崩1. 简介 Sentinel 是阿里巴巴开源的分布式系统的流量防护组件&#xff0c;主要用来做流量控制、熔断降级、系统保护。它的定位类似于 Hystrix&#xff0c;但更侧重在 流量治理 上。目前在微服务…

Delphi 5 操作Word表格选区问题解析

在 Delphi 5 中操作 Word 时&#xff0c;您遇到的 Selection.MoveDown 方法选中整行而不是仅移动一行的原因与 Word 的选择机制和单元格内部结构有关。以下是详细解释和解决方案&#xff1a;问题分析单元格的选区范围&#xff1a;当使用 .Cell(r, c).Select 选中单元格时&#…

小米fastboot不能被电脑识别但手机正常使用模式时能被电脑识别

小米手机USB连接电脑不识别解决方法 – MIUI历史版本 小米手机USB连接电脑不识别解决方法 问题经验 手机连接电脑通常会自动安装驱动&#xff0c;如果驱动安装失败或是电脑不能识别手机设备&#xff0c;可尝试下面方法。 1. 尝试换根 USB 数据线&#xff0c;最好是手机原…

Git 版本管理工具基本操作汇总—命令总结

通常&#xff0c;很多朋友在使用 Git 的时候都会直接用 IDE 中集成的插件或者自带的工具等来实现代码的拉取、提交、合并以及其他操作&#xff0c;当然这肯定也是可以的&#xff0c;但是长期这样子操作&#xff0c;我们就会忽略掉 Git 业务运行的底层逻辑。那么&#xff0c;我这…

自学嵌入式第三十三天:网络编程-UDP

一、OSI模型(open system interconnect)开放系统互联模型&#xff0c;分为7层应用层&#xff1a;为网络用户提供各种服务&#xff0c;例如电子邮件、文件传输等&#xff1b;表示层&#xff1a;为不同主机间的通信提供统一的数据表示形式。加密解密&#xff0c;压缩&#xff1b;…

A*(Astar)算法详解与应用

算法背景A*&#xff08;A-Star&#xff09;算法是一种在图形平面上&#xff0c;有多个节点的路径中&#xff0c;求出最低通过成本的算法。其历史可以追溯到早期的图搜索算法&#xff0c;如Dijkstra算法和贪心最佳优先搜索&#xff08;Greedy Best-First Search&#xff09;。是…

word删除指定页面

常规程序因为wps的 .docx 文件是基于段落和节的结构&#xff0c;而不是“物理页”&#xff0c;所以无法直接按“第几页”删除在普通程序里面无法读取到他的页码&#xff0c;但是在宏编程里面他能读取到页码&#xff0c;我们就根据宏编程来这样删除。程序会自动打开选择要删除的…

RK3568平台开发系列讲解:瑞芯微平台4G模块篇移植

更多内容可以加入Linux系统知识库套餐(教程+视频+答疑) 🚀返回专栏总目录 文章目录 一、硬件图片 二、功能宏 三、增加PID/VID 支持 3.1、usb_device_id 结构体 3.2、usb_device_id 的注册 沉淀、分享、成长,让自己和他人都能有所收获!😄 一、硬件图片 目标: 结果…

面试 (一)

目录 1. HashMap是怎么解决冲突的 是用什么数据结构实现的 2. 为什么hashmap的初始长度为16 3. 多线程的核心参数 4. 多线程怎么工作的 5. CISCS是怎么实现的 6. JUC知识 7. C和java的区别 8. JVM底层编译的东西 9. 公平锁和非公平锁 10. 有人恶意攻击你的平台每秒发送…

计算机毕设选题:基于Python+Django的健康饮食管理系统设计【源码+文档+调试】

精彩专栏推荐订阅&#xff1a;在 下方专栏&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f496;&#x1f525;作者主页&#xff1a;计算机毕设木哥&#x1f525; &#x1f496; 文章目录 一、项目介绍二…

vscode terminal远程连接linux服务器GUI图形界面

看了很多教程&#xff0c;不清楚具体原理&#xff0c;但总之自己是摸索出来了&#xff1a; 1.下载vcxsrv&#xff0c;最后双击exe程序启动&#xff1a; 每一步的配置如下&#xff1a;2.修改配置 vscode中按下“ctrlshiftp”&#xff0c;然后输入“Remote-SSH: Open SSH Configu…

文档外发管理产品哪个品牌强?安全与效率双优产品推荐

在企业间协作日益加深的今天&#xff0c;企业对文档外发管理相关产品的安全性和效率要求越来越高。无论是日常业务协作&#xff0c;还是跨组织数据交换&#xff0c;如何确保文件在传输过程中不被泄露、篡改&#xff0c;同时又能高效流转&#xff0c;成为企业IT管理的重要课题。…

【教程】2025 IDEA 快速创建springboot(maven)项目

第一步&#xff1a;【新建】-【module】&#xff0c;左边选择springboot&#xff0c;右边填写相关信息。第二步&#xff1a;选择相关依赖。第三步&#xff1a;删掉一些无关的文件&#xff0c;保持项目简洁创建springboot项目已经结束了&#xff0c;下面是构建项目的架构以及环境…

【小白笔记】移动硬盘为什么总比电脑更容易满?

我明明只复制了10个G的文件&#xff0c;为什么我的移动硬盘就满了&#xff1f; 大家好&#xff0c;我是个刚入门的小白&#xff0c;最近遇到了一个让我百思不得其解的问题。我把电脑里的一些文件&#xff0c;总共加起来也就10个G左右&#xff0c;心想移动硬盘还有几十个G的空位…