MySql基础篇---002 DQL使用篇: SQL介绍,单表查询:where/分组/排序/分页,运算符,多表关系设计:逻辑外键,自连接,多表查询:自查询,单行函数,聚合函数,子查询

第03章 SQL介绍

讲师:尚硅谷-宋红康(江湖人称:康师傅)

官网:http://www.atguigu.com


1. SQL概述

1.1 SQL背景知识

  • 1946 年,世界上第一台电脑诞生,如今,借由这台电脑发展起来的互联网已经自成江湖。在这几十年里,无数的技术、产业在这片江湖里沉浮,有的方兴未艾,有的已经几幕兴衰。但在这片浩荡的波动里,有一门技术从未消失,甚至“老当益壮”,那就是 SQL。

    • 45 年前,也就是 1974 年,IBM 研究员发布了一篇揭开数据库技术的论文《SEQUEL:一门结构化的英语查询语言》,直到今天这门结构化的查询语言并没有太大的变化,相比于其他语言,SQL 的半衰期可以说是非常长了。
  • 不论是前端工程师,还是后端算法工程师,都一定会和数据打交道,都需要了解如何又快又准确地提取自己想要的数据。更别提数据分析师了,他们的工作就是和数据打交道,整理不同的报告,以便指导业务决策。

  • SQL(Structured Query Language,结构化查询语言)是使用关系模型的数据库应用语言与数据直接打交道,由IBM上世纪70年代开发出来。后由美国国家标准局(ANSI)开始着手制定SQL标准,先后有SQL-86SQL-89SQL-92SQL-99等标准。

    • SQL 有两个重要的标准,分别是 SQL92 和 SQL99,它们分别代表了 92 年和 99 年颁布的 SQL 标准,我们今天使用的 SQL 语言依然遵循这些标准。
  • 不同的数据库生产厂商都支持SQL语句,但都有特有内容

1.2 SQL语言排行榜

自从 SQL 加入了 TIOBE 编程语言排行榜,就一直保持在 Top 10。

1.3 SQL 分类(DDL、DML、DCL、DQL)

SQL语言在功能上主要分为如下3大类:(不同人可能分类不同)

  • DDL(Data Definition Languages、数据定义语言),这些语句定义了不同的数据库、表、视图、索引等数据库对象,还可以用来创建、删除、修改数据库和数据表的结构。

    • 主要的语句关键字包括CREATEDROPALTER等。
  • DML(Data Manipulation Language、数据操作语言),用于添加、删除、更新和查询数据库记录,并检查数据完整性。

    • 主要的语句关键字包括INSERTDELETEUPDATESELECT等。
    • SELECT是SQL语言的基础,最为重要。
    • 查询操作又叫作DQL(Data Query Language):数据查询语言,用来查询数据库中表的记录
  • DCL(Data Control Language、数据控制语言),用于定义数据库、表、字段、用户的访问权限和安全级别。

    • 主要的语句关键字包括GRANT(赋予事务的相关权限)REVOKECOMMITROLLBACKSAVEPOINT等。

因为查询语句使用的非常的频繁,所以很多人把查询语句单拎出来一类:DQL(数据查询语言)。

还有单独将COMMIT(提交)、ROLLBACK(回滚) 取出来称为TCL (Transaction Control Language,事务控制语言)。

2. SQL语言的规则与规范

