MySQL之DQL数据查询操作

发布时间 2023-05-30 17:53:46作者: 蚂蚁小哥

一:SQL概述及规范

  SQL(Structured Query Language,结构化查询语言是使用关系模型的数据库应用语言,与数据直接打交道,由 IBM 上世纪70年代开发出来。后由美国国家标准局(ANSI)开始着手制定SQL标准,先后有SQL-86,SQL-89,SQL-92,SQL-99等标准。SQL有两个重要的标准,分别是SQL92 和SQL99它们分别代表了 92 年和 99 年颁布的 SQL 标准,我们今天使用的 SQL 语言依然遵循这些标准。
  注:不同的数据库生产厂商都支持SQL语句,但都有特有内容(比如MySQL和Oracle的语法有一点小差异)

  SQL有两个主要的标准,分别是 SQL92 和 SQL99 。92 和 99 代表了标准提出的时间,SQL92 就是 92 年提出的标准规范。当然除了 SQL92 和 SQL99 以外,还存在 SQL-86、SQL-89、SQL:2003、SQL:2008、SQL:2011 和 SQL:2016 等其他的标准。实际上最重要的 SQL 标准就是 SQL92 和 SQL99。一般来说 SQL92 的形式更简单,但是写的 SQL 语句会比较长,可读性较差。而 SQL99 相比于 SQL92 来说,语法更加复杂,但可读性更强。我们从这两个标准发布的页数也能看出,SQL92 的标准有 500 页,而 SQL99 标准超过了1000 页。实际上从 SQL99 之后,很少有人能掌握所有内容,因为确实太多了。就好比我们使用Windows、Linux 和 Office 的时候,很少有人能掌握全部内容一样。我们只需要掌握一些核心的功能,满足日常工作的需求即可。
  SQL92 和 SQL99 是经典的 SQL 标准,也分别叫做 SQL-2 和 SQL-3 标准。也正是在这两个标准发布之后,SQL影响力越来越大,甚至超越了数据库领域。现如今 SQL 已经不仅仅是数据库领域的主流语言,还是信息领域中信息处理的主流语言。在图形检索、图像检索以及语音检索中都能看到 SQL 语言的使用。

1:SQL分类

  SQL语句的五种分类分别是DQL、DML、DDL、TCL和TCL,下面对SQL语句的五种分类进行列

①:DDL(Data Definition Languages、数据定义语言)
    数据定义语言DDL用来创建数据库中的各种对象,创建、删除、修改表的结构,比如表、视图、索引、同义词、聚簇等,简称DDL。和DML相比,
   DML是修改数据库表中的数据,而DDL是修改数据中表的结构。
    主要的语句关键字包括:CREATE、DROP、ALTER②:DML(Data Manipulation Language、数据操作语言)
    用户通过它可以实现对数据库中表里面的数据进行的基本操作。用于添加、删除、更新和查询数据库记录,并检查数据完整性。
    主要的语句关键字包括 INSERT、DELETE、UPDATE③:DQL(Data Query Language、数据库查询语言)
    基本结构是由SELECT子句,FROM子句,WHERE子句组成的查询块,用来查询需要获取的数据
    主要的语句关键字包括 SELECT
③:DCL(Data Control Language、数据控制语言)
    用来定义数据库、表、字段、用户的某种特权和安全级别;还可以控制数据库操纵事务发生的时间及效果,对数据库实行监视等。
    主要的语句关键字包括 GRANT 、 REVOKE 、SAVEPOINT⑤、TCL(事务控制语言、Trasactional Control Languag)
    TCL经常被用于快速原型开发、脚本编程、GUI和测试等方面(用来事务控制)
    主要的语句关键字包括 COMMIT、ROLLBACK
注:TCL也可以归为到DCL中,为了细分才拆分;DQL也可以归为到DML中,因为查询的频繁被单独从DML摘出来

2:SQL语言的基本规范

1:基本规范
  ①:SQL可以写在一行或者多行。为了提高可读性,各子句分行写,必要时使用缩进
  ②:每条命令可以使用 ; 或 \g 或 \G 结束
  ③:关键字不能被缩写也不能分行
  ④:必须保证所有的()、单引号、双引号是成对结束的
  ⑤:必须使用英文状态下的半角输入方式
  ⑥:字符串型和日期时间类型的数据尽量使用单引号(' ')表示
  ⑦:列的别名,尽量使用双引号(" "),而且不建议省略 as 
  ⑧:若语句写错了或不想执行本次语句,则需要在语句最后面输入 \c 代表放弃这个语句的执行 2:大小写规范 ①:MySQL 在 Windows 环境下是大小写不敏感的 ②:MySQL 在 Linux 环境下是大小写敏感的 数据库名、表名、表的别名、变量名是严格区分大小写的 关键字、函数名、列名(字段名)、列的别名(字段的别名) 是忽略大小写的 ③:推荐采用统一的书写规范 数据库名、表名、表别名、字段名、字段别名等都小写 SQL 关键字、函数名、绑定变量等都大写 3:注释 单行注释:# 注释文字(MySQL特有的方式) 单行注释:-- 注释文字(-- 后面必须包含一个空格。) 多行注释:/* 注释文字 */ 4:基本命名规则 ①:数据库、表名不得超过30个字符,变量名限制为29个 ②:命名时只能包含 A–Z, a–z, 09, _ 共63个字符 ③:数据库名、表名、字段名等对象名中间不要包含空格 ④:同一个MySQL软件中,数据库不能同名;同一个库中,表不能重名;同一个表中,字段不能重名 ⑤:必须保证你的字段没有和保留字、数据库系统或常用方法冲突。 如果坚持使用,请在SQL语句中使用 ``(着重号)引起来;如 `demo_table` ⑥:保持字段名和类型的一致性,在命名字段并为其指定数据类型的时候一定要保证一致性。 假如数据类型在一个表里是整数,那在另一个表里可就别变成字符型了 后面后针对这些规范执行,也会再后面强调重要规范

测试数据库及下文的案例数据库创建

基础资料:MySQL运算符介绍

二:DQL基础语法

基本语法
  SELECT 查询的字段 FROM 表名;
①:查询出来的结果集数据都是放在一个虚拟的表展示;
②:SELECT 查询列表类似 java 的打印语句
SELECT 后根的查询列表可以由多个组成,中间使用逗号隔开
如:SELECT 字段1 , 字段2 , 字段3 ... FROM 表名;
问:查询student表中的sname、sage、saddress字段
答:SELECT sname,sage,ssex,saddress FROM student;

SQL执行顺序:
  
SELECT sname,sage,ssex,saddress FROM student;
基本语句执行顺序是先看后面 FROM 是否可以找到此表,找到再执行 SELECT 查询
# ①:from student
# ②:select sname,sage,saddress

Ⅰ:查询列表可以是字段、表达式、常量、函数等

查询常量:select 100 from dual;
查询表达式:select 12%5;
查询单个字段:select sname from student
查询多个字段:select sname , saddress from student;
查询所有字段:select  *  from student;    -- 一般不推荐使用
查询函数当前使用的数据库:select database();
查询函数当前数据库版本:select version();
查询函数当前数据库使用者:select user();

注:一般情况下,除非需要使用表中所有的字段数据,最好不要使用通配符‘*’。使用通配符虽然可以节省输入查询语句的时间,但是
  获取不需要
的列数据通常会降低查询和所使用的应用程序的效率。通配符的优势是,当不知道所需要的列的名称时,可以通过它获
  取它们。在生产环境下,
不推荐你直接使用 SELECT * 进行查询。
注:在没有 FROM 后面的表时也可以使用 DUAL 代替,代表不用查找具体的数据库表了
  如:select 100+1 from dual (切记这样的使用dual则不能再查询 * )

Ⅱ:起别名

select  sname  as  姓名  from  student;
select  sname  as  "姓名"  from  student;
select  sname  as  "姓   名"  from student;
select  sname  姓名  from  student;
说明:使用别名可用 AS 关键字或者空格隔开,也可以用引号把别名引起来,当别名中间有空格就必须用引号引起来
注:议别名简短,见名知意;列名和别名之间加入关键字AS,别名使用双引号,

Ⅲ:MySQL中的加号 “+” 的使用

①:加法运算 前提2个操作数都是数值
select 100+200==> 300
②:其中一个操作数为字符型,将字符型数据强制转换成数值型,如果无法转换,则直接当做 0 处理
select  'hello'+100==> 0+100 ==> 100
  注:按照Java中的语法应该为 hello100 但是SQL却不同,要想拼接则使用 CONCAT(value,value,...)函数 ④:其中一个操作数为null和任何数相加都为null select null+100==> null

Ⅳ:去重查询 DISTINCT

查询数据库student学生表所有学生的年龄
    select distinct sage from student;
    +------+
    | sage |
    +------+
    |   21 |
    |   23 |
    |   24 |
    |   25 |
    |   22 |
    +------+
    5 rows in set (0.00 sec)        -- 查询出5条记录

Ⅴ:扩展使用 CONCAT()拼接函数

使用拼接函数:select concat( '值'  ,'值' , ...);
注:如果一旦里面的值有一个为null,从而所有的都变成null
    select concat('博客园','蚂蚁小哥') AS "打 印";
    +-----------------------+
    | 打 印                 |
    +-----------------------+
    | 博客园蚂蚁小哥          |
    +-----------------------+
    1 row in set (0.00 sec)
举例存在NULL的值拼接(因为null写不出来,所以我通过表查询出来)
    select sid,sname,saddress from student where saddress is null;
    +-----+-----------+----------+
    | sid | sname     | saddress |
    +-----+-----------+----------+
    |  20 | 周迟蒲    | NULL     |
    |  48 | 张桥共    | NULL     |
    +-----+-----------+----------+
    select concat(sname,' --- ',saddress) from student where sid = '20';
    +--------------------------------+
    | concat(sname,' --- ',saddress) |
    +--------------------------------+
    | NULL                           |
    +--------------------------------+

Ⅵ:扩展使用 IFNULL(value1,value2);

判断 值1 如果为空就显示 值2 反之显示 值1
select concat(sname, '---',ifnull(saddress , '地址为空')) as "显示" from student where sid = 20;
+--------------------------+
| 显示                     |
+--------------------------+
| 周迟蒲---地址为空          |
+--------------------------+
select ifnull(null,'')
+--------------------+
| ifnull(null,'')  |
+--------------------+
||
+--------------------+

Ⅶ:查询表结构的几种方式

desc 表名;
describe 表名;
show columns from 表名;
show create table 表名;  -- 这个特殊,查看创建表语句
mysql> desc student;
+----------+----------------------+------+-----+------------+----------------+
| Field    | Type                 | Null | Key | Default    | Extra          |
+----------+----------------------+------+-----+------------+----------------+
| sid      | int(11)              | NO   | PRI | NULL       | auto_increment |
| sname    | varchar(5)           | NO   |     | NULL       |                |
| ssex     | enum('','')       | YES  |     ||                |
| sage     | tinyint(11) unsigned | NO   |     | 20         |                |
| scredit  | double(3,1)          | YES  |     | 60.0       |                |
| smoney   | decimal(4,1)         | YES  |     | 0.0        |                |
| saddress | varchar(10)          | YES  |     | NULL       |                |
| senrol   | date                 | YES  |     | 2020-12-12 |                |
| fid      | int(11)              | YES  | MUL | NULL       |                |
| tid      | int(11)              | YES  | MUL | NULL       |                |
+----------+----------------------+------+-----+------------+----------------+
Field:表示字段名称。
Type:表示字段类型,
Null:表示该列是否可以存储NULL值。
Key:表示该列是否已编制索引。
    PRI表示该列是表主键的一部分;
    UNI表示该列是UNIQUE索引的一部分;
    MUL表示在列中某个给定值允许出现多次。
Default:表示该列是否有默认值,如果有,那么值是多少。
Extra:表示可以获取的与给定列有关的附加信息,例如 AUTO_INCREMENT 等。

Ⅷ:着重号

我们需要保证表中的字段、表名等没有和保留字、数据库系统或常用方法冲突。
如果真的相同,请在SQL语句中使用一对``(着重号)引起来。如下面SQL中的order表和MySQL的排序关键字冲突 如:
select * from `order`

Ⅸ:总结

  MySQL中的SQL语句是不区分大小写的,因此SELECT和select的作用是相同的,但是,许多开发人员习惯将关键字大写、数据列和表名小写,读者也应该养成一个良好的编程习惯,这样写出来的代码更容易阅读和维护
  在 MySQL 里面,空值不等于空字符串。一个空字符串的长度是 0,而一个空值的长度是空。而且,在 MySQL 里面,空值是占用空间的。

三:条件查询 WHERE

基本语法:

  SELECT 查询的字段 FROM 表名 WHERE 筛选条件

问:查询student表中的sname、sage、saddress字段;并且为男生
答:SELECT sname,sage,ssex,saddress FROM student where ssex = '';
SQL执行顺序
    # ①:执行 from      查找表
    # ②:执行 where     筛选数据
    # ③:执行 select    查询指定字段展示到虚拟表    

四:查询并排序 ORDER BY

基本语法:

  SELECT 查询的字段 FROM 表名 WHERE 筛选条件 ORDER BY 排序字段 [ASC(默认)/DESC] 

注:如果没有使用排序操作,默认情况下查询返回的数据是按照添加数据的顺序显示的;可使用别名代替排序字段名

ASC:【ascend/升序】  DESC:【descend/降序】

单列排序:
  问:查询学生表中姓名、年龄、学分、零花钱这四个字段,学分从高到底(降序)排序
    SELECT sname,sage,scredit,smoney from student ORDER BY scredit DESC 
  问:查询学生表中姓名、年龄、学分、零花钱这四个字段,学分从低到高(升序)排序
    SELECT sname,sage,scredit,smoney from student ORDER BY scredit 

多列排序:
  问:查询学生表中姓名、年龄、学分、零花钱这四个字段,年龄从高到底(降序)排序,并且学分从低到高(升序)排序
    SELECT sname,sage,scredit,smoney from student ORDER BY sage DESC , scredit

SQL执行顺序:
执行顺序
    1. 执行from          查询表是否存在
    2. 执行where         筛选符合条件的数据
    3. 执行select       查询指定字段展示到虚拟表
    4. 执行order by    对指定的字段进行排序

注:可以使用不在SELECT列表中的列排序。在对多列进行排序的时候,首先排序的第一列必须有相同的列值,才会对第二列进行排序。如果第一列数据中所有值都是唯一的,将不再对第二列进行排序。

五:查询并分页 LIMIT n,m

基本语法
  SELECT 查询的字段 FROM 表名 WHERE 筛选条件 LIMIT 【位置偏移量,】显示的条目数
查询返回的记录太多了,查看起来很不方便,所以LIMIT可以对数据进行分页显示;所谓分页显示,就是将数据库中的结果集,一段一段显示出来需要的条件。

分页查询:
    问:查询学生表中前10条数据
        SELECT * FROM student LIMIT 0,10    或者 SELECT * FROM student LIMIT 10
    问:查询学生表中11条到20条数据
        SELECT * FROM student LIMIT 10,10
    问:查询学生表中21条到30条数据
        SELECT * FROM student LIMIT 20,10
  规律:分页显式公式:(当前页数-1)*每页条数,每页条数
    SELECT * FROM table LIMIT(PageNo - 1)*PageSize,PageSize;
    注意:LIMIT 子句必须放在整个SELECT语句的最后!

MySQL 8.0中可以使用“LIMIT 3 OFFSET 4”,意思是获取从第5条记录开始后面的3条记录,和“LIMIT 4,3;”返回的结果相同。
SELECT * FROM student LIMIT 3 OFFSET 4
mysql> SELECT * FROM student LIMIT 3 OFFSET 4;
+-----+-----------+------+------+---------+--------+--------------+------------+------+------+
| sid | sname     | ssex | sage | scredit | smoney | saddress     | senrol     | fid  | tid  |
+-----+-----------+------+------+---------+--------+--------------+------------+------+------+
|   5 | 钱勤堃    | 女   |   23 |     8.0 |  101.7 | 安徽合肥     | 2018-01-05 |    5 |    1 |
|   6 | 潘恩依    | 女   |   24 |    22.0 |  967.7 | 安徽合肥     | 2018-11-18 |    6 |    4 |
|   7 | 陈国柏    | 女   |   24 |    10.0 |  697.5 | 安徽六安     | 2018-10-12 |    7 |    2 |
+-----+-----------+------+------+---------+--------+--------------+------------+------+------+
指定条目数查询:
  问:查询学生表中查询出来的三条记录
    SELECT * FROM student LIMIT 3

  使用分页优点:约束返回结果的数量可以减少数据表的网络传输量 ,也可以提升查询效率 。如果我们知道返回结果只有1条,就可以使用 LIMIT 1 ,告诉 SELECT 语句只需要返回一条记录即可。这样的好处就是 SELECT 不需要扫描完整的表,只需要检索到一条符合条件的记录即可返回。

六:多表查询

  多表查询也称为关联查询,指两个或者更多的表一起完成查询操作;不过这些能关联查询的表是有一些联系的(一对多,一对一);它们之间是一定有关联字段,这个关联字段可能建立了外键,也可能没有建立外键。

1:一对一、一对多、多对多概念

  我就以博文开头的SQL建表语句资料进行一个说明这几个概念

一对一:表中的某一列对应着另外一张表中的某一列(A表有10条数据,关联对应的B表也会有10条数据)
    这种一一对应的关系并不常见。一一对应的列可以放在一个表里面,其实这样做一定有原因,例如,把一张有50个字段的大表分成若干小表,
    可以确保安全,防止信息泄露、防止单表数据量过大影响查询效率。
    比如学生表和家庭表的关系(这里我们就考虑一个学生只有一个家庭,一个家庭只有一个孩子的情况),对于每个学生的家庭信息是
    一个敏感信息,敏感信息除了统计学生家庭的管理员可以看到之外,其他人不能看到,这时家庭这些列数据经常被放在一个单独的表里面。
一对多:一张表中的一行数据对应另外表中的多行数据 这种关系在关系型列表中最常见。比如按照我上面的示例来说,一个老师表(辅导员)对应着多个学生数据信息(一个辅导员多个学生)
多对一:表中的多列对应另外表中的多列 可以将多对多关系视为通过中间表连接的两个一对多关系。中间表通常称为“连接表” 也称为“交叉引用表”。该表用于将其他两个表连接在一起 比如说一个学生可以报多个社团,一个社团同时也可以被多个学生报名,这就是一个多对多关系,说白了就是两个一对多,不过多对多需要 一个连接表来进行两个表的关联

2:多表连接及笛卡尔积(或交叉连接)理解

案例:查询辅导员信息和对应的学生信息
    SELECT t.tid, t.tname, s.sid, s.sname FROM teacher t, student s
    执行完我们会发现,出现了很多条数据,就是奇怪,先说一下,这个是因为没有连接条件导致的笛卡尔积错误,笛卡尔积查出的数据是连接表的乘积,
    就比如teacher表4条数据,student表65条数据,那么上面的SQL查询出来260条数据

笛卡尔积(或交叉连接)的理解:
    笛卡尔乘积是一个数学运算。假设我有两个集合 X 和 Y,那么 X 和 Y 的笛卡尔积就是 X 和 Y 的所有可能组合,也就是第一个对象来自于 X,
    第二个对象来自于 Y 的所有可能。组合的个数即为两个集合中元素个数的乘积数。

SQL92中,笛卡尔积也称为交叉连接,英文是CROSS JOIN
SQL99中,使用CROSS JOIN关键字表示交叉连接。它的作用就是可以把任意表进行连接,即使这两张表不相关。
在MySQL中如下情况会出现笛卡尔积:
    -- SQL92语法造成交叉连接
    SELECT t.tid, t.tname, s.sid, s.sname FROM teacher t, student s
    -- SQL99语法造成交叉连接
    SELECT t.tid, t.tname, s.sid, s.sname FROM teacher t CROSS JOIN student s 交叉连接写法
    SELECT t.tid, t.tname, s.sid, s.sname FROM teacher t INNER JOIN student s 内连接没有on连接条件
    SELECT t.tid, t.tname, s.sid, s.sname FROM teacher t JOIN student s 内连接没有on连接条件
笛卡尔积的错误会在下面条件下产生:
    ①:省略多个表的连接条件(或关联条件)
    ②:连接条件(或关联条件)无效
    ③:所有表中的所有行互相连接
    为了避免笛卡尔积, 可以在 WHERE 加入有效的连接条件。
    加入连接条件后,查询语法:
    SELECT table1.column, table2.column FROM table1, table2 WHERE table1.column1 = table2.column2; #连接条件
        在WHERE子句中写入连接条件即可
            -- SQL92语法解决交叉连接(笛卡尔积)
            SELECT t.tid, t.tname, s.sid, s.sname FROM teacher t, student s WHERE t.tid = s.sid
            -- SQL99语法解决交叉连接(笛卡尔积)
            SELECT t.tid, t.tname, s.sid, s.sname FROM teacher t CROSS JOIN student s USING(tid)
            SELECT t.tid, t.tname, s.sid, s.sname FROM teacher t INNER JOIN student s ON t.tid = s.tid
            SELECT t.tid, t.tname, s.sid, s.sname FROM teacher t JOIN student s USING(tid)
            这里的每个表名我使用了别名方式,所以只能使用别名的方式来点出指定的属性
多表连接(查询当前辅导员的学生和学生的家庭信息)
     -- SQL92语法解决两张以上的表连接
        SELECT t.tid, t.tname, s.sid, s.sname, f.fguardian,ftel FROM teacher t, student s, family f
            WHERE t.tid = s.tid AND s.fid = f.fid
     -- SQL99语法解决两张表以上的连接
        SELECT t.tid, t.tname, s.sid, s.sname, f.fguardian,ftel  FROM 
            teacher t INNER JOIN student s ON t.tid = s.tid
            INNER JOIN family f ON s.fid = f.fid

3:多表查询(等值连接&非等值连接)

等值连接:
    -- SQL92语法等值连接
    SELECT t.tid, t.tname, s.sid, s.sname FROM teacher t, student s WHERE t.tid = s.tid
    -- SQL99语法等值连接
    SELECT t.tid, t.tname, s.sid, s.sname FROM teacher t INNER JOIN student s ON t.tid = s.tid
非等值连接:
    非等值连接就是两种或者两张以上的表之间没有一个指定的连接字段,其实非等值连接可以做等级评定
    -- SQL92语法非等值连接
        SELECT t.tid, t.tname, s.sid, s.sname, s.smoney, m.`level` 
        FROM teacher t,student s,money_grades m 
        WHERE t.tid = s.tid AND s.smoney BETWEEN m.highest AND m.lowest 
    -- SQL99语法等值连接
        SELECT t.tid, t.tname, s.sid, s.sname, s.smoney, m.`level` 
        FROM teacher t INNER JOIN student s ON t.tid = s.tid INNER JOIN money_grades m 
        WHERE s.smoney BETWEEN m.highest AND m.lowest 

4:多表查询(自连接&非自连接)

CREATE TABLE `tabbar` (
  `id` int(11) NOT NULL PRIMARY KEY COMMENT '主键ID',
  `tab_name` varchar(10) DEFAULT NULL COMMENT '导航标签名称',
  `parent_id` int(11) DEFAULT NULL COMMENT '父级导航名称'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `tabbar` (`id`, `tab_name`, `parent_id`) VALUES
(1, '游戏模块', NULL),(2, '英雄联盟', 1),(3, '穿越火线', 1),
(4, '体育模块', NULL),(5, '排球比赛', 4),(6, '篮球比赛', 4);
自连接基本建表语句
非自连接:上面的案例多表连接都是非自连接(多张不同的表进行连接)
自连接:当表a和表b本质上是同一张表,只是用区别名的方式虚拟出来两张表进行一个连接
    查询tabbar表中的标签名称和父类的标签名称
     -- SQL92语法自连接
        select a.tab_name , b.tab_name from tabbar a, tabbar b WHERE a.id = b.parent_id
     -- SQL99语法自连接
        select a.tab_name , b.tab_name from tabbar a RIGHT JOIN tabbar b ON a.id = b.parent_id
        select a.tab_name , b.tab_name from tabbar a INNER JOIN tabbar b ON a.id = b.parent_id

5:多表查询(内连接&外连接)

内连接:
    合并具有同一列的两个以上的表的行, 结果集中不包含一个表与另一个表不匹配的行
    -- SQL92语法内连接(上面案例92语法基本都为内连接)
        SELECT t.tid, t.tname, s.sid, s.sname FROM teacher t, student s WHERE t.tid = s.tid
    -- SQL99语法内连接(上面案例99语法基本都为内连接)
        SELECT t.tid, t.tname, s.sid, s.sname FROM teacher t INNER JOIN student s ON t.tid = s.tid
外连接:
    两个表在连接过程中除了返回满足连接条件的行以外还返回左(或右)表中不满足条件的行,这种连接称为左(或右)外连接。
    没有匹配的行时, 结果表中相应的列为空(NULL)。
    说明:外连接分为左外连接、右外连接、全外连接(满外连接)
        如果是左外连接,则连接条件中左边的表也称为主表,右边的表称为从表。
        如果是右外连接,则连接条件中右边的表也称为主表,左边的表称为从表。
        在SQL92中只有左右连接,没有全外连接(满外连接)
    -- SQL92语法外连接(使用(+)创建左或者右连接)
       SELECT s.sname, s.saddress, f.fguardian, f.ftel FROM student s, family f WHERE s.fid(+) = f.fid 左连接
       SELECT s.sname, s.saddress, f.fguardian, f.ftel FROM student s, family f WHERE s.fid(+) = f.fid 右连接
       注:上面两种方式在MySQL下是不支持92语法的 (+) 来实现左右连接,只可以内连接(Oracle支持)
    -- SQL99语法外连接
       LEFT OUTER JOINLEFT JOIN(左外连接)
       RIGHT OUTER JOINRIGHT JOIN(右外连接)
       FULL OUTER JOINFULL JOIN(全外连接、满外连接)注:MySQL不支持这种全外连接
       SELECT s.sname, s.saddress, f.fguardian, f.ftel FROM student s LEFT JOIN family f ON s.fid = f.fid 左外
       SELECT s.sname, s.saddress, f.fguardian, f.ftel FROM student s RIGHT JOIN family f ON s.fid = f.fid 右外

补充:全外连接(满外连接)

  全外连接的结果 = 左右表匹配的数据 + 左表没有匹配到的数据 + 右表没有匹配到的数据。SQL99是支持满外连接的。使用FULL JOIN 或 FULL OUTER JOIN来实现。需要注意的是,MySQL不支持FULL JOIN,但是可以用 LEFT JOIN UNION RIGHT join代替。

6:合并查询UNION的使用

  合并查询结果利用UNION关键字,可以将SELECT语句查询出的多条结果进行一个结果组合变成单个结果集。注:多个SELECT查询出的结果合并时,列数、类型必须相同,并且相互对应;字段名称也尽量保持一致,因为字段名称取的是第一个SELECT查询出的结果的列名。各个SELECT语句之间使用UNION或UNION ALL关键字分隔。

基本语法:
  SELECT column,... FROM table1 UNION [ALL] SELECT column,... FROM table2
    UNION:操作符返回两个查询的结果集的并集,去除重复记录。
    UNION ALL:操作符返回两个查询的结果集的并集。对于两个结果集的重复部分,不去重。
  注:执行UNION ALL语句时所需要的资源比UNION语句少。如果明确知道合并数据后的结果数据不存在重复数据,或者不需要去除重复的数据,则尽量使用UNION ALL语句,以提高数据查询的效率。

举例:查询学生数据中性别男的一批数据在前面,性别女的一批数据在后面,并进行年龄降序排列
    (SELECT * FROM student where ssex = '' ORDER BY sage DESC)
    UNION ALL 
    (SELECT * FROM student where ssex = '' ORDER BY sage DESC)

7:使用MySQL中SQL99语法完成7种SQL JOINS连接

  通过上面我们可以知道,在SQL92中只有内连接 ,但是在SQL99中有7种连接分别如下介绍的,但是MySQL支持的SQL92只有内连接和左右连接,其它的连接则我们需要使用特殊手段进行处理,下面则进行说明

七种连接方式实现:
①:内连接 INNER JOINMySQL支持 也叫自然连接同时也叫全等连接,即左边表和右边表共有数据的查询)
    学生表和家庭表进行一个内连接,保证学生数据和家庭数据进行一一匹配,不符合则被筛除
    SELECT s.sname, s.saddress, f.fguardian, f.ftel FROM student s INNER JOIN family f ON s.fid = f.fid
②:左(外)连接 LEFT JOINMySQL99支持 即查询左边表的独有数据)
    学生表和家庭表进行一个左连接,保证学生数据不遗漏,无法匹配的学生数据的家庭则位null
    SELECT s.sname, s.saddress, f.fguardian, f.ftel FROM student s LEFT JOIN family f ON s.fid = f.fid
