1. 数据库的基本概念

1.1 术语

DB:数据库(database),存储数据的仓库,它保存一系列有组织的数据。

DBMS:数据库管理软件,数据库只能通过 DBMS 创建和操作。

SQL:结构化查询语言,是DBMS 和 DB 通信的语言。

1.2 数据库特点

  1. 数据存放在表中,表再放到库中。
  2. 一个数据库可以有多个表,每个表都有一个名字,用来标识自己,表具有唯一性。
  3. 表具有一些特性,这些特性定义了数据在表中如何存储。
  4. 表由列组成,也称为字段,所有表都是由一个或多个列组成的。
  5. 表中的数据是按行存储的。

1.3 DBMS 分类

  • 基于共享文件系统
  • 基于客户端/服务端

1.4 SQL 分类

  1. DQL: Data QueryLanguage 数据查询语言标准语
  2. DML:Data Manipulation Language 数据操纵语言
  3. DDL:Data Definition Language 数据库模式定义语言

2. Mysql 基础知识

2.1 Mysql 登录和退出

  1. 通过 mysql 命令行工具(只适用于 root 用户)

  2. 通过 windows 命令行

    mysql [-h 主机名 -P 端口号] -u 用户名 -p密码
  3. 退出

    exit

2.2 Mysql 常见命令

// 查看有哪些数据库
show databases;

// 进入到指定的数据库
use 数据库名

// 显示数据库有哪些表
show tables;

// 查看其他数据库有哪些表
show tables from 数据库名

// 查看当前处于哪个数据库
select database();

// 查看 mysql 版本(登录到了 mysql 服务器)
select version();

// 查看 mysql 版本(没有登录到 mysql 服务器)
mysql -version

2.3 Mysql 语法规范

  1. 不区分大小写,建议关键字大写,其他小写。
  2. 每条命令以 ; 结尾。
  3. 每条命令根据需要可以进行缩进和换行。

2.4 Mysql 注释

  1. 单行注释

    # 单行注释
    -- 单行注释
  2. 多行注释

    /*
     多行注释
    */

3. DQL

3.1 基础查询

语法:select 查询列表(多个列表用逗号隔开) from 表名;

查询列表:表中的字段、函数、表达式、常量值。

结果:一个虚拟的表。

示例:

# 查询表中单个字段
SELECT 
  last_name 
FROM
  employees ;

# 查询表中多个字段
SELECT 
  last_name,
  first_name 
FROM
  employees ;

# 查询表中的所有字段
# 方式一:列出表中的所有字段
SELECT 
  `employee_id`,
  `first_name`,
  `last_name`,
  `email`,
  `phone_number`,
  `job_id`,
  `salary`,
  `commission_pct`,
  `manager_id`,
  `manager_id`,
  `hiredate` 
FROM
  employees ;

# 方式二:使用匹配符:*
SELECT 
  * 
FROM
  employees ;

# 查询常量
SELECT 100;

# 查询表达式
SELECT 100*22;

# 查询字符串,字符串使用单引号或双引号括起来
SELECT 'opendragonhuang';
SELECT "opendragonhuang";

# 查询函数
SELECT VERSION();

注意:如果 * 还和其他的列组合时,* 只能放置第一位置,不让回出错。

3.1.1 起别名

含义:给查询的列表或表命取一个名字。

语法:

select 查询列表1 [as] 别名1,...,查询列表n [as] 别名n from 表名1 [as] 表别名1, ..., 表名n [as] 表别名n;

作用:

  1. 便于理解。
  2. 区分查询时重名的字段。

注意事项:当别名有关键字或空格等特殊字符时,建议用单引号或双引号括起来。

示例:

# 给字段其别名
SELECT 
  last_name AS 姓,
  first_name AS 名 
FROM
  employees ;

SELECT 
  last_name 姓,
  first_name 名 
FROM
  employees ;

# 给字段和表取别名
SELECT 
  last_name 姓,
  first_name 名 
FROM
  employees e ;

3.1.2 去重

含义:查询的字段往往会包含相同的值,去重就是去掉查询中重复出现的值。

语法:select distinct 查询列表 from 表名;

示例:

# 去掉重复的 department_id
SELECT DISTINCT 
  department_id 
FROM
  employees ;

# 去掉 department_id 和 job_id 都相同的记录
SELECT DISTINCT 
  department_id,
  job_id 
FROM
  employees ;

注意事项:

  1. distinct 只能放在第一个字段中。
  2. distinct 作用于后面的所有字段,而不只是 distinct 紧跟着的那个字段

3.1.3 + 号作用

在 Mysql 中 + 只有一个作用,那就是当作算术运算符。

# 算术运算
select 100+20; 
# 碰到字符,就会试图把字符转换成数字参与运算,如果转换失败,则当中 0 处理
select '100'+20; 
# 只要一方是 null ,则整个结果一定是 null
select null+20; 

3.1.4 字符串连接

  1. concat() : 用于连接两个或多个字符串。
  2. ifnull(字段名, 值1):如果字段对应的值为 null,则用值 1 代替 null。
  3. isnull(字段名):判断字段的值是否为 null,为 null 返回 0,不为 null 返回 1。

示例:

# 字符串连接
SELECT 
  CONCAT(last_name, ' ', first_name) 姓名 
FROM
  employees ;

# ifnull
SELECT 
  CONCAT(
    last_name,
    ' ',
    first_name,
    ':',
    IFNULL(commission_pct, 0)
  ) 信息 
FROM
  employees ;

# isnull
SELECT 
  ISNULL(commission_pct) 
FROM
  employees ;

3.1.5 练习

# 练习 1
# 1.显示表 departments 的结构,并查询其中全部的数据
DESC departments ;

SELECT 
  * 
FROM
  departments ;

# 2.显示出表 employees 中全部 job_id(不能重复)
SELECT DISTINCT 
  job_id 
FROM
  employees ;

# 3.显示出表 employees 的全部列,各个列之间用逗号隔开,列头显示 OUT_PUT
# 列太多,值列出部分参考
SELECT 
  CONCAT(
    employee_id,
    ',',
    first_name,
    ',',
    last_name
  ) OUT_PUT 
FROM
  employees ;

3.2 条件查询

语法:

select 查询列表 
from 表名
where 筛选条件;

筛选条件([]表示 Mysql 可以用,但不推荐用):

  1. 按关系筛选:>, >=, <, <=, <>, [!=]
  2. 按逻辑筛选(用于连接关系运算符):[&&, ||, !], and, or, not
  3. 模糊筛选:like, between .. and .., in, is null,is not null

3.2.1 关系条件查询

# 查询薪水大于 12000的员工信息
SELECT 
  * 
FROM
  employees 
WHERE salary > 12000 ;

# 查询部门编号不等于90的员工姓名、部门编号和薪水
SELECT 
  last_name,
  department_id,
  salary 
FROM
  employees 
WHERE department_id <> 90 ;

3.2.2 逻辑条件查询

#查询薪水在 9000 和 12000 之间的员工信息
SELECT 
  * 
FROM
  employees 
WHERE salary >= 9000 
  AND salary < 12000 ;

3.2.3 模糊条件查询

(1)like
  1. % : 匹配任意多个字符,包括 0 个。
  2. _ : 匹配任意一个字符
  3. 转义字符:当需要匹配 % 和 _ 时,使用转义字符 % 和 \_ 匹配

示例:

# 匹配名字含有 a 的员工的所有信息
SELECT 
  * 
FROM
  employees 
WHERE last_name LIKE '%a%' ;

# 匹配名字第二个字符为 a 的员工的所以信息
SELECT 
  * 
FROM
  employees 
WHERE last_name LIKE '_a%' ;