2.1 基本规则(强制准守)

  • SQL 可以写在一行或者多行。为了提高可读性,各子句分行写,必要时使用缩进
  • 每条命令以 ; 或 \g 或 \G 结束(如果只有一条语句可写可不写分号,如果多条语句必须写分号。)
  • 关键字不能被缩写也不能分行
  • 关于标点符号
    • 必须保证所有的()、单引号、双引号是成对结束的
    • 必须使用英文状态下的半角输入方式
    • sql语言标准规范就是这样列名:双引号,日期时间:单引号。只不过mysql对SQL标准支持不严格,oracle支持的严格。
    • mysql对单引号双引号不严谨,oracle对单双引号严谨写错会报错,推荐MySQL中字符串型和日期时间类型的数据使用单引号,列的别名使用双引号
    • 字符串型和日期时间类型的数据可以使用单引号(’ ')表示(即:有的版本的数据库双引号也可以,但推荐使用单引号)
    • 列的别名,尽量使用双引号(" "),而且不建议省略as
    • 在自定义名称时,针对多个单词不要使用驼峰命名,而是使用下划线连接。(例如:tab_name,而不是 tabName )

2.2 SQL大小写规范 (建议遵守)

  • MySQL 在 Windows 环境下是大小写不敏感的
  • MySQL 在 Linux 环境下是大小写敏感的
    • 数据库名、表名、表的别名、变量名是严格区分大小写的
    • 关键字、函数名、列名(或字段名)、列的别名(字段的别名) 是忽略大小写的。
  • 推荐采用统一的书写规范:
    • 数据库名、表名、表别名、字段名、字段别名等都小写
    • SQL 关键字、函数名、绑定变量等都大写

2.3 注 释

可以使用如下格式的注释结构

单行注释:#注释文字(MySQL特有的方式)
单行注释:-- 注释文字(--后面必须包含一个空格。)
多行注释:/* 注释文字  */

2.4 命名规则((强制准守))

  • 数据库、表名不得超过30个字符,变量名限制为29个
  • 必须只能包含 A–Z, a–z, 0–9, _共63个字符
  • 数据库名、表名、字段名等对象名中间不要包含空格
  • 同一个MySQL软件中,数据库不能同名;同一个库中,表不能重名;同一个表中,字段不能重名
  • 必须保证你的字段没有和保留字、数据库系统或常用方法冲突。如果坚持使用,请在SQL语句中使用`(着重号)引起来
  • 保持字段名和类型的一致性,在命名字段并为其指定数据类型的时候一定要保证一致性。假如数据类型在一个表里是整数,那在另一个表里可就别变成字符型了

举例:

#以下两句是一样的,不区分大小写
show databases;
SHOW DATABASES;

#创建表格
#create table student info(...); #表名错误,因为表名有空格
create table student_info(...); 

#其中order使用``飘号,因为order和系统关键字或系统函数名等预定义标识符重名了
CREATE TABLE `order`(
    id INT,
    lname VARCHAR(20)
);

select id as "编号", `name` as "姓名" from t_stu; #起别名时,as都可以省略
select id as 编号, `name` as 姓名 from t_stu; #如果字段别名中没有空格,那么可以省略""
select id as 编 号, `name` as 姓 名 from t_stu; #错误,如果字段别名中有空格,那么不能省略""

2.5 数据导入指令

方式一:Dos窗口命令行下:source +文件的全路径名。
方式二:使用图形化工具,比如SqlYog:选择 "工具 "–“执行sql脚本”—“选中xxx.sql即可”

在命令行客户端登录mysql,使用source指令导入

mysql> source d:\mysqldb.sql
mysql> desc employees;
+----------------+-------------+------+-----+---------+-------+
| Field          | Type        | Null | Key | Default | Extra |
+----------------+-------------+------+-----+---------+-------+
| employee_id    | int(6)      | NO   | PRI | 0       |       |
| first_name     | varchar(20) | YES  |     | NULL    |       |
| last_name      | varchar(25) | NO   |     | NULL    |       |
| email          | varchar(25) | NO   | UNI | NULL    |       |
| phone_number   | varchar(20) | YES  |     | NULL    |       |
| hire_date      | date        | NO   |     | NULL    |       |
| job_id         | varchar(10) | NO   | MUL | NULL    |       |
| salary         | double(8,2) | YES  |     | NULL    |       |
| commission_pct | double(2,2) | YES  |     | NULL    |       |
| manager_id     | int(6)      | YES  | MUL | NULL    |       |
| department_id  | int(4)      | YES  | MUL | NULL    |       |
+----------------+-------------+------+-----+---------+-------+
11 rows in set (0.00 sec)

第04章 单表查询

  • 语法结构:
    在这里插入图片描述

1. 基本查询

1.1 SELECT…

SELECT 1; #没有任何子句
SELECT 9/2; #没有任何子句

# 等价于
select 1 from dual; #dual伪表,简单运行没必要再查询表了就用伪表代替。

1.2 SELECT … FROM

  • 语法:
# 字段可以写多个,用逗号隔开
SELECT   字段1,字段2,... FROM  表名
  • 选择全部列:
# *:表中的所有字段(列)
# 数据库查询的结果叫作结果集
SELECT * FROM   departments;

在这里插入图片描述

一般情况下,除非需要使用表中所有的字段数据,最好不要使用通配符‘*’。使用通配符虽然可以节省输入查询语句的时间,但是获取不需要的列数据通常会降低查询和所使用的应用程序的效率。通配符的优势是,当不知道所需要的列的名称时,可以通过它获取它们。

在生产环境下,不推荐你直接使用SELECT *进行查询。

  • 选择特定的列:
# 可以换行写
SELECT department_id, location_id
FROM   departments;

在这里插入图片描述

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

1.3 列的别名

  • 重命名一个列

  • 便于计算

  • 紧跟列名,也可以在列名和别名之间加入关键字AS,别名使用双引号,以便在别名中包含空格或特殊的字符并区分大小写

  • AS 可以省略

  • 建议别名简短,见名知意

  • 举例

  SELECT last_name AS name, commission_pct comm
  FROM   employees;

在这里插入图片描述

  # 注意:起别名如果是一个单词可以不用加"",如果是多个分开的单词 "Annual Salary"则需要加"",否则自认为Annual是别名,整体报错。
  # 列明推荐使用双引号,不要使用单引号。mysql语法不严谨在oracle中使用单引号就报错。
  # 字符串和日期推荐使用单引号
  SELECT last_name "Name", salary*12 "Annual Salary"
  FROM   employees;

在这里插入图片描述

1.4 去除重复行

默认情况下,查询会返回全部行,包括重复行。

#查询员工表中一共有哪些部门id呢?
#错误的:没有去重的情况
SELECT department_id FROM   employees;

在这里插入图片描述

在SELECT语句中使用关键字DISTINCT去除重复行

#正确的:去重的情况
SELECT DISTINCT department_id FROM   employees;

在这里插入图片描述

针对于:

#仅仅是没有报错,但是没有实际意义。指的是整体不同:只要同行的2个列不完全一致就会显示出来。
SELECT DISTINCT department_id,salary  FROM employees;

这里有两点需要注意:

  1. DISTINCT 需要放到所有列名的前面,如果写成SELECT salary, DISTINCT department_id FROM employees会报错。
  2. DISTINCT 其实是对后面所有列名的组合进行去重,你能看到最后的结果是 74 条,因为这 74 个部门id不同,都有 salary 这个属性值。如果你想要看都有哪些不同的部门(department_id),只需要写DISTINCT department_id即可,后面不需要再加其他的列名了。

1.5 空值参与运算

  • 所有运算符或列值遇到null值,运算的结果都为null
# 1. 空值:null
# 2. null不等同于0,'','null'
SELECT * FROM employees;

#3. 空值参与运算:结果一定也为空。
SELECT employee_id,salary "月工资",salary * (1 + commission_pct) * 12 "年工资",commission_pct
FROM employees;
#实际问题的解决方案:引入IFNULL
SELECT employee_id,salary "月工资",salary * (1 + IFNULL(commission_pct,0)) * 12 "年工资",commission_pct

这里你一定要注意,在 MySQL 里面, 空值不等于空字符串。一个空字符串的长度是 0,而一个空值的长度是空。而且,在 MySQL 里面,空值是占用空间的。

1.6 着重号

  • 错误的
#就是查询时表名和数据库的关键字相同了,就要一对``进行标识后,就可以查询成功了。
mysql> SELECT * FROM ORDER;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'ORDER' at line 1
  • 正确的
mysql> SELECT * FROM `ORDER`;
+----------+------------+
| order_id | order_name |
+----------+------------+
|        1 | shkstart   |
|        2 | tomcat     |
|        3 | dubbo      |
+----------+------------+
3 rows in set (0.00 sec)

mysql> SELECT * FROM `order`;
+----------+------------+
| order_id | order_name |
+----------+------------+
|        1 | shkstart   |
|        2 | tomcat     |
|        3 | dubbo      |
+----------+------------+
3 rows in set (0.00 sec)
  • 结论

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

1.7 查询常数

SELECT 查询还可以对常数进行查询。对的,就是在 SELECT 查询结果中增加一列固定的常数列。这列的取值是我们指定的,而不是从数据表中动态取出的。

你可能会问为什么我们还要对常数进行查询呢?

SQL 中的 SELECT 语法的确提供了这个功能,一般来说我们只从一个表中查询数据,通常不需要增加一个固定的常数列,但如果我们想整合不同的数据源,用常数列作为这个表的标记,就需要查询常数。

比如说,我们想对 employees 数据表中的员工姓名进行查询,同时增加一列字段corporation,这个字段固定值为“尚硅谷”,可以这样写:

# 就是指如果`尚硅谷'只要1行,123,employee_id其它字段有很多行107行,那么查询的结果为107行并且每行都会添加`尚硅谷'
SELECT '尚硅谷',123,employee_id,last_name
FROM employees;

在这里插入图片描述

2. 显示表结构

使用DESCRIBE 或 DESC 命令,表示表结构

# 可以查看数据类型,主键,是否允许为Null,约束等。
DESCRIBE employees;DESC employees;
mysql> desc employees;
+----------------+-------------+------+-----+---------+-------+
| Field          | Type        | Null | Key | Default | Extra |
+----------------+-------------+------+-----+---------+-------+
| employee_id    | int(6)      | NO   | PRI | 0       |       |
| first_name     | varchar(20) | YES  |     | NULL    |       |
| last_name      | varchar(25) | NO   |     | NULL    |       |
| email          | varchar(25) | NO   | UNI | NULL    |       |
| phone_number   | varchar(20) | YES  |     | NULL    |       |
| hire_date      | date        | NO   |     | NULL    |       |
| job_id         | varchar(10) | NO   | MUL | NULL    |       |
| salary         | double(8,2) | YES  |     | NULL    |       |
| commission_pct | double(2,2) | YES  |     | NULL    |       |
| manager_id     | int(6)      | YES  | MUL | NULL    |       |
| department_id  | int(4)      | YES  | MUL | NULL    |       |
+----------------+-------------+------+-----+---------+-------+
11 rows in set (0.00 sec)

其中,各个字段的含义分别解释如下:

  • Field:表示字段名称。
  • Type:表示字段类型,这里 barcode、goodsname 是文本型的,price 是整数类型的。
  • Null:表示该列是否可以存储NULL值。
  • Key:表示该列是否已编制索引。PRI表示该列是表主键的一部分UNI表示该列是UNIQUE索引的一部分MUL表示在列中某个给定值允许出现多次
  • Default:表示该列是否有默认值,如果有,那么值是多少。
  • Extra:表示可以获取的与给定列有关的附加信息,例如AUTO_INCREMENT等。

3. 条件查询(where)

3.1 语法&运算符

  • 语法:在基本查询的基础上加上where关键字,where之后的查询条件可以有多个。
    在这里插入图片描述
  • 运算符:学习条件查询主要就是,学习条件的构建方式,在sql语句中构建条件的运算符有很多,主要分为2大类。(运算符详情查看第05章
  • 注意:在sql语句中判断null值不能使用等于null,而是使用is null。

在这里插入图片描述

3.2 案例测试

-- 1. 查询 姓名 为 杨逍 的员工
select * from emp where name = '杨逍';

-- 2. 查询在 id小于等于5 的员工信息
select * from emp where id <= 5;

-- 3. 查询 没有分配职位 的员工信息  -- 判断 null , 用 is null
select * from emp where job is null;

-- 4. 查询 有职位 的员工信息  -- 判断 不是null , 用 is not null
select * from emp where job is not null ;

-- 5. 查询 密码不等于 '123456' 的员工信息
select * from emp where password <> '123456';
select * from emp where password != '123456';

-- 6. 查询入职日期 在 '2000-01-01' (包含) 到 '2010-01-01'(包含) 之间的员工信息
select * from emp where entrydate between '2000-01-01' and '2010-01-01' ;

-- 7. 查询 入职时间 在 '2000-01-01' (包含) 到 '2010-01-01'(包含) 之间 且 性别为女 的员工信息
select * from emp where (entrydate between '2000-01-01' and '2010-01-01') and  gender = 2;

-- 8. 查询 职位是 2 (讲师), 3 (学工主管), 4 (教研主管) 的员工信息
select * from emp where job = 2 or job = 3 or job = 4;

select * from emp where job in (2,3,4);

-- 9. 查询姓名为两个字的员工信息
select * from emp where name like '__';

-- 10. 查询姓 '张' 的员工信息  ---------> 张%
select * from emp where name like '张%';

-- 11. 查询姓名中包含 '三' 的员工信息
select * from emp where name like '%三%';

4. 分组查询(group by)

4.1、聚合函数&分组语法结构

说明:

  • 分组查询就是在条件查询where之后加上一个关键字group by,来指定分组的字段,如果在分组之后还要进行条件的筛选,就可以在group by之后加上关键字having,通过此关键字来指定分组后的条件。
  • 分组操作一般和聚合函数结合使用。

聚合函数:详情查看第09章
在这里插入图片描述

分组查询:
在这里插入图片描述

4.2、案例测试

-- 聚合函数

-- 1. 统计该企业员工数量 -- count
-- A. count(字段)
select count(id) from emp;
select count(job) from emp; -- null值不参与聚合函数运算

-- B. count(*),推荐,因为mysql底层专门对此做了优化操作。
select count(*) from emp;

-- C. count(值)
select count(1) from emp;

-- 2. 统计该企业员工 ID 的平均值
select avg(id) from emp;

-- 3. 统计该企业最早入职的员工的入职日期
select min(entrydate) from emp;

-- 4. 统计该企业最近入职的员工的入职日期
select max(entrydate) from emp;

-- 5. 统计该企业员工的 ID 之和
select sum(id) from emp;




-- 分组
-- 1. 根据性别分组 , 统计男性和女性员工的数量  -- count
select gender , count(*) from emp group by gender;

-- 2. 先查询入职时间在 '2015-01-01' (包含) 以前的员工 , 并对结果根据职位分组 , 获取员工数量大于等于2的职位 -- count
select job ,count(*)  from emp where entrydate <= '2015-01-01'  group by job having count(*) >= 2;

5. 排序查询(order by)

5.1 语法&注意事项

在这里插入图片描述

5.2 单列排序

#1. 排序

# 如果没有使用排序操作,默认情况下查询返回的数据是按照添加数据的顺序显示的。
#写了ORDER BY关键字后,不写升序 降序关键字默认是ASC升序,如果连orderby关键字都不写,默认情况下查询返回的数据是按照添加数据的顺序显示的。
SELECT * FROM employees;

# 1.1 基本使用
# 使用 ORDER BY 对查询到的数据进行排序操作。
# 升序:ASC (ascend)
# 降序:DESC (descend)

# 练习:按照salary从高到低的顺序显示员工信息
SELECT employee_id,last_name,salary
FROM employees
ORDER BY salary DESC;

# 练习:按照salary从低到高的顺序显示员工信息
SELECT employee_id,last_name,salary
FROM employees
ORDER BY salary ASC;


SELECT employee_id,last_name,salary
FROM employees
ORDER BY salary; # 如果在ORDER BY 后没有显式指名排序的方式的话,则默认按照升序排列。

5.3 多列排序

#2. 我们可以使用列的别名,进行排序
SELECT employee_id,salary,salary * 12 annual_sal
FROM employees
ORDER BY annual_sal;

#列的别名只能在 ORDER BY 中使用,不能在WHERE中使用。
#如下操作报错!
SELECT employee_id,salary,salary * 12 annual_sal
FROM employees
WHERE annual_sal > 81600;

#3. 强调格式:WHERE 需要声明在FROM后,ORDER BY之前。
# 排序的字段不一定是查询的字段,不会报错。
/*为什么在where中不能使用列别名???
因为:sql查询不是按照写sql的从前到后顺序写的:
    第一步:先走from看 是那个表,在看筛选条件where。
    第二步:先看看你想要查询出来什么select,这时你可能会定义别名。
    第三部:此时已经有别名了在走排序才能使用列别名,之前使用where时还没有列别名所以不能使用。同理表别名在where之前就有了,所以能够使用。*/

SELECT employee_id,salary
FROM employees
WHERE department_id IN (50,60,70)
ORDER BY department_id DESC;

#4. 二级排序(多字段排序)

#练习:显示员工信息,按照department_id的降序排列,在department_id的降序的基础上在进行salary的升序排列
# 多个字段排序中间使用逗号隔开,如果是多字段排序,当第一个字段值相同时,才会根据第二个字段进行排序。
SELECT employee_id,salary,department_id
FROM employees
ORDER BY department_id DESC,salary ASC;
  • 可以使用不在SELECT列表中的列排序。
  • 在对多列进行排序的时候,首先排序的第一列必须有相同的列值,才会对第二列进行排序。如果第一列数据中所有值都是唯一的,将不再对第二列进行排序。

6. 分页(limit)

6.1 背景

背景1:查询返回的记录太多了,查看起来很不方便,怎么样能够实现分页查询呢?

背景2:表里有 4 条数据,我们只想要显示第 2、3 条数据怎么办呢?

6.2 实现规则

  • 分页原理

    所谓分页显示,就是将数据库中的结果集,一段一段显示出来需要的条件。

  • MySQL中使用 LIMIT 实现分页

  • 格式:

在这里插入图片描述

LIMIT [位置偏移量,行数] 

第一个“位置偏移量”参数指示MySQL从哪一行开始显示,是一个可选参数,如果不指定“位置偏移量”,将会从表中的第一条记录开始(第一条记录的位置偏移量是0,第二条记录的位置偏移量是1,以此类推);第二个参数“行数”指示返回的记录条数。

  • 举例
#2. 分页
#2.1 mysql使用limit实现数据的分页显示

# 需求1:每页显示20条记录,此时显示第1页
SELECT employee_id,last_name
FROM employees
LIMIT 0,20;


# 需求2:每页显示20条记录,此时显示第2页
SELECT employee_id,last_name
FROM employees
LIMIT 20,20;


# 需求3:每页显示20条记录,此时显示第3页
SELECT employee_id,last_name
FROM employees
LIMIT 40,20;

#需求:每页显示pageSize条记录,此时显示第pageNo页:
#公式:LIMIT (pageNo-1) * pageSize,pageSize;


#2.2 WHERE ... ORDER BY ...LIMIT 声明顺序如下:

# LIMIT的格式: 严格来说:LIMIT 位置偏移量,条目数
# 结构"LIMIT 0,条目数" 等价于 "LIMIT 条目数"

SELECT employee_id,last_name,salary
FROM employees
WHERE salary > 6000
ORDER BY salary DESC
#limit 0,10;
LIMIT 10;

#练习:表里有107条数据,我们只想要显示第 32、33 条数据怎么办呢?

SELECT employee_id,last_name
FROM employees
LIMIT 31,2;

#2.3 MySQL8.0新特性:LIMIT ... OFFSET ...   
# LIMIT后面跟每页显示的个数,OFFSET后面写位置偏移量,相当于颠倒一下。

#练习:表里有107条数据,我们只想要显示第 32、33 条数据怎么办呢?

SELECT employee_id,last_name
FROM employees
LIMIT 2 OFFSET 31;

#练习:查询员工表中工资最高的员工信息
SELECT employee_id,last_name,salary
FROM employees
ORDER BY salary DESC
#limit 0,1
LIMIT 1;

MySQL 8.0中可以使用“LIMIT 3 OFFSET 4”,意思是获取从第5条记录开始后面的3条记录,和“LIMIT 4,3;”返回的结果相同。

  • 分页显式公式:(当前页数-1)*每页条数,每页条数
SELECT * FROM table 
LIMIT(PageNo - 1)*PageSize,PageSize;
  • 注意:LIMIT 子句必须放在整个SELECT语句的最后!
  • 使用 LIMIT 的好处

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

6.3 拓展:不同数据库使用的分页关键字不同

在不同的 DBMS 中使用的关键字可能不同。在 MySQL、PostgreSQL、MariaDB 和 SQLite 中使用 LIMIT 关键字,而且需要放到 SELECT 语句的最后面。

  • 如果是 SQL Server 和 Access,需要使用 TOP 关键字,比如:
SELECT TOP 5 name, hp_max FROM heros ORDER BY hp_max DESC
  • 如果是 DB2,使用FETCH FIRST 5 ROWS ONLY这样的关键字:
SELECT name, hp_max FROM heros ORDER BY hp_max DESC FETCH FIRST 5 ROWS ONLY
  • 如果是 Oracle,你需要基于 ROWNUM 来统计行数:
  • ROWNUM相当于隐藏的一列,每列都有。
SELECT rownum,last_name,salary FROM employees WHERE rownum < 5 ORDER BY salary DESC;

需要说明的是,这条语句是先取出来前 5 条数据行,然后再按照 hp_max 从高到低的顺序进行排序。但这样产生的结果和上述方法的并不一样。我会在后面讲到子查询,你可以使用

SELECT rownum, last_name,salary
FROM (
    SELECT last_name,salary
    FROM employees
    ORDER BY salary DESC)
WHERE rownum < 10;

得到与上述方法一致的结果。

7. 单表查询-综合测案例1

  • 页面原型+需求文档
    在这里插入图片描述
    在这里插入图片描述

  • aql语句:

----案例1∶按需求完成员工管理的条件分页查询 - 根据输入条件,查询第一页数据,每页展示10条记录
--输入条件:
  --姓名:张      (模糊匹配)
  --性别:男      (精确查询)
  --入职时间: 2000-01-012015-12-31     (范围查询)
  --支持分页查询
  --查询的结果按照倒序排序

select * from emp 
where name like '%张%' 
  and gender = 1 
  and entrydate between '2000-01-01' and '2010-01-01' 
order by update_time desc 
limit 0,10 ;

8. 单表查询-综合测案例2

  • 页面原型+需求文档
    在这里插入图片描述

  • sql语句:

--案例2-1 :根据需求,完成员工性别信息的统计 - count(*)
-- if(条件表达式,true取值,false取值)
-- 说明:按性别统计所以要进行分组
--      查询出的性别为1、2,而返回给页面的性别是男、女,所以需要使用流程控制语句积习进行转换
-- 后面是起的别名
select
        if(gender = 1, '男性员工' , '女性员工') '性别',
        count(*) '人数'
from emp group by gender;


--案例2-2:根据需求,完成员工职位信息的统计
-- case 表达式 when 值1 then 结果1 when 值2 then结果2 ... else ... end
-- 2个分支可以用if。多个分支用case
--解释:如果表达式匹配到了值1则取值结果1,如果表达式匹配到了值2则取值结果2,
--     如果前面都没有匹配到这取最后的这个默认值else ... end
select
       (case when job = 1 then '班主任' when job = 2 then '讲师' when job = 3 then '教研主管' when job = 4 then '学工主管' else '无职位' end ) '职位',
       count(*)
from emp group by job;


第05章_运算符(重新听下)

1. 算术运算符

算术运算符主要用于数学运算,其可以连接运算符前后的两个数值或表达式,对数值或表达式进行加(+)、减(-)、乘(*)、除(/)和取模(%)运算。

在这里插入图片描述

1.1 加法与减法运算符

#1. 算术运算符: +  -  *  /  div  % mod

mysql> SELECT 100, 100 + 0, 100 - 0, 100 + 50, 100 + 50 -30, 100 + 35.5, 100 - 35.5 FROM dual;
+-----+---------+---------+----------+--------------+------------+------------+
| 100 | 100 + 0 | 100 - 0 | 100 + 50 | 100 + 50 -30 | 100 + 35.5 | 100 - 35.5 |
+-----+---------+---------+----------+--------------+------------+------------+
| 100 |     100 |     100 |      150 |          120 |      135.5 |       64.5 |
+-----+---------+---------+----------+--------------+------------+------------+
1 row in set (0.00 sec)

# 在SQL中,+没有连接的作用,就表示加法运算。此时,会将字符串转换为数值(隐式转换)
SELECT 100 + '1'  # 在Java语言中,结果是:1001。 
FROM DUAL;

SELECT 100 + 'a' #此时将'a'看做0处理
FROM DUAL;

SELECT 100 + NULL  # null值参与运算,结果为null
FROM DUAL;

由运算结果可以得出如下结论:

  • 一个整数类型的值对整数进行加法和减法操作,结果还是一个整数;
  • 一个整数类型的值对浮点数进行加法和减法操作,结果是一个浮点数;
  • 加法和减法的优先级相同,进行先加后减操作与进行先减后加操作的结果是一样的;
  • 在Java中,+的左右两边如果有字符串,那么表示字符串的拼接。但是在MySQL中+只表示数值相加。如果遇到非数值类型,先尝试转成数值,如果转失败,就按0计算。(补充:MySQL中字符串拼接要使用字符串函数CONCAT()实现)

1.2 乘法与除法运算符

# 分母如果为0,则结果为null
mysql> SELECT 100, 100 * 1, 100 * 1.0, 100 / 1.0, 100 / 2,100 + 2 * 5 / 2,100 /3, 100 DIV 0 FROM dual;
+-----+---------+-----------+-----------+---------+-----------------+---------+-----------+
| 100 | 100 * 1 | 100 * 1.0 | 100 / 1.0 | 100 / 2 | 100 + 2 * 5 / 2 | 100 /3  | 100 DIV 0 |
+-----+---------+-----------+-----------+---------+-----------------+---------+-----------+
| 100 |     100 |     100.0 |  100.0000 | 50.0000 |        105.0000 | 33.3333 |      NULL |
+-----+---------+-----------+-----------+---------+-----------------+---------+-----------+
1 row in set (0.00 sec)
#计算出员工的年基本工资
SELECT employee_id,salary,salary * 12 annual_sal 
FROM employees;

由运算结果可以得出如下结论:

  • 一个数乘以整数1和除以整数1后仍得原数;
  • 一个数乘以浮点数1和除以浮点数1后变成浮点数,数值与原数相等;
  • 一个数除以整数后,不管是否能除尽,结果都为一个浮点数
  • 一个数除以另一个数,除不尽时,结果为一个浮点数,并保留到小数点后4位;
  • 乘法和除法的优先级相同,进行先乘后除操作与先除后乘操作,得出的结果相同。
  • 在数学运算中,0不能用作除数,在MySQL中,一个数除以0为NULL

1.3 求模(求余)运算符

将t22表中的字段i对3和5进行求模(求余)运算。

# 取模运算: % mod
# 可以看到查询结果的正负与被除数无关,只和除数相同。
SELECT 12 % 3,12 % 5, 12 MOD -5,-12 % 5,-12 % -5
FROM DUAL;

在这里插入图片描述

#筛选出employee_id是偶数的员工
SELECT * FROM employees
WHERE employee_id MOD 2 = 0;

可以看到,100对3求模后的结果为3,对5求模后的结果为0。

2. 比较运算符

比较运算符用来对 表达式左边的操作数和右边的操作数进行比较,比较的结果为真则返回1,比较的结果为假则返回0,其他情况则返回NULL。

比较运算符经常被用来作为SELECT查询语句的条件来使用,返回符合条件的结果记录。
在这里插入图片描述

2.1 等号运算符

  • 等号运算符(=)判断等号两边的值、字符串或表达式是否相等,如果相等则返回1,不相等则返回0。

  • 在使用等号运算符时,遵循如下规则:

    • 如果等号两边的值、字符串或表达式都为字符串,则MySQL会按照字符串进行比较,其比较的是每个字符串中字符的ANSI编码是否相等。
    • 如果等号两边的值都是整数,则MySQL会按照整数来比较两个值的大小。
    • 如果等号两边的值一个是整数,另一个是字符串,则MySQL会将字符串转化为数字进行比较。
    • 如果等号两边的值、字符串或表达式中有一个为NULL,则比较结果为NULL。
  • 对比:SQL中赋值符号使用 :=

SELECT 1 = 2,1 != 2,1 = '1',1 = 'a',0 = 'a' #字符串存在隐式转换。如果转换数值不成功,则看做0
FROM DUAL;

SELECT 'a' = 'a','ab' = 'ab','a' = 'b' #两边都是字符串的话,则按照ANSI的比较规则进行比较。
FROM DUAL;

SELECT 1 = NULL,NULL = NULL # 只要有null参与判断,结果就为null
FROM DUAL;

SELECT last_name,salary,commission_pct
FROM employees
#where salary = 6000;
WHERE commission_pct = NULL;  #此时执行,不会有任何的结果

mysql> SELECT 1 = 2, 0 = 'abc', 1 = 'abc' FROM dual;
+-------+-----------+-----------+
| 1 = 2 | 0 = 'abc' | 1 = 'abc' |
+-------+-----------+-----------+
|     0 |         1 |         0 |
+-------+-----------+-----------+
1 row in set, 2 warnings (0.00 sec)
#查询salary=10000,注意在Java中比较是==
SELECT employee_id,salary FROM employees WHERE salary = 10000;

2.2 安全等于运算符

安全等于运算符(<=>)与等于运算符(=)的作用是相似的,唯一的区别是‘<=>’可以用来对NULL进行判断。在两个操作数均为NULL时,其返回值为1,而不为NULL;当一个操作数为NULL时,其返回值为0,而不为NULL。

mysql> SELECT 1 <=> '1', 1 <=> 0, 'a' <=> 'a', (5 + 3) <=> (2 + 6), '' <=> NULL,NULL <=> NULL FROM dual;
+-----------+---------+-------------+---------------------+-------------+---------------+
| 1 <=> '1' | 1 <=> 0 | 'a' <=> 'a' | (5 + 3) <=> (2 + 6) | '' <=> NULL | NULL <=> NULL |
+-----------+---------+-------------+---------------------+-------------+---------------+
|         1 |       0 |           1 |                   1 |           0 |             1 |
+-----------+---------+-------------+---------------------+-------------+---------------+
1 row in set (0.00 sec)
#查询commission_pct等于0.40
SELECT employee_id,commission_pct FROM employees WHERE commission_pct = 0.40;
SELECT employee_id,commission_pct FROM employees WHERE commission_pct <=> 0.40;
#如果把0.40改成 NULL 呢?

可以看到,使用安全等于运算符时,两边的操作数的值都为NULL时,返回的结果为1而不是NULL,其他返回结果与等于运算符相同。

2.3 不等于运算符

不等于运算符(<>和!=)用于判断两边的数字、字符串或者表达式的值是否不相等,如果不相等则返回1,相等则返回0。不等于运算符不能判断NULL值。如果两边的值有任意一个为NULL,或两边都为NULL,则结果为NULL。
SQL语句示例如下:

mysql> SELECT 1 <> 1, 1 != 2, 'a' != 'b', (3+4) <> (2+6), 'a' != NULL, NULL <> NULL; 
+--------+--------+------------+----------------+-------------+--------------+
| 1 <> 1 | 1 != 2 | 'a' != 'b' | (3+4) <> (2+6) | 'a' != NULL | NULL <> NULL |
+--------+--------+------------+----------------+-------------+--------------+
|      0 |   1    |       1    |            1   |     NULL    |         NULL |
+--------+--------+------------+----------------+-------------+--------------+
1 row in set (0.00 sec)

3. 非符号类型的运算符

在这里插入图片描述

3.1 空运算符

空运算符(IS NULL或者ISNULL)判断一个值是否为NULL,如果为NULL则返回1,否则返回0。
SQL语句示例如下:

mysql> SELECT NULL IS NULL, ISNULL(NULL), ISNULL('a'), 1 IS NULL;
+--------------+--------------+-------------+-----------+
| NULL IS NULL | ISNULL(NULL) | ISNULL('a') | 1 IS NULL |
+--------------+--------------+-------------+-----------+
|            1 |            1 |           0 |         0 |
+--------------+--------------+-------------+-----------+
1 row in set (0.00 sec)
#查询commission_pct等于NULL。比较如下的四种写法
SELECT employee_id,commission_pct FROM employees WHERE commission_pct IS NULL;
SELECT employee_id,commission_pct FROM employees WHERE commission_pct <=> NULL;
SELECT employee_id,commission_pct FROM employees WHERE ISNULL(commission_pct);
SELECT employee_id,commission_pct FROM employees WHERE commission_pct = NULL;
SELECT last_name, manager_id
FROM   employees
WHERE  manager_id IS NULL;

3.2 非空运算符

非空运算符(IS NOT NULL)判断一个值是否不为NULL,如果不为NULL则返回1,否则返回0。
SQL语句示例如下:

mysql> SELECT NULL IS NOT NULL, 'a' IS NOT NULL,  1 IS NOT NULL; 
+------------------+-----------------+---------------+
| NULL IS NOT NULL | 'a' IS NOT NULL | 1 IS NOT NULL |
+------------------+-----------------+---------------+
|                0 |               1 |             1 |
+------------------+-----------------+---------------+
1 row in set (0.01 sec)
#查询commission_pct不等于NULL
SELECT employee_id,commission_pct FROM employees WHERE commission_pct IS NOT NULL;
SELECT employee_id,commission_pct FROM employees WHERE NOT commission_pct <=> NULL;
SELECT employee_id,commission_pct FROM employees WHERE NOT ISNULL(commission_pct);

3.3 最小值运算符

语法格式为:LEAST(值1,值2,…,值n)。其中,“值n”表示参数列表中有n个值。在有两个或多个参数的情况下,返回最小值。

mysql> SELECT LEAST (1,0,2), LEAST('b','a','c'), LEAST(1,NULL,2);
+---------------+--------------------+-----------------+
| LEAST (1,0,2) | LEAST('b','a','c') | LEAST(1,NULL,2) |
+---------------+--------------------+-----------------+
|       0       |        a           |      NULL       |
+---------------+--------------------+-----------------+
1 row in set (0.00 sec)

由结果可以看到,当参数是整数或者浮点数时,LEAST将返回其中最小的值;当参数为字符串时,返回字母表中顺序最靠前的字符;当比较值列表中有NULL时,不能判断大小,返回值为NULL。

3.4 最大值运算符

语法格式为:GREATEST(值1,值2,…,值n)。其中,n表示参数列表中有n个值。当有两个或多个参数时,返回值为最大值。假如任意一个自变量为NULL,则GREATEST()的返回值为NULL。

mysql> SELECT GREATEST(1,0,2), GREATEST('b','a','c'), GREATEST(1,NULL,2);
+-----------------+-----------------------+--------------------+
| GREATEST(1,0,2) | GREATEST('b','a','c') | GREATEST(1,NULL,2) |
+-----------------+-----------------------+--------------------+
|               2 | c                     |               NULL |
+-----------------+-----------------------+--------------------+
1 row in set (0.00 sec)

由结果可以看到,当参数中是整数或者浮点数时,GREATEST将返回其中最大的值;当参数为字符串时,返回字母表中顺序最靠后的字符;当比较值列表中有NULL时,不能判断大小,返回值为NULL。

3.5 BETWEEN AND运算符

BETWEEN运算符使用的格式通常为SELECT D FROM TABLE WHERE C BETWEEN A AND B,此时,当C大于或等于A,并且C小于或等于B时,结果为1,否则结果为0。

mysql> SELECT 1 BETWEEN 0 AND 1, 10 BETWEEN 11 AND 12, 'b' BETWEEN 'a' AND 'c';
+-------------------+----------------------+-------------------------+
| 1 BETWEEN 0 AND 1 | 10 BETWEEN 11 AND 12 | 'b' BETWEEN 'a' AND 'c' |
+-------------------+----------------------+-------------------------+
|                 1 |                    0 |                       1 |
+-------------------+----------------------+-------------------------+
1 row in set (0.00 sec)
SELECT last_name, salary
FROM   employees 
WHERE  salary BETWEEN 2500 AND 3500;

3.6 IN运算符

IN运算符用于判断给定的值是否是IN列表中的一个值,如果是则返回1,否则返回0。如果给定的值为NULL,或者IN列表中存在NULL,则结果为NULL。

mysql> SELECT 'a' IN ('a','b','c'), 1 IN (2,3), NULL IN ('a','b'), 'a' IN ('a', NULL);
+----------------------+------------+-------------------+--------------------+
| 'a' IN ('a','b','c') | 1 IN (2,3) | NULL IN ('a','b') | 'a' IN ('a', NULL) |
+----------------------+------------+-------------------+--------------------+
|            1         |        0   |         NULL      |         1          |
+----------------------+------------+-------------------+--------------------+
1 row in set (0.00 sec)
SELECT employee_id, last_name, salary, manager_id
FROM   employees
WHERE  manager_id IN (100, 101, 201);

3.7 NOT IN运算

NOT IN运算符用于判断给定的值是否不是IN列表中的一个值,如果不是IN列表中的一个值,则返回1,否则返回0。

mysql> SELECT 'a' NOT IN ('a','b','c'), 1 NOT IN (2,3);
+--------------------------+----------------+
| 'a' NOT IN ('a','b','c') | 1 NOT IN (2,3) |
+--------------------------+----------------+
|                 0        |            1   |
+--------------------------+----------------+
1 row in set (0.00 sec)

3.8 LIKE运算符

LIKE运算符主要用来匹配字符串,通常用于模糊匹配,如果满足条件则返回1,否则返回0。如果给定的值或者匹配条件为NULL,则返回结果为NULL。

LIKE运算符通常使用如下通配符:

%”:匹配0个或多个字符。
“_”:只能匹配一个字符。

SQL语句示例如下:

mysql> SELECT NULL LIKE 'abc', 'abc' LIKE NULL;  
+-----------------+-----------------+
| NULL LIKE 'abc' | 'abc' LIKE NULL |
+-----------------+-----------------+
|          NULL   |          NULL   |
+-----------------+-----------------+
1 row in set (0.00 sec)
SELECT	first_name
FROM 	employees
WHERE	first_name LIKE 'S%';
SELECT last_name
FROM   employees
WHERE  last_name LIKE '_o%';

ESCAPE

  • 回避特殊符号的:使用转义符。例如:将[%]转为[ %]、[]转为[ ],然后再加上[ESCAPE‘$’]即可。
SELECT job_id
FROM   jobs
WHERE  job_id LIKE ‘IT\_%;
  • 如果使用\表示转义,要省略ESCAPE。如果不是\,则要加上ESCAPE。
SELECT job_id
FROM   jobs
WHERE  job_id LIKE ‘IT$_%escape ‘$‘;

3.9 REGEXP运算符

REGEXP运算符用来匹配字符串,语法格式为:expr REGEXP 匹配条件。如果expr满足匹配条件,返回1;如果不满足,则返回0。若expr或匹配条件任意一个为NULL,则结果为NULL。

REGEXP运算符在进行匹配时,常用的有下面几种通配符:

1)‘^’匹配以该字符后面的字符开头的字符串。
(2)‘$’匹配以该字符前面的字符结尾的字符串。
(3)‘.’匹配任何一个单字符。
(4)“[...]”匹配在方括号内的任何字符。例如,“[abc]”匹配“a”或“b”或“c”。为了命名字符的范围,使用一个‘-’。“[a-z]”匹配任何字母,而“[0-9]”匹配任何数字。
(5)‘*’匹配零个或多个在它前面的字符。例如,“x*”匹配任何数量的‘x’字符,“[0-9]*”匹配任何数量的数字,而“*”匹配任何数量的任何字符。

SQL语句示例如下:

mysql> SELECT 'shkstart' REGEXP '^s', 'shkstart' REGEXP 't$', 'shkstart' REGEXP 'hk';
+------------------------+------------------------+-------------------------+
| 'shkstart' REGEXP '^s' | 'shkstart' REGEXP 't$' | 'shkstart' REGEXP 'hk'  |
+------------------------+------------------------+-------------------------+
|                      1 |                      1 |                       1 |
+------------------------+------------------------+-------------------------+
1 row in set (0.01 sec)
mysql> SELECT 'atguigu' REGEXP 'gu.gu', 'atguigu' REGEXP '[ab]';
+--------------------------+-------------------------+
| 'atguigu' REGEXP 'gu.gu' | 'atguigu' REGEXP '[ab]' |
+--------------------------+-------------------------+
|                        1 |                       1 |
+--------------------------+-------------------------+
1 row in set (0.00 sec)

4. 逻辑运算符

逻辑运算符主要用来判断表达式的真假,在MySQL中,逻辑运算符的返回结果为1、0或者NULL。

MySQL中支持4种逻辑运算符如下:

在这里插入图片描述

4.1 逻辑非运算符(not)

逻辑非(NOT或!)运算符表示当给定的值为0时返回1;当给定的值为非0值时返回0;当给定的值为NULL时,返回NULL。

mysql> SELECT NOT 1, NOT 0, NOT(1+1), NOT !1, NOT NULL;    
+-------+-------+----------+--------+----------+
| NOT 1 | NOT 0 | NOT(1+1) | NOT !1 | NOT NULL |
+-------+-------+----------+--------+----------+
|     0 |     1 |        0 |      1 |     NULL |
+-------+-------+----------+--------+----------+
1 row in set, 1 warning (0.00 sec)
SELECT last_name, job_id
FROM   employees
WHERE  job_id NOT IN ('IT_PROG', 'ST_CLERK', 'SA_REP');

4.2 逻辑与运算符(and)

逻辑与(AND或&&)运算符是当给定的所有值均为非0值,并且都不为NULL时,返回1;当给定的一个值或者多个值为0时则返回0;否则返回NULL。

mysql> SELECT 1 AND -1, 0 AND 1, 0 AND NULL, 1 AND NULL;
+----------+---------+------------+------------+
| 1 AND -1 | 0 AND 1 | 0 AND NULL | 1 AND NULL |
+----------+---------+------------+------------+
|        1 |       0 |          0 |       NULL |
+----------+---------+------------+------------+
1 row in set (0.00 sec)
SELECT employee_id, last_name, job_id, salary
FROM   employees
WHERE  salary >=10000
AND    job_id LIKE '%MAN%';

4.3 逻辑或运算符(or)

逻辑或(OR或||)运算符是当给定的值都不为NULL,并且任何一个值为非0值时,则返回1,否则返回0;当一个值为NULL,并且另一个值为非0值时,返回1,否则返回NULL;当两个值都为NULL时,返回NULL。

mysql> SELECT 1 OR -1, 1 OR 0, 1 OR NULL, 0 || NULL, NULL || NULL;     
+---------+--------+-----------+-----------+--------------+
| 1 OR -1 | 1 OR 0 | 1 OR NULL | 0 || NULL | NULL || NULL |
+---------+--------+-----------+-----------+--------------+
|       1 |      1 |         1 |    NULL   |       NULL   |
+---------+--------+-----------+-----------+--------------+
1 row in set, 2 warnings (0.00 sec)
#查询基本薪资不在9000-12000之间的员工编号和基本薪资
SELECT employee_id,salary FROM employees 
WHERE NOT (salary >= 9000 AND salary <= 12000);

SELECT employee_id,salary FROM employees 
WHERE salary <9000 OR salary > 12000;

SELECT employee_id,salary FROM employees 
WHERE salary NOT BETWEEN 9000 AND 12000;
SELECT employee_id, last_name, job_id, salary
FROM   employees
WHERE  salary >= 10000
OR     job_id LIKE '%MAN%';

注意:

OR可以和AND一起使用,但是在使用时要注意两者的优先级,由于AND的优先级高于OR,因此先对AND两边的操作数进行操作,再与OR中的操作数结合。

4.4 逻辑异或运算符(XOR)

逻辑异或(XOR)运算符是当给定的值中任意一个值为NULL时,则返回NULL;如果两个非NULL的值都是0或者都不等于0时,则返回0;如果一个值为0,另一个值不为0时,则返回1。

mysql> SELECT 1 XOR -1, 1 XOR 0, 0 XOR 0, 1 XOR NULL, 1 XOR 1 XOR 1, 0 XOR 0 XOR 0;
+----------+---------+---------+------------+---------------+---------------+
| 1 XOR -1 | 1 XOR 0 | 0 XOR 0 | 1 XOR NULL | 1 XOR 1 XOR 1 | 0 XOR 0 XOR 0 |
+----------+---------+---------+------------+---------------+---------------+
|        0 |       1 |       0 |       NULL |             1 |             0 |
+----------+---------+---------+------------+---------------+---------------+
1 row in set (0.00 sec)
select last_name,department_id,salary 
from employees
where department_id in (10,20) XOR salary > 8000;

5. 位运算符

位运算符是在二进制数上进行计算的运算符。位运算符会先将操作数变成二进制数,然后进行位运算,最后将计算结果从二进制变回十进制数。

MySQL支持的位运算符如下:
在这里插入图片描述

5.1 按位与运算符

按位与(&)运算符将给定值对应的二进制数逐位进行逻辑与运算。当给定值对应的二进制位的数值都为1时,则该位返回1,否则返回0。

mysql> SELECT 1 & 10, 20 & 30;
+--------+---------+
| 1 & 10 | 20 & 30 |
+--------+---------+
|      0 |      20 |
+--------+---------+
1 row in set (0.00 sec)

1的二进制数为0001,10的二进制数为1010,所以1 & 10的结果为0000,对应的十进制数为0。20的二进制数为10100,30的二进制数为11110,所以20 & 30的结果为10100,对应的十进制数为20。

5.2 按位或运算符

按位或(|)运算符将给定的值对应的二进制数逐位进行逻辑或运算。当给定值对应的二进制位的数值有一个或两个为1时,则该位返回1,否则返回0。

mysql> SELECT 1 | 10, 20 | 30; 
+--------+---------+
| 1 | 10 | 20 | 30 |
+--------+---------+
|     11 |      30 |
+--------+---------+
1 row in set (0.00 sec)

1的二进制数为0001,10的二进制数为1010,所以1 | 10的结果为1011,对应的十进制数为11。20的二进制数为10100,30的二进制数为11110,所以20 | 30的结果为11110,对应的十进制数为30。

5.3 按位异或运算符

按位异或(^)运算符将给定的值对应的二进制数逐位进行逻辑异或运算。当给定值对应的二进制位的数值不同时,则该位返回1,否则返回0。

mysql> SELECT 1 ^ 10, 20 ^ 30; 
+--------+---------+
| 1 ^ 10 | 20 ^ 30 |
+--------+---------+
|     11 |      10 |
+--------+---------+
1 row in set (0.00 sec)

1的二进制数为0001,10的二进制数为1010,所以1 ^ 10的结果为1011,对应的十进制数为11。20的二进制数为10100,30的二进制数为11110,所以20 ^ 30的结果为01010,对应的十进制数为10。

再举例:

mysql> SELECT 12 & 5, 12 | 5,12 ^ 5 FROM DUAL;
+--------+--------+--------+
| 12 & 5 | 12 | 5 | 12 ^ 5 |
+--------+--------+--------+
|      4 |     13 |      9 |
+--------+--------+--------+
1 row in set (0.00 sec)

在这里插入图片描述

5.4 按位取反运算符

按位取反(~)运算符将给定的值的二进制数逐位进行取反操作,即将1变为0,将0变为1。

mysql> SELECT 10 & ~1;
+---------+
| 10 & ~1 |
+---------+
|      10 |
+---------+
1 row in set (0.00 sec)

由于按位取反(~)运算符的优先级高于按位与(&)运算符的优先级,所以10 & ~1,首先,对数字1进行按位取反操作,结果除了最低位为0,其他位都为1,然后与10进行按位与操作,结果为10。

5.5 按位右移运算符

按位右移(>>)运算符将给定的值的二进制数的所有位右移指定的位数。右移指定的位数后,右边低位的数值被移出并丢弃,左边高位空出的位置用0补齐。

mysql> SELECT 1 >> 2, 4 >> 2;
+--------+--------+
| 1 >> 2 | 4 >> 2 |
+--------+--------+
|      0 |      1 |
+--------+--------+
1 row in set (0.00 sec)

1的二进制数为0000 0001,右移2位为0000 0000,对应的十进制数为0。4的二进制数为0000 0100,右移2位为0000 0001,对应的十进制数为1。

5.6 按位左移运算符

按位左移(<<)运算符将给定的值的二进制数的所有位左移指定的位数。左移指定的位数后,左边高位的数值被移出并丢弃,右边低位空出的位置用0补齐。

mysql> SELECT 1 << 2, 4 << 2;  
+--------+--------+
| 1 << 2 | 4 << 2 |
+--------+--------+
|      4 |     16 |
+--------+--------+
1 row in set (0.00 sec)

1的二进制数为0000 0001,左移两位为0000 0100,对应的十进制数为4。4的二进制数为0000 0100,左移两位为0001 0000,对应的十进制数为16。

6. 运算符的优先级

在这里插入图片描述
数字编号越大,优先级越高,优先级高的运算符先进行计算。可以看到,赋值运算符的优先级最低,使用“()”括起来的表达式的优先级最高。

7. 拓展:使用正则表达式查询

正则表达式通常被用来检索或替换那些符合某个模式的文本内容,根据指定的匹配模式匹配文本中符合要求的特殊字符串。例如,从一个文本文件中提取电话号码,查找一篇文章中重复的单词或者替换用户输入的某些敏感词语等,这些地方都可以使用正则表达式。正则表达式强大而且灵活,可以应用于非常复杂的查询。

MySQL中使用REGEXP关键字指定正则表达式的字符匹配模式。下表列出了REGEXP操作符中常用字符匹配列表。
在这里插入图片描述

1. 查询以特定字符或字符串开头的记录
字符‘^’匹配以特定字符或者字符串开头的文本。

在fruits表中,查询f_name字段以字母‘b’开头的记录,SQL语句如下:

mysql> SELECT * FROM fruits WHERE f_name REGEXP '^b';

2. 查询以特定字符或字符串结尾的记录
字符‘$’匹配以特定字符或者字符串结尾的文本。

在fruits表中,查询f_name字段以字母‘y’结尾的记录,SQL语句如下:

mysql> SELECT * FROM fruits WHERE f_name REGEXP 'y$';

3. 用符号"."来替代字符串中的任意一个字符
字符‘.’匹配任意一个字符。
在fruits表中,查询f_name字段值包含字母‘a’与‘g’且两个字母之间只有一个字母的记录,SQL语句如下:

mysql> SELECT * FROM fruits WHERE f_name REGEXP 'a.g';

4. 使用"*“和”+"来匹配多个字符
星号‘*’匹配前面的字符任意多次,包括0次。加号‘+’匹配前面的字符至少一次。

在fruits表中,查询f_name字段值以字母‘b’开头且‘b’后面出现字母‘a’的记录,SQL语句如下:

mysql> SELECT * FROM fruits WHERE f_name REGEXP '^ba*';

在fruits表中,查询f_name字段值以字母‘b’开头且‘b’后面出现字母‘a’至少一次的记录,SQL语句如下:

mysql> SELECT * FROM fruits WHERE f_name REGEXP '^ba+';

5. 匹配指定字符串
正则表达式可以匹配指定字符串,只要这个字符串在查询文本中即可,如要匹配多个字符串,多个字符串之间使用分隔符‘|’隔开。

在fruits表中,查询f_name字段值包含字符串“on”的记录,SQL语句如下:

mysql> SELECT * FROM fruits WHERE f_name REGEXP 'on';

在fruits表中,查询f_name字段值包含字符串“on”或者“ap”的记录,SQL语句如下:

mysql> SELECT * FROM fruits WHERE f_name REGEXP 'on|ap';

之前介绍过,LIKE运算符也可以匹配指定的字符串,但与REGEXP不同,LIKE匹配的字符串如果在文本中间出现,则找不到它,相应的行也不会返回。REGEXP在文本内进行匹配,如果被匹配的字符串在文本中出现,REGEXP将会找到它,相应的行也会被返回。对比结果如下所示。

在fruits表中,使用LIKE运算符查询f_name字段值为“on”的记录,SQL语句如下:

mysql> SELECT * FROM fruits WHERE f_name like 'on';
Empty set(0.00 sec)

6. 匹配指定字符中的任意一个
方括号“[]”指定一个字符集合,只匹配其中任何一个字符,即为所查找的文本。

在fruits表中,查找f_name字段中包含字母‘o’或者‘t’的记录,SQL语句如下:

mysql> SELECT * FROM fruits WHERE f_name REGEXP '[ot]';

在fruits表中,查询s_id字段中包含4、5或者6的记录,SQL语句如下:

mysql> SELECT * FROM fruits WHERE s_id REGEXP '[456]';

7. 匹配指定字符以外的字符
“[^字符集合]”匹配不在指定集合中的任何字符。

在fruits表中,查询f_id字段中包含字母ae和数字12以外字符的记录,SQL语句如下:

mysql> SELECT * FROM fruits WHERE f_id REGEXP '[^a-e1-2]';

8. 使用{n,}或者{n,m}来指定字符串连续出现的次数
“字符串{n,}”表示至少匹配n次前面的字符;“字符串{n,m}”表示匹配前面的字符串不少于n次,不多于m次。例如,a{2,}表示字母a连续出现至少2次,也可以大于2次;a{2,4}表示字母a连续出现最少2次,最多不能超过4次。

在fruits表中,查询f_name字段值出现字母‘x’至少2次的记录,SQL语句如下:

mysql> SELECT * FROM fruits WHERE f_name REGEXP 'x{2,}';

在fruits表中,查询f_name字段值出现字符串“ba”最少1次、最多3次的记录,SQL语句如下:

mysql> SELECT * FROM fruits WHERE f_name REGEXP 'ba{1,3}';

第06章 多表设计

1、概述

项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系,基本上分为三种:

  • 一对多(多对一)
  • 多对多
  • 一对一

2、一对多

需求:

  • 根据 页面原型 及 需求文档 ,完成部门(一)员工(多)模块的表结构设计。
    在这里插入图片描述
    在这里插入图片描述
  • 一对多关系实现:在数据库表中多的一方,添加字段,来关联一的一方的主键。

3、一对多-外键

3.1、外键约束-概述

现象:

  • 部门数据可以直接删除,然而还有部分员工归属于该部门下,此时就出现了数据的不完整、不一致问题。

问题分析:

  • 目前上述的两张表,在数据库层面,并未建立关联,所以是无法保证数据的一致性和完整性的。
  • 解决:使用外键约束

外键约束:

  • 语法:
    在这里插入图片描述
  • 当然在项目开发阶段,一般都是通过图形化界面的方式指定外键

3.2、物理外键VS逻辑外键

在这里插入图片描述

4、一对一(外键)

  • 案例: 用户 与 身份证信息 的关系
  • 关系: 一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他字段放在另一张表中,以提升操作效率
  • 实现:在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(UNIQUE)
  • 设置外键为唯一的目的:保证它们之间是绝对的一对一关系

在这里插入图片描述
在这里插入图片描述

5、多对多

  • 案例: 学生 与 课程的关系
  • 关系: 一个学生可以选修多门课程,一门课程也可以供多个学生选择
  • 实现:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键

在这里插入图片描述

6、综合案例

6.1、项目介绍&需求说明

苍穹外卖项目介绍

  • 苍穹外卖是一个外卖平台,餐厅的管理端,餐厅的老板和员工都可以登录到这个后台管理系统,之后在这个后台管理中配置当前餐厅所经营的一些菜品的分类、菜品、以及菜品的套餐等数据,在后台管理系统中配置好后,移动端的用户就可以基于微信小程序来进行点餐了。
  • 后台管理系统:
    在这里插入图片描述
  • 用户端系统:
    在这里插入图片描述

需求

  • 参考资料中提供的 《苍穹外卖_管理后台》 页面原型,设计分类管理菜品管理套餐管理模块的表结构。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

6.2、实现步骤

  • 阅读页面原型及需求文档,分析各个模块涉及到的表结构,及表结构之间的关系。

    • 分类表----菜品表(一对多)
    • 分类表----套餐表(一对多)
    • 套餐表----菜品表(多对多)
      在这里插入图片描述
  • 根据页面原型及需求文档,分析各个表结构中具体的字段及约束。

    • 分类表
      在这里插入图片描述

    • 菜品表
      在这里插入图片描述

    • 套餐表
      在这里插入图片描述

    • 菜品-套餐关系表
      在这里插入图片描述

7、自连接

语法:

select 字段列表 
from   表A 别名A 
	   join  表A 别名B  on 条件...;

自连接查询,可以是内连接查询,也可以是外连接查询。

-- 1. 查询员工 及其 所属领导的名字
-- 表结构: emp

select a.name , b.name from emp a , emp b where a.managerid = b.id;

-- 2. 查询所有员工 emp 及其领导的名字 emp , 如果员工没有领导, 也需要查询出来
-- 表结构: emp a , emp b

select a.name '员工', b.name '领导' from emp a left join emp b on a.managerid = b.id;

第07章_多表查询

概念:多表查询,也称为关联查询,指两个或更多个表一起完成查询操作。

前提条件:这些一起查询的表之间是有关系的(一对一、一对多),它们之间一定是有关联字段,这个关联字段可能建立了外键,也可能没有建立外键。比如:员工表和部门表,这两个表依靠“部门编号”进行关联。

1. 一个案例引发的多表连接

1.1 案例说明

在这里插入图片描述

为什么要进行多表查询???

#查询员工名为'Abel'的人在哪个城市工作?
SELECT * 
FROM employees
WHERE last_name = 'Abel';#在员工表根据已知条件姓名,可以查出所在那个部门的id为80

SELECT *
FROM departments
WHERE department_id = 80;#在部门表根据这个人所在的那个部门id,查询出这个部门的位置id


SELECT *
FROM locations 
WHERE location_id = 2500;# 在位置表中根据位置id,查询所在城市。

说明:这样查询3次也能查询出来,但是每次查询都需要网络进行连接交互需要耗费时间,写3次查询出来肯定
比一次速度慢,所以不用这样查询。

为什么要分为3张表呢,可不可以和成一张表呢???

可以,但是有缺点。
场景一:假设部门表的IT部门,对应在员工表有300个数据。部门表写部门的详细信息,现在部门表只需要写1条数据,员工表需要写
300条数据 每条数据后只需写一个部门id编号即可。如果合成一块写 员工表的每个员工都要由原来的简单写一个部门id变为部门的
详细信息,字数太多显得雍余。

场景二:假设现在很多员工没有部门,现在只需要在部门表显示为null即可,如果合在一块写有部门的员工要写详细信息(位置,部门,
部门id等很多字段)而没有部门的员工没有办法写部门信息,导致很多字段为空,显然不合适。这还是2张表,更多表的话更加严重。
字段越多问题:io一次性加载的字段是有限的,字段越多加载次数也越多,效率也更低。

从多个表中获取数据:3张表分别是:员工表 部门表 位置表

在这里插入图片描述

#案例:查询员工的姓名及其部门名称
#错误的方式1
SELECT last_name, department_name
FROM employees, departments; #查询出2889条记录

或者:

#错误的方式2
SELECT employee_id,department_name
FROM employees CROSS JOIN departments;#查询出2889条记录

#分析: 出现笛卡尔积的错误   错误的原因:缺少了多表的连接条件

在这里插入图片描述

查询结果:

+-----------+----------------------+
| last_name | department_name      |
+-----------+----------------------+
| King      | Administration       |
| King      | Marketing            |
| King      | Purchasing           |
| King      | Human Resources      |
| King      | Shipping             |
| King      | IT                   |
| King      | Public Relations     |
| King      | Sales                |
| King      | Executive            |
| King      | Finance              |
| King      | Accounting           |
| King      | Treasury             |
...
| Gietz     | IT Support           |
| Gietz     | NOC                  |
| Gietz     | IT Helpdesk          |
| Gietz     | Government Sales     |
| Gietz     | Retail Sales         |
| Gietz     | Recruiting           |
| Gietz     | Payroll              |
+-----------+----------------------+
2889 rows in set (0.01 sec)

分析错误情况:

SELECT COUNT(employee_id) FROM employees;
#输出107行 员工表有107行

SELECT COUNT(department_id)FROM departments;
#输出27行  部门表有27行

SELECT 107*27 FROM dual; #107*27=2889,说明每个部门下都匹配了一遍所有员工,这种方式称为现笛卡尔积错误。

我们把上述多表查询中出现的问题称为:笛卡尔积的错误

1.2 笛卡尔积(或交叉连接)的理解

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

在这里插入图片描述

SQL92中,笛卡尔积也称为交叉连接,英文是 CROSS JOIN。在 SQL99 中也是使用 CROSS JOIN表示交叉连接。它的作用就是可以把任意表进行连接,即使这两张表不相关。在MySQL中如下情况会出现笛卡尔积:

#查询员工姓名和所在部门名称
SELECT last_name,department_name FROM employees,departments;
SELECT last_name,department_name FROM employees CROSS JOIN departments;
SELECT last_name,department_name FROM employees INNER JOIN departments;
SELECT last_name,department_name FROM employees JOIN departments;

1.3 案例分析与问题解决(表别名)

  • 笛卡尔积的错误会在下面条件下产生

    • 省略多个表的连接条件(或关联条件)
    • 连接条件(或关联条件)无效
    • 所有表中的所有行互相连接
  • 为了避免笛卡尔积, 可以在 WHERE 加入有效的连接条件。

  • 如何找关联条件:主表的主键等于从表的外键(主表有一列肯定和从表中的一列,表示意义相同,那么这2个列就应该相连,即连接条件。)
    在这里插入图片描述

  • 加入连接条件后,查询语法:

  SELECT	table1.column, table2.column
  FROM	table1, table2
  WHERE	table1.column1 = table2.column2;  #连接条件
  • 在 WHERE子句中写入连接条件。

  • 正确写法:

  #案例:查询员工的姓名及其对应的部门名称 107个员工应该查询出107条数据并且对应的部门
  #1. 员工表共有107条数据,查询出106条数据,因为有一条员工没有部门为null.
  SELECT last_name, department_name
  FROM employees, departments
  #两个表的连接条件
  WHERE employees.department_id = departments.department_id; 
  
  #2. 如果查询语句中出现了多个表中都存在的字段,则必须指明此字段所在的表。
  SELECT employees.employee_id,departments.department_name,employees.department_id
  FROM employees,departments
  WHERE employees.`department_id` = departments.department_id;
  
  #建议:从sql优化的角度,建议多表查询时,每个字段前都指明其所在的表。
  #原因:如果没有指明 先是到每个表中找到是否有这个字段在进行比较。如果指定了就不会再找别的表了。
  
  
  #3.可以给表起别名,在SELECT和WHERE中使用表的别名。
  SELECT emp.employee_id,dept.department_name,emp.department_id
  FROM employees emp,departments dept
  WHERE emp.`department_id` = dept.department_id;
  
  #如果给表起了别名,一旦在SELECT或WHERE中使用表名的话,则必须使用表的别名,而不能再使用表的原名。列名没有此用法。
  #如下的操作是错误的:
  #原因:还是sql执行顺序问题,先走from并且起表别名,再走where这时已经用别名覆盖了只能使用表别名,再走select。
  SELECT emp.employee_id,departments.department_name,emp.department_id
  FROM employees emp,departments dept
  WHERE emp.`department_id` = departments.department_id;
  
  #4. 结论:如果有n个表实现多表的查询,则需要至少n-1个连接条件
  #练习:查询员工的employee_id,last_name,department_name,city
  SELECT e.employee_id,e.last_name,d.department_name,l.city,e.department_id,l.location_id
  FROM employees e,departments d,locations l
  WHERE e.`department_id` = d.`department_id`
  AND d.`location_id` = l.`location_id`;
  
  • 在表中有相同列时,在列名之前加上表名前缀。

2. 多表查询分类讲解

分类1:等值连接 vs 非等值连接

等值连接(列名前缀)

在这里插入图片描述

SELECT employees.employee_id, employees.last_name, 
       employees.department_id, departments.department_id,
       departments.location_id
FROM   employees, departments
WHERE  employees.department_id = departments.department_id;

在这里插入图片描述

在这里插入图片描述

拓展1:多个连接条件与 AND 操作符

在这里插入图片描述

拓展2:区分重复的列名

  • 多个表中有相同列时,必须在列名之前加上表名前缀。
  • 在不同表中具有相同列名的列可以用表名加以区分。
SELECT employees.last_name, departments.department_name,employees.department_id
FROM employees, departments
WHERE employees.department_id = departments.department_id;

拓展3:表的别名

  • 使用别名可以简化查询。

  • 列名前使用表名前缀可以提高查询效率。

SELECT e.employee_id, e.last_name, e.department_id,
       d.department_id, d.location_id
FROM   employees e , departments d
WHERE  e.department_id = d.department_id;

需要注意的是,如果我们使用了表的别名,在查询字段中、过滤条件中就只能使用别名进行代替,不能使用原有的表名,否则就会报错。

阿里开发规范

强制】对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(或 表名)进行限定。

说明:对多表进行查询记录、更新记录、删除记录时,如果对操作列没有限定表的别名(或表名),并且操作列在多个表中存在时,就会抛异常。

正例:select t1.name from table_first as t1 , table_second as t2 where t1.id=t2.id;

反例:在某业务中,由于多表关联查询语句没有加表的别名(或表名)的限制,正常运行两年后,最近在 某个表中增加一个同名字段,在预发布环境做数据库变更后,线上查询语句出现出 1052 异常:Column ‘name’ in field list is ambiguous。

拓展4:连接多个表

在这里插入图片描述

总结:连接 n个表,至少需要n-1个连接条件。比如,连接三个表,至少需要两个连接条件。

练习:查询出公司员工的 last_name,department_name, city

非等值连接

在这里插入图片描述

# 员工表  工资登记表  这2个表之间没有关联关系
SELECT *
FROM job_grades;

#查询出员工表所在的薪资等级是什么?显示的结果来自2张表,这2张表没有关系在一起查询可以使用非等值查询。
SELECT e.last_name,e.salary,j.grade_level
FROM employees e,job_grades j
#where e.`salary` between j.`lowest_sal` and j.`highest_sal`;
WHERE e.`salary` >= j.`lowest_sal` AND e.`salary` <= j.`highest_sal`;

在这里插入图片描述

在这里插入图片描述

分类2:自连接 vs 非自连接(自查询)

在这里插入图片描述

  • 当table1和table2本质上是同一张表,只是用取别名的方式虚拟成两张表以代表不同的意义。然后两个表再进行内连接,外连接等查询。

题目:查询employees表,返回“Xxx works for Xxx”

# 查询员工以及对应的员工的上级,查询显示的数据都来自同一张表,需要别名进行区分看成2张表,在使用等值查询条件进行查询。
SELECT CONCAT(worker.last_name ,' works for ' 
       , manager.last_name)
FROM   employees worker, employees manager
WHERE  worker.manager_id = manager.employee_id ;

在这里插入图片描述

在这里插入图片描述

练习:查询出last_name为 ‘Chen’ 的员工的 manager 的信息。

分类3:内连接 vs 外连接

除了查询满足条件的记录以外,外连接还可以查询某一方不满足条件的记录。

在这里插入图片描述

  • 1.内连接: 合并具有同一列的两个以上的表的行, 结果集中不包含一个表与另一个表不匹配的行相当于查询A、B交集部分数据

  • 2.外连接: 两个表在连接过程中除了返回满足连接条件的行以外还返回左(或右)表中不满足条件的行 ,这种连接称为左(或右) 外连接。没有匹配的行时, 结果表中相应的列为空(NULL)。

  • 3.外连接的分类:左外连接、右外连接、满外连接

    • 3.1 左外连接:两个表在连接过程中除了返回满足连接条件的行以外还返回左表中不满足条件的行,这种连接称为左外连接。(查询左表所有数据,包括两张表交集部分数据)

    • 3.2 两个表在连接过程中除了返回满足连接条件的行以外还返回右表中不满足条件的行,这种连接称为右外连接。(查询右表所有数据,包括两张表交集部分数据
      在这里插入图片描述

  • 4.如果是左外连接,则连接条件中左边的表也称为主表,右边的表称为从表

    如果是右外连接,则连接条件中右边的表也称为主表,左边的表称为从表

SQL92:隐式内连接

说明

  1. sql92的内连接也叫做隐式内连接
  2. sql99的内连接也叫做显示内连接

隐式内连接语法

select  字段列表   from1 ,2   where   条件 ... ;
SQL92:使用(+)创建外连接
  • 在 SQL92 中采用(+)代表从表所在的位置。即左或右外连接中,(+) 表示哪个是从表。

  • Oracle 对 SQL92 支持较好,而 MySQL 则不支持 SQL92 的外连接。

  • 注意:上面讲的多表查询连接都是SQL92标准的内连接

  #SQL92语法实现外连接:使用 +  ----------MySQL不支持SQL92语法中外连接的写法!oracle支持
  #左外连接
  #即有+的代表从表
  SELECT last_name,department_name
  FROM employees ,departments  
  WHERE employees.department_id = departments.department_id(+);#主表  从表:说明是左外连接
  
  #右外连接
  SELECT last_name,department_name
  FROM employees ,departments
  WHERE employees.department_id(+) = departments.department_id;# 从表  主表 :说明是右外连接
  • 而且在 SQL92 中,只有左外连接和右外连接,没有满(或全)外连接。

3. SQL99语法实现多表查询

3.1 基本语法

  • 使用JOIN…ON子句创建连接的语法结构:
  SELECT table1.column, table2.column,table3.column
  FROM table1
      JOIN table2 ON table1 和 table2 的连接条件
          JOIN table3 ON table2 和 table3 的连接条件

它的嵌套逻辑类似我们使用的 FOR 循环:

  for t1 in table1:
      for t2 in table2:
         if condition1:
             for t3 in table3:
                if condition2:
                    output t1 + t2 + t3

SQL99 采用的这种嵌套结构非常清爽、层次性更强、可读性更强,即使再多的表进行连接也都清晰可见。如果你采用 SQL92,可读性就会大打折扣。

  • 语法说明:

    • 可以使用 ON 子句指定额外的连接条件
    • 关联条件:使用外键描述的表关系,所以这个关联条件就是外键。
    • 这个连接条件是与其它条件分开的。
    • ON 子句使语句具有更高的易读性
    • 关键字JOIN、INNER JOIN、CROSS JOIN的含义是一样的,都表示内连接

3.2 显示内连接(INNER JOIN-ON)

说明

  1. sql92的内连接也叫做隐式内连接
  2. sql99的内连接也叫做显示内连接
  • 语法:
/*之前条件语句的关键字都是where,而在sql99语法中写条件语句关键字习惯上:关联条件使用on(关联语句:一般和join配合使用)
其它的普通条件使用where,当然都用where也可以,但是分开写可读性更好。*/
SELECT 字段列表
FROM A表 INNER JOIN B表
ON 关联条件
WHERE 等其他子句;

题目1:

SELECT e.employee_id, e.last_name, e.department_id, 
       d.department_id, d.location_id
FROM   employees e JOIN departments d
ON     (e.department_id = d.department_id);

在这里插入图片描述

题目2:

#join一张表就on一个关联条件
SELECT employee_id, city, department_name
FROM   employees e 
		  JOIN   departments d ON d.department_id = e.department_id 
		  JOIN   locations   l ON d.location_id = l.location_id;

在这里插入图片描述

3.3 外连接(OUTER JOIN)

3.3.1 左外连接(LEFT OUTER JOIN)
  • 语法:
#实现查询结果是A
SELECT 字段列表
FROM A表 LEFT JOIN B表
ON 关联条件
WHERE 等其他子句;
  • 举例:
#outer可以省略掉
SELECT e.last_name, e.department_id, d.department_name
FROM   employees e  LEFT OUTER JOIN departments d
ON   (e.department_id = d.department_id) ;

在这里插入图片描述

3.3.2 右外连接(RIGHT OUTER JOIN)
  • 语法:
#实现查询结果是B
SELECT 字段列表
FROM A表 RIGHT JOIN B表
ON 关联条件
WHERE 等其他子句;
  • 举例:
#outer可以省略掉
SELECT e.last_name, e.department_id, d.department_name
FROM   employees e  RIGHT OUTER JOIN departments d
ON    (e.department_id = d.department_id) ;

在这里插入图片描述

需要注意的是,LEFT JOIN 和 RIGHT JOIN 只存在于 SQL99 及以后的标准中,在 SQL92 中不存在,只能用 (+) 表示。

3.3.3 满外连接(FULL OUTER JOIN)
  • 满外连接的结果 = 左右表匹配的数据 + 左表没有匹配到的数据 + 右表没有匹配到的数据。
  • SQL99是支持满外连接的。使用FULL JOIN 或 FULL OUTER JOIN来实现。
  • 需要注意的是,MySQL不支持FULL JOIN,但是可以用 LEFT JOIN UNION RIGHT join代替。
#满外连接:mysql不支持FULL OUTER JOIN
SELECT last_name,department_name
FROM employees e FULL OUTER JOIN departments d
ON e.`department_id` = d.`department_id`;

4. UNION的使用

合并查询结果
利用UNION关键字,可以给出多条SELECT语句,并将它们的结果组合成单个结果集。合并时,两个表对应的列数和数据类型必须相同,并且相互对应。各个SELECT语句之间使用UNION或UNION ALL关键字分隔。

语法格式:

SELECT column,... FROM table1
UNION [ALL]
SELECT column,... FROM table2

UNION操作符

在这里插入图片描述

UNION 操作符返回两个查询的结果集的并集,去除重复记录

优点:会去重 。缺点:效率低。

UNION ALL操作符

在这里插入图片描述

UNION ALL操作符返回两个查询的结果集的并集。对于两个结果集的重复部分,不去重

优点:效率高。缺点:不去重。

因为使用UNION ALL的查询效率更高,但是查询的缺点是不去重,那么我们就想要使用UNION ALL查询提升效率,并且能够去重

改如何办呢???

可以使用左外连接去重后的+右外连接没有去重的实现全外连接,或者左外连接没有去重+右外连接去重的。详情查看下面5.7 中的全外连接查询

注意:执行UNION ALL语句时所需要的资源比UNION语句少。如果明确知道合并数据后的结果数据不存在重复数据,或者不需要去除重复的数据,则尽量使用UNION ALL语句,以提高数据查询的效率

举例:查询部门编号>90或邮箱包含a的员工信息

#方式1
SELECT * FROM employees WHERE email LIKE '%a%' OR department_id>90;
#方式2
SELECT * FROM employees  WHERE email LIKE '%a%'
UNION
SELECT * FROM employees  WHERE department_id>90;

举例:查询中国用户中男性的信息以及美国用户中年男性的用户信息

SELECT id,cname FROM t_chinamale WHERE csex='男'
UNION ALL
SELECT id,tname FROM t_usmale WHERE tGender='male';

5. 7种SQL JOINS的实现

在这里插入图片描述

5.7.1 代码实现

#中图:内连接 A∩B 交集
SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
ON e.`department_id` = d.`department_id`;
#左上图:左外连接
SELECT employee_id,last_name,department_name
FROM employees e LEFT JOIN departments d
ON e.`department_id` = d.`department_id`;
#右上图:右外连接
SELECT employee_id,last_name,department_name
FROM employees e RIGHT JOIN departments d
ON e.`department_id` = d.`department_id`;
#左中图:A - A∩B
SELECT employee_id,last_name,department_name
FROM employees e LEFT JOIN departments d
ON e.`department_id` = d.`department_id`
WHERE d.`department_id` IS NULL
#右中图:B-A∩B
SELECT employee_id,last_name,department_name
FROM employees e RIGHT JOIN departments d
ON e.`department_id` = d.`department_id`
WHERE e.`department_id` IS NULL
# 左下图:满外连接
# 方式1:左上图 UNION ALL 右中图

SELECT employee_id,department_name
FROM employees e LEFT JOIN departments d
ON e.`department_id` = d.`department_id`
UNION ALL
SELECT employee_id,department_name
FROM employees e RIGHT JOIN departments d
ON e.`department_id` = d.`department_id`
WHERE e.`department_id` IS NULL;


# 方式2:左中图 UNION ALL 右上图

SELECT employee_id,department_name
FROM employees e LEFT JOIN departments d
ON e.`department_id` = d.`department_id`
WHERE d.`department_id` IS NULL
UNION ALL
SELECT employee_id,department_name
FROM employees e RIGHT JOIN departments d
ON e.`department_id` = d.`department_id`;

#右下图
#左中图 + 右中图  A ∪B- A∩B 或者 (A -  A∩B) ∪ (B - A∩B)
SELECT employee_id,last_name,department_name
FROM employees e LEFT JOIN departments d
ON e.`department_id` = d.`department_id`
WHERE d.`department_id` IS NULL
UNION ALL
SELECT employee_id,last_name,department_name
FROM employees e RIGHT JOIN departments d
ON e.`department_id` = d.`department_id`
WHERE e.`department_id` IS NULL

5.7.2 语法格式小结

  • 左中图
#实现A -  A∩B
select 字段列表
from A表 left join B表
on 关联条件
where 从表关联字段 is null and 等其他子句;
  • 右中图
#实现B -  A∩B
select 字段列表
from A表 right join B表
on 关联条件
where 从表关联字段 is null and 等其他子句;
  • 左下图
#实现查询结果是A∪B
#用左外的A,union 右外的B
select 字段列表
from A表 left join B表
on 关联条件
where 等其他子句

union 

select 字段列表
from A表 right join B表
on 关联条件
where 等其他子句;
  • 右下图
#实现A∪B -  A∩B  或   (A -  A∩B) ∪ (B - A∩B)
#使用左外的 (A -  A∩B)  union 右外的(B - A∩B)
select 字段列表
from A表 left join B表
on 关联条件
where 从表关联字段 is null and 等其他子句

union

select 字段列表
from A表 right join B表
on 关联条件
where 从表关联字段 is null and 等其他子句

5.8 多表查询思维导图

总结

  • 内连接查询:推荐使用sql92语法:多张表之间用逗号隔开,关联条件和普通条件都用where
  • 外连接查询:推荐使用sql99语法:多张表之间使用关键字(ltft/right join)连接,关联条件使用on,普通条件使用where

在这里插入图片描述

6. SQL99语法新特性

6.1 自然连接

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

在SQL92标准中:

#2个连接条件,比较灵活,使用几个连接条件就写几个。(推荐使用)
SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
ON e.`department_id` = d.`department_id`
AND e.`manager_id` = d.`manager_id`;

在 SQL99 中你可以写成:

#会自动的帮你查询所有的连接条件。缺点:不够灵活,强制使用所有的连接条件,但是如果就想要使用一个连接条件没有办法解决。
SELECT employee_id,last_name,department_name
FROM employees e NATURAL JOIN departments d;

6.2 USING连接

当我们进行连接的时候,SQL99还支持使用 USING 指定数据表里的同名字段进行等值连接。但是只能配合JOIN一起使用。比如:

SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
USING (department_id);

你能看出与自然连接 NATURAL JOIN 不同的是,USING 指定了具体的相同的字段名称,你需要在 USING 的括号 () 中填入要指定的同名字段。同时使用 JOIN...USING 可以简化 JOIN ON 的等值连接。它与下面的 SQL 查询结果是相同的:

SELECT employee_id,last_name,department_name
FROM employees e ,departments d
WHERE e.department_id = d.department_id;

7. 章节小结

表连接的约束条件可以有三种方式:WHERE, ON, USING

  • WHERE:适用于所有关联查询

  • ON:只能和JOIN一起使用,只能写关联条件。虽然关联条件可以并到WHERE中和其他条件一起写,但分开写可读性更好。

  • USING:只能和JOIN一起使用,而且要求两个关联字段在关联表中名称一致,而且只能表示关联字段值相等

#关联条件
#把关联条件写在where后面
SELECT last_name,department_name 
FROM employees,departments 
WHERE employees.department_id = departments.department_id;

#把关联条件写在on后面,只能和JOIN一起使用
SELECT last_name,department_name 
FROM employees INNER JOIN departments 
ON employees.department_id = departments.department_id;

SELECT last_name,department_name 
FROM employees CROSS JOIN departments 
ON employees.department_id = departments.department_id;

SELECT last_name,department_name  
FROM employees JOIN departments 
ON employees.department_id = departments.department_id;

#把关联字段写在using()中,只能和JOIN一起使用
#而且两个表中的关联字段必须名称相同,而且只能表示=
#查询员工姓名与基本工资
SELECT last_name,job_title
FROM employees INNER JOIN jobs USING(job_id);

#n张表关联,需要n-1个关联条件
#查询员工姓名,基本工资,部门名称
SELECT last_name,job_title,department_name FROM employees,departments,jobs 
WHERE employees.department_id = departments.department_id 
AND employees.job_id = jobs.job_id;


# 3张表查询可以写一个inner join 在on一下分开写,也可以像这样统一写一个on
SELECT last_name,job_title,department_name 
FROM employees INNER JOIN departments INNER JOIN jobs 
ON employees.department_id = departments.department_id 
AND employees.job_id = jobs.job_id;

注意:

我们要控制连接表的数量。多表连接就相当于嵌套 for 循环一样,非常消耗资源,会让 SQL 查询性能下降得很严重,因此不要连接不必要的表。在许多 DBMS 中,也都会有最大连接表的限制。

【强制】超过三个表禁止 join。需要 join 的字段,数据类型保持绝对一致;多表关联查询时, 保证被关联的字段需要有索引。

说明:即使双表 join 也要注意表索引、SQL 性能。

来源:阿里巴巴《Java开发手册》

附录:常用的 SQL 标准有哪些

在正式开始讲连接表的种类时,我们首先需要知道 SQL 存在不同版本的标准规范,因为不同规范下的表连接操作是有区别的。

SQL 有两个主要的标准,分别是 SQL92SQL99。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 语言的使用。

第08章_单行函数

1. 函数的理解

1.1 什么是函数

函数在计算机语言的使用中贯穿始终,函数的作用是什么呢?它可以把我们经常使用的代码封装起来,需要的时候直接调用即可。这样既提高了代码效率,又提高了可维护性。在 SQL 中我们也可以使用函数对检索出来的数据进行函数操作。使用这些函数,可以极大地提高用户对数据库的管理效率

在这里插入图片描述

从函数定义的角度出发,我们可以将函数分成内置函数自定义函数。在 SQL 语言中,同样也包括了内置函数和自定义函数。内置函数是系统内置的通用函数,而自定义函数是我们根据自己的需要编写的,本章及下一章讲解的是 SQL 的内置函数。

1.2 不同DBMS函数的差异

我们在使用 SQL 语言的时候,不是直接和这门语言打交道,而是通过它使用不同的数据库软件,即 DBMS。DBMS 之间的差异性很大,远大于同一个语言不同版本之间的差异。实际上,只有很少的函数是被 DBMS 同时支持的。比如,大多数 DBMS 使用(||)或者(+)来做拼接符,而在 MySQL 中的字符串拼接函数为concat()。大部分 DBMS 会有自己特定的函数,这就意味着采用 SQL 函数的代码可移植性是很差的,因此在使用函数的时候需要特别注意。

1.3 MySQL的内置函数及分类

MySQL提供了丰富的内置函数,这些函数使得数据的维护与管理更加方便,能够更好地提供数据的分析与统计功能,在一定程度上提高了开发人员进行数据分析与统计的效率。

MySQL提供的内置函数从实现的功能角度可以分为数值函数、字符串函数、日期和时间函数、流程控制函数、加密与解密函数、获取MySQL信息函数、聚合函数等。这里,我将这些丰富的内置函数再分为两类多行函数聚合函数(或分组函数或单行函数)

两种SQL函数

在这里插入图片描述

单行函数

  • 操作数据对象
  • 接受参数返回一个结果
  • 只对一行进行变换
  • 每行返回一个结果
  • 可以嵌套
  • 参数可以是一列或一个值

2. 数值函数

2.1 基本函数

函数用法
ABS(x)返回x的绝对值
SIGN(X)返回X的符号。正数返回1,负数返回-1,0返回0
PI()返回圆周率的值 ,注意里面不需要在写参数
CEIL(x),CEILING(x)返回大于或等于某个值的最小整数
FLOOR(x)返回小于或等于某个值的最大整数
LEAST(e1,e2,e3…)返回列表中的最小值
GREATEST(e1,e2,e3…)返回列表中的最大值
MOD(x,y)返回X除以Y后的余数
RAND()返回0~1的随机值
RAND(x)返回0~1的随机值,其中x的值用作种子值,相同的X值会产生相同的随机数
ROUND(x)返回一个对x的值进行四舍五入后,最接近于X的整数
ROUND(x,y)返回一个对x的值进行四舍五入后最接近X的值,并保留到小数点后面Y位
TRUNCATE(x,y)返回数字x截断为y位小数的结果
SQRT(x)返回x的平方根。当X的值为负数时,返回NULL

举例:

#基本的操作
SELECT ABS(-123),ABS(32),SIGN(-23),SIGN(43),PI(),CEIL(32.32),CEILING(-43.23),FLOOR(32.32),
FLOOR(-43.23),MOD(12,5)
FROM DUAL;
#解释不好理解的函数:
CEIL(x),CEILING(x)2个函数等价,向上取整。例:3233之间不管是32.1,还是32.9向上取整都为33。
FLOOR(x):正好相反,向下取整,例:3233之间不管是32.1,还是32.9向下取整都为32MOD(x,y):返回X除以Y后的余数。

在这里插入图片描述

#取随机数
SELECT RAND(),RAND(),RAND(10),RAND(10),RAND(-1),RAND(-1)
FROM DUAL;
#解释:
RAND():获取0~1之间的随机数
RAND(10),RAND(10):如果2个随机函数里面放的参数相同,那么获取的随机数也相同。

在这里插入图片描述

四舍五入,截断操作
SELECT ROUND(12.33),ROUND(12.343,2),ROUND(12.324,-1),TRUNCATE(12.66,1),TRUNCATE(12.66,-1)
FROM DUAL;

解释:四舍五入
SELECT ROUND(123.556):默认条件下只保留整数,对小数四舍五入。123.556---->124
ROUND(123.456,0):保留0位小数,123.456--->123
ROUND(123.456,1):保留1位小数,123.456--->123.5
ROUND(123.456,2):保留2位小数,123.456--->123.46
ROUND(123.456,-1):保留到2这个位置,3不满足5舍去,123.456--->120
ROUND(153.456,-2):保留到1这个位置,5满足,3不满足5舍去,123.456--->200

解释:截断操作。里面没有一个参数的写法。
TRUNCATE(123.456,0):保留到整数位, 123.456--->123
TRUNCATE(123.496,1):保留到1位小数,123.496--->123.4
TRUNCATE(129.45,-1):保留到2的位置,129.45 --->120

#单行函数可以嵌套
SELECT TRUNCATE(ROUND(123.456,2),0)
FROM DUAL;

在这里插入图片描述

2.2 角度与弧度互换函数

函数用法
RADIANS(x)将角度转化为弧度,其中,参数x为角度值
DEGREES(x)将弧度转化为角度,其中,参数x为弧度值
#角度与弧度的互换
SELECT RADIANS(30),RADIANS(60),RADIANS(90),DEGREES(2*PI()),DEGREES(RADIANS(90))
FROM DUAL;

2.3 三角函数

函数用法
SIN(x)返回x的正弦值,其中,参数x为弧度值
ASIN(x)返回x的反正弦值,即获取正弦为x的值。如果x的值不在-1到1之间,则返回NULL
COS(x)返回x的余弦值,其中,参数x为弧度值
ACOS(x)返回x的反余弦值,即获取余弦为x的值。如果x的值不在-1到1之间,则返回NULL
TAN(x)返回x的正切值,其中,参数x为弧度值
ATAN(x)返回x的反正切值,即返回正切值为x的值
ATAN2(m,n)返回两个参数的反正切值
COT(x)返回x的余切值,其中,X为弧度值

举例:

ATAN2(M,N)函数返回两个参数的反正切值。
与ATAN(X)函数相比,ATAN2(M,N)需要两个参数,例如有两个点point(x1,y1)和point(x2,y2),使用ATAN(X)函数计算反正切值为ATAN((y2-y1)/(x2-x1)),使用ATAN2(M,N)计算反正切值则为ATAN2(y2-y1,x2-x1)。由使用方式可以看出,当x2-x1等于0时,ATAN(X)函数会报错,而ATAN2(M,N)函数则仍然可以计算。

ATAN2(M,N)函数的使用示例如下:

SELECT SIN(RADIANS(30)),DEGREES(ASIN(1)),TAN(RADIANS(45)),DEGREES(ATAN(1)),DEGREES(ATAN2(1,1))
FROM DUAL;

在这里插入图片描述

2.4 指数与对数

函数用法
POW(x,y),POWER(X,Y)返回x的y次方
EXP(X)返回e的X次方,其中e是一个常数,2.718281828459045
LN(X),LOG(X)返回以e为底的X的对数,当X <= 0 时,返回的结果为NULL
LOG10(X)返回以10为底的X的对数,当X <= 0 时,返回的结果为NULL
LOG2(X)返回以2为底的X的对数,当X <= 0 时,返回NULL
mysql> SELECT POW(2,5),POWER(2,4),EXP(2),LN(10),LOG10(10),LOG2(4)
    -> FROM DUAL;
+----------+------------+------------------+-------------------+-----------+---------+
| POW(2,5) | POWER(2,4) | EXP(2)           | LN(10)            | LOG10(10) | LOG2(4) |
+----------+------------+------------------+-------------------+-----------+---------+
|       32 |         16 | 7.38905609893065 | 2.302585092994046 |         1 |       2 |
+----------+------------+------------------+-------------------+-----------+---------+
1 row in set (0.00 sec)

2.5 进制间的转换

函数用法
BIN(x)返回x的二进制编码
HEX(x)返回x的十六进制编码
OCT(x)返回x的八进制编码
CONV(x,f1,f2)返回f1进制数变成f2进制数
mysql> SELECT BIN(10),HEX(10),OCT(10),CONV(10,2,8)
    -> FROM DUAL;
+---------+---------+---------+--------------+
| BIN(10) | HEX(10) | OCT(10) | CONV(10,2,8) |
+---------+---------+---------+--------------+
| 1010    | A       | 12      | 2            |
+---------+---------+---------+--------------+
1 row in set (0.00 sec)

3. 字符串函数(CONCAT)

函数用法
ASCII(S)返回字符串S中的第一个字符的ASCII码值
CHAR_LENGTH(s)返回字符串s的字符数。作用与CHARACTER_LENGTH(s)相同
LENGTH(s)返回字符串s的字节数,和字符集有关
CONCAT(s1,s2,…,sn)连接s1,s2,…,sn为一个字符串
CONCAT_WS(x, s1,s2,…,sn)同CONCAT(s1,s2,…)函数,但是每个字符串之间要加上x
INSERT(str, idx, len, replacestr)将字符串str从第idx位置开始,len个字符长的子串替换为字符串replacestr
REPLACE(str, a, b)用字符串b替换字符串str中所有出现的字符串a
UPPER(s) 或 UCASE(s)将字符串s的所有字母转成大写字母
LOWER(s) 或LCASE(s)将字符串s的所有字母转成小写字母
LEFT(str,n)返回字符串str最左边的n个字符
RIGHT(str,n)返回字符串str最右边的n个字符
LPAD(str, len, pad)用字符串pad对str最左边进行填充,直到str的长度为len个字符
RPAD(str ,len, pad)用字符串pad对str最右边进行填充,直到str的长度为len个字符
LTRIM(s)去掉字符串s左侧的空格
RTRIM(s)去掉字符串s右侧的空格
TRIM(s)去掉字符串s开始与结尾的空格
TRIM(s1 FROM s)去掉字符串s开始与结尾的s1
TRIM(LEADING s1 FROM s)去掉字符串s开始处的s1
TRIM(TRAILING s1 FROM s)去掉字符串s结尾处的s1
REPEAT(str, n)返回str重复n次的结果
SPACE(n)返回n个空格
STRCMP(s1,s2)比较字符串s1,s2的ASCII码值的大小
SUBSTR(s,index,len)返回从字符串s的index位置其len个字符,作用与SUBSTRING(s,n,len)、MID(s,n,len)相同
LOCATE(substr,str)返回字符串substr在字符串str中首次出现的位置,作用于POSITION(substr IN str)、INSTR(str,substr)相同。未找到,返回0
ELT(m,s1,s2,…,sn)返回指定位置的字符串,如果m=1,则返回s1,如果m=2,则返回s2,如果m=n,则返回sn
FIELD(s,s1,s2,…,sn)返回字符串s在字符串列表中第一次出现的位置
FIND_IN_SET(s1,s2)返回字符串s1在字符串s2中出现的位置。其中,字符串s2是一个以逗号分隔的字符串
REVERSE(s)返回s反转后的字符串
NULLIF(value1,value2)比较两个字符串,如果value1与value2相等,则返回NULL,否则返回value1

注意:MySQL中,字符串的位置是从1开始的

举例:

mysql> SELECT FIELD('mm','hello','msm','amma'),FIND_IN_SET('mm','hello,mm,amma')
    -> FROM DUAL;
+----------------------------------+-----------------------------------+
| FIELD('mm','hello','msm','amma') | FIND_IN_SET('mm','hello,mm,amma') |
+----------------------------------+-----------------------------------+
|                                0 |                                 2 |
+----------------------------------+-----------------------------------+
1 row in set (0.00 sec)
mysql> SELECT NULLIF('mysql','mysql'),NULLIF('mysql', '');
+-------------------------+---------------------+
| NULLIF('mysql','mysql') | NULLIF('mysql', '') |
+-------------------------+---------------------+
| NULL                    | mysql               |
+-------------------------+---------------------+
1 row in set (0.00 sec)

4. 日期和时间函数

4.1 获取日期、时间(NOW())

函数用法
CURDATE() ,CURRENT_DATE()返回当前日期,只包含年、月、日
CURTIME() , CURRENT_TIME()返回当前时间,只包含时、分、秒
NOW() / SYSDATE() / CURRENT_TIMESTAMP() / LOCALTIME() / LOCALTIMESTAMP()返回当前系统日期和时间
UTC_DATE()返回UTC(世界标准时间)日期
UTC_TIME()返回UTC(世界标准时间)时间

举例:

SELECT CURDATE(),CURTIME(),NOW(),SYSDATE()+0,UTC_DATE(),UTC_DATE()+0,UTC_TIME(),UTC_TIME()+0
FROM DUAL;

在这里插入图片描述

4.2 日期与时间戳的转换

函数用法
UNIX_TIMESTAMP()以UNIX时间戳的形式返回当前时间。SELECT UNIX_TIMESTAMP() ->1634348884
UNIX_TIMESTAMP(date)将时间date以UNIX时间戳的形式返回。
FROM_UNIXTIME(timestamp)将UNIX时间戳的时间转换为普通格式的时间

举例:

mysql> SELECT UNIX_TIMESTAMP(now());
+-----------------------+
| UNIX_TIMESTAMP(now()) |
+-----------------------+
|            1576380910 |
+-----------------------+
1 row in set (0.01 sec)

mysql> SELECT UNIX_TIMESTAMP(CURDATE());
+---------------------------+
| UNIX_TIMESTAMP(CURDATE()) |
+---------------------------+
|                1576339200 |
+---------------------------+
1 row in set (0.00 sec)

mysql> SELECT UNIX_TIMESTAMP(CURTIME());
+---------------------------+
| UNIX_TIMESTAMP(CURTIME()) |
+---------------------------+
|                1576380969 |
+---------------------------+
1 row in set (0.00 sec)

mysql> SELECT UNIX_TIMESTAMP('2011-11-11 11:11:11')
+---------------------------------------+
| UNIX_TIMESTAMP('2011-11-11 11:11:11') |
+---------------------------------------+
|                            1320981071 |
+---------------------------------------+
1 row in set (0.00 sec)
mysql> SELECT FROM_UNIXTIME(1576380910);
+---------------------------+
| FROM_UNIXTIME(1576380910) |
+---------------------------+
| 2019-12-15 11:35:10       |
+---------------------------+
1 row in set (0.00 sec)

4.3 获取月份、星期、星期数、天数等函数

函数用法
YEAR(date) / MONTH(date) / DAY(date)返回具体的日期值
HOUR(time) / MINUTE(time) / SECOND(time)返回具体的时间值
MONTHNAME(date)返回月份:January,…
DAYNAME(date)返回星期几:MONDAY,TUESDAY…SUNDAY
WEEKDAY(date)返回周几,注意,周1是0,周2是1,。。。周日是6
QUARTER(date)返回日期对应的季度,范围为1~4
WEEK(date) , WEEKOFYEAR(date)返回一年中的第几周
DAYOFYEAR(date)返回日期是一年中的第几天
DAYOFMONTH(date)返回日期位于所在月份的第几天
DAYOFWEEK(date)返回周几,注意:周日是1,周一是2,。。。周六是7

举例:

SELECT YEAR(CURDATE()),MONTH(CURDATE()),DAY(CURDATE()),
HOUR(CURTIME()),MINUTE(NOW()),SECOND(SYSDATE())
FROM DUAL;

在这里插入图片描述

SELECT MONTHNAME('2021-10-26'),DAYNAME('2021-10-26'),WEEKDAY('2021-10-26'),
QUARTER(CURDATE()),WEEK(CURDATE()),DAYOFYEAR(NOW()),
DAYOFMONTH(NOW()),DAYOFWEEK(NOW())
FROM DUAL;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TUZk9YJ8-1639388823467)(images/image-20211025214818623.png)]