③:右(外)连接 RIGHT JOINMySQL99支持 即查询右边表的独有数据)
    学生表和家庭表进行一个左连接,保证家庭数据不遗漏,无法匹配的家庭数据的学生则位null
    SELECT s.sname, s.saddress, f.fguardian, f.ftel FROM student s RIGHT JOIN family f ON s.fid = f.fid
④:其它连接 (A独有且无法与B关联的数据)
    查找左表独有部分,则需查找左表和右表的共有部分并加上左表的独有部分,再将左表和右表的共有部分剔除即可
    (也就是挑选右的主键为空的数据)
    SELECT s.sname, s.saddress, f.fguardian, f.ftel FROM student s LEFT JOIN family f ON s.fid = f.fid
        WHERE f.fid IS NULL
⑤:其它连接 (B独有且无法与A关联的数据)
    查找右表独有部分,则需查找右表和左表的共有部分并加上右表的独有部分,再将右表和左表的共有部分剔除即可
    (也就是挑选左的主键为空的数据)
    SELECT s.sname, s.saddress, f.fguardian, f.ftel FROM student s RIGHT JOIN family f ON s.fid = f.fid
        WHERE s.sid IS NULL;
⑥:全连接(满外连接) FULL OUTER JOINMySQL不支持
    表示将左表和右表的公共部分及左表、右表的独有部分,所有数据都查询出来
    全连接 FULL OUTER JOIN 在MySQL语法报错!但是可以通过UNION关键字进行查询。UNION会把重复的行去掉,返回的行都是唯一的。
    如果想保留重复行,可以使用UNION ALL关键字。
    -- 使用UNION方式实现:(不推荐)
    UNION其实就是将左表和右表的共有部分和左表的独有部分(即左连接LEFT JOIN),加上右表的独有部分(即右连接RIGHT JOIN)
    合并起来,并进行去重即可。
    (SELECT s.sname, s.saddress, f.fguardian, f.ftel FROM student s LEFT JOIN family f ON s.fid = f.fid)
    UNION
    (SELECT s.sname, s.saddress, f.fguardian, f.ftel FROM student s RIGHT JOIN family f ON s.fid = f.fid);
    -- 使用UNION ALL方式实现:(推荐)
    这种方式就是方式②和方式⑤结合,或者方式③和方式④结合;这里我就以方式②和方式⑤结合
    (SELECT s.sname, s.saddress, f.fguardian, f.ftel FROM student s LEFT JOIN family f ON s.fid = f.fid)
    UNION ALL
    (SELECT s.sname, s.saddress, f.fguardian, f.ftel FROM student s RIGHT JOIN family f ON s.fid = f.fid
        WHERE s.sid IS NULL);
