MYSQL 常用语法学习汇总

数据库的概念

  • DB:数据库,保存一组有组织的数据的容器(database)
  • DBMS:数据库管理系统,又称为数据库软件(产品),用于管理DB中的数据(Database Management System) 常用的就是:MySQL, Oracle, DB2
  • SQL:结构化查询语言,用于和DBMS通信的语言

优点:几乎所有DBMS 都支持SQL, 简单易学, 语言强大,灵活使用语言元素,可以进行非常复杂和高级的数据库操作

数据库存储数据的特点

​ 1、将数据放到中,表再放到
​ 2、一个数据库中可以有多个表每个表都有一个的名字,用来标识自己。表名具有唯一性
​ 3、表具有一些特性,这些特性定义了数据在表中如何存储,类似java中 “类”的设计。
​ 4、表由列组成,我们也称为
字段
。所有表都是由一个或多个列组成的,每一列类似java 中的”属性”
​ 5、表中的数据是按行存储的,每一行类似于java中的**“对象”**。

MySQL 的介绍和安装

属于Oracle 公司

优点:

  • 开源, 可免费使用
  • 性能高: 执行很快
  • 简单

DBMS 分为两类:

  • 基于文件系统的DBMS(Access)
  • 基于客户机----服务器DBMS (MySQL, Oracle, SqlServer)

MySQL: 社区版和企业版

下载5.5 或者 5.6 即可

卸载:

控制面板 -> 卸载 并去C盘 -> Program Files(×86) -> MySQL 卸载 再去 隐藏文件ProgramData 删掉MySQL

还是不行就去清理注册表

安装目录下的my.ini 是配置文件 (可以点开看看, 里面有许多配置信息)

MySQL服务的启动和停止

方式一:计算机——右击管理——服务
**方式二:**通过管理员身份运行cmd
net start 服务名(mysql)(启动服务)
net stop 服务名(停止服务)

MySQL服务的登录和退出

方式一:通过mysql自带的客户端(Command Linme Client) 只限于root用户

**方式二:**通过管理员身份运行cmd
登录:
mysql -h主机名 -P端口号 -u用户名 -p密码

(本地的话: 主机名:localhost, 端口号:3306) 输入-p 可以直接输入密码, 也可以直接按回车

本机直接可以简写: mysql -u 用户 -p 密码

退出:
exit 或 ctrl+C

如果无法识别命令行, 就是环境没配置好

MySQL的常见命令

在cmd 按键盘上下键可以切换刚刚输入过的命令

1.查看当前所有的数据库
show databases; #显示的前三个数据库不能动
2.打开指定的库
use 库名
3.查看当前库的所有表
show tables;
4.查看其它库的所有表
show tables from 库名; #但是当前仍然在刚刚进入的表中,只是查看了其他库的表
5.创建表
create table 表名(
	列名 列类型,
	列名 列类型, 字符串用 varchar(20)
	...
);
6.查看表结构
desc 表名;
7.查看自己在那个表中
select database();
8.查看版本
select version();

dos 命令不用加分号, sql 命令才加分号

一个询问就是一个sql 文件

数据冗余 -> 分类存储

MySQL的语法规范

  • 不区分大小写,但规范关键字大写其他表名、列名小写
  • 每条命令最好用分号结尾
  • 每条命令根据需要,可以进行缩进 或 换行
  • 注释:
    单行注释:#注释文字
    单行注释:-- 注释文字 (-- 后要加空格)
    多行注释:/* 注释文字 */

可以使用图形化界面来方便我们