4.4 日期的操作函数

函数用法
EXTRACT(type FROM date)返回指定日期中特定的部分,type指定返回的值

EXTRACT(type FROM date)函数中type的取值与含义:

在这里插入图片描述
在这里插入图片描述

SELECT EXTRACT(MINUTE FROM NOW()),EXTRACT( WEEK FROM NOW()),
EXTRACT( QUARTER FROM NOW()),EXTRACT( MINUTE_SECOND FROM NOW())
FROM DUAL;

4.5 时间和秒钟转换的函数

函数用法
TIME_TO_SEC(time)将 time 转化为秒并返回结果值。转化的公式为:小时*3600+分钟*60+秒
SEC_TO_TIME(seconds)将 seconds 描述转化为包含小时、分钟和秒的时间

举例:

mysql> SELECT TIME_TO_SEC(NOW());
+--------------------+
| TIME_TO_SEC(NOW()) |
+--------------------+
|               78774 |
+--------------------+
1 row in set (0.00 sec)
mysql> SELECT SEC_TO_TIME(78774);
+--------------------+
| SEC_TO_TIME(78774) |
+--------------------+
| 21:52:54            |
+--------------------+
1 row in set (0.12 sec)

4.6 计算日期和时间的函数

第1组:

函数用法
DATE_ADD(datetime, INTERVAL expr type),ADDDATE(date,INTERVAL expr type)返回与给定日期时间相差INTERVAL时间段的日期时间
DATE_SUB(date,INTERVAL expr type),SUBDATE(date,INTERVAL expr type)返回与date相差INTERVAL时间间隔的日期