⑦:其它连接 (A独有且无法与B关联的数据和B独有且无法与A关联的数据)
    查询左表独有部分并加上右表独有部分;实际就是方式④和方式⑤结合,并使用UNION或者UNION ALL进行连接即可。
    (SELECT s.sname, s.saddress, f.fguardian, f.ftel FROM student s LEFT JOIN family f ON s.fid = f.fid
        WHERE f.fid IS NULL)
    UNION ALL
    (SELECT s.sname, s.saddress, f.fguardian, f.ftel FROM student s RIGHT JOIN family f ON s.fid = f.fid
        WHERE s.sid IS NULL);

8:SQL99语法新特性之自然连接

  SQL99 在 SQL92 的基础上提供了一些特殊语法,比如 NATURAL JOIN 用来表示自然连接。我们可以把自然连接理解为 SQL92 中的等值连接。它会帮你自动查询两张连接表中所有相同的字段,然后进行等值连接

在SQL92中使用:
    SELECT a.字段1, b.字段1, ... FROM 表A a, 表B b ON a.字段2 = b.字段2 AND a.字段3 = b.字段3
在SQL99中使用:
    SELECT a.字段1, b.字段1, ... FROM 表A a NATURAL JOIN 表B b

9:SQL99语法新特性之USING连接

当我们进行连接的时候,SQL99还支持使用 USING 指定数据表里的同名字段进行等值连接。但是只能配合JOIN一起使用。比如:
    原始:
        SELECT t.tid, t.tname, s.sid, s.sname FROM teacher t INNER JOIN student s ON t.tid = s.tid
    优化(tid为左表和右表相同字段):
        SELECT t.tid, t.tname, s.sid, s.sname FROM teacher t INNER JOIN student s USING(tid)