# 匹配姓氏包含有 _ 的员工的所有信息
SELECT 
  * 
FROM
  employees 
WHERE last_name LIKE '%\_%' ;
(2)between ... and ...

between ... and ...a >= x and a <= y 比较:

  1. between ... and ... sql 语句更加的简洁
SELECT 
  CONCAT(first_name, last_name) AS 姓名,
  salary AS 薪水 
FROM
  employees 
WHERE salary >= 10000 
  AND salary <= 12000 ;

# 上面的等价于 
SELECT 
  CONCAT(first_name, last_name) AS 姓名,
  salary AS 薪水 
FROM
  employees 
WHERE salary BETWEEN 10000 
  AND 12000 ;
(3)in

作用:判断字段的值是否属于 in 列表中的某一项。

特点:

  1. 让 sql 语句的更简洁。
  2. in 列表的值的类型必须一样或相兼容。

示例:

SELECT 
  * 
FROM
  employees 
WHERE employee_id IN (100, 101, 107) ;

# 上面等价于
SELECT 
  * 
FROM
  employees 
WHERE employee_id = 100 
  OR employee_id = 101 
  OR employee_id = 107 ;
(4)is null 和 is not null
  1. != 和 <> 不能用来判断字段的值是否为 null
  2. is null 用来判断字段值是否为 null
  3. is not null 用来判断字段值是否不为 null

示例:

# 查看奖金率为 0 的员工姓名
SELECT 
  last_name 
FROM
  employees 
WHERE commission_pct IS NULL ;

# 查看奖金率不为 0 的员工姓名
SELECT 
  last_name 
FROM
  employees 
WHERE commission_pct IS NOT NULL ;
(5) <=>

<=> 和 is null 对比:

  1. is null 只能判断字段值是否为 null,可读性好,建议使用。
  2. <=> 可以判断普通数值是否相等和判断字段的值是否为 null,可读性低,不建议使用。

示例:

SELECT 
  first_name,
  commission_pct 
FROM
  employees 
WHERE commission_pct <=> NULL ;

SELECT 
  first_name,
  salary 
FROM
  employees 
WHERE salary <=> 12000 ;

3.3 排序查询的数据

语法:

select 查询列表 from 表名
[where 筛选条件]
order by 字段名1 [ASC|DESC], ..., 字段名 n [ASC|DESC]

关键字解释:

  1. DESC(descent): 下降
  2. ASC(ascend):上升

示例:

# 按薪水从高到低排序员工信息
SELECT * FROM employees
ORDER BY salary DESC;

# 查询部门编号>=90的员工信息,并按员工编号降序
SELECT * FROM employees
WHERE department_id >= 90
ORDER BY employee_id;

# 查询员工信息按年薪降序
SELECT *, salary*(1+IFNULL(commission_pct, 0))*12 年薪
FROM employees
ORDER BY 年薪 DESC;

# 查询员工信息按年薪升序
SELECT *, salary*(1+IFNULL(commission_pct, 0))*12 年薪
FROM employees
ORDER BY 年薪 ASC;

# 查询员工名,并且按名字的长度降序
SELECT last_name, LENGTH(last_name) 名字长度
FROM employees
ORDER BY 名字长度 DESC;

# 查询员工信息,要求先按工资降序,再按employee_id升序
SELECT * FROM employees
ORDER BY salary DESC, employee_id ASC;

3.4 函数

特点:隐藏细节,提高代码重用性。

关注点:函数名称和函数功能。

函数分类:

  1. 文本处理函数

3.4.1 文本处理函数

# 获取字符串占用的字节长度
SELECT 
  LENGTH('中国') ;

# 连接字符串函数
SELECT 
  CONCAT(first_name, ' ', last_name) 
FROM
  employees ;

# 把字母转大写函数
SELECT 
  UPPER(first_name) 
FROM
  employees ;

# 把字母转小写函数
SELECT 
  LOWER(first_name) 
FROM
  employees ;

# 截取子字符串函数, 
# 第一个参数为截取的位置,从 1 开始,第二参数为待截取字符的个数,如果没有第二个参数则截取到字符串末尾。
SELECT 
  SUBSTR('Hello world!', 2) ;

SELECT 
  SUBSTR('Hello world!', 7, 5) ;

# 返回子字符串第一次出现的位置,没有返回 0
SELECT 
  INSTR('Hello world!', 'world') ;

# 去除字符串两边多余的空格
SELECT 
  TRIM('   Hello world!    ') ;

3.4.2 数值处理函数

# 四舍五入函数, 第二个参数表示小数点后保留几位
SELECT ROUND(1.56);
SELECT ROUND(1.56, 1);

# 向上取整
SELECT CEIL(1.34);

# 向下取整
SELECT FLOOR(1.34);

# 截断
SELECT TRUNCATE(1.2345, 2);

# 取余, 结果 = x - x/y*y
SELECT MOD(10, 3);
SELECT MOD(-10, 3);

3.4.3 日期和时间处理函数

# 获取日期和时间
SELECT NOW();

# 获取日期
SELECT CURDATE();

# 获取时间
SELECT CURTIME();

# 获取指定的部分:年、月、日、小时、分钟、秒
SELECT YEAR(NOW());
SELECT MONTH(NOW());

# 月份字符表示
SELECT MONTHNAME(NOW());

# 从 0 开始, 0 为星期一
SELECT WEEKDAY(NOW());
# 从 1 开始, 1 是星期天
SELECT DAYOFWEEK(NOW());

SELECT DAY(NOW());
SELECT HOUR(NOW());
SELECT MINUTE(NOW());
SELECT SECOND(NOW());

3.4.4 分组函数

说明:用于统计,又称为统计函数,聚合函数。

种类:sum(), avg(), min(), max(), count()

特点:

  1. 所有的分组函数都会忽略 null
  2. sum()avg() 只能处理数值类型
  3. min() , max()count() 可以处理所有类型

示例:

# 求平均值
SELECT AVG(salary)
FROM employees;

# 求最大值
SELECT MAX(salary)
FROM employees;

# 求最小值
SELECT MIN(salary)
FROM employees;

# 求和
SELECT SUM(salary)
FROM employees;

# 求平均
SELECT SUM(salary)/COUNT(salary), AVG(salary)
FROM employees;

count(*) 和 count(1) 效率比较:

  1. MYISAM 存储引擎:count(*) 效率较高。
  2. INNODB:count(*) 和 count(1) 效率差不多,但比 count(字段) 更高一些。

3.4.5 流程函数

# IF
SELECT IF(commission_pct IS NOT NULL, commission_pct, 0)
FROM employees;

# case
SELECT last_name, salary old_salary,
CASE department_id
WHEN 30 THEN salary*1.3
WHEN 40 THEN salary*1.4
WHEN 50 THEN salary*1.5
ELSE salary
END new_salary
FROM employees;

SELECT last_name, salary,
CASE
WHEN salary > 15000 THEN 'A'
WHEN salary > 12000 THEN 'B'
WHEN salary > 10000 THEN 'C'
WHEN salary > 8000 THEN 'D'
ELSE 'E'
END 等级
FROM employees;

3.4.6 其他函数

# 获取数据库版本
SELECT VERSION();

# 获取当前用户
SELECT USER();

# 获取当前处于的数据库
SELECT DATABASE();

5. 分组查询

语法:

select 字段列表,分组函数
from 表
[where 筛选条件]
group by 分组列表(分组列表必须是字段列表里面的)
order by 字段名

筛选条件分类:

数据源 位置 关键字
分组前筛选 原始数据 group by 之前 where
分组后筛选 分组后的数据 group by 之后 having

结论:

  1. 优先考虑分组前筛选
  2. 支持单个分组和多个分组,多个根据多个字段同时相同分组
  3. 支持排序