上述函数中type的取值:

在这里插入图片描述

举例:

SELECT DATE_ADD(NOW(), INTERVAL 1 DAY) AS col1,DATE_ADD('2021-10-21 23:32:12',INTERVAL 1 SECOND) AS col2,
ADDDATE('2021-10-21 23:32:12',INTERVAL 1 SECOND) AS col3,
DATE_ADD('2021-10-21 23:32:12',INTERVAL '1_1' MINUTE_SECOND) AS col4,
DATE_ADD(NOW(), INTERVAL -1 YEAR) AS col5, #可以是负数
DATE_ADD(NOW(), INTERVAL '1_1' YEAR_MONTH) AS col6 #需要单引号
FROM DUAL;
SELECT DATE_SUB('2021-01-21',INTERVAL 31 DAY) AS col1,
SUBDATE('2021-01-21',INTERVAL 31 DAY) AS col2,
DATE_SUB('2021-01-21 02:01:01',INTERVAL '1 1' DAY_HOUR) AS col3
FROM DUAL;

第2组:

函数用法
ADDTIME(time1,time2)返回time1加上time2的时间。当time2为一个数字时,代表的是,可以为负数
SUBTIME(time1,time2)返回time1减去time2后的时间。当time2为一个数字时,代表的是,可以为负数
DATEDIFF(date1,date2)返回date1 - date2的日期间隔天数
TIMEDIFF(time1, time2)返回time1 - time2的时间间隔
FROM_DAYS(N)返回从0000年1月1日起,N天以后的日期
TO_DAYS(date)返回日期date距离0000年1月1日的天数
LAST_DAY(date)返回date所在月份的最后一天的日期
MAKEDATE(year,n)针对给定年份与所在年份中的天数返回一个日期
MAKETIME(hour,minute,second)将给定的小时、分钟和秒组合成时间并返回
PERIOD_ADD(time,n)返回time加上n后的时间