10:多表查询总结

表连接的约束条件可以有三种方式:WHERE, ON, USING
  WHERE:适用于所有关联查询
  ON:只能和JOIN一起使用,只能写关联条件。虽然关联条件可以并到WHERE中和其他条件一起写,但分开写可读性更好。
  USING:只能和JOIN一起使用,而且要求两个关联字段在关联表中名称一致,而且只能表示关联字段值相等
  注:我们要控制连接表的数量。多表连接就相当于嵌套 for 循环一样,非常消耗资源,会让 SQL 查询性能下降得很严重,因此不要连接不必要的表。在许多 DBMS 中,也都会有最大连接表的限制

11:基本多表查询案例(结合上方的7种SQL连接)

-- 门派表 
CREATE TABLE `t_dept`(
    `id`       INT ( 11 ) NOT NULL AUTO_INCREMENT, -- 门派ID
    `deptName` VARCHAR(30) DEFAULT NULL,           -- 门派姓名
    `address`  VARCHAR(40) DEFAULT NULL,           -- 门派地址
    PRIMARY KEY (`id`)
) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
-- 人员表
CREATE TABLE `t_emp`(
    `id`     INT ( 11 ) NOT NULL AUTO_INCREMENT,    -- 人员ID
    `name`   VARCHAR(20) DEFAULT NULL,              -- 人员姓名
    `age`    INT ( 3 ) DEFAULT NULL,                -- 人员年龄
    `deptId` INT ( 11 ) DEFAULT NULL,               -- 人员引用的门派ID
    empno    INT NOT NULL,                          -- 人员工号
    PRIMARY KEY (`id`),
    KEY      `idx_dept_id` ( `deptId` )
) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
-- 添加数据
INSERT INTO t_dept(deptName,address) VALUES('华山','华山');
INSERT INTO t_dept(deptName,address) VALUES('丐帮','洛阳');
INSERT INTO t_dept(deptName,address) VALUES('峨眉','峨眉山');
INSERT INTO t_dept(deptName,address) VALUES('武当','武当山');
INSERT INTO t_dept(deptName,address) VALUES('明教','光明顶');
INSERT INTO t_dept(deptName,address) VALUES('少林','少林寺');
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('风清扬',90,1,100001);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('岳不群',50,1,100002);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('令狐冲',24,1,100003);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('洪七公',70,2,100004);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('乔峰',35,2,100005);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('灭绝师太',70,3,100006);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('周芷若',20,3,100007);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张三丰',100,4,100008);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张无忌',25,5,100009);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('韦小宝',18,null,100010);
基本的建表和数据插入
-- 1.所有有门派的人员信息 (A、B两表共有)
SELECT d.deptName ,d.address ,e.name,e.age FROM t_dept d INNER JOIN t_emp e ON d.id = e.deptId
-- 2.列出所有用户,并显示其门派信息 (A的全集)
SELECT d.deptName ,d.address ,e.name,e.age FROM t_dept d RIGHT JOIN t_emp e ON d.id = e.deptId
-- 3.列出所有门派 (B的全集)
SELECT d.deptName ,d.address ,e.name,e.age FROM t_dept d LEFT JOIN t_emp e ON d.id = e.deptId
-- 4.所有不入门派的人员 (A的独有)
SELECT  d.deptName ,d.address ,e.name,e.age  FROM t_dept d RIGHT JOIN t_emp e ON d.id = e.deptId WHERE d.id IS NULL
-- 5.所有没人入的门派 (B的独有)
SELECT  d.deptName ,d.address ,e.name,e.age  FROM t_dept d LEFT JOIN t_emp e ON d.id = e.deptId WHERE e.id IS NULL
-- 6.列出所有人员和门派的对照关系 (AB全有)
    -- 注 MySQL Full Join的实现 因为MySQL不支持FULL JOIN,下面是替代方法 left join + union(可去除重复数据) + right join