示例:

# 查询每个工种的平均工资
SELECT job_id, AVG(salary) 平均工资
FROM employees
GROUP BY job_id;

# 查询每个位置的部门个数
SELECT location_id, COUNT(1)
FROM departments
GROUP BY location_id;

# 查询邮箱中包含 a字符,每个部门的平均工资
SELECT department_id, AVG(salary)
FROM employees
WHERE email LIKE '%a%'
GROUP BY department_id;

# 每个领导下有奖金的员工最高工资
SELECT manager_id, MAX(salary)
FROM employees
WHERE commission_pct IS NOT NULL
GROUP BY manager_id;

# 员工总数 > 2 的部门
SELECT department_id, COUNT(1) 员工总数
FROM employees
GROUP BY department_id
HAVING 员工总数 > 2;

# 查询每个工种有奖金的最高工资 > 12000 的最高工资和工种编号
SELECT job_id, MAX(salary) 最高工资
FROM employees
WHERE commission_pct IS NOT NULL
GROUP BY job_id
HAVING 最高工资 > 12000;

# 领导编号 > 102 的每个领导手下最低工资  > 5000 的领导编号
SELECT manager_id, MIN(salary) 最低工资
FROM employees
WHERE  manager_id > 102
GROUP BY manager_id
HAVING 最低工资 > 5000;

# 按员工姓名的长度分组,查询每个员工个数,筛选员工个数>5 的有哪些
SELECT COUNT(1), LENGTH(first_name) 姓名长度
FROM employees
GROUP BY 姓名长度
HAVING 姓名长度 > 5;


# 案列
#1.查询各job_id的员工工资的最大值,最小值,平均值,总和,并按job_id升序
SELECT job_id, MAX(salary), MIN(salary), AVG(salary), SUM(salary)
FROM employees
GROUP BY job_id
ORDER BY job_id ASC;
#2.查询员工最高工资和最低工资的差距(DIFFERENCE)
SELECT MAX(salary)-MIN(salary)
FROM employees;
#3.查询各个管理者手下员工的最低工资,其中最低工资不能低于6000,没有管理者的员工不计算在内
SELECT manager_id, MIN(salary) 最低工资
FROM employees
WHERE manager_id IS NOT NULL
GROUP BY manager_id
HAVING 最低工资 >= 6000; 
#4.查询所有部门的编号,员工数量和工资平均值,并按平均工资降序
SELECT department_id, COUNT(1), AVG(salary) 平均工资
FROM employees
GROUP BY department_id
ORDER BY 平均工资 DESC;
#5.选择具有各个job_id的员工人数
SELECT job_id, COUNT(*) 总人数
FROM employees
GROUP BY job_id;

6. 连接查询

笛卡尔积现象:表 1 中有 m 个数据,表2 中有 n 个数据,查询出来的结果:m x n 个数据。

如何避免笛卡尔积现象:添加有效的连接条件。

连接查询的分类:

  1. 按年代分类

    1. sql92:只支持内连接
    2. sql99:支持内连接和外连接
  2. 按功能分类

    1. 内连接
    - 等值连接
    - 非等值连接
    - 自连接
        2. 外连接
    • 左外连接
    • 右外连接
    • 全外连接
        3. 交叉连接(mysql 不支持)
    

6.1 sql92

6.1.1 内连接查询

(1)等值连接查询
# 查询女神名和对应的男神名
SELECT `name`, boyName
FROM beauty be, boys bo
WHERE be.`boyfriend_id` = bo.`id`;

# 查询员工名和对应的部门名
SELECT first_name, department_name
FROM employees e, departments d
WHERE e.`department_id` = d.`department_id`;

# 查询员工名、工种号、工种名
SELECT first_name, e.job_id, job_title
FROM employees e, jobs j
WHERE e.`job_id` = j.`job_id`;

# 查询有奖金的员工名、部门名
SELECT first_name, department_name
FROM employees e, departments d
WHERE e.`department_id` = d.`department_id`
AND e.`commission_pct` IS NOT NULL;

# 查询城市名中第二个字符为o的部门名和城市名
SELECT department_name, city
FROM departments d, locations l
WHERE d.`location_id` = l.`location_id`
AND l.`city` LIKE '_o%';

# 查询每个城市的部门个数
SELECT city, COUNT(*) 个数
FROM locations l, departments d
WHERE l.`location_id` = d.`location_id`
GROUP BY city;

# 查询有奖金的每个部门的部门名和部门的领导编号和该部门的最低工资
SELECT e.`department_id`, d.department_name, e.manager_id, MIN(salary)
FROM employees e, departments d
WHERE e.`department_id` = d.`department_id`
AND e.`commission_pct` IS NOT NULL
GROUP BY e.`department_id`;


# 查询每个工种的工种名和员工的个数,并且按员工个数降序
SELECT j.job_id, job_title, COUNT(1) 个数
FROM employees e, jobs j
WHERE e.`job_id` = j.`job_id`
GROUP BY j.`job_id`
ORDER BY 个数 DESC;

# 查询员工名、部门名和所在的城市
SELECT first_name, department_name, city
FROM employees e, departments d, locations l
WHERE e.`department_id` = d.`department_id`
AND d.`location_id` = l.`location_id`;
(2) 非等值连接
# 查询员工的工资和工资级别
SELECT first_name, salary, grade_level
FROM employees e, job_grades j
WHERE salary BETWEEN lowest_sal AND highest_sal;
(3)自连接
# 查询员工名和上级的名称
SELECT e1.first_name 员工名, e2.first_name 领导名
FROM employees e1, employees e2
WHERE e1.`manager_id` = e2.`employee_id`;

6.1.2 测试题

#1. 显示所有员工的姓名,部门号和部门名称。 
SELECT first_name, e.department_id, department_name
FROM employees e, departments d
WHERE e.`department_id` = d.`department_id`;

#2. 查询 90 号部门员工的 job_id 和 90 号部门的 location_id 
SELECT job_id, location_id
FROM employees e, departments d
WHERE e.`department_id` = d.`department_id`
AND d.`department_id` = 90;

#3. 选择所有有奖金的员工的 last_name , department_name , location_id , city 
SELECT last_name, department_name, d.location_id, city
FROM employees e, departments d, locations l
WHERE e.`department_id` = d.`department_id`
AND d.`location_id` = l.`location_id`
AND e.`commission_pct` IS NOT NULL;

#4. 选择 city 在 Toronto 工作的员工的 last_name , job_id , department_id , department_name  
SELECT last_name, job_id, e.department_id, department_name
FROM employees e, departments d, locations l
WHERE e.`department_id` = d.`department_id`
AND d.`location_id` = l.`location_id`
AND city = 'Toronto';

#5.查询每个工种、每个部门的部门名、工种名和最低工资 
SELECT e.`department_id`, department_name, j.job_id, job_title, MIN(salary)
FROM employees e, departments d, jobs j
WHERE e.`department_id` = d.`department_id`
AND e.`job_id` = j.`job_id`
GROUP BY j.job_id, e.department_id;

#6.查询每个国家下的部门个数大于 2 的国家编号 
SELECT country_id, COUNT(*) 个数
FROM departments d, locations l
WHERE d.`location_id` = l.`location_id`
GROUP BY country_id
HAVING 个数 > 2;

#7、选择指定员工的姓名,员工号,以及他的管理者的姓名和员工号,结果类似于下面的格 式 
SELECT e1.`last_name` 员工, e1.`employee_id` 员工编号, e2.`first_name` 管理, e2.`employee_id` 管理编号
FROM employees e1, employees e2
WHERE e1.`manager_id` = e2.`employee_id`;

6.2 sql 99

语法:

select 查询列表
from 表1 别名 
【连接类型】 join 表2 别名 on 连接条件
...
【连接类型】 join 表n 别名 on 连接条件
【where 筛选条件】
【group by 分组】
【having 筛选条件】
【order by 排序列表】

分类:

  1. 内连接:inner
  2. 外连接:
    1. 左外:left 【outer】
    2. 右外:right【outer】
    3. 全外:full 【outer】
  3. 交叉连接:cross

6.2.1 内连接

(1)等值连接
# 查询员工名、部门名
SELECT first_name, department_name
FROM employees e
INNER JOIN departments d
ON e.`department_id` = d.`department_id`;

# 查询名字中包含e的员工名和工种名(添加筛选)
SELECT first_name, job_title
FROM employees e
INNER JOIN jobs j
ON e.`job_id` = j.`job_id`
WHERE first_name LIKE '%e%';

# 查询部门个数>3的城市名和部门个数,(添加分组+筛选)
SELECT city, COUNT(1) 个数
FROM departments d
INNER JOIN locations l
ON d.`location_id` = l.`location_id`
GROUP BY city
HAVING 个数 > 3;

# 查询哪个部门的员工个数>3的部门名和员工个数,并按个数降序(添加排序)
SELECT department_name, COUNT(*) 个数
FROM employees e
INNER JOIN departments d
ON e.`department_id` = d.`department_id`
GROUP BY e.`department_id`
HAVING 个数 > 3;

# 查询员工名、部门名、工种名,并按部门名降序(添加三表连接)
SELECT first_name, department_name, job_title
FROM employees e
INNER JOIN departments d
ON e.`department_id` = d.`department_id`
INNER JOIN jobs j
ON e.`job_id` = j.`job_id`
ORDER BY department_name DESC;
(2)非等值连接
# 查询员工的工资级别
SELECT first_name, salary, grade_level
FROM employees e
INNER JOIN job_grades j
ON salary BETWEEN lowest_sal AND highest_sal;

# 查询工资级别的个数>20的个数,并且按工资级别降序
SELECT first_name, salary, grade_level, COUNT(*) 个数
FROM employees e
INNER JOIN job_grades j
ON salary BETWEEN lowest_sal AND highest_sal
GROUP BY grade_level
HAVING 个数 > 20
ORDER BY grade_level DESC;
(3)自连接
# 查询员工的名字、上级的名字
SELECT e1.first_name 员工名, e2.first_name 上级名
FROM employees e1
INNER JOIN employees e2
ON e1.`manager_id` = e2.`employee_id`;

#查询姓名中包含字符k的员工的名字、上级的名字
SELECT e1.first_name 员工名, e2.first_name 上级名
FROM employees e1
INNER JOIN employees e2
ON e1.`manager_id` = e2.`employee_id`
WHERE e1.`first_name` LIKE '%k%';

6.2.1 外连接

应用场景:用于查询一个表中有,另一个表没有的记录.

特点:

  1. 外连接的查询结果为主表中的所有记录
    • 如果从表中有和它匹配的,则显示匹配的值
    • 如果从表中没有和它匹配的,则显示null
  2. 外连接查询结果=内连接结果+主表中有而从表没有的记录
    • 左外连接,left join 左边的是主表
    • 右外连接,right join 右边的是主表
  3. 左外和右外交换两个表的顺序,可以实现同样的效果
  4. 全外连接=内连接的结果+表1中有但表2没有的+表2中有但表1没有的

示例:

# 外连接 
# 查询男朋友不在男神表的的女神名
SELECT b.*, bo.*
FROM beauty b
LEFT JOIN boys bo
ON b.`boyfriend_id` = bo.`id`
WHERE bo.id IS NULL;

# 查询哪个部门没有员工
SELECT d.*, e.*
FROM departments d
LEFT JOIN employees e
ON e.`department_id` = d.`department_id`
WHERE e.`employee_id` IS NULL;

6.2.3 交叉连接

就是笛卡尔乘积。

6.3 sql92 和 sql99 对比

功能:sql99支持的较多。

可读性:sql99实现连接条件和筛选条件的分离,可读性较高。

6.4 示例

# 1.查询编号>3的女神的男朋友信息,如果有则列出详细,如果没有,用null填充
SELECT b.*, bo.*
FROM beauty b
LEFT JOIN boys bo
ON b.`boyfriend_id` = bo.`id`
WHERE b.`id` > 3;


# 2.查询哪个城市没有部门
SELECT l.`city`
FROM locations l
LEFT JOIN departments d
ON l.`location_id` = d.`location_id`
WHERE d.`department_id` IS NULL;

# 3.查询部门名为SAL或IT的员工信息
SELECT e.*
FROM employees e
INNER JOIN departments d
ON e.`department_id` = d.`department_id`
WHERE d.`department_name` IN('SAL', 'IT');

7. 子查询

含义:出现在其他语句中的select语句,称为子查询或内查询。外部的查询语句,称为主查询或外查询。

分类:

  1. 按结果集的行列数不同分类
    1. 标量子查询(结果集只有一行一列)
    2. 列子查询(结果集只有一列多行)
    3. 行子查询(结果集有一行多列)
    4. 表子查询(结果集一般为多行多列)
  2. 按子查询出现的位置分类
    1. select 后面
      1. 仅仅支持标量子查询
    2. from 后面
      1. 支持表子查询
    3. where 或 having 后面
      1. 标量子查询(单行)
      2. 列子查询 (多行)
      3. 行子查询
    4. exists 后面
      1. 表子查询

7.1 where 或 having 后面

特点:

  1. 子查询放在小括号内
  2. 子查询一般放在条件的右侧
  3. 标量子查询,一般搭配着单行操作符使用:< >= <= = <>
  4. 列子查询,一般搭配着多行操作符使用:in、any/some、all
  5. 子查询的执行优先于主查询执行,主查询的条件用到了子查询的结果

示例:

# 谁的工资比 Abel 高?
SELECT * FROM employees
WHERE salary > (
    SELECT salary FROM employees
    WHERE last_name = 'Abel'
);

# 返回job_id与141号员工相同,salary比143号员工多的员工 姓名,job_id 和工资
SELECT first_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
);

# 查询最低工资大于50号部门最低工资的部门id和其最低工资
SELECT department_id, MIN(salary) min_salary
FROM employees
GROUP BY department_id
HAVING min_salary > (
    SELECT MIN(salary)
    FROM employees
    WHERE department_id = 50    
);

# 返回location_id是1400或1700的部门中的所有员工姓名
SELECT last_name, first_name
FROM employees
WHERE department_id IN(
    SELECT department_id
    FROM departments
    WHERE location_id IN (1400, 1700)
);

SELECT last_name, first_name
FROM employees e
INNER JOIN departments d
ON e.`department_id` = d.`department_id`
WHERE d.`location_id` IN (1400, 1700);

# 返回其它工种中比job_id为‘IT_PROG’工种任一工资低的员工的员工号、姓名、job_id 以及salary
SELECT employee_id, last_name, first_name, job_id, salary
FROM employees
WHERE job_id <> 'IT_PROG' 
AND salary < (
    SELECT MAX(salary)
    FROM employees
    WHERE job_id = 'IT_PROG'
)

SELECT employee_id, last_name, first_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为‘IT_PROG’部门所有工资都低的员工的员工号、姓名、job_id 以及salary
SELECT employee_id, last_name, first_name, job_id, salary
FROM employees
WHERE job_id <> 'IT_PROG' 
AND salary < (
    SELECT MIN(salary)
    FROM employees
    WHERE job_id = 'IT_PROG'
)

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