举例:

SELECT ADDTIME(NOW(),20),SUBTIME(NOW(),30),SUBTIME(NOW(),'1:1:3'),DATEDIFF(NOW(),'2021-10-01'),
TIMEDIFF(NOW(),'2021-10-25 22:10:10'),FROM_DAYS(366),TO_DAYS('0000-12-25'),
LAST_DAY(NOW()),MAKEDATE(YEAR(NOW()),12),MAKETIME(10,21,23),PERIOD_ADD(20200101010101,10)
FROM DUAL;
mysql> SELECT ADDTIME(NOW(), 50);
+---------------------+
| ADDTIME(NOW(), 50)  |
+---------------------+
| 2019-12-15 22:17:47 |
+---------------------+
1 row in set (0.00 sec)

mysql> SELECT ADDTIME(NOW(), '1:1:1');
+-------------------------+
| ADDTIME(NOW(), '1:1:1') |
+-------------------------+
| 2019-12-15 23:18:46     |
+-------------------------+
1 row in set (0.00 sec)
mysql> SELECT SUBTIME(NOW(), '1:1:1');
+-------------------------+
| SUBTIME(NOW(), '1:1:1') |
+-------------------------+
| 2019-12-15 21:23:50     |
+-------------------------+
1 row in set (0.00 sec)