(SELECT d.deptName ,d.address ,e.name,e.age FROM t_dept d RIGHT JOIN t_emp e ON d.id = e.deptId)
UNION
(SELECT d.deptName ,d.address ,e.name,e.age FROM t_dept d LEFT JOIN t_emp e ON d.id = e.deptId)
-- 7.列出所有没入派的人员和没人入的门派 (A的独有+B的独有)
(SELECT d.deptName ,d.address ,e.name,e.age FROM t_dept d RIGHT JOIN t_emp e ON d.id = e.deptId WHERE d.id IS NULL)
UNION ALL
(SELECT d.deptName ,d.address ,e.name,e.age FROM t_dept d LEFT JOIN t_emp e ON d.id = e.deptId WHERE e.id IS NULL)
具体案例及实现

七:MySQL子查询

  子查询可以按照两种方式进行分类。若按照期望的数量,可以将子查询分为标量子查询(单行子查询)和多值子查询(多行子查询);若按查询对外部查询的依赖,可分为不相关子查询(self-contained subquery)和相关子查询(correlated subquery)。注:标量子查询和多值子查询既可以是独立子查询,也可以是相关子查询。

子查询是指在一个SELECT语句中嵌套另一个SELECT语句。MYSQL数据库从4.1版本开始支持子查询,并且支持所有SQL标准的子查询,
也扩展了一些其独有的子查询标准。下面是一个子查询:
    SELECT * FROM t1 WHERE colimn1 = (SELECT column1 FROM t2);