SQL的语言分类

  • DQL(Data Query Language:数据查询语言
    select
  • DML(Data Manipulate Language): 数据操作语言
    insert 、update、delete
  • DDL(Data Define Languge: 数据定义语言
    create、drop、alter
  • TCL(Transaction Control Language: 事务控制语言
    commit、rollback

DQL语言

基础查询

USE myemployees
# 基础查询
/*
语法:
select 查询列表 from 表名;

特点:
1. 查询列表可以是: 表中的字段, 常量, 表达式, 函数
2. 查询结果是虚拟的表格, 不是真实存在的
*/
#1. 查询单个字段
SELECT
	`last_name`
FROM
	employees;
	# 查询表中多个字段  查询的可以不按表中的顺序
SELECT
	`last_name`,
	`salary`,
	email 
FROM
	employees;
	# 查询表中的所有顺序
SELECT
	* 
FROM
	employees;
# 查询常量值
SELECT 100;
SELECT 
# 查询表达式
SELECT 100%98
# 查询函数 调用该函数并得到返回值并显示
SELECT VERSION();

#给字段其别名(提高可读性, 如果要查询的字段有重名的情况, 这个可以帮我们区分开)
#方式一: 使用 AS
SELECT 100%98 AS 结果;
SELECT last_name AS 姓, first_name AS 名 FROM employees;
#方式二: 使用空格
SELECT last_name 姓, first_name 名 FROM employees;

#案例: 如果别名和关键字重名 需要我们加双引号或单引号
Select salary AS 'out put' from employees; 

#去重
#案例: 查询涉及到的所有的部门编号
SELECT DISTINCT department_id FROM employees;

# +号的作用: 运算符
/*
SELECT 100+90;
SELECT '123'+90   # 试图把字符型转换为数值型, 成功:做加法运算 失败:字符型转化为0
只要一方为null 则结果必为null
*/
#案例: 查询员工的姓和名并连接成一个字段, 显示为姓名
SELECT CONCAT(last_name, first_name) AS '姓名' FROM employees;

**注意: **

  • 在以后执行这些操作之前, 最好还是在最开始加上 USE 库名
  • ` 符号是用来区分这个是不是一个字段的, 但是也可以不加, 如果你的字段的是大写的,那需要加上, 不然可读性太差
  • 执行时,可以选中然后点击执行

条件查询

条件查询:根据条件过滤原始表的数据,查询到想要的数据

语法:
	select 
		要查询的字段|表达式|常量值|函数
	from 
		表
	where 
		条件;

分类:
一、条件表达式
示例:salary > 10000
条件运算符:

> < >= <= = != <> 最后一个也是不等于

二、逻辑表达式
示例:salary > 10000 && salary < 20000

逻辑运算符:

AND(&&):两个条件如果同时成立,结果为true,否则为false
OR(||):两个条件只要有一个成立,结果为true,否则为false
NOT(!):如果条件成立,则not后为false,否则为true

三、模糊查询

BETWEEN AND: 包含临界值, 但是不能调换顺序

LIKE: 一般搭配通配符%(任意多个字符, 包括零个字符的情况), 下划线_表示任意单个字符

IN: 判断某字段的值是否属于in 列表中的某一项, 提高了语句简洁度, IN 列表的值的类型必须一致或兼容, 不支持写通配符

IS NULL: = <> 不能用于判断null , 所以有了它, 但是它只能判断null 值

安全等于<=> :可以用于判断null, 但是符号长的丑, 用的不多

IFNULL: salary * 12 * (1 + IFNULL(commission_pct, 0)) AS ‘年薪’; 如果commission_pct 为空, 其值就为0

#案例: 查询员工中包含字符a 的员工信息
SELECT *
FROM employees
WHERE last_name LIKE '%a%'; #字符用单引号, 百分号代表通配符, 默认不区分大小写

#案例: 查询员工名第二个字符为_的员工名 用转义字符/k
SELECT *
FROM employees
WHERE last_name LIKE '_"$_%' ESCAPE '$';

#案例:员工编号在100 到120 之间的员工信息
SELECT *
FROM employees
WHERE employee_id BETWEEN 100 AND 120; # 包含临界值, 但是不能调换顺序

#案例:查询员工的工种编号是 IT_PROG, AD_VP, AD_PRES 中的员工名和工种编号
SELECTlast_name, job_id
FROM employees
WHERE job_id IN('IT_PROG', 'AD_VP', 'AD_PRES');

#案例:查询没有奖金的员工名和奖金率
SELECT last_name, commission_pct
FROM employees
WHERE commission_pct IS NULL;

#案例:查询有奖金的员工名和奖金率
SELECT last_name, commission_pct
FROM employees
WHERE commission_pct IS NOT NULL;

#案例: 查询员工号为176 的员工的姓名和年薪
SELECT last_name, department_id, salary * 12 * (1 + IFNULL(commission_pct, 0)) AS '年薪'
FROM employees
WHERE employee_id = 176;

面试题:

select * from employees; 和 select * from employees where commission_pct like ‘%%’ and last_name like ‘%%’ 的结果是否一样, 并说明原因

答: 不一样, 如果判断的字段有null值, 结果就不一样. 如果有就一样

排序查询

ORDER BY 可以支持单个字段, 多个字段, 表达式, 函数, 别名

一般放在查询语句的最后面, 但 limit 子句除外

语法:

SELECt 查询列表
FROM 表
WHERE 筛选条件
ORDER BY 排序列表 ASC | DESC # 升序或降序, 不写默认是升序
#案例:查询员工信息,要求工资从高到低排序
SELECT * FROM employees ORDER BY salary DESC; #降序

#案例:查询部门编号>=90 的员工信息, 按入职时间的先后进行排序
SELECT *
FROM employees
WHERE department_id >=90
OEDER BY hiredate ASC;

#案例:按年薪的高低显示员工的信息和 年薪(按表达式排序)
SELECT *, salary * 12 * (1 + IFNULL(commission_pct, 0) AS 年薪
FROM employees
ORDER BY salary * 12 * (1 + IFNULL(commission_pct, 0)) DESC;

#按年薪的高低显示员工的信息和 年薪(按别名排序)
ELECT *, salary * 12 * (1 + IFNULL(commission_pct, 0) AS 年薪
FROM employees
ORDER BY 年薪 DESC;
                        
#案例:按姓名的长度显示员工的姓名和工资(按函数排序)
SELECT LENGTH(last_name) 姓名长度, last_name, salary
FROM employees
ORDER BY 姓名长度 DESC;

#案例: 查询员工信息, 要求先按工资排序, 再按员工编号排序(多字段排序)
SELECT *
FROM employees
ORDER BY salary ASC, employee_id ASC;          

常见函数

**概念:**把逻辑语句封装在 方法体, 对外暴露方法名

语法:

SELECT 函数名()
FROM 表; #如果函数的参数用到了表中的字段就需要加FROM 表, 没用到就不用加

分类:

单行函数: CONCAT, LENGTH, IFNULL 等 有一个返回值

分组函数: 做统计使用, 又称为统计函数, 聚合函数, 组函数

单行函数:

  • 字符函数
  • 数学函数
  • 日期函数
  • 其他函数[补充]
  • 流程控制函数[补充] if - else switch case

字符函数:

# LENGTH: 获取参数值的字节个数
SELECT LENGTH('jo'); #2
SELECT LENGTH('土豆123'); #9  取决于字符集, utf-8 三字节 gbk 就是两字节

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

# UPPER, LOWER
SELECT UPPER('shit'); #SHIT

#示例:把姓变大写, 名变小写然后拼接
SELECT CONCAT(UPPER(last_name), LOWER(first_name)) 姓名 FROM employees;

#substr, substring 索引从1开始
SELECT SUBSTR('shit bro', 2); # 返回索引位置开始之后的字符
SELECT SUBSTR('shit bro', 1, 3); # 返回从指定索引处指定的字符长度的字符

#INSTR 返回字串的起始索引, 不区分大小写, 如果没有返回0
SELECT INSTR('shit', 'bro shit') AS out_put;

#TRIM 去掉前后空格
SELECT TRIM(' 是啊 ');
SELECT TRIM('a' FROM 'aaaa是啊aaaaa'); #去掉前后的字符a

#LPAD 用指定字符来左填充达到指定长度 RPAD 右填充
SELECT LPAD('shit bro', 10, '*'); #如果长度小于字符串, 也会截断

例题:

# 查询所有学院的邮箱的用户名
SELECT substr(email, 1, instr(email, '@') - 1) 用户名
FROM stuinfo;

数学函数:

#ROUND 四舍五入
SELECT ROUND(-1.55); # 绝对值四舍五入再加负号 -2
SELECT ROUND(1.567, 2); # 小数点后保留两位 1.57

#CEIL 向上取整 FLOOR 向下取整
SELECT CEIL(1.02); #2
SELECT CEIL(-1.02); #-1
SELECT FLOOR(-9.1); #-10

#TRUNCATE 截断
SELECT TRUNCATE(1.655, 1); #1.6

#MOD MOD(a, b) = a - a / b * b
SELECT MOD(10, 3); #1
SELECT MOD(-10, 3); #-1

日期函数

#NOW 返回当前系统日期 + 时间
SELECT NOW();

#CURDATE 返回当前系统日期, 不含时间
SELECT CURDATE();

#CURTIME 返回当前时间, 不包含时间
SELECT CURTIME();

#获取指定部分, 年YEAR(), 月MONTH(), MONTHNAME(), 日, 小时, 分, 秒
SELECT YEAR(NOW()) 年;
SELECT YEAR('2001-02-18') 年;
SELECT YAER(hiredate) 年 FROM  employees;

#str_to_date: 把日期格式的字符转化成指定格式的日期
STR_TO_DATE('2-18-2001', '%m-%d-%Y');
#date_formate: 将日期转换成字符
DATE_FORMAT('2001/2/18', '%Y年%m月%d日');
Y 四位年份 y 两位年份
m 01, 02...     c 1, 2, 3... 月
d 01, 02... 日
H 24小时 h 12小时 时
i 00, 01, 02... 分
s 00, 01... 秒

其他函数:

SELECT VERSION();
SELECT DATABASE();
SELECT USER();

流程控制函数:

# if 函数: if else 的效果
SELECT IF(10 > 5, '对', '错');

SELECT last_name, commission_pct, IF(commission_pct IS NULL, '没奖金', '有奖金')
FROM employees;

# case 函数
#使用一: switch case 的效果
CASE 判断的字段或表达式
WHEN 常量1 THEN 显示的值1或语句1;
WHEN 常量2 THEN 显示的值2或表达式2;
...
ELSE 要显示的值n或语句n;
END

#案例: 查询员工的工资,要求:
/*
部门号=30, 显示工资为1.1倍
部门号=40,显示工资为1.2倍
其他部门显示原始工资
*/
SELECT salary 原始工资, department_id,
CASE department_id
WHEN 30 THEN salary * 1.1
WHEN 40 THEN salary * 1.2
ELSE salary
END AS 新工资
FROM employees;

#使用二: 类似于多重if
CASE
WHEN 条件一 THEN 显示的值1或语句1;
WHEN 条件二 THEN 显示的值2或语句2;
...
ELSE 要显示的值n或语句n;
END

#案例: 查询员工的工资,要求:
/*
工资大于等于20000, 显示A级别
工资小于于20000,显示B级别
*/
SELECT salary,
CASE
WHEN salary >= 20000 THEN 'A'
WHEN salary < 20000 THEN 'B'
END AS 工资级别
FROM employees;

分组函数:

做统计使用, 又称为统计函数, 聚合函数, 组函数

# sum 求和, avg 平均值, max 最大值, min 最小值, count 计算个数    

SELECT SUM(salary) FROM employees;
SELECT AVG(salary) FROM employees;
SELECT SUM(salary) 和, ROUND(AVG(salary), 2) 平均 FROM employees;

# 参数支持类型: 
SELECT SUM(last_name), AVG(last_name) FROM employees; # 结果为0, 虽然不报错, 但是没有意义 SUM: 只支持数值型, 
# MAX, MIN : 支持字符型, 日期型
# COUNT: 都支持
# 以上函数均忽略null 值

#可以和DISTINCT 搭配
SELECT SUM(DISTINCT salary) FROM employees;
...

#count 函数 详细介绍
SELECT COUNT(*) FROM employees; # 统计所有行数(只要一行有一个不为null 就计数)
SELECT COUNT(1) FROM employees; # 统计所有行数

# 效率问题
/*
MYISAM 存储引擎下 COUNT(*)效率最高
INNODB 存储引擎下 都差不多, 但是比COUNT(字段) 高
一般用COUNT(*) 来统计行数!!!
*/

# 和分组函数一同查询的字段有限制
SELECT AVG(salary), employee_id FROM employees; # 要求查询的字段是GROUP BY后的字段

#查询员工最大入职时间和最小入职时间相差天数 (DATEDIFF 函数)
SELECT DATEDIFF (MAX(hiredate), MIN(hiredate)) FROM employees;

分组查询

GROUP BY 子句语法

可以把表中的数据分成若干组

特点:
分组查询的条件分两类:

  • 分组前筛选(WHERE) 性能好, 优先考虑
  • 分组后筛选 (HAVING)
#引入: 查询每个部门的平均工资
/*
语法:
SELECT 分组函数, 列(要求写在GROUP BY 后面)
FROM 表
[WHERE 条件]
GROUP BY 分组列表
[ORDER BY 子句]
注意: 查询列表必须特殊, 要求是分组函数和 GROUP BY 后出现的字段
*/

#案例: 查询每个工种的最高工资
SELCET MAX(salary), job_id
FROM employees
GROUP BY job_id;

#案例: 查询每个位置上的部门个数
SELECT COUNT(*), location_id
FROM departments
GROUP BY location_id;

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

---------------------------------------------------------
#分组后筛选

#案例: 查询哪个部门的员工部门的员工个数大于2 (分组后查询)
SELECT COUNT(*), department_id
FROM employees
GROUP BY department_id
HAVING COUNT(*) > 2;

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

#案例3: 查询领导编号大于102的每个领导手下的员工最低工资大于5000的领导编号是谁, 以及其最低工资
SELECT MIN(salary), manager_id
FROM employees
WHERE manager_id > 102g
GROUP BY manager_id
HAVING MIN(salary) > 5000;

按函数或表达式分组

#案例: 按员工姓名的长度分组, 查询每一组员工个数, 筛选员工个数大于5的有哪些
SELECT COUNT(*), LENGTH(last_name) len_name
FROM employees
GROUP BY len_name  # 支持别名
HAVING COUNT(*) > 5;

按多个字段分组

#案例: 查询每个部门每个工种的员工的平均工资
SELECT AVG(salary), department_id, job_id
FROM employees
GROUP BY department_id, job_id;

添加排序

#案例: 查询每个部门每个工种且部门编号不为null 的员工的平均工资, 并且按照平均工资的高低显示
SELECT AVG(salary), depatmen_id, job_id
FROM employees
WHERE department_id IS NOT NULL # 优先考虑分组前筛选
GROUP BY department_id, job_id
ORDER BY AVG(salary) DESC;

连接查询

又称多表查询, 当查询的字段来自多个表时, 就会用到连接查询

笛卡尔现象:当表1 m 行 表2 n 行, 结果 = m * n 行

原因: 如果连接条件省略或无效则会出现

解决办法:添加上连接条件

分类:

  • 按年代分类:
    • sql92 标准: 仅支持内连接
    • sql99 标准 (推荐) 除了全外连接其他都支持
  • 按功能分类:
    • 内连接
      • 等值连接
      • 非等值连接
      • 自连接
    • 外连接:
      • 右外连接
      • 左外连接
      • 全外连接
    • 交叉连接

92标准(跳过别看):

等值连接

  1. 多表等值连接的结果为多表交集部分
  2. n表连接, 至少需要n - 1个连接条件
  3. 多表的顺序无要求
  4. 一般需要给表起别名
  5. 可以搭配排序, 分组, 筛选使用
#案例: 查询女生对应的男生名
SELECT NAME, boyName 
FROM boys, beauty
WHERE beauty.boyfriend_id = boys.id;

#案例: 查询员工名对应的部门名
SELECT last_name, department_name
FROM employees, departments
WHERE employees.department_id = departments.department_id;

# 查询员工名, 工种号, 工种名
# 给表起别名
SELECT last_name, e.job_id, job_title # 这里也要限定
FROM employees e, jobs j
WHERE e.job_id = j.job_id;

# 可以加筛选
#案例: 查询有奖金的员工名, 部门名
SELECT last_name, department_name
FROM employees e, departments d
WHERE e.department_id = d.department_id
AND e.commission_pct IS NOT NULL;

# 可以加分组
#案例: 查询每个城市的部门个数
SELECT COUNT(*) 部门个数, city
FROM departments d, locations l
WHERE d.location_id = l.location_id;
GROUP BY city;

#可以加排序
...

#三表连接
# 查询员工名, 部门名和所在的城市, 并排序
SELECT last_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
ORDER BY department_name DESC;

注意: 如果为表取了别名, 则原来的字段不能用原来的表名去限定

非等值连接

#案例: 查询员工的工资和工资级别
SELECT salary, grade_level
FROM employees e, job_grades g
WHERE salary BETWEEN g.`lowerst_sal` AND g.`highest_sal`;

自连接

同一个表找多遍

#案例: 查询员工名和上级的名称
SELECT e.employee_id, e.last_name, m.emplpoyee_id, m.last_name
FROM employees e, employees m
WHERE e.employee_id = m.employee_id;

99标准(看我就行):

语法:

SELECT 查询列表
FROM 表1 别名 
连接类型 JOIN 表2 别名
on 连接条件
where 筛选条件
GROUP BY
HAVING
ORDER BY
# 连接类型分类:
内连接: innner
外连接: 右外: right  左外: right 全外: full
交叉连接: cross

内连接:

语法:

SELECT 查询列表
FROM 表1 别名
INNER JOIN 表2 别名
ON 连接条件;

分类: 等值, 非等值, 自连接

#案例: 查询员工名, 部门名
SELECT last_name, depaartment_name
FROM employees e
INNER JOIN departments d
ON e.department_id = d.department_id;

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

#案例: 查询部门个数大于3的城市名和部门个数
SELECT city, COUNT(*) 部门个数
FROM departments d
INNER JOIN lcoations l
ON d.location_id = l.location_id
GROUP BY city
HAVING COUNT(*) > 3;

#案例:查询那个部门员工个数大于3 的部门名和员工个数, 降序排序
SELCET COUNT(*), department_name
FROM employees e
INNER JOIN departments d
ON e.department_id = d.department_id
GROUP BY department_name
HAVING COUNT(*) > 3
ORDER BY COUNT(*) DESC;

# 多表连接
#案例: 查询员工名, 部门名, 工种名, 并按部门名降序
SELECT 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
ORDER BY department_name DESC;

特点: INNER 可以省略, 可读性强, 可以排序, 分组, 筛选, 和92连接效果一样

非等值连接

# 查询员工的工资级别
SELECT salary, grade_level
FROM employees e
JOIN job_grades g
ON e.salary BETWEEN g.lowset_sal AND g.highest_sal;

# 查询工资级别的个数大于20的个数, 并且按工资级别降序
SELECT COUNT(*), grade_level
FROM employees e
JOIN job_grades g
ON e.salary BETWEEN g.lowset_sal AND g.highest_sal
GROUP BY grade_level
HAVING COUNT(*) > 20
ORDER BY grade_level DESC;

自连接

# 查询员工名字以及上级的名字
SELECT e.last_name, m.last_name
FROM employees e
JOIN employees m
ON e.manager_id = m.employee_id;

外连接

应用场景: 查询一个表中有, 另一个表中没有的字段时

特点:

  1. 外连接的查询结果为主表中的所有记录, 如果从表种有和它匹配的值则显示匹配的值, 如果没有则显示null
  2. 外连接查询结果 = 内连接结果 + 主表有的而从表没有的记录 (你最终想查的表做主表)
  3. 左外连接: 左边为主表, 右外连接: 右边为主表
  4. 左外和右外交换两个表的顺序, 可以实现同样的效果
# 查询男朋友不在男神表的女神的名字
# 左外连接
SELECT b.name, bo.*
FROM beauty b
LEFT OUTER JOIN boys bo
ON b.boyfriend_id = bo.id
WHERE bo.id IS NULL;
# 右外连接
SELECT b.name, bo.*
FROM boys bo
RIGHT OUTER JOIN beauty b
ON b.boyfriend_id = bo.id
WHERE bo.id IS NULL;

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

全外连接

SELECT b.*, bo.*
FROM beauty b
FULL OUTER JOIN boys bo
ON b.boyfriend_id = bo.id;

最终效果:

会把交集查出来,

主表有的, 从表没有的查出来,

从表有的, 主表没有的查出来

交叉连接

结果是笛卡尔乘积

SELECT b.*, bo.*
FROM beauty b
CROSS JOIN boys bo;

建议用99 语法!!!

AB 集 中间一部分重合:

总结: 求交集用内连接

交集 + A 集 用左外连接 交集 + B 集用右外连接

其他加WHERE 条件可以继续求A集 - 交集

全部集就是用全外连接, 全集 - 交集用WHERE

子查询

出现在其他语句 (主要代表查询语句) 内部中的SELECT 语句, 称为子查询或内查询

出现在外部的查询语句, 称为主查询或外查询

示例:

SELECT first_name FROM employees WHERE
department_id IN (
    SELECT department_id FROM departments
    WHERE location_id = 1700
)

分类:

  • 子查询出现的位置:

    • SELECT 后面 : 仅支持标量子查询

    • FROM 后面 : 支持表子查询

    • WHERE 或 HAVING 后面 : 标量子查询(单行), 列子查询(多行,列子查询搭配多行操作符使用: IN, ANY/SOME, ALL), 行子查询

    • EXISTS 后面 (相关子查询) : 表子查询

  • 按结果集的行列数不同:

    • 标量子查询 (结果集只有一行一列)

    • 列子查询 (结果集只有一列多行)

    • 行子查询 (结果集只有一行多列)

    • 表子查询 (都是表子查询)

WHERE 或 HAVING 后:

特点:

  1. 子查询放在小括号内
  2. 子查询一般放在条件的右侧
  3. 标量子查询一般搭配单行操作符使用 : < > >= <= = <>
  4. 列子查询搭配多行操作符使用: IN, ANY/SOME, ALL
  • 标量子查询:

    #案例: 查询job_id 与141号员工相同, salary 比141号员工高的员工姓名, 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 = 141
    );
    
    #案例: 返回公司工资最少的员工的last_name, job_id 和salary
    SELECT last_name, job_id, salary
    FROM employees
    WHERE salary = (
        SELECT MIN(salary)
        FROM employees
    );
    
    #案例: 查询最低工资大于50号部门最低工资的部门id 和其最低工资
    SELECT MIN(salary), department_id
    FROM employees
    GROUP BY department_id
    HAVING MIN(salary) > (
        SELECT MIN(salary)
        FROM employees
        WHERE department_id = 50
    );
    

    特点: 子查询的执行优先于主查询

    注意: 如果里面不是标量子查询, 就会报错

  • 列子查询(多行子查询)

    列子查询搭配多行操作符使用: IN / NOT IN(等于列表中任意一个), ANY / SOME(和子查询返回的某一个值比较), ALL(和子查询返回的所有值比较)

    # 返回location_id 是1400 或1700 的部门的所有员工的姓名
    SELECT last_name
    FROM employees
    WHERE department_id IN(
        SELECT DISTINCT department_id
        FROM departments
        WHERE department_id IN(1400, 1700)
    );
    
    # 返回其他工种中比job_id 为'IT_PROG' 工种任一工资低的员工的员工号, 姓名, job_id 以及salary
    SELECT last_name, employee_id, job_id, salary
    FROM employees
    WHERE salary < ANY(
        SELECT DISTINCT salary
        FROM employees
        WHERE job_id = 'IT_PROG'
    ) AND job_id <> 'IT_PROG';
    #也可以这样
    SELECT last_name, employee_id, job_id, salary
    FROM employees
    WHERE salary < (
        SELECT MAX(salary)
        FROM employees
        WHERE job_id = 'IT_PROG'
    ) AND job_id <> 'IT_PROG';
    
  • **行子查询(结果集一行多列或多行多列) **

SELECT 后面

#案例: 查询每个部门员工个数
SELECT d.*,(
    SELECT COUNT(*)
    FROM employees e
    WHERE e.department_id = d.department_id
) 个数
FROM departments d;

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

特点: 只能返回的是一行一列, 只支持标量子查询


FROM 后面

# 查询每个部门的平均工资的工资等级
# 这张表必须起别名
# ②连接①的结果集和job_grades表, 筛选条件 平均工资 between lowest_sal and highest_sal
SELECT ag_dep.*, g.grade_level
FROM (
    SELECT AVG(salary) ag, department_id
    FROM employees
    GROUP BY department_id
) ag_dep
INNER JOIN job_grades g
ON ag_dep.ag BETWEEN lowest_sal AND highest_sal;

exists后面(相关子查询)

# 语法:
exists (完整的查询语句)
# 结果: 1 或 0
SELECT EXISTS(SELECT emopyee_id FROM employees WHERE salary = 300000);

# 查询有员工的部门名
SELECT department_name
FROM departments
WHERE EXISTS (
    SELECT *
    FROM employees e
    WHERE d.department_id = e.department_id
);

# IN 方式代替
SELECT department_name
FROM departments d
WHERE d.department_id IN (
    SELECT department_id
    FROM employees
);

JOIN 后面也可加子表

例题:

# 查询各部门比本部门平均工资高的员工的员工号, 姓名, 工资
SELECT employee_id, salary, last_name, e.department_id
FROM employees e
INNER JOIN (
    SELECT AVG(salary) ag, department_id
    FROM employees
    GROUP BY department_id
) ag_dep
on e.deparmtent_id = ag_dep.department_id
WHERE salary > ag_dep.ag; 

# 查询名字中包含字母u的员工在相同部门的员工的姓名和员工工号
SELECT last_name, department_id
FROM employees
WHERE department_id IN(
    SELECT DISTINCT department_id
    FROM employees
    WHERE last_name LIKE '%u%'
);

# 查询管理者为King 的员工姓名和工资
SELECT last_name, salary
FROM employees
WHERE manager_id IN (
    SELECT  employee_id
    FROM empoyees
    WHERE last_name = 'King'
);

子查询经典例题

# 查询工资最低的员工信息: last_name, salary
SELECT last_name, saalry
FROM employees
WHERE salary = (
    SELECT MIN(salary)
    FROM employees
);

# 查询平均工资最低的部门信息
SELECT *
FROM departments
WHERE department_id = (
    SELECT department_id
    FROM employees
    GROUP BY department_id
    ORDER BY AVG(salary)
    LIMIT 1;
);

# 查询平均工资最低的部门信息和平均工资
SELECT d.*, ag_dep.ag
FROM departments d
JOIN (
    SELECT AVG(salary) ag, department_id
    FROM employees
    GROUP BY department_id
    ORDER BY AVG(salary)
    LIMIT 1
) ag_dep
ON d.department_id = ag_dep.department_id;

# 查询平均工资高于公司平均工资的部门有哪些
SELECT AVG(salary), department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary) > (
    SELECT AVG(salary)
    FROM employees
);

# 查询平均工资最高的部门的manager 的详细信息: last_name department_id email
SELECT last_name, d.department_id, email
FROM employees e
INNER JOIN departments d
ON d.manager_id = e.employee_id
WHERE d.department_id = (
    SELECT department_id
    FROM employees
    GROUP BY department_id
    ORDER BY AVG(salary) DESC
    LIMIT 1
);

分页查询

应用场景: 当要显示的数据一页显示不全, 需要分页提交sql 请求

# 语法
SELECT 查询列表
FROM 表
[JOIN TYPE] JOIN 表2
WHERE 条件
GROUPE BY 分组字段
HAVING 分组后筛选条件
ODER BY 排序字段 ASC
LIMIT 起始索引, 条目数; # 这个地方索引从0 开始
# 查询前五条员工信息
SELECT * FROM employees LIMIT 0, 5;
SELECT * FROM employees LIMIT 5; # 如果是起始索引为0, 也可以省略

# 查询有奖金的员工信息, 并且工资高的在前面
SELECT * FROM employees WHERE commission_pct IS NOT NULL ORDER BY salary DESC LIMIT 10;

特点:

  1. LIMIT 在执行和语法上都是最后

  2. 公式, page 为要显示的页数, size 为每一页的条目数:

    SELECT 查询列表
    FROM 表
    LIMIT (page - 1) * size, size;
    

联合查询

union : 将多条查询语句的结果合并成一个结果

应用场景:

查询所有中国男性的信息以及外国男性信息, 两个国家的人员信息存在两个表中

当我们查询的结果来自多个表, 但多个表没有直接的连接关系,

注意:

  • 联合查询的列数必须是一致的
  • 要求多条查询语句的每一列的类型和顺序最好一致
  • UNION 会去重 UNION ALL 可以防止去重
# 案例: 查询部门编号大于90 或邮箱包含a 的员工信息
SELECT * FROM employees WHERE email LIKE '%a%' OR department_id > 90;
# UNION
SELECT * FROM employees WHERE email LIKE '%a%'
UNION 
SELECT * FROM employees WHERE department_id > 90;

DML语言

数据操作语言: 插入(INSERT), 修改(UPDATE), 删除(DELETE)

插入语句

插入方式一:

语法:

INSERT INTO 表名(列名, ...) VALUES(值1, ...);

**注意: **

  1. 插入的值的类型必须兼容
  2. 可以为null 的列 直接写 NULL 或者 在写列名那里省略
  3. 列名的顺序可以变
  4. 列数和值的个数必须相同
  5. 可以省略列名, 但是默认是所有列, 并且列的顺序和表中的顺序一致

插入方式二:

语法:

INSERT INTO 表名
SET 列名 = 值, 列名 = 值, ...;

区别:

  1. 方式一支持插入多行, 方式二不支持

    INSERT INTO beauty
    VALUES (13, ...), (17, ...) ...;
    
  2. 方式一支持子查询, 方式二不支持

    INSERT INTO beauty(id, NAME, phone)
    SELECT id, boyName, '123456' FROM boys WHERE id < 3;
    

修改语句

修改单表记录

语法:

UPDATE 表名 
SET 列 = 新值, 列 = 新值 ...
WHERE 筛选条件;

修改多表的记录

语法:

92:

UPDATE 表1 别名, 表2, 别名 SET 列 = 值 WHERE 连接条件 AND 筛选条件

99:

UPDATE 表1 别名
[JOIN TYPE] JOIN 表2 别名
ON 连接条件
SET 列 = 值, ...
WHERE 筛选条件

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

#案例: 修改没有男朋友的女神的男朋友的编号都为2 号
UPDATE boys b
INNER JOIN beauty b
ON bo.`id` = b.`boyfriend_id`
SET b.`boyfriend_id` = 2
WHERE bo.`id` IS NULL;

删除语句

语法:

单表删除:

DELETE FROM 表名 WHERE 筛选条件;

多表删除:

99语法:
DELETE 表1的别名, 表2的别名 # 删除谁的记录这里就写谁的别名
FROM 表1 别名
[JOIN TYPE] JOIN 表2 别名
ON 连接条件
WHERE 筛选条件;

# 删除张无忌的女朋友的信息 
DELETE b
FROM beauty b
INNER JOIN boys bo 
ON b.`boyfriend_id` = bo.`id`
WHERE bo.`boyName` = '张无忌';

# 删除黄晓明的信息以及他女朋友的信息
DELETE b, bo
FROM beauty b
INNER JOIN boys bo
ON b.`boyfriend` = bo.`id`
WHERE bo.`boyName` = '黄晓明';

删掉整个表的数据:

TRUNCATE TABLE 表名; # 不能加 WHERE

区别:

  1. DELETE 可以加 WHERE 条件
  2. TRUNCATE 删除效率高一点点
  3. 假如要删除的表中有自增长列, 如果用DELETE 删除后再插入数据, 自增长列的值从断点开始, 而TRUNCATE 删除后, 再插入数据, 自增长列的值从1 开始
  4. DELETE 删除有返回值, TRUNCATE 没有返回值
  5. TRUNCATE 删除不能回滚, DELETE 删除可以回滚

DDL 语言

数据定义语言

库和表的管理

库的管理: 创建, 修改, 删除

表的管理: 创建, 修改, 删除

创建: create

修改: alter

删除: drop

库的管理

库的创建

语法:

CREATE DATABASE IF NOT EXISTS 库名;

库的修改

更改库的字符集

ALTER DATABASE 库名 CHARACTER SET utf-8;

库的删除

DROP DATABASE IF EXISTS 库名;

表的管理

表的创建

语法:

CREATE TABLE 表名(
    列名 列的类型[(长度) 约束], 
    列名 列的类型[(长度) 约束], 
    列名 列的类型[(长度) 约束]
);

案例:

# 创建book 表
CREATE TABLE book(
    id INT,
    bName VARCHAR(20),
    price DOUBLE,
    authorId INT,
    publishDate DATETIME
);

# 创建作者表
CREATE TABLE EXISTS author(
    id INT,
    au_name, VARCHAR(20),
    nation VARCHAR(10)
);

表的修改

修改列名, 表名, 类型或约数, 添加列, 删除列

ALTER TABLE 表名 add|drop|modify|change COLUMN 列名 列类型 约束

# 修改列名
ALTER TABLE book CHANGE COLUMN publishdate pubDate DATETIME; #COLUMN 可以省略
# 修改列的类型
ALTER TABLE book MODIFY COLUMN pubdate TIMESTAMP;
# 添加新列
ALTER TABLE author ADD COLUMN annual DOUBLE; # 添加年薪列
# 删除列
ALTER TABLE author DROP COLUMN annual; # 不能用IF EXISTS
# 修改表名
ALTER TABLE author RENAME TO book_author;

表的替换

DROP TABLE IF EXISTS book_author;
SHOW TABLES; # 查看所有表

通用语法:

DROP DATABSE IF EXISTS 旧库名;
CREATE DATABASE 新库名;

DROP TABLE IF EXISTS 旧表名;
CREATE TABLE 表名();

表的复制:

仅仅复制结构:

CREATE TABLE copy LIKE author;

结构 + 数据:

CREATE TABLE copy2
SELECT * FROM author;

只复制部分数据

CREATE TABLE copy3
SELECT id, au_name
FROM author
WHERE nation = '中国';

仅仅复制某些字段

CREATE TABLE copy4
SELECT id, au_name
FROM author
WHERE 0;

例:

# 根据表 employees 创建 employees2
CREATE TABLE employees2 LIKE myemployees.employees;

常见数据类型

数值型: 整形, 小数(定点数, 浮点数)

字符型: 较短文本(char, varchar), 较长文本(text, blob(较长的二进制数据))

日期型:

整型

整数类型 字节 范围
Tinyint 1 有符号: -128 ~ 127 无符号: 0 ~ 255
Smallint 2 有符号: -32768 ~ 32767 无符号: 0 ~ 65535
Mediumint 3 有符号: -8388608 ~ 8388607 无符号: 0 ~ 1677215
Int, integer 4 有符号: -2147483648 ~ 2147483647无符号: 0 ~ 4294967295
Bigint 8 有符号: -9223372036854775808 ~ 9223372036854775807 无符号: 0 ~ 9223372036854775807 * 2 + 1

设置无符号类型

  • 如果超出范围, out of range 异常, 并且插入的是临界值
  • 不设置长度默认长度为11, 无符号为10
  • ZEROFILL (UNSIGNED) 关键字: 长度的作用是数值的宽度, 不够宽度会填充0) 加了这个关键字会自动为无符号类型
CREATE TABLE tab_int(
    t1 UNSIGNED INT 
);

小数

浮点数类型 字节 范围
float 4 ±1.75494351E-38 ~ ±3.402823466E+38
double 8 ±2.2250738585072014E-308 ~ ±1.7976931348623157E+308
定点数类型 字节 范围
DEC(M,D) DECIMAL(M,D) M + 2 最大取值范围和double 相同, 给定decimal 的有效取值范围由M 和O 决定

特点:

  • M 和 D : 可以省略, D代表小数点保留几位, M代表总位数, 因此 M - D 代表整数位数
  • 如果是DECIMAL 则M 默认为10, D 默认为0
  • 定点型精度较高, 货币运算优先考虑这个

原则: 所选择的类型越简单越好, 能保存的数值的类型越小越好

字符

char, varchar

text, blob (JDBC 会讲如何保存二进制数据)

字符串类型 最多字符数 描述及存储需求
char(M) M可以省略, 默认为1 M M为0 ~ 255 之间的整数
varchar(M) M 不可以省略 M M为0 ~ 65535 之间的整数

区别: char(M) 长度固定为M(哪怕只用1个长度), 更耗费空间

varchar(M) 长度根据你输入的定 但是效率比char 低

一个M 可以省略, 一个不行

BINARY, VARBINARY, 类似char 和 varchar, 不同的是它们包含二进制字符串而不包含非二进制字符串

ENUM 枚举类型, 要求插入的值必须属于列表指定的值之一. 如果列表成员为1 ~ 255 则需要一个字节存储, 如果为255 ~ 65535 则需要两个字节, 最多65535个成员

CREATE TABLE tab_char(
    c1 ENUM('a', 'b', 'c')
);
INSERT INTO tab_char VALUES('a');

SET 集合类型: 和ENUM 类似, 可以保存0 ~ 64个成员. 区别为: SET 类型一次可以选取多个成员, 但ENUM 只能选一个, 根据成员个数不同, 占用的字节不同

成员数 字节数
1 ~ 8 1
9 ~ 16 2
17 ~ 24 3
25 ~ 32 4
33 ~ 64 8
CREATE TABLE tab_set(
    s1 SET('a', 'b', 'c')
);
INSERT INTO tab_set VALUES('a, b');

日期

日期和时间类型 字节 最小值 最大值
DATE 4 1000-01-01 9999-12-31
DATETIME 8 1000-01-01 00:00:00 9999-12-31 23:29:29
TIMESTAMP 4 1970010108001 2038年的某个时刻
TIME 3 -838:59:59 838:59:59
YEAR 1 1901 2155

区别:

1、Timestamp支持的时间范围较小,取值范围:19700101080001 ~ 2038年的某个时间, Datetime的取值范围:1000-1-1 ~ 9999-12-31

2、timestamp和实际时区有关,更能反映实际的日期,而datetime则只能反映出插入时的当地时区

3、timestamp的属性受Mysql版本和SQLMode的影响很大

CREATE TABLE tab_date(
    t1 DATETIME,
    t2 TIMESTAMP
);
INSERT INTO tab_date VALUES(NOW(), NOW());
SHOW VARIABLES LIKE 'time_zone'; # 查看时区?
SET time_zone = '+9:00'; # 设置时区

常见约束

一种限制, 用于限制表中的数据, 为了保证表中数据的可靠性

分类:

  1. NOT NULL 非空

  2. DEFAULT 默认, 保证该字段有默认值

  3. PRIMARY KEY 主键, 该字段的值具有唯一性, 且自动非空

    组合主键: PRIMARY KEY(id, name) 代表id 和name 不能同时重复, 但不推荐

  4. UNIQUE 唯一, 可以为空, 但一个表只能一个为空, 也可以组合唯一, 但是不推荐

  5. CHECK 检查约束[mysql 不支持, 但是不报错]

  6. FOREIGN KEY REFERENCES 外键, 用于限制两个表的关系, 用于保证该字段的值必须来自主表关联列的值 (在从表添加外表约束, 用于引用主表某列的值) 如工种编号, 专业编号等

    • 从表的外键列和主表的关联列的类型要求兼容
    • 主表的关联列必须是一个key(一般是主键或唯一)
    • 插入数据时, 先插入主表, 再插入从表, 删除数据时先删除从表, 再删除主表

添加约束的时机: 创建表时, 修改表时

分类: 列级约束: 六大约束语法都支持, 但外键约束没有效果

表级约束: 除了非空, 默认 其他都支持, 语法如下

CREATE TABLE info(
    name VARCHAR(20) NOT NULL,
    gender CHAR(1) CHECK(gender = '男' OR gender = '女'), 
    majorId INT FOREIGN KEY REFERENCES major(id), # 外键
    age INT DEFAULT 18
);

CREATE TABLE major(
    id INT PRIMARY KEY,
    majorName VARCAHR(20)
);
DESC info;
SHOW INDEX FROM info;

#表级约束
DROP TABLE IF EXISTS info;
CREATE TABLE info(
    id INT,
    name VARCHAR(20),
    gender CHAR(1),
    age INT, 
    majorId INT,
    CONSTRAINT pk PRIMARY KEY(id), # 主键
    CONSTRAINT uq UNIQUE(seat), #唯一键
    CONSTRAINT ck CHECK(gender = '男' OR gender = '女'), 
    CONSTRAINT fk_info_major FOREIGN KEY(majorId) REFERENCES major(id) # 外键
);

[CONSTRAINT 名字] 可以省略, 名字不可以重复

一般外键会写成表级约束, 其他列级约束, 约束可以同时加多个, 空格分开就行


修改表时添加约束:

CREATE TABLE info(
    id INT,
    name VARCHAR(20),
    gender CHAR(1),
    age INT, 
    majorId INT,
);
ALTER TABLE info MODIFY COLUMN name VARCHAR(20) NOT NULL;
ALTER TABLE info MODIFY COLUMN name VARCHAR(20); # 删除约束
ALTER TABLE info MODIFY COLUMN age INT DEFAULT 18;
# 也支持表级约束
ALTER TABLE info ADD PRIMARY KEY(id);
ALTER TABLE info ADD [CONSTRAINT fk_info_major] FOREIGN KEY(majorId) REFERENCES major(id) # 外键
...

删除约束

ALTER TABLE info MODIFY COLUMN name VARCHAR(20);
# 删除主键, 唯一
ALTER TABLE info DROP PRIMARY KEY;
ALTER TABLE info DROP INDEX seat; # SHOW INDEX FROM info 可以查看约束的名字, 这里seat 是默认的名字

#删除外键
ALTER TABLE info DROP FOREIGN KEY fk_info_major;

标识列

又称为自增长列, 可以不用手动的插入值, 系统提供默认的序列值(从1开始) AUTO_INCREMENT

  1. 不一定和主键搭配, 但要求要求是一个key (外键, 唯一, 主键)
  2. 一个表只能有一个标识列
  3. 标识列的类型只能是数值型

创建表时设置标识列:

CREATE TABLE tab_identity(
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20)
);
INSERT INTO tab_identity(name) VALUES(1, 'shit');
INSERT INTO tab_identity(id, name) VALUES(null, 'shit'); # 前面写了id 就写null

#设置变量来设置步长, 和起始值, mysql 中不能设置起始值
SHOW VARIABLES LIKE '%auto_increment%';
SET auto_increment_increment = 3;

# 但是如果想给改变起始值, 可以手动插入一个你想要的起始值, 后面的值会依次递增

修改表时设置删除标识列

ALTER TABLE tab_identity MODIFY COLUMN id INT PRIMARY KEY AUTO_INCREMENT;
ALTER TABLE tab_identity MODIFY COLUMN id INT; # 删除标识列, 但是主键仍然保留

TCL 语言

Transaction Control Language 事务控制语言

事务: 一个或一组sql语句组成的一个执行单元, 每个sql语句相互依赖, 如果某个语句出现问题, 整个单元将回滚. 所有受影响的数据回到以前的状态. 这个执行单元要么全部执行, 要么全部不执行

存储引擎

1、概念:在mysql中的数据用各种不同的技术存储在文件(或内存)中。

2、通过**show engines;**来查看mysql支持的存储引擎。

3、 在mysql中用的最多的存储引擎有:innodb,myisam ,memory 等。其中innodb支持事务,而myisam、memory等不支持事务


事务的ACID(acid) 属性:

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

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

  • 隔离性(Isolation)
    事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

  • 持久性(Durability)
    持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。

事务的创建

隐式事务: 事务没有明显的开启和结束的标记 : INSERT UPDATE DELETE 语句

显式事务: 事务有明显的开启和结束的标记 前提: 先设置自动提交功能为禁用

SET autocommit = 0; # 开启
SHOW VARIABLES LIKE 'autocommit';
START TRANSACTION; # 可以不写
# 编写sql 语句(SELECT INSERT UPDATE DELETE) DDL语言没有事务
# 转账
UPDATE account SET balance = 500 WHERE username = 'lmy';
UPDATE account SET balance = 1500 WHERE username = 'yy';
ROLLBACK; # 回滚事务
COMMIT; # 结束事务

# 演示savepoint 和 rooback 可以搭配
SET autocommit = 0;
SEART TRANSACTION;
DELETE FROM account WHERE id = 25;
SAVEPOINT a; # 设置保存点
DELETE FROM account WHERE id = 28;
ROLLBACK TO a; # 回滚到保存点

数据库的隔离级别

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

    • 脏读: T1 读取了T2 更新但是没有提交的字段, 若T2回滚, T1读取的内容就是无效的
    • 不可重复读: T1 读 T2 然后T2 更新了, T1再次读取, 值就不同了
    • 幻读: T1 读 T2 然后T2 插入了几行, T1再次读取, T1就多了几行
  • 数据库提供的4种隔离级别:

    隔离级别 描述
    READ UNCOMMITTED(读未提交数据) 允许事务读取未被其他事务提交的变更, 三个问题都会出现
    READ COMMITED(读已提交数据) 只允许事务读取已经被其他事务提交的变更, 可以避免脏读 , 但其他两个问题可能出现
    REPEATABLE READ(可重复读) 确保事务可以多次从一个字段读取相同的值, 在这个事务持续期间, 禁止其他事务对这个字段进行更新, 幻读的问题仍然存在
    SERIALIZABLE(串行化) 确保一个事务可以从一个表中读取相同的行, 在这个事务期间, 禁止其他的DML 语言操作, 可以避免所有问题, 但效率十分低下
  • Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READ COMMITED

  • Mysql 支持 4 种事务隔离级别. Mysql 默认的事务隔离级别为: REPEATABLE READ

  • 每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别.

    • 查看当前的隔离级别: SELECT @@tx_isolation;
    • 设置当前 mySQL 连接的隔离级别: set session transaction isolation level [ read committed ];
    • 设置数据库系统的全局的隔离级别: set global transaction isolation level [ read committed ];

DELETE 和 TRUNCATE 在事务使用时的区别

DELETE 可以回滚, TRUNCATE 不能回滚

# DELETE  可以回滚
SET autocommit = 0;
START TRANSACTION;
DELETE FROM account;
ROLLBACK;

#TRUNCATE  不能回滚
SET autocommit = 0;
START TRANSACTION;
TRUNCATE TABLE account;
ROLLBACK;

视图

含义: 虚拟表, 和普通表一样使用, 通过表动态生成的数据, 只保存了sql 逻辑, 不保存查询结果

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

# 查询姓张的学生名和专业名
# 原始写法
SELECT name, majorName
FROM stuinfo s
INNER JOIN major m ON s.majorId = m.id;
WHERE s.name LIKE '张%';
# 视图
CREATE VIEW v
AS
SELECT name, majorName
FROM stuinfo s
INNER JOIN major m ON s.majorId = m.id;

# 使用视图后的写法
SELECT * FROM v WHERE name LIKE '张%';

创建视图:

CREATE VIEW 视图名
AS
查询语句;

修改视图:

CREATE OR REPLACE VIEW 视图名
AS
查询语句;

ALTER VIEW 视图名
AS
查询语句;

删除视图:

DROP VIEW 视图名1, 视图名2, ...;

查看视图

DESC 视图名;
SHOW CREATE VIEW 视图名; # 命令行用这个

例题:

# 创建一个视图, 要求查询部门的最高工资高于12000的部门信息
CREATE OR REPLACE VIEW v2
AS
SELECET MAX(salary) mx, department_id
FROM employees
GROUP BY department_id
HAVING MAX(salary) > 12000;

SELECT d.*, m.mx
FROM deparmtents d
JOING v2 m # 视图也可以取别名
ON m.department_id = d.deparmtnet_id;

视图的更新

更改视图的数据

1. 向视图插入数据
INSERT INTO 视图名 VALUES('yy', '[email protected]', 100); # 原始表也会有这样的数据
2. 修改
UPDATE 视图名 SET last_name = '张无忌' WHERE last_name = '张飞'; # 原始表也会改
2. 删除
DELETE FROM 视图名 WHERE last_name = '张无忌'; # 原始表也会删

所以一般会添加只读的权限, 不是所有视图可以更新, 一般也不会去更新视图, 以下不能更新:

  • 包含以下关键字的sql语句:分组函数、distinct、group by、having、union或者union all
  • 常量视图
  • Select中包含子查询
  • join
  • from一个不能更新的视图
  • where子句的子查询引用了from子句中的表

视图和表的对比

  • 创建的语法不同
  • 视图不保存数据, 只是保存了sql 逻辑, 表保存了实际的数据
  • 视图一般不去增删改

变量

分类:

  • 系统变量

    • 全局变量
    • 会话变量(针对客户端一次连接)
  • 自定义变量

    • 用户变量
    • 局部变量

系统变量:

变量由系统提供, 属于服务器层面

语法: SESSION可以省略

1. 查看所有系统变量
SHOW SESSION VARIABLES; #会话 SESSION 可以省略
SHOW GLOBAL VARIABLES; #全局
2. 查看满足条件的部分系统变量
SHOW GLOBAL VARIABLES LIKE '%char%';
3. 查看指定系统变量
SELECT @@GLOBAL|SESSION.系统变量名;
4. 赋值
SET GLOBAL|SESSION 系统变量名 = 值;
SET @@GLOBAL|SESSION.系统变量名 = 值;

注意: 如果时全局需要加GLOBAL 如果是会话就可以不写, 全局变量跨连接有效, 不能跨重启

服务器每次启动会为所有全局变量赋初始值

会话变量仅仅针对于当前连接有效


自定义变量

用户变量:

作用域: 针对于当前连接有效, 和会话变量的作用域, 变量的类型为弱类型, 可以修改变量类型

# 声明必须初始化
SET @用户变量名 = 值;
SET @用户变量名 := 值; # 用这个
SELECT @用户变量名 := 值;

# 赋值
再用一次 SET 或者 SELECT 
SELECT 字段 INTO @用户变量名 FROM 表; # 要求得到的是一个值

# 查看
SELECT @用户变量名;

局部变量:

作用域: 仅仅在局部有效:, 只在定义它的begin end 中有效

# 声明
DECLARE 变量名 类型;
DECLARE 变量名 类型 DEFAULT 值;

# 赋值
SET 局部变量名 = 值;
SET 局部变量名 := 值; 
SELECT @局部变量名 := 值;
SELECT 字段 INTO 局部变量名 FROM 表;  # 注意@ 的有无

#
SELECT 局部变量名;

对比:

作用域不同, 定义和使用位置不同, 语法也不同, 局部变量需要指明类型

SET @m = 1;
SET @n = 2;
SET @sum = @n + @m;

存储过程

类似 JAVA 中的方法

存储过程: 一组预先编译好的sql 语句的集合, 理解为批处理语句

减少了编译次数且减少了和数据库服务器连接的次数, 提高了效率

创建:

参数列表包含三部分: 参数模式 参数名 参数类型

参数模式: IN: 参数可以作为输入, 也就是需要调用方传入值

OUT: 作为输出, 可以作为返回值

INOUT: 既可以做输入, 又可以输出, 既需要传入值, 又可以返回值

存储过程体的每个sql 语句必须加分号.

存储过程的结尾可以用 DELIMITER 重新设置

语法: DELEMITER 结束标记 : DELEMITER $

CREATE PROCEDURE 存储过程名(参数列表)
BEGIN
       存储过程体(sql语句)
END

调用:

CALL 存储过程名(实参列表) 结束标记;
1. 空参
# 插五条到表中
DELIMITER $
CREATE PROCEDURE myp1()
BEGIN
           INSERT INTO admin(username, `password`) VALUES('shit', '0000'), ('s', '0000'), ('ss', '0000'), ('sss', '0000'), ('ssss', '0000');
END $

2. IN 模式参数的存储过程
# 创建存储过程实现根据女神名来查询对应男朋友信息
CREATE PROCEDURE myp2(IN name VARCHAR(20))
BEGIN
        SELECT bo.*
        FROM boys bo
        RIGHT JOIN beauty b ON bo.id = b.boyfriend_id # 右外连接, 考虑男朋友不存在
        WHERE b.name = name;
END $

# 创建存储过程 实现 判断用户账号密码是否正确
CREATE PROCEDURE myp3(IN uersname VARCHAR(20), IN PASSWORD VARCHAR(20))
BEGIN
      DECLARE result INT DEFAULT 0; # 声明并初始化
      SELECT COUNT(*) INTO result # 赋值
      FROM admin
      WHERE admin.username = username
      AND admin.password = PASSWORD;
      SELECT IF(result > 0, '成功', '失败'); # 使用
END $

如果报string 错误 可能是字符集的问题

设置客户端的编码 set character_set_client=gbk

设置连接器编码 set character_set_connection=gbk

设置返回值编码 set character_set_results=gbk

如果client(客户端)、connection(连接器)、results(返回值)都设置成gbk的话,可以简写成set names gbk

带OUT 模式的存储过程

可以带多个返回值

# 根据女生名返回对应的男生名
CREATE PROCEDURE myp4(IN beautyName VARCAHR(20), OUT boyName VARCHAR(20))
BEGIN
          SELECT bo.boyName INTO boyName
          FROM boys bo
          INNER JOIN beauty b ON bo.id = b.boyfriend_id
          WHERE b.name = beautyName;
END $

# 调用
SET @bName$
CALL myp4('小昭', @bName)$
SELECT @bName$

# 根据女生名, 返回对应的男生名和男生魅力值
CREATE PROCEDURE myp6(IN beautyName VARCHAR(20), OUT boyName VARCHAR(20), OUT userCP INT)
BEGIN
          SELECT bo.boyName, bo.uerCP INTO boyName, userCP
          FROM boys bo
          INNER JOIN beauty b ON bo.id = b.boyfriend_id
          WHERE b.name = beautyName;
END $

# 调用
CALL myp6('小昭', @bName, @userCP)$

带 INOUT 模式的存储过程

# 传入a 和 b 两个值, 要求都翻倍并返回
CREATE PROCEDURE myp7(INOUT a INT, INOUT b INT)
BEGIN
         SET a = a * 2;
         SET b = b * 2;
END $

删除存储过程

DROP PROCEDURE 存储过程名; # 一次只能删除一个

查看存储结构

SHOW CREATE PROCEDURE 存储过程名;
# 实现传入一个日期, 格式化成xx年xx月xx日返回
CREATE PROCEDURE test_pro4(IN myDate DATETIME, OUT strDate VARCHAR(50))
BEGIN
          SELECT DATE_FORMAT(myDate, '%y年%m月%d日') INTO strDate;
          
END $

# 创建存储过程或函数实现传入女神名称, 返回: 女神 and 男神 格式的字符串
CREATE PROCEDURE test(IN beautyName VARCHAR(20), OUT str VARCHAR(50))
BEGIN
           SELECT CONCAT(beautyName, ' and ', IFNULL(boyName, 'null')) INTO str
           FROM boys bo
           RIGHT JOIN beauty b ON b.boyfriend_id = bo.id
           WHERE b.name = beautyName;
END $

# 根据传入的条目数, 和起始索引, 查询beauty 表的记录
CREATE PROCEDURE test6( IN sIndex INT, IN size INT)
BEGIN
        SELECT * FROM beauty LIMIT sIndex, size;
END $

函数

类似 JAVA 中的方法

函数: 一组预先编译好的sql 语句的集合, 理解为批处理语句

区别: 有且仅有一个返回值, 存储过程适合做批量的插入, 更新,

函数适合做处理数据后返回的一个结果

函数的创建

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

注意:

参数列表: 参数名 参数类型

函数体: 要求有return 语句, 如果return 语句没有放在最后, 也不会报错

要使用DELIMITER 设置结束标记

调用:

SELECT 函数名(参数列表)
# 返回公司员工 无参有返回
CREATE FUNCITON myf1() RETURNS INT
BEGIN
      DECLARE c INT DEFAULT 0;
      SELECT COUNT(*) INTO c
      FROM employees;
      RETURN C;
END $
SELECT myf1()$

# 根据员工名返回工资
CREATE FUNCTION myf2(empName VARCHAR(20)) RETURNS DOUBLE
BEGIN
        SET @sal = 0; # 用户变量
        SELECT salary INTO @sal
        FROM employees
        WHERE last_name = empName;
        RETURN @sal;
END $
SELECT myf2('Kochhar')$

# 根据部门名 返回部门的平均工资
CREATE FUNCITON myf3(deptName VARCHAR(20)) RETURNS DOUBLE
BEGIN
     DECLARE sal DOUBLE;
     SELECT AVG(salary) INTO sal
     FROM employees e
     JOIN departments d ON e.department_id = d.department_id
     WHERE d.department_name = deptName;
     RETURN sal;
END

函数的查看和删除

# 查看
SHOW CREATE FUNCTION myf3;

# 删除
DROP FUNCTION myf3;

流程控制结构

顺序结构: 顺序执行

分支结构: 从多条路径选一条执行

循环结构: 重复执行一段代码

分支结构:

# IF 函数 实现简单的双分支
SELECT IF(表达式1, 表达式2, 表达式3); # 如果表达式1 成立 返回表达式2的值

# case 结构
情况1: 类似switch 语句, 一般用于实现等值判断
CASE 变量|表达式|字段
WHEN 要判断的值 THEN 返回的值1 或语句1;
WHEN 要判断的值 THEN 返回的值2 或语句2;
...
ELSE 要返回的值n
END CASE;

情况2: 类似 java 中的多重IF 语句, 一般用于实现区间判断
CASE 
WHEN 要判断的条件1 THEN 返回的值1 或语句1;
WHEN 要判断的条件2 THEN 返回的值2 或语句2;
...
ELSE 要返回的值n
END CASE;

特点: 可以作为表达式, 嵌套在其他语句中使用, BEGIN END 中或外面

可以作为独立语句使用, 只能放在BEGIN END 中

如果WHEN 中的条件成立, 则执行对应的THEN 后面的语句, 并且结束CASE. ELSE 可以省略, 省略了其他语句都不满足返回 NULL 可以结合AND, NOT NOT 使用

# 根据传人的成绩来显示等级, 90 - 100 A 80 - 90 B ... 
CREATE PROCEDURE test_case(IN socre INT)
BEGIN
        CASE
        WHEN socre >= 90 THEN SELECT 'A';
        WHEN socre >= 80 THEN SELECT 'B';
        WHEN socre >= 70 THEN SELECT 'C';
        ELSE SELECT 'C';
        END CASE;
END $

IF 结构 — 多重分支

IF 条件1 THEN 语句1;
ELSEIF 条件2 THEN 语句2;
ELSE 语句n;
END IF;

只能应用在BEGIN END

循环结构

分类: WHILE, LOOP, REPEAT

循环控制: ITERATE 类似 continue

LEAVE 类似 break;

# while
[标签:] WHILE 循环条件 do
        循环体;
END WHILE [标签]; # 写上了可以搭配循环控制用

# loop
[标签:] loop
       循环体;
END LOOP [标签]; # 可以模拟死循环

# repeat
[标签:] repeat
       循环体;
UNTIL 结束循环的条件
END REPEAT [标签];
# 根据次数插入admin表中多条记录
CREATE PROCEDURE pro_while1(IN c INT) # 没有返回值就用存储过程
BEGIN
       DECLARE i INT DEFAULT 1;
      a:WHILE i <= c DO
             INSERT INTO admin(username, 'password') VALUES(CONCATE('ROSE', i), '123456');
             SET i = i + 1;
       END WHILE a;
END $

# 添加leave 语句, 当插入了20条则停止
CREATE PROCEDURE pro_while1(IN c INT) # 没有返回值就用存储过程
BEGIN
       DECLARE i INT DEFAULT 1;
       a:WHILE i <= c DO
             INSERT INTO admin(username, 'password') VALUES(CONCATE('ROSE', i), '123456');
             IF i == 20 THEN LEAVE a; # 退出循环
             SET i = i + 1;
       END WHILE a;
END $

# 只插入偶数次
CREATE PROCEDURE pro_while1(IN c INT) # 没有返回值就用存储过程
BEGIN
       DECLARE i INT DEFAULT 0;
       a:WHILE i <= c DO
             INSERT INTO admin(username, 'password') VALUES(CONCATE('ROSE', i), '123456');
             IF i % 2 <> 0 THEN ITERATE a; # 退出循环
             SET i = i + 1;
       END WHILE a;
END $

# 已知表stringcontent 其中字段id 自增长 content VARCHAR(20) 向表插入指定个数的随机字符串
CREATE TABLE stringcontent(
    id INT PRIMARY KEY AUTO_INCREMENT,
    content VARCHAR(20)
);
DELIMITER $
CREATE PROCEDURE test(IN c INT)
BEGIN
       DECLARE i INT DEFAULT 1;
       DECLARE str VARCHAR(26) DEFAULT 'abcdefghijklmnopqrstuvwxyz';
       DECLARE strIndex INT DEFAULT 1; # 起始索引
       DECLARE len INT DEFAULT 1; # 截取长度
       WHILE i <= c DO
           SET len = FLOOR(RAND() * (26 - strIndex + 1) + 1);  # 产生随机数, 代表截取长度
           SET strIndex = FLOOR(RAND() * 26 + 1); # 随机整数 1 - 26
           INSERT INTO stringcontent(content) VALUES (SUBSTR(str, strIndex, len));
           SET i = i + 1;
END $

猜你喜欢

转载自blog.csdn.net/qq_45560445/article/details/116948727