mysql> SELECT SUBTIME(NOW(), '-1:-1:-1'); 
+----------------------------+
| SUBTIME(NOW(), '-1:-1:-1') |
+----------------------------+
| 2019-12-15 22:25:11        |
+----------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT FROM_DAYS(366);
+----------------+
| FROM_DAYS(366) |
+----------------+
| 0001-01-01     |
+----------------+
1 row in set (0.00 sec)
mysql> SELECT MAKEDATE(2020,1);
+------------------+
| MAKEDATE(2020,1) |
+------------------+
| 2020-01-01       |
+------------------+
1 row in set (0.00 sec)

mysql> SELECT MAKEDATE(2020,32);
+-------------------+
| MAKEDATE(2020,32) |
+-------------------+
| 2020-02-01        |
+-------------------+
1 row in set (0.00 sec)
mysql> SELECT MAKETIME(1,1,1);
+-----------------+
| MAKETIME(1,1,1) |
+-----------------+
| 01:01:01        |
+-----------------+
1 row in set (0.00 sec)
mysql> SELECT PERIOD_ADD(20200101010101,1);
+------------------------------+
| PERIOD_ADD(20200101010101,1) |
+------------------------------+
|               20200101010102 |
+------------------------------+
1 row in set (0.00 sec)
mysql> SELECT TO_DAYS(NOW());
+----------------+
| TO_DAYS(NOW()) |
+----------------+
|          737773 |
+----------------+
1 row in set (0.00 sec)

举例:查询 7 天内的新增用户数有多少?

SELECT COUNT(*) as num 
FROM new_user 
WHERE TO_DAYS(NOW())-TO_DAYS(regist_time)<=7

4.7 日期的格式化与解析

函数用法
DATE_FORMAT(date,fmt)按照字符串fmt格式化日期date值
TIME_FORMAT(time,fmt)按照字符串fmt格式化时间time值
GET_FORMAT(date_type,format_type)返回日期字符串的显示格式
STR_TO_DATE(str, fmt)按照字符串fmt对str进行解析,解析为一个日期

上述非GET_FORMAT函数中fmt参数常用的格式符:

格式符说明格式符说明
%Y4位数字表示年份%y表示两位数字表示年份
%M月名表示月份(January,…)%m两位数字表示月份(01,02,03。。。)
%b缩写的月名(Jan.,Feb.,…)%c数字表示月份(1,2,3,…)
%D英文后缀表示月中的天数(1st,2nd,3rd,…)%d两位数字表示月中的天数(01,02…)
%e数字形式表示月中的天数(1,2,3,4,5…)
%H两位数字表示小数,24小时制(01,02…)%h和%I两位数字表示小时,12小时制(01,02…)
%k数字形式的小时,24小时制(1,2,3)%l数字形式表示小时,12小时制(1,2,3,4…)
%i两位数字表示分钟(00,01,02)%S和%s两位数字表示秒(00,01,02…)
%W一周中的星期名称(Sunday…)%a一周中的星期缩写(Sun.,Mon.,Tues.,…)
%w以数字表示周中的天数(0=Sunday,1=Monday…)
%j以3位数字表示年中的天数(001,002…)%U以数字表示年中的第几周,(1,2,3。。)其中Sunday为周中第一天
%u以数字表示年中的第几周,(1,2,3。。)其中Monday为周中第一天
%T24小时制%r12小时制
%pAM或PM%%表示%

GET_FORMAT函数中date_type和format_type参数取值如下:

在这里插入图片描述

举例:

mysql> SELECT DATE_FORMAT(NOW(), '%H:%i:%s');
+--------------------------------+
| DATE_FORMAT(NOW(), '%H:%i:%s') |
+--------------------------------+
| 22:57:34                        |
+--------------------------------+
1 row in set (0.00 sec)
SELECT STR_TO_DATE('09/01/2009','%m/%d/%Y')
FROM DUAL;

SELECT STR_TO_DATE('20140422154706','%Y%m%d%H%i%s')
FROM DUAL;

SELECT STR_TO_DATE('2014-04-22 15:47:06','%Y-%m-%d %H:%i:%s')
FROM DUAL;
mysql> SELECT GET_FORMAT(DATE, 'USA');
+-------------------------+
| GET_FORMAT(DATE, 'USA') |
+-------------------------+
| %m.%d.%Y                |
+-------------------------+
1 row in set (0.00 sec)

SELECT DATE_FORMAT(NOW(),GET_FORMAT(DATE,'USA')),
FROM DUAL;
mysql> SELECT STR_TO_DATE('2020-01-01 00:00:00','%Y-%m-%d'); 
+-----------------------------------------------+
| STR_TO_DATE('2020-01-01 00:00:00','%Y-%m-%d') |
+-----------------------------------------------+
| 2020-01-01                                    |
+-----------------------------------------------+
1 row in set, 1 warning (0.00 sec)

5. 流程控制函数

流程处理函数可以根据不同的条件,执行不同的处理流程,可以在SQL语句中实现不同的条件选择。MySQL中的流程处理函数主要包括IF()、IFNULL()和CASE()函数。

函数用法
IF(value,value1,value2)如果value的值为TRUE,返回value1,否则返回value2
IFNULL(value1, value2)如果value1不为NULL,返回value1,否则返回value2
CASE WHEN 条件1 THEN 结果1 WHEN 条件2 THEN 结果2 … [ELSE resultn] END相当于Java的if…else if…else…
CASE expr WHEN 常量值1 THEN 值1 WHEN 常量值1 THEN 值1 … [ELSE 值n] END相当于Java的switch…case…
SELECT IF(1 > 0,'正确','错误')    
->正确
SELECT IFNULL(null,'Hello Word')
->Hello Word
SELECT CASE 
  WHEN 1 > 0
  THEN '1 > 0'
  WHEN 2 > 0
  THEN '2 > 0'
  ELSE '3 > 0'
  END
->1 > 0
SELECT CASE 1 
  WHEN 1 THEN '我是1'
  WHEN 2 THEN '我是2'
ELSE '你是谁'
SELECT employee_id,salary, CASE WHEN salary>=15000 THEN '高薪' 
				  WHEN salary>=10000 THEN '潜力股'  
				  WHEN salary>=8000 THEN '屌丝' 
				  ELSE '草根' END  "描述"
FROM employees; 
SELECT oid,`status`, CASE `status` WHEN 1 THEN '未付款' 
								   WHEN 2 THEN '已付款' 
								   WHEN 3 THEN '已发货'  
								   WHEN 4 THEN '确认收货'  
								   ELSE '无效订单' END 
FROM t_order;
mysql> SELECT CASE WHEN 1 > 0 THEN 'yes' WHEN 1 <= 0 THEN 'no' ELSE 'unknown' END;
+---------------------------------------------------------------------+
| CASE WHEN 1 > 0 THEN 'yes' WHEN 1 <= 0 THEN 'no' ELSE 'unknown' END |
+---------------------------------------------------------------------+
| yes                                                                  |
+---------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT CASE WHEN 1 < 0 THEN 'yes' WHEN 1 = 0 THEN 'no' ELSE 'unknown' END;  
+--------------------------------------------------------------------+
| CASE WHEN 1 < 0 THEN 'yes' WHEN 1 = 0 THEN 'no' ELSE 'unknown' END |
+--------------------------------------------------------------------+
| unknown                                                             |
+--------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> SELECT CASE 1 WHEN 0 THEN 0 WHEN 1 THEN 1 ELSE -1 END;
+------------------------------------------------+
| CASE 1 WHEN 0 THEN 0 WHEN 1 THEN 1 ELSE -1 END |
+------------------------------------------------+
|                                               1 |
+------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT CASE -1 WHEN 0 THEN 0 WHEN 1 THEN 1 ELSE -1 END;
+-------------------------------------------------+
| CASE -1 WHEN 0 THEN 0 WHEN 1 THEN 1 ELSE -1 END |
+-------------------------------------------------+
|                                               -1 |
+-------------------------------------------------+
1 row in set (0.00 sec)
SELECT employee_id,12 * salary * (1 + IFNULL(commission_pct,0))
FROM employees;
SELECT last_name, job_id, salary,
       CASE job_id WHEN 'IT_PROG'  THEN  1.10*salary
                   WHEN 'ST_CLERK' THEN  1.15*salary
                   WHEN 'SA_REP'   THEN  1.20*salary
       			   ELSE      salary END     "REVISED_SALARY"