在这个实例中,SELECT * FROM t1 是外部查询(outer query),SELECT column1 FROM t2 是子查询。
一般来说,称子查询嵌套(nested)于外部查询中。实际也可以将两个或两个以上的子查询进行嵌套。需要注意的是,子查询必须包含括号。
一个子查询会返回一个标量(单一值)、一个行、一个列或一个表(一行或多行及一列或多列),这些子查询被称为标量、行、列和表子查询。
可返回一个特定种类结果的子查询经常只能用于特定的语境中。子查询可以包括普通SELECT可以包含的任何关键词或子句, 如DISTINCT、GROUP BY、ORDER BY、LIMIT、JOIN、UNION等。但注意的是子查询语句只能在非GROUP BY和非LIMIT的外部查询里写子查询
子查询的限制是其外部语句必须是以下语句之一:SELECT、INSERT、UPDATE、DELETE、SET或DO。还有一个限制是,
目前我们修改一个表的时候子查询不能查询同一张表,虽然这样的操作可用于普通的DELETE、INSERT、REPLACE和UPDATE语句中, 但是对子查询不可以修改时进行这样的查询操作。

  mysql1093报错:(这是一个修改时使用子查询查询修改的表)
    1093错误为:修改一个表的时候子查询不能是同一个表,解决办法:把子查询再套一层,变成原来表的孙子查询就可以了,例如:
    insert into gg set id3= (select c.a+1 from (select max(id3) as a from gg) c) 这是对的
    但是 insert into gg set id3= (select max(id3) as a from gg) 就会报1093错误

 