# 查询员工编号最小并且工资最高的员工信息
SELECT * FROM employees
WHERE employee_id = (
    SELECT MIN(employee_id)
    FROM employees
)AND salary = (
    SELECT MAX(salary)
    FROM employees
);

7.2 select 后面

特点:仅仅支持标量子查询。

示例:

# 查询每个部门的员工个数
# 不能查询部门员工为 0 的部门
SELECT *, COUNT(1)
FROM employees
GROUP BY department_id;

# 可以显示部门员工为 0 的部门
SELECT d.*, (SELECT COUNT(*) FROM employees e WHERE d.`department_id` = e.department_id) 个数
FROM departments d;

# 查询员工号=102的部门名
SELECT department_name
FROM departments d
INNER JOIN employees e
ON d.`department_id` = e.`department_id`
WHERE e.`employee_id` = 102;

7.3 from 后面

特点:将子查询结果充当一张表,要求必须起别名。

示例:

# 查询每个部门的平均工资的工资等级
SELECT department_id, grade_level
FROM (
    SELECT department_id, AVG(salary) avg_salary
    FROM employees
    GROUP BY department_id
) a
INNER JOIN job_grades
ON a.avg_salary BETWEEN lowest_sal AND highest_sal;

7.4 exists

语法:exists(完整的查询语句),用了检查查询是否有结果。

特点:结果为 1或 0 。

示例:

# 查询有员工的部门名
SELECT  DISTINCT department_name
FROM departments d
INNER JOIN employees e
ON d.`department_id` = e.`department_id`;

SELECT department_name
FROM departments d
WHERE d.`department_id` IN (
    SELECT department_id
    FROM employees
);

SELECT department_name
FROM departments d
WHERE EXISTS(
    SELECT * FROM employees e
    WHERE d.`department_id` = e.`department_id`
);

8. 分页查询

语法:

select 查询列表
from 表
[join type join 表2
on 连接条件
where 筛选条件
group by 分组字段
having 分组后的筛选
order by 排序的字段]
limit [offset,] size;

参数说明

  • offset : 要显示条目的起始索引(起始索引从0开始)
  • size : 要显示的条目个数

特点:limit语句放在查询语句的最后(limit从语法上和执行上都是最后)。

分页查询规律:

# page 要显示的页数, size ,每一页显示的记录数
select 查询列表
from 表
limit (page-1)*size, size;

9. DML

9.1 插入

语法:

# 语法一
insert into 表名[(字段列表)] values(值列表)

# 语法二
insert into 表名
set 字段1 = 值1,
...
字段n = 值n;

示例:

# 插入的值的类型要与列的类型一致或兼容
INSERT INTO employees (
  employee_id,
  first_name,
  last_name
) 
VALUES
  (1, 'huang', 'opendragon') ;

# 字段约束不可以为null的列必须插入值,可以为null的列可以插入,也可以不插入。
INSERT INTO beauty (
  id,
  NAME,
  sex,
  borndate,
  phone,
  photo,
  boyfriend_id
) 
VALUES
  (
    13,
    '唐艺昕',
    '女',
    '1990-4-23',
    '1898888888',
    NULL,
    2
  ) ;


INSERT INTO beauty (id, NAME, sex, phone) 
VALUES
  (15, '娜扎', '女', '1388888888') ;

# 列数和值的个数必须一致
INSERT INTO beauty (NAME, sex, id, phone) 
VALUES
  ('关晓彤', '女', 17, '110') ;

# 可以省略列名,默认所有列,而且列的顺序和表中列的顺序一致
INSERT INTO beauty 
VALUES
  (
    18,
    '张飞',
    '男',
    NULL,
    '119',
    NULL,
    NULL
  ) ;

# 使用方式二插入
INSERT INTO employees SET employee_id = 19,
last_name = 'opendragon',
first_name = 'huang' ;

# 插入多行
INSERT INTO employees (
  employee_id,
  first_name,
  last_name
) 
VALUES
  (1, 'huang', 'opendragon'),
  (2, 'huang', 'opendragon'),
  (3, 'huang', 'opendragon') ;

# 使用子查询插入
INSERT INTO test 
SELECT 
  * 
FROM
  employees 
WHERE last_name = 'opendragon' ;


INSERT INTO test (
  first_name,
  last_name,
  email,
  phone_number
) 
SELECT 
  first_name,
  last_name,
  email,
  phone_number 
FROM
  employees 
WHERE last_name LIKE '%a%' ;

方式一插入和方式二插入对比:

  1. 方式一支持插入多行,方式二不支持。
  2. 方式一支持子查询,方式二不支持。

9.2 更新

语法:

# 修改单表记录,没有筛选条件则更新所有记录
update 表名
set 字段1 = 值1,
    ...
    字段n = 值n
where 筛选条件;

# 修改多表记录
# sql92 语法
update 表名1 别名1, ... 表2 别名n
set 字段1 = 值1,
    ...
    字段n = 值n
where 连接条件 and 筛选条件;

# sql99 语法
update 表名1 别名1 [inner|left|right]
join  表名2 别名2
on 连接条件1
...
join  表名n 别名n
on 连接条件n-1
set 字段1 = 值1,
    ...
    字段n = 值n
where 筛选条件;

示例:

# 单表修改
UPDATE 
  test 
SET
  salary = 12000 
WHERE first_name = 'huang' ;

# 修改张无忌的女朋友的手机号为114
UPDATE 
  beauty b 
  INNER JOIN boys bo 
    ON b.`boyfriend_id` = bo.id SET b.`phone` = 114 
WHERE bo.`boyName` = '张无忌' ;

9.3 删除

语法:

delete from 表名
where 筛选条件;

delete 别名1, ..., 别名n 
from 表名1 别名1, ..., 表名n 别名n
where 连接条件 and 筛选条件;

delete 别名1, ..., 别名n
from 表名1 别名1 [inner|lefet|right]
join  表名2 别名2
on 连接条件1
...
join  表名n 别名n
on 连接条件n-1
where 筛选条件;

truncate table 表名;

10. DDL

10.1 库的管理

10.1.1 数据库的创建

语法:

create database [if not exists] 库名;

10.1.2 数据库的修改

语法:

# 修改数据库名
rename datebase 旧的数据库名 to 新的数据库名;
# 修改数据库的字符集
drop database 库名 character set gbk;

10.1.3 数据库的删除

语法:

drop database [if exists] 库名;

10.2 表的管理

10.2.1 表的创建

语法:

create table [if not exists] 表名(
    字段名1 字段类型1 约束,
    ....
    字段名n 字段类型n 约束,
);

10.2.2 表的修改

语法:

alter table 表名 add|drop|modify|change column 列名 [列类型 约束];

示例:

# 添加新列
ALTER TABLE books ADD COLUMN info TEXT;
# 修改列名
ALTER TABLE books CHANGE COLUMN info test TEXT;
# 修改列的类型或约束
ALTER TABLE books MODIFY COLUMN test VARCHAR(200);
# 删除列
ALTER TABLE books DROP COLUMN test;
# 修改表名
ALTER TABLE books RENAME TO new_books;

10.2.3 表的删除

语法:

drop table [if exists] 表名;

10.2.4 表的复制

示例:

# 只复制表的结构
CREATE TABLE IF NOT EXISTS bk_books LIKE books;

# 复制表的结构和全部数据
CREATE TABLE IF NOT EXISTS bk_books
SELECT * FROM books;

11. 约束和自动增长

11.1 约束

含义:一种限制,用于限制表中的数据和保证表中数据的准确和可靠。

约束种类:

  1. NOT NULL
  2. DEFAULT
  3. PRIMARY KEY
  4. UNIQUE
  5. FOREIGN KEY
  6. CHECK(mysql 不支持)