FROM   employees;

在这里插入图片描述

练习:查询部门号为 10,20, 30 的员工信息, 若部门号为 10, 则打印其工资的 1.1 倍, 20 号部门, 则打印其工资的 1.2 倍, 30 号部门打印其工资的 1.3 倍数。

6. 加密与解密函数

加密与解密函数主要用于对数据库中的数据进行加密和解密处理,以防止数据被他人窃取。这些函数在保证数据库安全时非常有用。

函数用法
PASSWORD(str)返回字符串str的加密版本,41位长的字符串。加密结果不可逆,常用于用户的密码加密
MD5(str)返回字符串str的md5加密后的值,也是一种加密方式。若参数为NULL,则会返回NULL
SHA(str)从原明文密码str计算并返回加密后的密码字符串,当参数为NULL时,返回NULL。SHA加密算法比MD5更加安全
ENCODE(value,password_seed)返回使用password_seed作为加密密码加密value
DECODE(value,password_seed)返回使用password_seed作为加密密码解密value

可以看到,ENCODE(value,password_seed)函数与DECODE(value,password_seed)函数互为反函数。

举例:

mysql> SELECT PASSWORD('mysql'), PASSWORD(NULL);
+-------------------------------------------+----------------+
| PASSWORD('mysql')                         | PASSWORD(NULL) |
+-------------------------------------------+----------------+
| *E74858DB86EBA20BC33D0AECAE8A8108C56B17FA |                |
+-------------------------------------------+----------------+
1 row in set, 1 warning (0.00 sec)
SELECT md5('123')
->202cb962ac59075b964b07152d234b70
SELECT SHA('Tom123')
->c7c506980abc31cc390a2438c90861d0f1216d50
mysql> SELECT ENCODE('mysql', 'mysql');
+--------------------------+
| ENCODE('mysql', 'mysql') |
+--------------------------+
| íg ¼ ìÉ                  |
+--------------------------+
1 row in set, 1 warning (0.01 sec)
mysql> SELECT DECODE(ENCODE('mysql','mysql'),'mysql');
+-----------------------------------------+
| DECODE(ENCODE('mysql','mysql'),'mysql') |
+-----------------------------------------+
| mysql                                   |
+-----------------------------------------+
1 row in set, 2 warnings (0.00 sec)

7. MySQL信息函数

MySQL中内置了一些可以查询MySQL信息的函数,这些函数主要用于帮助数据库开发或运维人员更好地对数据库进行维护工作。

函数用法
VERSION()返回当前MySQL的版本号
CONNECTION_ID()返回当前MySQL服务器的连接数
DATABASE(),SCHEMA()返回MySQL命令行当前所在的数据库
USER(),CURRENT_USER()、SYSTEM_USER(),SESSION_USER()返回当前连接MySQL的用户名,返回结果格式为“主机名@用户名”
CHARSET(value)返回字符串value自变量的字符集
COLLATION(value)返回字符串value的比较规则

举例:

mysql> SELECT DATABASE();
+------------+
| DATABASE() |
+------------+
| test       |
+------------+
1 row in set (0.00 sec)

mysql> SELECT DATABASE();
+------------+
| DATABASE() |
+------------+
| test       |
+------------+
1 row in set (0.00 sec)
mysql> SELECT USER(), CURRENT_USER(), SYSTEM_USER(),SESSION_USER();
+----------------+----------------+----------------+----------------+
| USER()         | CURRENT_USER() | SYSTEM_USER()  | SESSION_USER() |
+----------------+----------------+----------------+----------------+
| root@localhost | root@localhost | root@localhost | root@localhost |
+----------------+----------------+----------------+----------------+

mysql> SELECT CHARSET('ABC');
+----------------+
| CHARSET('ABC') |
+----------------+
| utf8mb4        |
+----------------+
1 row in set (0.00 sec)
mysql> SELECT COLLATION('ABC');
+--------------------+
| COLLATION('ABC')   |
+--------------------+
| utf8mb4_general_ci |
+--------------------+
1 row in set (0.00 sec)

8. 其他函数

MySQL中有些函数无法对其进行具体的分类,但是这些函数在MySQL的开发和运维过程中也是不容忽视的。

函数用法
FORMAT(value,n)返回对数字value进行格式化后的结果数据。n表示四舍五入后保留到小数点后n位
CONV(value,from,to)将value的值进行不同进制之间的转换
INET_ATON(ipvalue)将以点分隔的IP地址转化为一个数字
INET_NTOA(value)将数字形式的IP地址转化为以点分隔的IP地址
BENCHMARK(n,expr)将表达式expr重复执行n次。用于测试MySQL处理expr表达式所耗费的时间
CONVERT(value USING char_code)将value所使用的字符编码修改为char_code

举例:

# 如果n的值小于或者等于0,则只保留整数部分
mysql> SELECT FORMAT(123.123, 2), FORMAT(123.523, 0), FORMAT(123.123, -2); 
+--------------------+--------------------+---------------------+
| FORMAT(123.123, 2) | FORMAT(123.523, 0) | FORMAT(123.123, -2) |
+--------------------+--------------------+---------------------+
| 123.12             | 124                | 123                 |
+--------------------+--------------------+---------------------+
1 row in set (0.00 sec)
mysql> SELECT CONV(16, 10, 2), CONV(8888,10,16), CONV(NULL, 10, 2);+-----------------+------------------+-------------------+| CONV(16, 10, 2) | CONV(8888,10,16) | CONV(NULL, 10, 2) |+-----------------+------------------+-------------------+| 10000           | 22B8             | NULL              |+-----------------+------------------+-------------------+1 row in set (0.00 sec)
mysql> SELECT INET_ATON('192.168.1.100');+----------------------------+| INET_ATON('192.168.1.100') |+----------------------------+|                 3232235876 |+----------------------------+1 row in set (0.00 sec)# 以“192.168.1.100”为例,计算方式为192乘以256的3次方,加上168乘以256的2次方,加上1乘以256,再加上100。
mysql> SELECT INET_NTOA(3232235876);+-----------------------+| INET_NTOA(3232235876) |+-----------------------+| 192.168.1.100         |+-----------------------+1 row in set (0.00 sec)
mysql> SELECT BENCHMARK(1, MD5('mysql'));+----------------------------+| BENCHMARK(1, MD5('mysql')) |+----------------------------+|                          0 |+----------------------------+1 row in set (0.00 sec)mysql> SELECT BENCHMARK(1000000, MD5('mysql')); +----------------------------------+| BENCHMARK(1000000, MD5('mysql')) |+----------------------------------+|                                0 |+----------------------------------+1 row in set (0.20 sec)
mysql> SELECT CHARSET('mysql'), CHARSET(CONVERT('mysql' USING 'utf8'));+------------------+----------------------------------------+| CHARSET('mysql') | CHARSET(CONVERT('mysql' USING 'utf8')) |+------------------+----------------------------------------+| utf8mb4          | utf8                                   |+------------------+----------------------------------------+1 row in set, 1 warning (0.00 sec)

第09章_聚合函数

我们上一章讲到了 SQL 单行函数。实际上 SQL 函数还有一类,叫做聚合(或聚集、分组)函数,它是对一组数据进行汇总的函数,输入的是一组数据的集合,输出的是单个值。

1. 聚合函数介绍

  • 什么是聚合函数

聚合函数作用于一组数据,并对一组数据返回一个值。

在这里插入图片描述

  • 聚合函数类型

    • AVG()
    • SUM()
    • MAX()
    • MIN()
    • COUNT()
    • 其他:方差、标准差、中位数
  • 聚合函数语法

在这里插入图片描述

  • 聚合函数不能嵌套调用。比如不能出现类似“AVG(SUM(字段名称))”形式的调用。
  • 多行函数在统计时会对null值进行过滤,直接将null值丢弃,不参与统计。

1.1 AVG和SUM函数

可以对数值型数据使用AVG 和 SUM 函数。

SELECT AVG(salary), MAX(salary),MIN(salary), SUM(salary)
FROM   employees
WHERE  job_id LIKE '%REP%';

#1.1 AVG / SUM :只适用于数值类型的字段(或变量)

SELECT AVG(salary),SUM(salary),AVG(salary) * 107
FROM employees;
#如下的操作没有意义,不能对字符串使用,mysql中对字符串使用为0,oracle中直接报错。
SELECT SUM(last_name),AVG(last_name),SUM(hire_date)
FROM employees;

在这里插入图片描述

1.2 MIN和MAX函数

可以对任意数据类型的数据使用 MIN 和 MAX 函数。

SELECT MIN(hire_date), MAX(hire_date)
FROM	  employees;

#1.2 MAX / MIN :适用于数值类型、字符串类型、日期时间类型的字段(或变量)
#解释,能够排序就能求最大值最小值。比如按照姓名的abcd首字母排序。

SELECT MAX(salary),MIN(salary)
FROM employees;

SELECT MAX(last_name),MIN(last_name),MAX(hire_date),MIN(hire_date)
FROM employees;

在这里插入图片描述

1.3 COUNT函数

  • COUNT(*)返回表中记录总数,适用于任意数据类型
SELECT COUNT(*)
FROM	  employees
WHERE  department_id = 50;

在这里插入图片描述

  • COUNT(expr) 返回expr不为空的记录总数。
SELECT COUNT(commission_pct)
FROM   employees
WHERE  department_id = 50;


#1.3 COUNT:
# ① 作用:计算指定字段在查询结构中出现的个数(不包含NULL值的)
# 字段可以是不是表中的字段。COUNT(1)

SELECT COUNT(employee_id),COUNT(salary),COUNT(2 * salary),COUNT(1),COUNT(2),COUNT(*)
FROM employees ;

SELECT *
FROM employees;

#如果计算表中有多少条记录,如何实现?
#方式1:COUNT(*)
#方式2:COUNT(1)  表示一条记录当做一个1,计算出一共有多少个1。COUNT(2) 表示一条记录当做一个2看待。
#方式3:COUNT(具体字段) : 不一定对!

#② 注意:计算指定字段出现的个数时,是不计算NULL值的。
SELECT COUNT(commission_pct) #35
FROM employees;

SELECT commission_pct
FROM employees
WHERE commission_pct IS NOT NULL;
#③ 公式:AVG = SUM / COUNT
# 上面这些 AVG SUM MAX MIN COUNT都会自动过滤掉空值null
SELECT AVG(salary),SUM(salary)/COUNT(salary),
AVG(commission_pct),SUM(commission_pct)/COUNT(commission_pct),
SUM(commission_pct) / 107
FROM employees;

#需求:查询公司中平均奖金率
#错误的!
SELECT AVG(commission_pct)
FROM employees;

#正确的:
SELECT SUM(commission_pct) / COUNT(IFNULL(commission_pct,0)),
AVG(IFNULL(commission_pct,0)) #为null的一列当做0,记录有几个0记录。不为Null的记录几个,加起来就是所有记录。
FROM employees;
# 如何需要统计表中的记录数,使用COUNT(*)、COUNT(1)、COUNT(具体字段) 哪个效率更高呢?
# 如果使用的是MyISAM 存储引擎,则三者效率相同,都是O(1)
# 如果使用的是InnoDB 存储引擎,则三者效率:COUNT(*) = COUNT(1)> COUNT(字段)

在这里插入图片描述

  • 问题:用count(*),count(1),count(列名)谁好呢?

    其实,对于MyISAM引擎的表是没有区别的。这种引擎内部有一计数器在维护着行数。

    Innodb引擎的表用count(*),count(1)直接读行数,复杂度是O(n),因为innodb真的要去数一遍。但好于具体的count(列名)。

  • 问题:能不能使用count(列名)替换count(*)?

    不要使用 count(列名)来替代 count(*)count(*)是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。

    说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。

2. GROUP BY

2.1 基本使用

在这里插入图片描述

可以使用GROUP BY子句将表中的数据分成若干组

SELECT column, group_function(column)
FROM table
[WHERE	condition]
[GROUP BY	group_by_expression]
[ORDER BY	column];

明确:WHERE一定放在FROM后面

在SELECT列表中所有未包含在组函数中的列都应该包含在 GROUP BY子句中

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

在这里插入图片描述

在这里插入图片描述

包含在 GROUP BY 子句中的列不必包含在SELECT 列表中

SELECT   AVG(salary)
FROM     employees
GROUP BY department_id ;

#2. GROUP BY 的使用

#需求:查询各个部门的平均工资,最高工资
SELECT department_id,AVG(salary),SUM(salary)
FROM employees
GROUP BY department_id

#需求:查询各个job_id的平均工资
SELECT job_id,AVG(salary)
FROM employees
GROUP BY job_id;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2fZxCLAw-1639489880568)(images/1554981574152.png)]

2.2 使用多个列分组

在这里插入图片描述

SELECT   department_id dept_id, job_id, SUM(salary)
FROM     employees
GROUP BY department_id, job_id ;

#需求:查询各个department_id,job_id的平均工资(多个分组条件,比如同一个部门的同一种工种的工资)
#方式1:
SELECT department_id,job_id,AVG(salary)
FROM employees
GROUP BY  department_id,job_id;
#方式2:先分工种在分部门效果相同,因为不管是先部门后工种,还是先工种后部门,最终结果都是相同部门相同工种的在一块。
SELECT job_id,department_id,AVG(salary)
FROM employees
GROUP BY job_id,department_id;

在这里插入图片描述

在这里插入图片描述

2.3 GROUP BY中使用WITH ROLLUP

使用WITH ROLLUP关键字之后,在所有查询出的分组记录之后增加一条记录,该记录计算查询出的所有记录的总和,即统计记录数量。

SELECT department_id,AVG(salary)
FROM employees
WHERE department_id > 80
GROUP BY department_id WITH ROLLUP;

#错误的!
SELECT department_id,job_id,AVG(salary)
FROM employees
GROUP BY department_id;

#结论1:SELECT中出现的非组函数的字段必须声明在GROUP BY 中。
#      反之,GROUP BY中声明的字段可以不出现在SELECT中。
#       非组函数:不是计算多行函数的字段, AVG SUM MAX MIN COUNT。

#结论2:GROUP BY 声明在FROM后面、WHERE后面,ORDER BY 前面、LIMIT前面,
       即:from where 分组 排序 分页。都有的话按照这个排序。

#结论3:MySQL中GROUP BY中使用WITH ROLLUP

SELECT department_id,AVG(salary)
FROM employees
GROUP BY department_id WITH ROLLUP;

#需求:查询各个部门的平均工资,按照平均工资升序排列
# AVG(salary) avg_sal 计算公司所有人的平均薪资。
SELECT department_id,AVG(salary) avg_sal
FROM employees
GROUP BY department_id
ORDER BY avg_sal ASC;

#说明:当使用ROLLUP时,不能同时使用ORDER BY子句进行结果排序,即ROLLUP和ORDER BY是互相排斥的。
#错误的:
SELECT department_id,AVG(salary) avg_sal
FROM employees
GROUP BY department_id WITH ROLLUP
ORDER BY avg_sal ASC;

注意:

当使用ROLLUP时,不能同时使用ORDER BY子句进行结果排序,即ROLLUP和ORDER BY是互相排斥的。

3. HAVING

3.1 基本使用

在这里插入图片描述

过滤分组:HAVING子句

  1. 行已经被分组。
  2. 使用了聚合函数。
  3. 满足HAVING 子句中条件的分组将被显示。
  4. HAVING 不能单独使用,必须要跟 GROUP BY 一起使用。

在这里插入图片描述

SELECT   department_id, MAX(salary)
FROM     employees
GROUP BY department_id
HAVING   MAX(salary)>10000 ;

在这里插入图片描述

  • 非法使用聚合函数 : 不能在 WHERE 子句中使用聚合函数.如下:
SELECT   department_id, AVG(salary)
FROM     employees
WHERE    AVG(salary) > 8000
GROUP BY department_id;

#3. HAVING的使用 (作用:用来过滤数据的)
#练习:查询各个部门中最高工资比10000高的部门信息
#错误的写法:
SELECT department_id,MAX(salary)
FROM employees
WHERE MAX(salary) > 10000
GROUP BY department_id;


#要求1:如果过滤条件中使用了聚合函数,则必须使用HAVING来替换WHERE。否则,报错。
#要求2:HAVING 必须声明在 GROUP BY 的后面。

#正确的写法:
SELECT department_id,MAX(salary)
FROM employees
GROUP BY department_id
HAVING MAX(salary) > 10000;

#要求3:开发中,我们使用HAVING的前提是SQL中使用了GROUP BY。
#练习:查询部门id为10,20,30,40这4个部门中最高工资比10000高的部门信息
#方式1:推荐,执行效率高于方式2.
SELECT department_id,MAX(salary)
FROM employees
WHERE department_id IN (10,20,30,40)
GROUP BY department_id
HAVING MAX(salary) > 10000;

#方式2:
问题1where中不能使用多行函数()函数,而having中可以写组函数也可也写普通的条件,那么
还要where干什么,都使用having不就行了????
答:首先having和分组连用,如果没有分组只能使用where过滤条件,再则即便有2个过滤条件,一个
是分组函数用于having中,另一个是普通过滤条件推荐使用where而不是都写在having中,因为where的
效率高。
问题2:既然方式1和方式2这样写都对,那么那种效率更高呢????
答:方式1,普通过滤条件写在where中比都写在having中效率高,解释如下SQL底层执行原理。
SELECT department_id,MAX(salary)
FROM employees
GROUP BY department_id
HAVING MAX(salary) > 10000 AND department_id IN (10,20,30,40);

#结论:当过滤条件中有聚合函数时,则此过滤条件必须声明在HAVING中。
#      当过滤条件中没有聚合函数时,则此过滤条件声明在WHERE中或HAVING中都可以。但是,建议大家声明在WHERE中。

/*
  WHERE 与 HAVING 的对比
1. 从适用范围上来讲,HAVING的适用范围更广。 
2. 如果过滤条件中没有聚合函数:这种情况下,WHERE的执行效率要高于HAVING
*/

#4. SQL底层执行原理
#4.1 SELECT 语句的完整结构
/*

#sql92语法:
SELECT ....,....,....(存在聚合函数)
FROM ...,....,....
WHERE 多表的连接条件 AND 不包含聚合函数的过滤条件
GROUP BY ...,....
HAVING 包含聚合函数的过滤条件
ORDER BY ....,...(ASC / DESC )
LIMIT ...,....


#sql99语法:
SELECT ....,....,....(存在聚合函数)
FROM ... (LEFT / RIGHT)JOIN ....ON 多表的连接条件 
(LEFT / RIGHT)JOIN ... ON ....
WHERE 不包含聚合函数的过滤条件
GROUP BY ...,....
HAVING 包含聚合函数的过滤条件
ORDER BY ....,...(ASC / DESC )
LIMIT ...,....


*/


#4.2 SQL语句的执行过程:
#FROM ...,...-> ON -> (LEFT/RIGNT  JOIN) -> WHERE -> GROUP BY -> HAVING -> SELECT -> DISTINCT -> 
# ORDER BY -> LIMIT
解释1:为什么筛选条件放置where中效率高:。因为放在where中先进行过滤掉不需要的数据在进行分组,
而放在having中需要先分组在进行过滤,这样一些不需要的数据也要进行分组自然效率慢。
解释2:为什么where中包含聚合函数就不能放在where中呢,因为分组函数执行顺序在having后或者select后面,都是
在分组之后,where在分组之前当然不能使用分组函数。