1:一个简单的子查询案例(单行子查询)

  查询其它学生零花钱(smoney)比 "丘耿徽" 零花钱高的其它学生

-- 使用单行子查询的方式
    SELECT sname,smoney FROM student WHERE smoney > ( SELECT smoney FROM student WHERE sname = "丘耿徽");
    -- 拆分:
        外查询:(查询比smoney条件大的数据)
            SELECT sname,smoney FROM student WHERE smoney > ?
        子查询:(查询指定学生的零花钱)
            SELECT smoney FROM student WHERE sname = "丘耿徽"
        -- 注:子查询需要使用括号框住

-- 使用自连接查询的方式查询
    SELECT s1.sname "姓名",s1.smoney "零花钱", s1.smoney - s2.smoney "相差金额"
    FROM student s1 INNER JOIN student s2 ON s1.smoney > s2.smoney
    WHERE s2.sname = "丘耿徽"
    -- 连接方式使用 “ON s1.smoney > s2.smoney” 说明只连接比当前smoney大的数据

2:单行不相关子查询

单行子查询的比较操作符:
    >   <   >=  <=  =   <>      
    注意:LIKE关键字后面也可以
-- 查询学生表中零花钱大于50号ID的学生信息
-- 使用单行不相关子查询
    SELECT * FROM student WHERE smoney > ( SELECT smoney FROM student WHERE sid = 50 )

-- 查询关联辅导员表外键(tid)和学生25号ID一样,并且零花钱比学生35号零花钱多
    -- 使用2个单行不相关子查询
    SELECT * FROM student WHERE
        tid = ( SELECT tid FROM student WHERE sid = 25 ) AND smoney > ( SELECT smoney FROM student WHERE sid = 35 )

-- 查询学生表零花钱最少的学生信息
    -- 使用单行不相关子查询
    SELECT * FROM student WHERE smoney = (SELECT MIN(smoney) FROM student)
    -- 使用排序和LIMIT查询
    SELECT * FROM student WHERE smoney IS NOT NULL ORDER BY smoney ASC LIMIT 1

-- 查询与ID 1号和7号学生的sage和smoney相同的其它学生
    -- 使用单行不相关子查询   (方式1 不成对比较)
    SELECT * FROM student WHERE
        sage IN( SELECT sage FROM student WHERE sid IN(1,7) ) AND
        saddress IN( SELECT saddress FROM student WHERE sid IN(1,7) )
    -- 使用单行不相关子查询   (方式2 成对比较 就是说拿(sage,saddress)一组条件在一行比较)
    SELECT * FROM student WHERE
        (sage,saddress) IN( SELECT sage,saddress FROM student WHERE sid IN(1,7) )

-- 关联辅导员和学生表,按照辅导员分组,查询辅导员ID为3的组信息的最低学生零花钱,并过滤出其它组高于ID为3的组的信息
    -- 在HAVING中使用单行子查询
    SELECT t.tid,t.tname,MIN(s.smoney) "minMoney" FROM student s RIGHT JOIN teacher t USING(tid)
    GROUP BY s.tid HAVING minMoney > (SELECT MIN(smoney) FROM student WHERE tid = 3)

-- 查询学生姓名(sname)、 学分(scredit),level(级别),若学生学分大于ID为4的学生学分时则level显示“优秀”否则“一般”
    -- 在CASE表达式中使用单列子查询
        SELECT sname, scredit,
               CASE
                   WHEN scredit >= (SELECT scredit FROM student WHERE sid = 4) THEN "优秀"
                   ELSE "一般"
                   END AS level
        FROM student ORDER BY level

3:多行不相关子查询

IN:
    等于列表中的任意一个
ANY:
    是一个MySQL运算符,如果对于任何子查询条件的比较为TRUE,则返回布尔值TRUE。换句话说,如果执行外部SQL查询时满足任何子查询条件,
    则此关键字返回true。ANY关键字必须跟在比较运算符之后。ANY运算符的作用类似于将表的值与子查询条件提供的结果集中的每个值进行比较
ALL:
   是一个MySQL运算符,如果执行外部SQL查询时满足所有子查询查出来的数据条件,则返回true
   外部查询语句条件找到与子查询的全部子查询数据所匹配,则返回 TRUE 结果。
SOME:
    实际上是ANY的别名,作用相同,一般常使用ANY
-- 查询其它学生中比住址saddress为NULL的任意一名学生的零花钱都要高的学生姓名、住址、零花钱
    SELECT sname,saddress,smoney FROM student
    WHERE smoney > ANY ( SELECT smoney FROM student WHERE saddress IS NULL )

-- 查询其它学生中比住址saddress为NULL的全部学生的零花钱都要高的学生姓名、住址、零花钱
    -- 因为子查询出来的有个smoney为NULL,所以NULL与其它学生比较都为NULL(NULL=false)
    SELECT sname,saddress,smoney FROM student
    WHERE smoney > ALL ( SELECT smoney FROM student WHERE saddress IS NULL )

-- 查询平均学分最低的班级的辅导员名称
    -- 使用不相关单行子查询
    SELECT t.tname , AVG(scredit) FROM student s RIGHT JOIN teacher t USING(tid)
    GROUP BY t.tname
    HAVING AVG(scredit) = (
        SELECT MIN(avgCredit) FROM (
            SELECT AVG(scredit) "avgCredit" FROM student WHERE tid IS NOT NULL GROUP BY tid) tabA)
    -- 使用不相关多行子查询
    SELECT t.tname , AVG(scredit) FROM student s RIGHT JOIN teacher t USING(tid)
    GROUP BY t.tname
    HAVING AVG(scredit) <=  ALL (
        SELECT AVG(scredit) FROM student WHERE tid IS NOT NULL GROUP BY tid)