添加约束的时机:

  1. 创建表的时候
  2. 修改表时(此时表还没有插入数据,如果插入数据则不能修改)

约束分类:

  1. 列级约束
    1. FOREIGN KEY 没有效果
  2. 表级约束
    1. NOT NULLDEFAULT 不支持

主键和唯一对比:

保证唯一性 是否为空 一个表是否可以有多个 是否可以组合
主键 最多一个 ✓,但不推荐
唯一 可以有多个 ✓,但不推荐

外键:

  1. 要求在从表中设置外键关系
  2. 从表中设置外键的类型必须和主表中关联字段的类型一致或相兼容
  3. 主表中关联的字段必须是一个主键或唯一
  4. 插入数据先插入主表,在插入从表
  5. 删除数据先删除从表,在删除主表

11.1.1 添加约束

示例:

# 添加列级约束
DROP TABLE IF EXISTS stu_info;
CREATE TABLE IF NOT EXISTS stu_info(
    stu_id INT PRIMARY KEY,
    gender CHAR(1) DEFAULT '男',
    age INT NOT NULL,
    seat INT UNIQUE
);

# 查看表的结构
DESC stu_info;

# 添加表级约束
DROP TABLE IF EXISTS majors;
CREATE TABLE IF NOT EXISTS majors(
    major_id INT PRIMARY KEY,
    major_name VARCHAR(20) NOT NULL
);
DROP TABLE IF EXISTS stu_info;
CREATE TABLE IF NOT EXISTS stu_info(
    stu_id INT,
    gender CHAR(1) DEFAULT '男',
    age INT NOT NULL,
    seat INT,
    major_id INT,
    CONSTRAINT pk PRIMARY KEY(stu_id),
    CONSTRAINT uq UNIQUE(seat),
    CONSTRAINT fk FOREIGN KEY(major_id) REFERENCES majors(major_id)
);

# 通用写法
DROP TABLE IF EXISTS majors;
CREATE TABLE IF NOT EXISTS majors(
    major_id INT PRIMARY KEY,
    major_name VARCHAR(20) NOT NULL
);
DROP TABLE IF EXISTS stu_info;
CREATE TABLE IF NOT EXISTS stu_info(
    stu_id INT PRIMARY KEY,
    gender CHAR(1) DEFAULT '男',
    age INT NOT NULL,
    seat INT UNIQUE,
    major_id INT,
    FOREIGN KEY(major_id) REFERENCES majors(major_id)
);

11.1.2 修改约束

示例:

DROP TABLE IF EXISTS majors;
CREATE TABLE IF NOT EXISTS majors(
    major_id INT PRIMARY KEY,
    major_name VARCHAR(20) NOT NULL
);
DROP TABLE IF EXISTS stu_info;
CREATE TABLE stu_info(
    stu_id INT,
    gender CHAR(1),
    age INT,
    seat INT,
    major_id INT
);

# 添加非空和默认约束
ALTER TABLE stu_info MODIFY COLUMN gender CHAR(1) DEFAULT '男' NOT NULL;
ALTER TABLE stu_info MODIFY COLUMN age INT NOT NULL;

# 添加主键
# 列级
ALTER TABLE stu_info MODIFY COLUMN stu_id INT PRIMARY KEY;
# 表级
ALTER TABLE stu_info ADD PRIMARY KEY(stu_id);

# 添加唯一
# 列级
ALTER TABLE stu_info MODIFY COLUMN seat INT UNIQUE;
# 表级
ALTER TABLE stu_info ADD UNIQUE(seat);

# 添加外键
ALTER TABLE stu_info ADD CONSTRAINT fk FOREIGN KEY(major_id) REFERENCES majors(major_id);

11.1.3 删除约束

示例:

# 删除非空约束和默认约束
ALTER TABLE stu_info MODIFY  COLUMN gender CHAR(1);
ALTER TABLE stu_info MODIFY COLUMN age INT;

# 删除主键
ALTER TABLE stu_info DROP PRIMARY KEY;

# 删除唯一
ALTER TABLE stu_info DROP INDEX seat;

# 删除外键
ALTER TABLE stu_info DROP FOREIGN KEY fk;

11.2 自动增长列

含义:可以不用手动的插入值,系统提供默认的序列值。

特点:

  1. 自动增长列只能和 key 进行搭配。
  2. 一个表最多只能有一个自动增长列。
  3. 自动增长类的类型只能是数值型。
  4. 通过 SET auto_increment_increment=3; 设置步长。

示例:

# 主键和自动增长配合
DROP TABLE IF EXISTS majors;
CREATE TABLE IF NOT EXISTS majors(
    major_id INT PRIMARY KEY AUTO_INCREMENT,
    major_name VARCHAR(20) NOT NULL
);

12. 事务

12.1 事务简介

事务由单独单元的一个或多个SQL语句组成,在这 个单元中,每个MySQL语句是相互依赖的。而整个单独单元作

为一个不可分割的整体,如果单元中某条SQL语句一 旦执行失败或产生错误,整个单元将会回滚。所有受到影响

的数据将返回到事物开始以前的状态;如果单元中的所有SQL语句均执行成功,则事物被顺利执行。

12.2 事务的属性

  1. 原子性(Atomicity)

    原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

  2. 一致性(Consistency)

    事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

  3. 隔离性(Isolation)

    事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事

    务是隔离的,并发执行的各个事务之间不能互相干扰。

  4. 持久性(Durability)

    持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不

    应该对其有任何影响。

12.3 数据库的隔离级别

对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并

发问题:

  • 脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容

    就是临时且无效的.

  • 不可重复读: 对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段,

    值就不同了.

  • 幻读: 对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1

    再次读取同一个表, 就会多出几行.

数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题.

一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程

度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。

数据库提供的 4 种事务隔离级别:

隔离级别 描述
READ UNCOMMITTED 允许事务读取未被其他事务提交的变更。
READ COMMITTED 只运行事务读取已经被其他事务提交的变更。
REPEATABLE READ 确保事务多次从一个字段读取相同的值,在这个事务持续期间,禁止其他事物对这个字段进行更新。
SERIALIZABLE 确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,所有并发问题都可以避免,但性能十分低下。

Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE. Oracle 默认的事务隔离级别

为: READ COMMITED 。

Mysql 支持 4 中事务隔离级别。 Mysql 默认的事务隔离级别为: REPEATABLE READ 。

12.3.1 Mysql 事务隔离级别设置

每启动一个 mysql 程序, 就会获得一个单独的数据库连接。 每个数据库连接都有一个全局变量 @@tx_isolation, 表

示当前的事务隔离级别。 MySQL 默认的隔离级别为 Repeatable Read 。

查看当前的隔离级别:

SELECT @@tx_isolation;

设置当前 mySQL 连接的隔离级别:
set transaction isolation level read committed;

设置数据库系统的全局的隔离级别:
set global transaction isolation level read committed;

13. 视图

概念:MySQL 从 5.0.1 版本开始提供视图功能。一种虚拟存在的表,行和列的数据来自定义视图时查询语句中使

用的表 ,并且是在使用视图时动态生成的,只保存了 sql 逻辑,不保存查询结果。

应用场景:

  • 多个地方用到同样的查询结果
  • 该查询结果使用的 sql 语句较复杂

视图和表的对比:

创建语法的关键字 是否实际占用物理空间 使用
视图 create view 只保存 sql 的逻辑 增删改查,只是一般不增删改
create table 保存数据 增删改查

13.1 视图的创建

语法:

create view 视图名
as
sql 语句;

示例:

# 查询姓名中包含a字符的员工名、部门名和工种信息
-- 创建视图
CREATE VIEW myv1
AS
SELECT first_name, last_name, department_name, job_title
FROM employees e
INNER JOIN departments d
ON e.`department_id` = d.`department_id`
INNER JOIN jobs j
ON e.`job_id` = j.`job_id`;
-- 查询视图
SELECT * FROM myv1
WHERE first_name LIKE '%a%';

# 查询各部门的平均工资级别
-- 创建视图
CREATE VIEW myv2
AS
SELECT e.department_id, department_name, AVG(salary) 平均工资
FROM employees e
INNER JOIN departments d
ON e.`department_id` = d.`department_id`
GROUP BY e.`department_id`;
-- 查询视图
SELECT v.*, j.`grade_level` 
FROM myv2 v
INNER JOIN job_grades j
ON v.`平均工资` BETWEEN j.`lowest_sal` AND j.`highest_sal`;

# 查询平均工资最低的部门信息
-- 创建视图
CREATE VIEW myv3
AS
SELECT d.*, AVG(salary) 平均工资
FROM employees e
INNER JOIN departments d
ON e.`department_id` = d.`department_id`
GROUP BY department_id;

-- 使用视图
SELECT *
FROM myv3
WHERE 平均工资 = (
    SELECT MIN(平均工资)
    FROM myv3
);

13.2 视图的修改、删除和查看

语法:

# 视图的修改
-- 方式一
create or replace view 视图名
as 
sql 语句;
-- 方式二
alter view 视图名
as
sql 语句;

# 视图删除
drop view 视图1, ..., 视图 n;

# 查看视图
-- 方式
# 查看视图表的结构
desc 视图名
-- 方式二
# 查看视图的创建语句
show create view 视图名

示例:

# 视图的修改
-- 创建视图
CREATE VIEW myv4
AS
SELECT e.*, d.department_name, d.location_id
FROM employees e
INNER JOIN departments d
ON e.department_id = d.department_id;
-- 使用视图
SELECT * FROM myv4;
-- 修改视图
CREATE OR REPLACE VIEW myv4
AS
SELECT d.department_id, department_name, AVG(salary) 平均工资
FROM employees e
INNER JOIN departments d
ON e.department_id = d.department_id
GROUP BY d.department_id; 
-- 修改视图
ALTER VIEW myv4
AS
SELECT * FROM employees;

# 查看视图
-- 查看视图表的结构
DESC myv4;
-- 查看视图的创建语句
SHOW CREATE VIEW myv4;

# 删除视图
DROP VIEW myv4;

14. 变量

变量的分类:

  1. 系统变量
    1. 全局变量
    2. 会话变量
  2. 自定义变量
    1. 用户变量
    2. 局部变量

变量的作用域:

  1. 系统变量
    1. 全局变量:作用范围为整个服务器,服务器重启,会回复默认值。
    2. 会话变量:作用范围为一次会话。
  2. 用户自定义变量
    1. 用户变量:作用范围为一次会话。
    2. 局部变量:作用范围为 begin 和 and 之间。

14.1 系统变量的使用

14.1.1 查看系统变量

语法:

# 查看全部的系统变量
show global | [session] variables;

# 查看满足条件的系统变量
show global | [session] variables like '';

# 查看指定的系统变量
select @@global | [session].系统变量名

示例:

# 查看全部的系统变量
-- 全局变量
show global variables;
-- 会话变量
show session variables;
show variables;

# 查看条件过滤的系统变量
-- 全局变量
show global variables like '%char%';
-- 会话变量
show session variables like '%char%';
SHOW VARIABLES LIKE '%char%';

# 查看指定的系统变量
-- 全局变量
select @@global.tx_isolation;
-- 会话变量
select @@session.tx_isolation;
select @@tx_isolation;

14.1.2 为系统变量赋值

语法:

set global | [session] 系统变量名 = 值;
set @@global | session.系统变量名 = 值;

示例:

# 设置系统变量的值
-- 全局变量
set @@global.autocommit = 0;
set global autocommit = 0;

-- 会话变量
set @@session.autocommit = 0;
set @@autocommit = 0;
set session autocommit = 0;
set autocommit = 0;

14.2 自定义变量的使用

14.2.1 用户变量

语法:

# 声明和初始化用户变量
set @用户变量名 = 值;
set @用户变量名 := 值;
select @用户变量名 := 值;

# 更新用户变量的值
set @用户变量名 = 值;
set @用户变量名 := 值;
select @用户变量名 := 值;
select 字段名 into @用户变量
from 表明;

# 查看用户变量的值
select @用户变量名

示例:

# 使用用户变量计算两个用户变量的和
SET @m = 10;
SET @n = 30;
SET @sum = @m+@n;
SELECT @sum;

14.2.2 局部变量

语法:

# 声明局部变量
declare 局部变量名 类型;

# 声明局部变量并初始化
declare 局部变量名 类型 default 默认值;

# 更新局部变量的值
set @局部变量名 = 值;
set @局部变量名 := 值;
select @局部变量名 := 值;
select 字段名 into 局部变量名
from 表名;

# 查看局部变量
select 局部变量名;

15. 存储过程

简介:一组预先编译好的SQL语句的集合,理解成批处理语句。

好处:

  1. 提高代码的重用性。
  2. 简化操作。
  3. 减少了编译次数并且减少了和数据库服务器的连接次数,提高了效率。

语法:

  1. 创建存储过程

    create procedure 存储过程名(参数列表)
    begin
        一组 sql 语句
    end
    

    如果存储过程体仅仅只有一句话,begin end可以省略存储过程体中的每条sql语句的结尾要求必须加分号。

  2. 使用存储过程

    call 存储过程名(参数列表);
    
  3. 删除存储过程

    drop 存储过程名列表;
    
  4. 查看存储过程

    show create procedure 存储过程名;
    

存储过程参数定义语法:

参数模式 参数名 参数类型;

参数模式说明:

  • in:该参数可以作为输入,也就是该参数需要调用方传入值
  • out:该参数可以作为输出,也就是该参数可以作为返回值
  • inout:该参数既可以作为输入又可以作为输出,也就是该参数既需要传入值,又可以返回值