在这里插入图片描述

3.2 WHERE和HAVING的对比

区别1:WHERE 可以直接使用表中的字段作为筛选条件,但不能使用分组中的计算函数作为筛选条件;HAVING 必须要与 GROUP BY 配合使用,可以把分组计算的函数和分组字段作为筛选条件。

这决定了,在需要对数据进行分组统计的时候,HAVING 可以完成 WHERE 不能完成的任务。这是因为,在查询语法结构中,WHERE 在 GROUP BY 之前,所以无法对分组结果进行筛选。HAVING 在 GROUP BY 之后,可以使用分组字段和分组中的计算函数,对分组的结果集进行筛选,这个功能是 WHERE 无法完成的。另外,WHERE排除的记录不再包括在分组中。

区别2:如果需要通过连接从关联表中获取需要的数据,WHERE 是先筛选后连接,而 HAVING 是先连接后筛选。 这一点,就决定了在关联查询中,WHERE 比 HAVING 更高效。因为 WHERE 可以先筛选,用一个筛选后的较小数据集和关联表进行连接,这样占用的资源比较少,执行效率也比较高。HAVING 则需要先把结果集准备好,也就是用未被筛选的数据集进行关联,然后对这个大的数据集进行筛选,这样占用的资源就比较多,执行效率也较低。

小结如下:

优点缺点
WHERE先筛选数据再关联,执行效率高不能使用分组中的计算函数进行筛选
HAVING可以使用分组中的计算函数在最后的结果集中进行筛选,执行效率较低

开发中的选择:

WHERE 和 HAVING 也不是互相排斥的,我们可以在一个查询里面同时使用 WHERE 和 HAVING。包含分组统计函数的条件用 HAVING,普通条件用 WHERE。这样,我们就既利用了 WHERE 条件的高效快速,又发挥了 HAVING 可以使用包含分组统计函数的查询条件的优点。当数据量特别大的时候,运行效率会有很大的差别。

4. SELECT的执行过程

4.1 查询的结构

#方式1:
SELECT ...,....,...
FROM ...,...,....
WHERE 多表的连接条件
AND 不包含组函数的过滤条件
GROUP BY ...,...
HAVING 包含组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...

#方式2:
SELECT ...,....,...
FROM ... JOIN ... 
ON 多表的连接条件
JOIN ...
ON ...
WHERE 不包含组函数的过滤条件
AND/OR 不包含组函数的过滤条件
GROUP BY ...,...
HAVING 包含组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...

#其中:
#(1)from:从哪些表中筛选
#(2)on:关联多表查询时,去除笛卡尔积
#(3)where:从表中筛选的条件
#(4)group by:分组依据
#(5)having:在统计结果中再次筛选
#(6)order by:排序
#(7)limit:分页

4.2 SELECT执行顺序

你需要记住 SELECT 查询时的两个顺序:

1. 关键字的顺序是不能颠倒的:

SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT...

2.SELECT 语句的执行顺序(在 MySQL 和 Oracle 中,SELECT 执行顺序基本相同):

FROM -> WHERE -> GROUP BY -> HAVING -> SELECT 的字段 -> DISTINCT -> ORDER BY -> LIMIT

在这里插入图片描述

比如你写了一个 SQL 语句,那么它的关键字顺序和执行顺序是下面这样的:

SELECT DISTINCT player_id, player_name, count(*) as num # 顺序 5
FROM player JOIN team ON player.team_id = team.team_id # 顺序 1
WHERE height > 1.80 # 顺序 2
GROUP BY player.team_id # 顺序 3
HAVING num > 2 # 顺序 4
ORDER BY num DESC # 顺序 6
LIMIT 2 # 顺序 7

在 SELECT 语句执行这些步骤的时候,每个步骤都会产生一个虚拟表,然后将这个虚拟表传入下一个步骤中作为输入。需要注意的是,这些步骤隐含在 SQL 的执行过程中,对于我们来说是不可见的。

4.3 SQL 的执行原理

SELECT 是先执行 FROM 这一步的。在这个阶段,如果是多张表联查,还会经历下面的几个步骤:

  1. 首先先通过 CROSS JOIN 求笛卡尔积,相当于得到虚拟表 vt(virtual table)1-1;
  2. 通过 ON 进行筛选,在虚拟表 vt1-1 的基础上进行筛选,得到虚拟表 vt1-2;
  3. 添加外部行。如果我们使用的是左连接、右链接或者全连接,就会涉及到外部行,也就是在虚拟表 vt1-2 的基础上增加外部行,得到虚拟表 vt1-3。

当然如果我们操作的是两张以上的表,还会重复上面的步骤,直到所有表都被处理完为止。这个过程得到是我们的原始数据。

当我们拿到了查询数据表的原始数据,也就是最终的虚拟表 vt1,就可以在此基础上再进行 WHERE 阶段。在这个阶段中,会根据 vt1 表的结果进行筛选过滤,得到虚拟表 vt2

然后进入第三步和第四步,也就是 GROUP 和 HAVING 阶段。在这个阶段中,实际上是在虚拟表 vt2 的基础上进行分组和分组过滤,得到中间的虚拟表 vt3vt4

当我们完成了条件筛选部分之后,就可以筛选表中提取的字段,也就是进入到 SELECT 和 DISTINCT 阶段

首先在 SELECT 阶段会提取想要的字段,然后在 DISTINCT 阶段过滤掉重复的行,分别得到中间的虚拟表 vt5-1vt5-2

当我们提取了想要的字段数据之后,就可以按照指定的字段进行排序,也就是 ORDER BY 阶段,得到虚拟表 vt6

最后在 vt6 的基础上,取出指定行的记录,也就是 LIMIT 阶段,得到最终的结果,对应的是虚拟表 vt7

当然我们在写 SELECT 语句的时候,不一定存在所有的关键字,相应的阶段就会省略。

同时因为 SQL 是一门类似英语的结构化查询语言,所以我们在写 SELECT 语句的时候,还要注意相应的关键字顺序,所谓底层运行的原理,就是我们刚才讲到的执行顺序。

第10章_子查询

讲师:尚硅谷-宋红康(江湖人称:康师傅)

官网:http://www.atguigu.com


子查询指一个查询语句嵌套在另一个查询语句内部的查询,这个特性从MySQL 4.1开始引入。

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

1. 需求分析与问题解决

1.1 实际问题

在这里插入图片描述

1)由一个具体的需求,引入子查询:

#需求:谁的工资比Abel的高?

#方式1:分开写sql
#先计算出Abel的工资
SELECT salary
FROM employees
WHERE last_name = 'Abel';

#在查询出大于Abel工资的人
SELECT last_name,salary
FROM employees
WHERE salary > 11000;

#缺点:写2条sql效率低,因为需要查询2次,在后台就需要交互2次,在考虑网络连接等。

#方式2:自连接的非等值查询
SELECT e2.last_name,e2.salary
FROM employees e1,employees e2
WHERE e2.`salary` > e1.`salary` #多表的连接条件
AND e1.last_name = 'Abel';

#优点:优于方式1,因为只查询一次
#缺点:并不是所有的子查询方式都可以改写为自连接

#方式3:子查询
SELECT last_name,salary
FROM employees
WHERE salary > (
		SELECT salary
		FROM employees
		WHERE last_name = 'Abel'
		);

在这里插入图片描述

1.2 子查询的基本使用

  • 称谓的规范:外查询(或主查询)、内查询(或子查询)

  • 子查询的基本语法结构:
    在这里插入图片描述

  • 子查询(内查询)在主查询之前一次执行完成。

  • 子查询的结果被主查询(外查询)使用 。

  • 注意事项

    • 子查询要包含在括号内
    • 将子查询放在比较条件的右侧
    • 单行操作符对应单行子查询,多行操作符对应多行子查询
#推荐写法:将子查询放在比较条件的右侧
SELECT last_name,salary
FROM employees
WHERE salary > (
		SELECT salary
		FROM employees
		WHERE last_name = 'Abel'
		);


#不推荐:条件写左边,可读性不好
SELECT last_name,salary
FROM employees
WHERE  (
	SELECT salary
	FROM employees
	WHERE last_name = 'Abel'
		) < salary;

这2种方式都可以正常查询出结果。

在这里插入图片描述

1.3 子查询的分类

分类方式1:

我们按内查询的结果返回一条还是多条记录,将子查询分为单行子查询多行子查询

  • 单行子查询

在这里插入图片描述

  • 多行子查询

在这里插入图片描述

分类方式2:

我们按内查询是否被执行多次,将子查询划分为相关(或关联)子查询不相关(或非关联)子查询

  • 子查询从数据表中查询了数据结果,如果这个数据结果只执行一次,然后这个数据结果作为主查询的条件进行执行,那么这样的子查询叫做不相关子查询

    • eg:查询工资大于本公司平均工资的员工信息。(子查询的结果是个常数,每次都和这个固定不变的值比较)
  • 同样,如果子查询需要执行多次,即采用循环的方式,先从外部查询开始,每次都传入子查询进行查询,然后再将结果反馈给外部,这种嵌套的执行方式就称为相关子查询

    • eg:查询工资大于员工所在部门平均工资的员工信息。(子查询的结果随着部门变化,每次都和这个变化的的值比较)

2. 单行子查询

2.1 单行比较操作符

操作符含义
=equal to
>greater than
>=greater than or equal to
<less than
<=less than or equal to
<>或!=not equal to

2.2 代码示例

子查询的编写技巧(或步骤):① 从里往外写 ② 从外往里写

题目:查询工资大于149号员工工资的员工的信息

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

在这里插入图片描述

在这里插入图片描述

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

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

在这里插入图片描述

题目:返回公司工资最少的员工的last_name,job_id和salary

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。

实现方式1:不成对比较

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

在这里插入图片描述

实现方式2:成对比较,条件相同可以合一块写,效率差不多。

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

在这里插入图片描述

2.3 HAVING 中的子查询

  • 首先执行子查询。
  • 向主查询中的HAVING 子句返回结果。

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

#查询的字段既包含了聚合函数和非聚合函数,则一定要加上GROUP BY,在前面讲过。
#分组之后使用HAVING 代替WHERE 
SELECT department_id,MIN(salary)
FROM employees
WHERE department_id IS NOT NULL
GROUP BY department_id
HAVING MIN(salary) > (
			SELECT MIN(salary)
			FROM employees
			WHERE department_id = 110
		     );

在这里插入图片描述

2.4 CASE中的子查询

在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;

在这里插入图片描述

2.5 子查询中的空值问题

#子查询内层没有返回行相当于为null,外层查询不返回任何行
SELECT last_name, job_id
FROM   employees
WHERE  job_id =
                (SELECT job_id
                 FROM   employees
                 WHERE  last_name = 'Haas');

在这里插入图片描述

子查询不返回任何行

2.6 非法使用子查询

#问题:内层子查询返回值为多行说明是多行子查询,而=是单行子查询符号所以报错
#解决:使用多行子查询符号
SELECT employee_id, last_name
FROM   employees
WHERE  salary =
                (SELECT   MIN(salary)
                 FROM     employees
                 GROUP BY department_id);

在这里插入图片描述

问题说明:多行子查询使用了单行比较符

3. 多行子查询

  • 也称为集合比较子查询
  • 内查询返回多行
  • 使用多行比较操作符

3.1 多行比较操作符

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

体会 ANY 和 ALL 的区别

3.2 代码示例

举例:查询公司中那些员工和各个部门的最低工资相等

# IN:
SELECT employee_id, last_name
FROM   employees
WHERE  salary IN
                (SELECT   MIN(salary)
                 FROM     employees
                 GROUP BY department_id); 

在这里插入图片描述

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

# ANY / ALL:
SELECT employee_id,last_name,job_id,salary
FROM employees
WHERE job_id <> 'IT_PROG'
AND salary < ANY (
		SELECT salary
		FROM employees
		WHERE job_id = 'IT_PROG'
		);

在这里插入图片描述
在这里插入图片描述

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

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

在这里插入图片描述

在这里插入图片描述

题目:查询平均工资最低的部门id

#错误写法:MySQL中聚合函数是不能嵌套使用的,单行函数可以。oracle无要求都可以使用。
select min(AVG(salary))
from employees
GROUP BY department_id;

#解决思路:把查询的结果当做一个新的表,里面只有普通的一列,当然可以使用多行函数了。
步骤:
   首先查询各个部门的平均工资:把查询的列起个别名:avg_sal
   select AVG(salary) avg_sal
   from employees 
   GROUP BY department_id;
   其次:把整个查询的结果当做一个表,并且需要起个表别名,否则报错:
   select avg_sal
   from (select AVG(salary) avg_sal
         from employees  
         GROUP BY department_id) t_dept_avg_sal;
   之后查询各个部门平均工资的最低工资:这个列当做普通的一列,就可以使用多行函数了。
   select min(avg_sal)
   from (select AVG(salary) avg_sal
         from employees  
         GROUP BY department_id) t_dept_avg_sal;
   最后查询各个部门平均工资的最低工资的部门id:以上运行的结果为部门平均工资的最低薪资,现在需要找哪一个部门的最低薪资。
   #方式1:
   SELECT department_id
   FROM employees
   GROUP BY department_id
   HAVING AVG(salary) =( select min(avg_sal)
                         from (select AVG(salary) avg_sal
                               from employees  
                               GROUP BY department_id
                              ) t_dept_avg_sal
                       );
   
   
# 说明:3层循环嵌套 ,由内到外:各个部门的平均工资,最小的部门平均工资,那个部门的平均工资最低
#方式2:优化,外层:查询某个部门的平均工资小于等于 内层:所以的平均工资,外层的查询结果只有一个小于所有的平均工资
#      那么这个外层部门的平均工资就是最小的。
SELECT department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary) <= ALL (
				SELECT AVG(salary) avg_sal
				FROM employees
				GROUP BY department_id
)

3.3 空值问题

#多行子查询,内查询的结果有多个,其中内查询的多个结果有一个为null,这整体为null.
SELECT last_name
FROM employees
WHERE employee_id NOT IN (
			SELECT manager_id
			FROM employees
			);

在这里插入图片描述

4. 相关子查询

4.1 相关子查询执行流程

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

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

在这里插入图片描述

在这里插入图片描述

说明:子查询中使用主查询中的列

4.2 代码示例

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

方式一:相关子查询

#6. 相关子查询
#回顾:查询员工中工资大于公司平均工资的员工的last_name,salary和其department_id
#6.1 
SELECT last_name,salary,department_id
FROM employees
WHERE salary > (
		SELECT AVG(salary)
		FROM employees
		);
		
#题目:查询员工中工资大于本部门平均工资的员工的last_name,salary和其department_id
#方式1:使用相关子查询
SELECT last_name,salary,department_id
FROM employees e1
WHERE salary > (
		SELECT AVG(salary)
		FROM employees e2
		WHERE department_id = e1.`department_id`
		);

在这里插入图片描述

方式二:在 FROM 中使用子查询

#方式2:在FROM中声明子查询
SELECT e.last_name,e.salary,e.department_id
FROM employees e,(
		SELECT department_id,AVG(salary) avg_sal
		FROM employees
		GROUP 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

from型的子查询:子查询是作为from的一部分,子查询要用()引起来,并且要给这个子查询取别名,
把它当成一张“临时的虚拟的表”来使用。

在ORDER BY 中使用子查询:

题目:查询员工的id,salary,按照department_name 排序

SELECT employee_id,salary
FROM employees e
ORDER BY (
	  SELECT department_name
	  FROM departments d
	  WHERE e.`department_id` = d.`department_id`
	);
	
#结论:在SELECT中,除了GROUP BY 和 LIMIT之外,其他位置都可以声明子查询!
/*
SELECT ....,....,....(存在聚合函数)
FROM ... (LEFT / RIGHT)JOIN ....ON 多表的连接条件 
(LEFT / RIGHT)JOIN ... ON ....
WHERE 不包含聚合函数的过滤条件
GROUP BY ...,....
HAVING 包含聚合函数的过滤条件
ORDER BY ....,...(ASC / DESC )
LIMIT ...,....
*/

题目:若employees表中employee_id与job_history表中employee_id相同的数目不小于2,输出这些相同id的员工的employee_id,last_name和其job_id

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

4.3 EXISTS 与 NOT EXISTS关键字

  • 关联子查询通常也会和 EXISTS操作符一起来使用,用来检查在子查询中是否存在满足条件的行。
  • 如果在子查询中不存在满足条件的行:
    • 条件返回 FALSE
    • 继续在子查询中查找
  • 如果在子查询中存在满足条件的行:
    • 不在子查询中继续查找
    • 条件返回 TRUE
  • NOT EXISTS关键字表示如果不存在某种条件,则返回TRUE,否则返回FALSE。

题目:查询公司管理者的employee_id,last_name,job_id,department_id信息

方式一:

#方式1:自连接
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;

方式二:

#方式2:子查询

SELECT employee_id,last_name,job_id,department_id
FROM employees
WHERE employee_id IN (
			SELECT DISTINCT manager_id
			FROM employees
			);

方式三:

#方式3:使用EXISTS
SELECT employee_id,last_name,job_id,department_id
FROM employees e1
WHERE EXISTS (
	       SELECT *
	       FROM employees e2
	       WHERE e1.`employee_id` = e2.`manager_id`
	     );

题目:查询departments表中,不存在于employees表中的部门的department_id和department_name

#方式1:
SELECT d.department_id,d.department_name
FROM employees e RIGHT JOIN departments d
ON e.`department_id` = d.`department_id`
WHERE e.`department_id` IS NULL;

#方式2:
SELECT department_id,department_name
FROM departments d
WHERE NOT EXISTS (
		SELECT *
		FROM employees e
		WHERE d.`department_id` = e.`department_id`
		);

/* 
子查询的编写技巧(或步骤):① 从里往外写  ② 从外往里写

如何选择?
① 如果子查询相对较简单,建议从外往里写。一旦子查询结构较复杂,则建议从里往外写
② 如果是相关子查询的话,通常都是从外往里写。

*/

在这里插入图片描述

4.4 相关更新

UPDATE table1 alias1
SET    column = (SELECT expression
                 FROM   table2 alias2
                 WHERE  alias1.column = alias2.column);

使用相关子查询依据一个表中的数据更新另一个表的数据。

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

# 1)
ALTER TABLE employees
ADD(department_name VARCHAR2(14));

# 2)
UPDATE employees e
SET department_name =  (SELECT department_name 
	                       FROM   departments d
	                       WHERE  e.department_id = d.department_id);

4.4 相关删除

 DELETE FROM table1 alias1
 WHERE column operator (SELECT expression
                        FROM   table2 alias2
                        WHERE  alias1.column = alias2.column);

使用相关子查询依据一个表中的数据删除另一个表的数据。

题目:删除表employees中,其与emp_history表皆有的数据

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

5. 抛一个思考题

问题:谁的工资比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 salary
		FROM employees
		WHERE last_name = 'Abel'
		);

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

解答:自连接方式好!

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

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

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值