4:单行/多行相关子查询

  相关子查询(Dependent Subquery 或 Correlated Subquery) 是指引用了外部查询列的子查询,即子查询会对外部查询的每行进行一次计算。但是在优化器内部,这是一个动态的过程,随情况的变化会有所不同,会有不止一种优化方式来处理相关子查询。
单行相关子查询模型:SELECT column1, column2, ... FROM table1 tA  WHERE column1 operator ( SELECT col1 FROM table2 WHERE expr1 = tA.expr2 )

FROM型的子查询:
    子查询是作为FROM的一部分,子查询要用()引起来,并且要给这个子查询取别名, 把它当成一张“临时的虚拟的表”来使用。
-- 按照非空地区分组求出每个地区的总零花钱,并查出零花钱总数大于5000的地区
    SELECT * FROM
     (SELECT saddress , SUM(smoney) sumMoney FROM student WHERE saddress IS NOT NULL GROUP BY saddress) copy1
    WHERE sumMoney >= 5000

-- 查询学生学分大于本班级平均学分的学生信息
    -- 使用单行相关子查询
    SELECT * FROM student stu WHERE scredit >= (SELECT AVG(scredit) FROM student WHERE tid = stu.tid)
    -- 使用FROM型子查询
    SELECT s.* FROM student s RIGHT JOIN
        ( SELECT tid,AVG(scredit) avgCredit FROM student WHERE tid IS NOT NULL GROUP BY tid ) tbA USING(tid)
    WHERE s.scredit > tbA.avgCredit

5:EXISTS关键字

有一个比较少见的MySQL关键词 exists,它可以应用在工作中遇到一些特定的业务场景,可以有更加多样化的解决方案

语法示例:SELECT column1 FROM tableA tA WHERE EXISTS ( SELECT * FROM tableB tB WHERE tB.column2 = tA.column1 );
括号中的子查询并不会返回具体的查询到的数据,只是会返回true或者false,如果外层sql的字段在子查询中存在则返回true,不存在则返回false
即使子查询的查询结果是null,只要是对应的字段是存在的,子查询中则返回true
总结一句:利用相关子查询,把外层查询的值代入EXISTS类型的子查询中,子查询查到数据则返回true,否则false
执行步骤:
    1:首先进行外层查询,在表tableA中查询满足条件的column1
    2:接下来进行内层查询,将满足条件的column1带入内层的表tableB中进行查询
    3:如果内层的表tableB满足查询条件,则返回true,该条数据保留
    4:如果内层的表tableB不满足查询条件,则返回false,则删除该条数据
    5:最终将外层的所有满足条件的数据进行返回
相关示例:其中介绍EXISTS方式和IN的方式
--
查询已经选课的学生名单 -- 使用相关子查询EXISTS方式 SELECT * FROM student stu WHERE EXISTS (SELECT * FROM course co WHERE co.sid = stu.sid ) -- 使用多行不相关子查询 SELECT * FROM student WHERE sid IN (SELECT DISTINCT sid FROM course) -- 查询未选课的学生名单 -- 使用相关子查询EXISTS方式 SELECT * FROM student stu WHERE NOT EXISTS (SELECT * FROM course co WHERE co.sid = stu.sid ) -- 使用多行不相关子查询 SELECT * FROM student WHERE sid NOT IN (SELECT DISTINCT sid FROM course) -- 查询未选课的并且学生低于60分以下的全部学生 -- 使用相关子查询EXISTS方式 SELECT * FROM student stu WHERE scredit < 60 AND NOT EXISTS ( SELECT * FROM course WHERE sid = stu.sid ) -- 使用多行不相关子查询 SELECT * FROM student stu WHERE scredit < 60 AND sid NOT IN (SELECT DISTINCT sid FROM course) -- 查询已选课的辅导员王老师和李老师的班级学生 -- 使用相关子查询EXISTS方式 SELECT stu.sname,stu.scredit,te.tname FROM student stu LEFT JOIN teacher te USING(tid) WHERE EXISTS ( SELECT * FROM course co WHERE co.sid = stu.sid ) AND te.tname IN("王老师","李老师") -- 使用多行不相关子查询 SELECT stu.sname,stu.scredit,te.tname FROM student stu LEFT JOIN teacher te USING(tid) WHERE sid IN (SELECT DISTINCT sid FROM course) AND te.tname IN("王老师","李老师")
EXISTS与IN的效率比较
①:循环嵌套查询执行原理:
   循环由外向内,外层循环执行一次,内层循环则需要完整的执行一次,内层执行完后返回执行结果,外层循环继续执行,直到外层循环完全执行完成
②:循环优化策略:
    有了上面的执行原理的说明,我们明白了一个道理:内层循环次数的多少不会影响到外层的次数,但是外层循环的次数直接会影响到内层
    循环的次数,外层循环每多一次,内层循环就需要多一次完整的循环,所以我们优化的目标其实就是使外层的循环次数尽量少,
    总结来说:小表驱动大表。小表就是外层循环,大表就是内层循环,也就是尽量减少外层循环的次数

EXISTS和IN查询原理的区别:
    EXISTS:它是对外表做Loop循环,外表循环后,把循环外表记录一个个丢给EXISTS子查询,子查询会根据外表的记录将整个内表完整循环一次,
            若条件匹配,内表查询到数据,则返回true,内表未查询到数据,则之返回false
    IN:把外表和内表做hash连接,先查询内表,返回一个数据列,这个数据列的值将提供给外层查询语句进行比较操作

总结:如果两个表中,其中A表查询出的数据集大,B表查询出的数据集小,则子查询表大的用EXISTS,子查询表小的用IN
结论:
外层小表,内层大表(或者将sql从左到右来看:左面小表,右边大表): exists 比 in 的效率高
外层大表,内层小表(或者将sql从左到右来看:左面大表,右边小表): in 比 exists 的效率高

NOT EXISTS 和 NOT IN
    如果查询语句使用了 NOT IN 那么内外表都进行全表扫描,没有用到索引;而 NOT EXISTS 的子查询依然能用到表上的索引。
    所以无论那个表大,用 NOT EXISTS 都比 NOT IN 要快。

  总结:我们在日常写SQL时使用子查询和使用自连接或者多表连接基本上都可以实现相同结果,一般情况下建议使用自连接,因为在许多 DBMS 的处理过程中,对于自连接的处理速度要比子查询快得多。可以这样理解,子查询实际上是通过未知表进行查询后的条件判断,而自连接是通过已知的自身数据表进行条件判断,因此在大部分 DBMS 中都对自连接处理进行了优化。