delimiter:用于设置 mysql 语句的结束关键字,如:`delimiter 为语句的结束符。

示例:

# 空参列表
# 插入到admin表中五条记录
DELIMITER $
CREATE PROCEDURE myp1()
BEGIN
    INSERT INTO admin(username, `password`)
    VALUES
    ('john1', '1000'),
    ('lily', '0000'),
    ('rose', '1111');

END$
# 调用存储过程
CALL myp1();

# 创建带in模式参数的存储过程
# 创建存储过程实现 根据女神名,查询对应的男神信息
DELIMITER $
CREATE PROCEDURE myp2(IN beauty_name VARCHAR(20))
BEGIN
    SELECT * FROM beauty be
    INNER JOIN boys bo
    ON be.boyfriend_id = bo.id
    WHERE be.name = beauty_name;
END$
DELIMITER ;

CALL myp2('热巴');

# 创建存储过程实现,用户是否登录成功
DELIMITER $
CREATE PROCEDURE myp3(IN username VARCHAR(20), IN `password` VARCHAR(20))
BEGIN
    DECLARE result INT DEFAULT 0;
    SELECT COUNT(*) INTO result
    FROM admin a
    WHERE a.username = username
    AND a.password = `password`;

    SELECT IF(result = 0, '失败', '成功');
END$
DELIMITER ;

CALL myp3('john', '8888');

# 创建out 模式参数的存储过程
# 根据输入的女神名,返回对应的男神名
DELIMITER $
CREATE PROCEDURE myp4(IN beauty_name VARCHAR(20), OUT boy_name VARCHAR(20))
BEGIN
    SELECT bo.boyName INTO boy_name
    FROM beauty be
    INNER JOIN boys bo
    ON be.boyfriend_id = bo.id
    WHERE be.name = beauty_name;
END$
DELIMITER ;

SET @boy_name = '000';
CALL myp4('热巴', @boy_name);
SELECT @boy_name;

# 创建带inout模式参数的存储过程
# 传入a和b两个值,最终a和b都翻倍并返回
DELIMITER $
CREATE PROCEDURE myp5(INOUT m INT, INOUT n INT)
BEGIN
    SET m = m*2;
    SET n = n*2;
END$
DELIMITER ;

SET @m = 4;
SET @n = 10;
CALL myp5(@m, @n);
SELECT @m, @n;

16. 函数

定义:函数的定义和存储过程的定义是一样的。

函数和存储过程的区别:

  • 存储过程:可以有0个返回,也可以有多个返回,适合做批量插入、批量更新。
  • 函数:有且仅有1 个返回,适合做处理数据后返回一个结果。

语法:

  1. 创建语法

    CREATE FUNCTION 函数名(参数列表) RETURNS 返回类型
    BEGIN
        函数体
    END
    

    注意:

    1. 参数列表包含两部分:参数名 参数类型
    2. 函数体:肯定会有return语句,如果没有会报错如果return语句没有放在函数体的最后也不报错,但不建议。
    3. 函数体中仅有一句话,则可以省略begin end 。
    4. 使用 delimiter语句设置结束标记。
  1. 调用语法

    select 函数名(参数列表)
    
  2. 删除语法

    drop function 函数名;
    
  3. 查看语法

    show create function 函数名;
    

示例:

# 创建函数,实现传入两个float,返回二者之和
DELIMITER $
CREATE FUNCTION myf1(num1 INT, num2 INT) RETURNS INT
BEGIN
    DECLARE SUM INT DEFAULT 0;
    SET SUM=num1+num2;
    RETURN SUM;
END$
DELIMITER ;

SELECT myf1(1, 2);

# 返回公司的员工个数
DELIMITER $
CREATE FUNCTION myf2() RETURNS INT
BEGIN
    DECLARE c INT DEFAULT 0;
    SELECT COUNT(*) INTO c 
    FROM employees;        
    RETURN c;
END$
DELIMITER ;

SELECT myf2();

# 根据员工名,返回它的工资
DELIMITER $
CREATE FUNCTION myf3(NAME VARCHAR(10)) RETURNS DOUBLE
BEGIN
    DECLARE s DOUBLE DEFAULT 0.0;
    SELECT salary INTO s
    FROM employees
    WHERE first_name = NAME;
    RETURN s;
END$
DELIMITER ;

SELECT myf3('Neena');

# 根据部门名,返回该部门的平均工资
DELIMITER $
CREATE FUNCTION myf4(department_name VARCHAR(20)) RETURNS DOUBLE
BEGIN
    DECLARE s DOUBLE DEFAULT 0.0;
    SELECT AVG(salary) INTO s
    FROM employees e
    INNER JOIN departments d
    ON e.department_id = d.department_id
    WHERE d.department_name = department_name;
    RETURN s;
END$
DELIMITER ;

SELECT myf4('Adm');

17. 流程控制

17.1 分支结构

17.1.1 if 函数

语法:

if(条件,值1,值2)

作用域:应用在begin end 中或外面。

17.1.2 case

语法:

# 情况1:类似于switch
case 变量或表达式
when 值1 then 语句1;
when 值2 then 语句2;
...
else 语句n;
end 

# 情况2:
case 
when 条件1 then 语句1;
when 条件2 then 语句2;
...
else 语句n;
end 

作用域:应用在 begin end 中或外面。

17.1.3 if

语法:

if 条件1 then 语句1;
elseif 条件2 then 语句2;
....
else 语句n;
end if;

作用域:只能应用在 begin end 中。

示例:

# 创建函数,实现传入成绩,如果成绩>90,返回A,如果成绩>80,返回B,如果成绩>60,返回C,否则返回D
DELIMITER $
CREATE FUNCTION score_leve(score FLOAT) RETURNS CHAR
BEGIN
    DECLARE ch CHAR;
    IF score > 90 THEN SET ch = 'A';
    ELSEIF score > 80 THEN SET ch = 'B';
    ELSEIF score > 60 THEN SET ch = 'C';
    ELSE SET ch = 'D';
    END IF;
    RETURN ch;
END $
DELIMITER ;


# 创建存储过程,如果工资<2000,则删除,如果5000>工资>2000,则涨工资1000,否则涨工资500
DELIMITER $
CREATE PROCEDURE delete_sal(IN sal DOUBLE)
BEGIN
    IF sal < 2000 THEN 
        DELETE FROM employees_bk WHERE employees_bk.`salary` = sal;
    ELSEIF sal BETWEEN 2000 AND 5000 THEN 
        UPDATE employees_bk SET salary = salary+1000 WHERE employees_bk.`salary` = sal;
    ELSE 
        UPDATE employees_bk SET salary = salary+500 WHERE employees_bk.`salary` = sal;
    END IF;

END $
DELIMITER ;

# 创建函数,实现传入成绩,如果成绩>90,返回A,如果成绩>80,返回B,如果成绩>60,返回C,否则返回D
DELIMITER $
CREATE FUNCTION score_level(score FLOAT) RETURNS CHAR
BEGIN
    DECLARE ch CHAR DEFAULT 'A';

    CASE
    WHEN score>90 THEN SET ch='A';
    WHEN score>80 THEN SET ch='B';
    WHEN score>60 THEN SET ch='C';
    ELSE SET ch='D';
    END CASE;
    RETURN ch;
END $
DELIMITER ;

17.2 循环

17.2.1 循环控制关键字

iterate:类似于 continue,继续,结束本次循环,继续下一次。
leave:类似于 break,跳出,结束当前所在的循环。

17.2.2 while 循环

语法:

【标签:】while 循环条件 do
    循环体;
end while【 标签】;

示例:

# 没有添加循环控制语句
# 批量插入,根据次数插入到admin表中多条记录
DELIMITER $
CREATE PROCEDURE insert_admin(IN insert_count INT)
BEGIN
    DECLARE i INT DEFAULT 1;
    a: WHILE i <= insert_count DO
        INSERT admin(username, PASSWORD)
        VALUE ('hkl', CONCAT('hkl', i));
        SET i = i+1;
    END WHILE a;    
END $
DELIMITER ;

# 添加leave语句
# 批量插入,根据次数插入到admin表中多条记录,如果次数>20则停止
DELIMITER $
CREATE PROCEDURE insert_admin(IN insert_count INT)
BEGIN
    DECLARE i INT DEFAULT 1;
    a: WHILE i <= insert_count DO
        INSERT admin(username, PASSWORD)
        VALUE ('hkl', CONCAT('hkl', i));
        SET i = i+1;
        IF i > 20 THEN LEAVE a;
        END IF;
    END WHILE a;    
END $
DELIMITER ;


# 添加iterate语句
# 批量插入,根据次数插入到admin表中多条记录,只插入偶数次
DELIMITER $
CREATE PROCEDURE insert_admin(IN insert_count INT)
BEGIN
    DECLARE i INT DEFAULT 1;
    a: WHILE i <= insert_count DO
        IF MOD(i, 2) <> 0 THEN
            ITERATE a;
        END IF;
        INSERT admin(username, PASSWORD)
        VALUE ('hkl', CONCAT('hkl', i));
        SET i = i+1;
    END WHILE a;    
END $
DELIMITER ;

17.2.3 loop 循环

语法:

语法:
【标签:】loop
    循环体;
end loop 【标签】;

17.2.4 repeat 循环

语法:

【标签:】repeat
    循环体;
until 结束循环的条件
end repeat 【标签】;