数据库

概念

数据库的英文单词: DataBase 简称 : DB

什么数据库?

  1. 存储数据的仓库
  2. 本质上是一个文件系统,还是以文件的方式存在服务器的电脑上的。
  3. 所有的关系型数据库都可以使用通用的SQL语句进行管理 DBMS DataBase Management System

常见数据库排行榜

数据库的特点:

  1. 持久化存储数据的。其实数据库就是一个文件系统
  2. 方便存储和管理数据
  3. 使用了统一的方式操作数据库 – SQL

MySQL数据库软件

安装

参见《MySQL基础.pdf》

卸载

  1. mysql的安装目录找到my.ini文件

    复制 datadir="C:/ProgramData/MySQL/MySQL Server 5.5/Data/"

  2. 卸载MySQL

    • 停止window的MySQL服务。 找到“控制面板”-> “管理工具”-> “服务”,停止MySQL后台服务

    • 卸载MySQL安装程序。找到“控制面板”-> “程序和功能”,卸载MySQL程序

  3. 删除C:/ProgramData目录下的MySQL文件夹。

配置

MySQL服务启动

  1. 手动。
  2. cmd–> services.msc 打开服务的窗口
  3. 使用管理员权限打开`cmd``
    • net start mysql : 启动mysql的服务
    • net stop mysql: 关闭mysql服务

MySQL登录

  1. mysql -uroot -p密码
  2. mysql -hip -uroot -p连接目标的密码
  3. mysql --host=ip --user=root --password=连接目标的密码

MySQL退出

  • net start mysql : 启动mysql的服务
  • net stop mysql: 关闭mysql服务

注意:如果出现mysql’不是内部或外部命令,也不是可运行的程序

解决:在Windows平台下,需要将mysql路径添加到环境变量中,

  1. 右键点击“我的电脑”-“属性”-“高级”-“环境变量”-“系统 变量”-双击“Path”

  2. 将mysql安装目录下的bin目录添加到环境变量中

MySQL目录结构

MySQL安装目录:

basedir=G:/mysql-5.7.29-winx64

配置文件 my.ini

MySQL数据目录:

datadir=G:/mysql-5.7.29-winx64/data


SQL

SQL概述

什么是SQL?

Structured Query Language:结构化查询语言
其实就是定义了操作所有关系型数据库的规则。每一种数据库操作的方式存在不一样的地方,称为“方言”。

SQL通用语法

  1. SQL 语句可以单行多行书写,以分号结尾。
  2. 可使用空格和缩进来增强语句的可读性。
  3. MySQL 数据库的 SQL 语句不区分大小写,关键字建议使用大写
  4. 3 种注释
    • 单行注释: -- 注释内容(注意–后有空格) 或 # 注释内容(mysql 特有)
    • 多行注释: /* 注释 */

SQL分类

  1. DDL(Data Definition Language)数据定义语言
    用来定义数据库对象:数据库,表,列等。关键字:create, drop,alter
  2. DML(Data Manipulation Language)数据操作语言
    用来对数据库中表的数据进行增删改。关键字:insert, delete, update
  3. DQL(Data Query Language)数据查询语言
    用来查询数据库中表的记录(数据)。关键字:select, where
  4. DCL(Data Control Language)数据控制语言(了解)
    用来定义数据库的访问权限和安全级别,及创建用户。关键字:GRANTREVOKE

DDL: 操作数据库、表

1. 操作数据库:CRUD

1.1 C(Create):创建

  • 创建数据库:create database 数据库名称;

  • 创建数据库,判断不存在,再创建:create database if not exists 数据库名称;

  • 创建数据库,并指定字符集 : create database 数据库名称 character set 字符集名;

  • 练习: 创建db4数据库,判断是否存在,并制定字符集为gbk

    1
    create database if not exists db4 character set gbk;

1.2 R(Retrieve):查询

  • 查询所有数据库的名称: show databases;

  • 查询某个数据库的字符集:查询某个数据库的创建语句: show create database 数据库名称;

示例:

1.3 U(Update):修改

修改数据库的字符集:alter database 数据库名称 character set 字符集名称;

1.4 D(Delete):删除

  • 删除数据库: drop database 数据库名称;

  • 判断数据库存在,存在再删除: drop database if exists 数据库名称;

1.5 使用数据库

  • 查询当前正在使用的数据库名称:select database();

  • 使用数据库 :use 数据库名称;

2. 操作表

2.1 C(Create):创建

语法:
1
2
3
4
5
6
create table 表名(
列名1 数据类型1,
列名2 数据类型2,
....
列名n 数据类型n
);

注意:最后一列,不需要加逗号','

数据库类型:
  1. int:整数类型 ; 如age int

  2. double:小数类型; 如score double(5,2)

  3. date:日期,只包含年月日:yyyy-MM-dd

  4. datetime:日期,包含年月日时分秒 :yyyy-MM-dd HH:mm:ss

  5. timestamp:时间戳类型,包含年月日时分秒:yyyy-MM-dd HH:mm:ss

    注意:如果将来不给这个字段赋值,或赋值为null,则默认使用当前的系统时间,来自动赋值

  6. varchar:字符串

    • name varchar(20):姓名最大20个字符

      注意:zhangsan 8个字符 张三 2个字符

创建表:

1
2
3
4
5
6
7
8
create table student(
id int,
name varchar(32),
age int ,
score double(4,1),
birthday date,
insert_time timestamp
);

复制表:create table 表名 like 被复制的表名;

2.2 R(Retrieve):查询

  • 查询某个数据库中所有的表名称: show tables;
  • 查询表结构: desc 表名;

2.3 U(Update):修改

  1. 修改表名: alter table 表名 rename to 新的表名;
  2. 修改表的字符集: alter table 表名 character set 字符集名称;
  3. 添加一列: alter table 表名 add 列名 数据类型;
  4. 修改列名称类型:
    • alter table 表名 change 列名 新列别 新数据类型;
    • alter table 表名 modify 列名 新数据类型;
  5. 删除列: alter table 表名 drop 列名;

2.4 D(Delete):删除

  • drop table 表名;
  • drop table if exists 表名 ;

DML:增删改表中数据

1. 添加数据:

语法

1
INSERT [INTO] 表名 [字段名] VALUES (字段值)

插入全部字段

  • 所有的字段名都写出来

    1
    INSERT INTO 表名(字段1,字段2,字段3,...) VALUES(值1,值2,值3,...)
  • 不写字段名

    1
    INSERT INTO 表名 VALUES(值1,值2,值3,...)

    插入部分数据

1
2
INSERT INTO 表名(字段1,字段2,...) VALUES(值1,值2,...)
-- 注意:没有添加的字段会使用NULL

注意:

  1. 列名和值要一一对应。

  2. 如果表名后不定义列名,则默认给所有列添加值

    1
    insert into 表名 values(值1,值2,...值n);
  3. 除了数字类型,其他类型需要使用引号(单双都可以)引起来

2. 删除数据:

语法:

1
delete from 表名 [where 条件]

注意:

  1. 如果不加条件,则删除表中所有记录。
  2. 如果要删除所有记录
    • delete from 表名; – 不推荐使用。有多少条记录就会执行多少次删除操作
    • TRUNCATE TABLE 表名; – 推荐使用,效率更高 :先删除表,然后再创建一张一样的空表

3. 修改数据:

语法:

1
update 表名 set 列名1 = 值1, 列名2 = 值2,... [where 条件];

不带条件修改数据

1
UPDATE 表名 SET 字段名=值; -- 修改所有的行

带条件修改数据

1
UPDATE 表名 SET 字段名=值 WHERE 字段名=值;

注意:

  1. 如果不加任何条件,则会将表中所有记录全部修改。

示例:

1
2
3
4
5
6
7
8
-- 不带条件修改数据,将所有的性别改成女
UPDATE student SET sex='女';

-- 带条件修改数据,将id号为2的学生性别改成男
UPDATE student SET sex='男' WHERE id=2;

-- 一次修改多个列,把id为3的学生,年龄改成26岁,address改成北京
UPDATE student SET age=26, address='北京' WHERE id=3;

DQL:查询表中的记录

1
select * from 表名;

语法:

1
2
3
4
5
6
7
select   字段列表
from 表名列表
where 条件列表
group by 分组字段
having 分组之后的条件
order by 排序
limit 分页限定

创建学生表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CREATE TABLE student3 ( 
id int, -- 编号
name varchar(20), -- 姓名
age int, -- 年龄
sex varchar(5), -- 性别
address varchar(100), -- 地址
math int, -- 数学
english int -- 英语 );
INSERT INTO student3(id,NAME,age,sex,address,math,english)
VALUES (1,'马云',55,'男','杭州',66,78),
(2,'马化腾',45,'女','深圳',98,87),
(3,'马景涛',55,'男','香港',56,77),
(4,'柳岩',20,'女','湖南',76,65),
(5,'柳青',20,'男','湖南',86,NULL),
(6,'刘德华',57,'男','香港',99,99),
(7,'马德',22,'女','香港',99,99),
(8,'德玛西亚',18,'男','南京',56,65);

基础查询

  1. 多个字段的查询

    1
    select 字段名1,字段名2... from 表名;

    注意:如果查询所有字段,则可以使用*来替代字段列表。

  2. 去除重复distinct

    注意结果集完全一样的列才可以去重

  3. 计算列

    • 一般可以使用四则运算计算一些列的值。(一般只会进行数值型的计算)
    • ifnull(表达式1,表达式2)null参与的运算,计算结果都为null
      • 表达式1:哪个字段需要判断是否为null
      • 表达式2:如果该字段为null后的替换值。
  4. 起别名asas也可以省略

条件查询

  1. where子句后跟条件

  2. 运算符

    • >、< 、<= 、>= 、= 、<>

    • BETWEEN...AND

    • IN( 集合)

    • LIKE:模糊查询

      占位符:

      • _:单个任意字符
      • %:多个任意字符
    • IS NULL

    • and&&

    • or||

    • not!

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
-- 1.查询年龄大于20岁
SELECT * FROM student WHERE age>=20;

-- 2.查询年龄等于20岁
SELECT * FROM student WHERE age=20;

-- 3.查询年龄不等于20岁
SELECT * FROM student WHERE age<>20
SELECT * FROM student WHERE age!=20

-- 4.查询年龄大于等于20 小于等于30
SELECT * FROM student WHERE age>=20 and age<=30;
SELECT * FROM student WHERE age>=20 && age<=30;
SELECT * FROM student WHERE BETWEEN 20 AND 30;

-- 5.查询年龄22岁,18岁,25岁的信息
SELECT * FROM student WHERE age=22 OR age=18 OR age=25;
SELECT * FROM student WHERE age IN (22,18,25);

-- 6.查询英语成绩为null
SELECT * FROM student WHERE english= NULL;-- 不对的,null值不能使用=(!=)判断
SELECT * FROM student WHERE english IS NULL;

-- 7.查询英语成绩不为null
SELECT * FROM student WHERE english IS NOT NULL;

-- 8.查询姓马的有哪些?
SELECT * FROM student name LIKE '马%';

-- 9.查询姓名第二个字是化的人
SELECT * FROM student name LIKE '_化%';

-- 10.查询姓名是3个字的人
SELECT * FROM student name LIKE '___';

-- 11.查询姓名中包含德的人
SELECT * FROM student name LIKE '%德%';

排序查询

语法:order by 子句

1
order by 排序字段1 排序方式1 ,排序字段2 排序方式2,...

排序方式:

  • ASC:升序,默认的。
  • DESC:降序。

注意:如果有多个排序条件,则当前边的条件值一样时,才会判断第二条件。

聚合函数:

将一列数据作为一个整体,进行纵向的计算。

  1. count:计算个数

    • 一般选择非空的列:主键

    • count(*)

  2. max:计算最大值

  3. min:计算最小值

  4. sum:计算和

  5. avg:计算平均值

注意:聚合函数的计算,排除null值。
解决方案:

1. 选择不包含非空的列进行计算
         2. `IFNULL`函数

分组查询:

语法:group by 分组字段;

注意:

  1. 分组之后查询的字段:分组字段、聚合函数
  2. wherehaving 的区别?
    • where分组之前进行限定,如果不满足条件,则不参与分组。having分组之后进行限定,如果不满足结果,则不会被查询出来
    • where 后不可以跟聚合函数,having可以进行聚合函数的判断。

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
-- 按照性别分组。分别查询男、女同学的平均分
SELECT sex, AVG(score) FROM student GROUP BY sex;

-- 按照性别分组。分别查询男、女同学的平均分,人数
SELECT AVG(score),COUNT(id) FROM student GROUP BY sex;

-- 按照性别分组。分别查询男、女同学的平均分,人数 要求:分数低于70分的人,不参与分组
SELECT sex, AVG(score), COUNT(id) FROM student WHERE score>70 GROUP BY sex;

-- 按照性别分组。分别查询男、女同学的平均分,人数 要求:分数低于70分的人,不参与分组,分组之后。人数要大于2个人
SELECT sex, AVG(score), COUNT(id) FROM student WHERE score>70 GROUP BY sex HAVING COUNT(id)>2;
--或者起别名
SELECT sex, AVG(score), COUNT(id) AS num FROM student WHERE score>70 GROUP BY sex HAVING num>2;

分页查询

语法

1
limit 开始的索引,每页查询的条数;

公式

1
开始的索引 = (当前的页码 - 1) * 每页显示的条数

案例

1
2
3
4
-- 每页显示3条记录 
SELECT * FROM student LIMIT 0,3; -- 第一页
SELECT * FROM student LIMIT 3,3; -- 第二页
SELECT * FROM student LIMIT 6,3; -- 第三页

DCL(Data Control Language)

SQL分类:

  1. DDL:操作数据库和表
  2. DML:增删改表中数据
  3. DQL:查询表中数据
  4. DCL:管理用户,授权

DBA:数据库管理员

DCL:管理用户,授权

管理用户

1. 添加用户:

语法:

1
CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码';

2. 删除用户

语法:

1
DROP USER '用户名'@'主机名';

3. 修改用户密码:

1
2
3
4
UPDATE USER SET PASSWORD = PASSWORD('新密码') WHERE USER = '用户名';
UPDATE USER SET PASSWORD = PASSWORD('abc') WHERE USER = 'lisi';
SET PASSWORD FOR '用户名'@'主机名' = PASSWORD('新密码');
SET PASSWORD FOR 'root'@'localhost' = PASSWORD('123');

mysql中忘记了root用户的密码?

  1. cmd -- > net stop mysql停止mysql服务

    需要管理员运行该cmd

  1. 使用无验证方式启动mysql服务: mysqld --skip-grant-tables

  2. 打开新的cmd窗口,直接输入mysql命令,敲回车。就可以登录成功

  3. use mysql;

  4. update user set password = password('你的新密码') where user = 'root';

  5. 关闭两个窗口

  6. 打开任务管理器,手动结束mysqld.exe的进程

  7. 启动mysql服务

  8. 使用新密码登录。

4. 查询用户:

1
2
3
4
5
-- 1. 切换到mysql数据库
USE myql;
-- 2. 查询user表
SELECT * FROM USER;
-- 通配符:% 表示可以在任意主机使用用户登录数据库

权限管理:

  1. 查询权限:
1
2
3
	-- 查询权限
SHOW GRANTS FOR '用户名'@'主机名';
SHOW GRANTS FOR 'lisi'@'%';
  1. 授予权限:

    1
    2
    3
    4
    -- 授予权限
    grant 权限列表 on 数据库名.表名 to '用户名'@'主机名';
    -- 给张三用户授予所有权限,在任意数据库任意表上
    GRANT ALL ON *.* TO 'zhangsan'@'localhost';
  2. 撤销权限:

    1
    2
    3
    -- 撤销权限:
    revoke 权限列表 on 数据库名.表名 from '用户名'@'主机名';
    REVOKE UPDATE ON db3.`account` FROM 'lisi'@'%';

约束

概念

对表中的数据进行限定,保证数据的正确性、有效性和完整性。

一个表如果添加了约束,不正确的数据将无法插入到表中。约束在创建表的时候添加比较合适。

分类:

  1. 主键约束primary key
  2. 非空约束not null
  3. 唯一约束unique
  4. 外键约束foreign key

非空约束:not null,值不能为null

什么是非空约束:

某一列不能为null。

创建表时添加约束

1
2
3
4
CREATE TABLE stu(
id INT,
NAME VARCHAR(20) NOT NULL -- name为非空
);

创建表完后,添加非空约束

1
ALTER TABLE stu MODIFY NAME VARCHAR(20) NOT NULL;

创建表完后删除name的非空约束

1
ALTER TABLE stu MODIFY NAME VARCHAR(20);

唯一约束:unique,值不能重复

什么是唯一约束:

表中某一列不能出现重复的值

创建表时,添加唯一约束

1
2
3
4
5
CREATE TABLE stu(
id INT,
phone_number VARCHAR(20) UNIQUE -- 添加了唯一约束
);
-- 注意mysql中,唯一约束限定的列的值可以有多个null

创建完后,删除唯一约束

1
ALTER TABLE stu DROP INDEX phone_number;

创建表后,添加唯一约束

1
ALTER TABLE stu MODIFY phone_number VARCHAR(20) UNIQUE;

主键约束:primary key

哪个字段应该作为表的主键?

通常不用业务字段作为主键,单独给每张表设计一个id的字段,把id作为主键。主键是给数据库和程序使用的,不是给最终的客户使用的。所以主键有没有含义没有关系,只要不重复,非空就行。

如:身份证,学号不建议做成主键

特点

  • 含义:非空且唯一
  • 一张表只能有一个字段为主键
  • 主键就是表中记录的唯一标识

疑问

如果一个字段设置了非空与唯一约束,该字段与主键的区别?

  • 主键数在一个表中,只能有一个。不能出现多个主键。主键可以单列,也可以是多列。
  • 自增长只能用在主键上

在创建表时,添加主键约束

1
2
3
4
create table stu(
id int primary key,-- 给id添加主键约束
name varchar(20)
);

删除主键

1
2
-- 错误 alter table stu modify id int ;
ALTER TABLE stu DROP PRIMARY KEY;

创建完表后,添加主键

1
2
3
ALTER TABLE stu MODIFY id INT PRIMARY KEY;
或者
ALTER TABLE stu ADD PRIMARY KEY(id);

自动增长AUTO_INCREMENT

概念:

主键如果让我们自己添加很有可能重复,我们通常希望在每次插入新记录时,数据库自动生成主键字段的值
如果某一列是数值类型的,使用 auto_increment 可以来完成值得自动增长

修改自增长的默认值起始值

默认地AUTO_INCREMENT的开始值是1,如果希望修改起始值,请使用下列SQL语法

  1. 创建表时指定起始值

    1
    2
    3
     CREATE TABLE 表名(
    列名 int primary key AUTO_INCREMENT
    ) AUTO_INCREMENT=起始值;

    示例:

    1
    2
    3
    4
    5
    6
    7
    -- 指定起始值为1000
    create table st4 (
    id int primary key auto_increment,
    name varchar(20)
    ) auto_increment = 1000;
    insert into st4 values (null, '孔明');
    select * from st4;
  2. 创建好以后修改起始值

    1
    ALTER TABLE 表名 AUTO_INCREMENT=起始值;

    示例:

    1
    2
    alter table st4 auto_increment = 2000;
    insert into st4 values (null, '刘备');
在创建表时,添加主键约束,并且完成主键自增长
1
2
3
4
create table stu(
id int primary key auto_increment,-- 给id添加主键约束
name varchar(20)
);
创建完后删除自动增长
1
ALTER TABLE stu MODIFY id INT;
创建完之后添加自动增长
1
ALTER TABLE stu MODIFY id INT AUTO_INCREMENT;

外键约束:foreign key

让表于表产生关系,从而保证数据的正确性。

单表的缺点

创建一个员工表包含如下列(id, name, age, dep_name, dep_location),id主键并自动增长,添加5条数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE TABLE emp ( 
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(30),
age INT,
dep_name VARCHAR(30),
dep_location VARCHAR(30)
);

-- 添加数据
INSERT INTO emp (NAME, age, dep_name, dep_location) VALUES ('张三', 20, '研发部', '广州');
INSERT INTO emp (NAME, age, dep_name, dep_location) VALUES ('李四', 21, '研发部', '广州');
INSERT INTO emp (NAME, age, dep_name, dep_location) VALUES ('王五', 20, '研发部', '广州');
INSERT INTO emp (NAME, age, dep_name, dep_location) VALUES ('老王', 20, '销售部', '深圳');
INSERT INTO emp (NAME, age, dep_name, dep_location) VALUES ('大王', 22, '销售部', '深圳');
INSERT INTO emp (NAME, age, dep_name, dep_location) VALUES ('小王', 18, '销售部', '深圳');

数据表的缺点:
  1. 数据冗余
  2. 后期还会出现增删改的问题
解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
-- 解决方案:分成2张表
-- 创建部门表(id,dep_name,dep_location)
-- 一方,主表
create table department(
id int primary key auto_increment,
dep_name varchar(20),
dep_location varchar(20)
);
-- 创建员工表(id,name,age,dep_id)
-- 多方,从表
create table employee(
id int primary key auto_increment,
name varchar(20),
age int,
dep_id int -- 外键对应主表的主键
)
-- 添加2个部门
insert into department values(null, '研发部','广州'),(null, '销售部', '深圳');
select * from department;

-- 添加员工,dep_id表示员工所在的部门
INSERT INTO employee (NAME, age, dep_id) VALUES ('张三', 20, 1);

INSERT INTO employee (NAME, age, dep_id) VALUES ('李四', 21, 1);
INSERT INTO employee (NAME, age, dep_id) VALUES ('王五', 20, 1);
INSERT INTO employee (NAME, age, dep_id) VALUES ('老王', 20, 2);
INSERT INTO employee (NAME, age, dep_id) VALUES ('大王', 22, 2);
INSERT INTO employee (NAME, age, dep_id) VALUES ('小王', 18, 2);
select * from employee;

存在的问题:

当我们在employeedep_id里面输入不存在的部门,数据依然可以添加. 但是并没有对应的部门,实际应用中不能出现这种情况。employeedep_id中的数据只能是department表中存在的id

目标:

需要约束dep_id只能是department表中已经存在id

解决方式:

使用外键约束

什么是外键约束

什么是外键:

在从表中与主表主键对应的那一列,如:员工表中的dep_id

什么是主表:

一方,用来约束别人的表

什么是从表:

多方,被别人约束的表

创建约束的语法

在创建表时,可以添加外键
1
2
3
4
5
create table 表名(
....
外键列
constraint 外键名称 foreign key (外键列名称) references 主表名称(主表列名称)
);

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- 1) 删除副表/从表 employee
drop table employee;

-- 2) 创建从表 employee 并添加外键约束emp_depid_fk
-- 多方,从表
create table employee(
id int primary key auto_increment,
name varchar(20),
age int,
dep_id int, -- 外键对应主表的主键
-- 创建外键约束
constraint emp_depid_fk foreign key (dep_id) references department(id)
)

-- 3) 正常添加数据
INSERT INTO employee (NAME, age, dep_id) VALUES ('张三', 20, 1);
INSERT INTO employee (NAME, age, dep_id) VALUES ('李四', 21, 1);
INSERT INTO employee (NAME, age, dep_id) VALUES ('王五', 20, 1);
INSERT INTO employee (NAME, age, dep_id) VALUES ('老王', 20, 2);
INSERT INTO employee (NAME, age, dep_id) VALUES ('大王', 22, 2);
INSERT INTO employee (NAME, age, dep_id) VALUES ('小王', 18, 2);

-- 4) 部门错误的数据添加失败
-- 插入不存在的部门
-- Cannot add or update a child row: a foreign key constraint fails
INSERT INTO employee (NAME, age, dep_id) VALUES ('老张', 18, 6);
创建表之后,删除外键
1
ALTER TABLE 表名 DROP FOREIGN KEY 外键名称;
创建表之后,添加外键
1
ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名称) REFERENCES 主表名称(主表列名称);

示例

1
2
3
4
5
6
-- 删除employee表的emp_depid_fk外键
alter table employee drop foreign key emp_depid_fk;

-- 在employee表情存在的情况下添加外键
alter table employee add constraint emp_depid_fk
foreign key (dep_id) references department(id);

外键的级联操作

新的问题:
1
2
3
4
5
6
7
8
select * from department;
-- 要把部门表中的id值2,改成5,能不能直接更新呢?
-- Cannot delete or update a parent row: a foreign key constraint fails
update department set id=5 where id=2;

-- 要删除部门id等于1的部门, 能不能直接删除呢?
-- Cannot delete or update a parent row: a foreign key constraint fails
delete from department where id=1;
什么是级联操作:

在修改和删除主表的主键时,同时更新或删除副表的外键值,称为级联操作

添加级联操作语法
1
2
ALTER TABLE 表名 ADD CONSTRAINT 外键名称 
FOREIGN KEY (外键字段名称) REFERENCES 主表名称(主表列名称) ON UPDATE CASCADE ON DELETE CASCADE ;
分类:
  • 级联更新:ON UPDATE CASCADE

    级联更新,只能是创建表的时候创建级联关系。更新主表中的主键,从表中的外键列也自动同步更新

  • 级联删除:ON DELETE CASCADE

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
-- 删除employee表,重新创建employee表,添加级联更新和级联删除
drop table employee;
create table employee(
id int primary key auto_increment,
name varchar(20),
age int,
dep_id int, -- 外键对应主表的主键
-- 创建外键约束
constraint emp_depid_fk foreign key (dep_id) references
department(id) on update cascade on delete cascade
)

-- 再次添加数据到员工表和部门表
INSERT INTO employee (NAME, age, dep_id) VALUES ('张三', 20, 1);
INSERT INTO employee (NAME, age, dep_id) VALUES ('李四', 21, 1);
INSERT INTO employee (NAME, age, dep_id) VALUES ('王五', 20, 1);
INSERT INTO employee (NAME, age, dep_id) VALUES ('老王', 20, 2);
INSERT INTO employee (NAME, age, dep_id) VALUES ('大王', 22, 2);
INSERT INTO employee (NAME, age, dep_id) VALUES ('小王', 18, 2);

-- 删除部门表?能不能直接删除?
drop table department;
-- 把部门表中id等于1的部门改成id等于10
update department set id=10 where id=1;
select * from employee;
select * from department;
-- 删除部门号是2的部门
delete from department where id=2;

数据库的设计

多表之间的关系

分类:

  1. 一对一(了解):如:人和身份证
    分析:一个人只有一个身份证,一个身份证只能对应一个人

  2. 一对多(多对一):如:部门和员工

    分析:一个部门有多个员工,一个员工只能对应一个部门

  3. 多对多:如:学生和课程

    分析:一个学生可以选择很多门课程,一个课程也可以被很多学生选择

实现关系:

  1. 一对多(多对一):如:部门和员工

    实现方式在多的一方建立外键,指向一的一方的主键

  2. 多对多:如:学生和课程

    实现方式多对多关系实现需要借助第三张中间表。中间表至少包含两个字段,这两个字段作为第三张表的外键,分别指向两张表的主键

  3. 一对一(了解):如:人和身份证

    实现方式一对一关系实现,可以在任意一方添加唯一外键指向另一方的主键。

一对多关系案例

需求:

一个旅游线路分类中有多个旅游线路

分析:

具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- 创建旅游线路分类表 tab_category
-- cid 旅游线路分类主键,自动增长
-- cname 旅游线路分类名称非空,唯一,字符串 100
CREATE TABLE tab_category (
cid INT PRIMARY KEY AUTO_INCREMENT,
cname VARCHAR(100) NOT NULL UNIQUE
);

-- 创建旅游线路表 tab_route
/*
rid 旅游线路主键,自动增长
rname 旅游线路名称非空,唯一,字符串 100
price 价格
rdate 上架时间,日期类型
cid 外键,所属分类
*/
CREATE TABLE tab_route(
rid INT PRIMARY KEY AUTO_INCREMENT,
rname VARCHAR(100) NOT NULL UNIQUE,
price DOUBLE,
rdate DATE,
cid INT,
FOREIGN KEY (cid) REFERENCES tab_category(cid)
);

多对多关系案例

需求:

一个用户收藏多个线路,一个线路被多个用户收藏

分析:

具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*创建用户表 tab_user
uid 用户主键,自增长
username 用户名长度 100,唯一,非空
password 密码长度 30,非空
name 真实姓名长度 100
birthday 生日
sex 性别,定长字符串 1
telephone 手机号,字符串 11
email 邮箱,字符串长度 100
*/
CREATE TABLE tab_user (
uid INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(100) UNIQUE NOT NULL,
PASSWORD VARCHAR(30) NOT NULL,
NAME VARCHAR(100),
birthday DATE,
sex CHAR(1) DEFAULT '男',
telephone VARCHAR(11),
email VARCHAR(100)
);

/*
创建收藏表 tab_favorite
rid 旅游线路 id,外键
date 收藏时间
uid 用户 id,外键
rid 和 uid 不能重复,设置复合主键,同一个用户不能收藏同一个线路两次
*/
CREATE TABLE tab_favorite (
rid INT, -- 线路id
DATE DATETIME,
uid INT, -- 用户id
-- 创建复合主键
PRIMARY KEY(rid,uid), -- 联合主键
FOREIGN KEY (rid) REFERENCES tab_route(rid),
FOREIGN KEY(uid) REFERENCES tab_user(uid)
);

数据库设计的范式

概念:

设计数据库时,需要遵循的一些规范。要遵循后边的范式要求,必须先遵循前边的所有范式要求

设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次规范,越高的范式数据库冗余越小。
目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。

分类:

1.第一范式(1NF):

每一列都是不可分割的原子数据项, 不能是集合、数组等非原子数据项。即表中的某个列有多个值时,必须拆分为不同的列。简而言之,第一范式每一列不可再拆分,称为原子性

2.第二范式(2NF):

1NF的基础上,非码属性必须完全依赖于码(在1NF基础上消除非主属性对主码的部分函数依赖

几个概念:
  1. 函数依赖:A–>B, 如果通过A属性(属性组)的值,可以确定唯一B属性的值。则称B依赖于A
    例如:学号–>姓名。 (学号,课程名称) –> 分数
  2. 完全函数依赖:A–>B,如果A是一个属性组,则B属性值得确定需要依赖于A属性组中所有的属性值。
    例如:(学号,课程名称) –> 分数
  3. 部分函数依赖:A–>B,如果A是一个属性组,则B属性值得确定只需要依赖于A属性组中某一些值即可。
    例如:(学号,课程名称) – > 姓名
  4. 传递函数依赖:A–>B, B – >C . 如果通过A属性(属性组)的值,可以确定唯一B属性的值,在通过B属性(属性组)的值可以确定唯一C属性的值,则称 C 传递函数依赖于A
    例如:学号–>系名,系名–>系主任
  5. :如果在一张表中,一个属性或属性组,被其他所有属性所完全依赖,则称这个属性(属性组)为该表的码
    例如:该表中码为:(学号,课程名称)
    • 主属性:码属性组中的所有属性
    • 非主属性:除过码属性组的属性

3.第三范式(3NF)

在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)


多表查询:

查询语法:

1
2
3
4
5
select
列名列表
from
表名列表
where....

准备sql

1
2
3
4
5
6
-- 创建部门表
CREATE TABLE dept(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20)
);
INSERT INTO dept (NAME) VALUES ('开发部'),('市场部'),('财务部');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 创建员工表
CREATE TABLE emp (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(10),
gender CHAR(1), -- 性别
salary DOUBLE, -- 工资
join_date DATE, -- 入职日期
dept_id INT,
FOREIGN KEY (dept_id) REFERENCES dept(id) -- 外键,关联部门表(部门表的主键)
);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('孙悟空','男',7200,'2013-02-24',1);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('猪八戒','男',3600,'2010-12-02',2);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('唐僧','男',9000,'2008-08-08',2);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('白骨精','女',5000,'2015-10-07',3);
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('蜘蛛精','女',4500,'2011-03-14',1);

笛卡尔积:

有两个集合A,B .取这两个集合的所有组成情况。

要完成多表查询,需要消除无用的数据

1
2
-- 需求:查询所有的员工和所有的部门
select * from emp,dept;

如何清除笛卡尔积现象的影响

我们发现不是所有的数据组合都是有用的,只有员工表.dept_id = 部门表.id 的数据才是有用的。所以需要通过条件过滤掉没用的数据

1
2
3
4
5
-- 设置过滤条件 Column 'id' in where clause is ambiguous
select * from emp,dept where id=5;
select * from emp,dept where emp.`dept_id` = dept.`id`;
-- 查询员工和部门的名字
select emp.`name`, dept.`name` from emp,dept where emp.`dept_id` = dept.`id`;

多表查询的分类:

1. 内连接查询:

1.1 隐式内连接:

看不到JOIN关键字,条件使用WHERE指定, 使用where条件消除无用数据

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 查询所有员工信息和对应的部门信息
SELECT * FROM emp,dept WHERE emp.`dept_id` = dept.`id`;

-- 查询员工表的名称,性别。部门表的名称
SELECT emp.name,emp.gender,dept.name FROM emp,dept WHERE emp.`dept_id` = dept.`id`;

SELECT
t1.name, -- 员工表的姓名
t1.gender,-- 员工表的性别
t2.name -- 部门表的名称
FROM
emp t1,
dept t2
WHERE
t1.`dept_id` = t2.`id`;

1.2 显式内连接:

使用INNER JOIN … ON语句, 可以省略INNER

语法:

1
select 字段列表 from 表名1 [inner] join 表名2 on 条件s

需求:查询唐僧的信息,显示员工id,姓名,性别,工资和所在的部门名称,我们发现需要联合2张表同时才能查询出需要的数据,使用内连接

  1. 确定查询哪些表

    1
    select * from emp inner join dept;
  2. 确定表连接条件,员工表.dept_id = 部门表.id 的数据才是有效的

    1
    2
    SELECT * FROM emp INNER JOIN dept ON emp.`dept_id` = dept.`id`;	
    SELECT * FROM emp JOIN dept ON emp.`dept_id` = dept.`id`;
  3. 确定查询条件,我们查询的是唐僧的信息,员工表.name=’唐僧’

    1
    select * from emp e inner join dept d on e.`dept_id` = d.`id` where e.`name`='唐僧';

  4. 确定查询字段,查询唐僧的信息,显示员工id,姓名,性别,工资和所在的部门名称

    1
    select e.`id`,e.`name`,e.`gender`,e.`salary`,d.`name` from emp e inner join dept d on e.`dept_id` = d.`id` where e.`name`='唐僧';
  5. 我们发现写表名有点长,可以给表取别名,显示的字段名也使用别名

    1
    select e.`id` 编号,e.`name` 姓名,e.`gender` 性别,e.`salary` 工资,d.`name` 部门名字 from emp e inner join dept d on e.`dept_id` = d.`id` where e.`name`='唐僧';

1.3. 内连接查询步骤:

  1. 确定查询哪些表
  2. 确定表连接的条件
  3. 确定查询的条件
  4. 确定查询的字段

2. 外链接查询:

2.1 左外连接:

使用LEFT OUTER JOIN … ON,OUTER可以省略, 查询的是左表所有数据以及其交集部分。

用左边表的记录去匹配右边表的记录,如果符合条件的则显示;否则,显示NULL

可以理解为:在内连接的基础上保证左表的数据全部显示(左表是部门,右表员工)

语法:

1
select 字段列表 from1 left [outer] join2 on 条件;

例子:

1
2
3
4
5
6
7
-- 在部门表中增加一个销售部
insert into dept (name) values ('销售部');
select * from dept;
-- 使用内连接查询
select * from dept d inner join emp e on d.`id` = e.`dept_id`;
-- 使用左外连接查询
select * from dept d left join emp e on d.`id` = e.`dept_id`;

2.2 右外连接:

使用RIGHT OUTER JOIN … ON,OUTER可以省略,查询的是右表所有数据以及其交集部分。

用右边表的记录去匹配左边表的记录,如果符合条件的则显示;否则,显示NULL
可以理解为:在内连接的基础上保证右表的数据全部显示

语法:

1
select 字段列表 from1 right [outer] join2 on 条件;

例子:

1
2
3
-- 在员工表中增加一个员工
insert into emp values (null, '沙僧','男',6666,'2013-12-05',null);
select * from emp;
1
2
3
4
-- 使用内连接查询
select * from dept inner join emp on dept.`id` = emp.`dept_id`;
-- 使用右外连接查询
select * from dept right join emp on dept.`id` = emp.`dept_id`;

3. 子查询:

什么是子查询

1
2
3
4
5
6
7
8
9
--需求:查询开发部中有哪些员工
--1.查询dept表
SELECT id FROM dept WHERE name='开发部';

--2.查询emp表
SELECT * FORM emp WHERE dept_id='1';

--使用子查询
SELECT NAME FORM emp WHERE dept_id=(SELECT id FROM dept WHERE name='开发部');

概念:

  1. 一个查询的结果做为另一个查询的条件
  2. 有查询的嵌套,内部的查询称为子查询
  3. 子查询要使用括号
1
2
3
4
5
6
7
-- 查询工资最高的员工信息
-- 1 查询最高的工资是多少 9000
SELECT MAX(salary) FROM emp;
-- 2 查询员工信息,并且工资等于9000的
SELECT * FROM emp WHERE emp.`salary` = 9000;
-- 一条sql就完成这个操作。子查询
SELECT * FROM emp WHERE emp.`salary` = (SELECT MAX(salary) FROM emp);

子查询三种情况

  1. 子查询的结果是单行单列的:

    子查询可以作为条件,使用运算符去判断。 运算符: > >= < <= =

    1
    2
    3
    4
    -- 1.查询员工工资小于平均工资的人
    SELECT * FROM emp WHERE emp.salary < (SELECT AVG(salary) FROM emp);
    -- 2.查询工资最高的员工
    SELECT * FROM emp WHERE salary= (SELECT MAX(salary) FROM emp);
  2. 子查询的结果是多行单列的:

    子查询可以作为条件,使用运算符in来判断

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    -- 1.查询'财务部'和'市场部'所有的员工信息
    -- 1.1 先查询开发部与财务部的id
    SELECT id FROM dept WHERE NAME in ('财务部''市场部');
    -- 1.2 再查询在这些部门id中有哪些员工
    SELECT * FROM emp WHERE dept_id = 3 OR dept_id = 2;
    -- 子查询
    SELECT * FROM emp WHERE dept_id IN (SELECT id FROM dept WHERE NAME in ('财务部''市场部'));

    -- 2.查询工资大于5000的员工,来自于哪些部门的名字
    -- 2.1 先查询大于5000的员工所在的部门id
    SELECT dept_id FROM emp WHERE salary>5000;
    -- 2.2 再查询在这些部门id中部门的名字
    SELECT dept.NAME FROM dept WHERE dept.id in (SELECT e.dept_id FROM emp e WHERE e.salary>5000);
  3. 子查询的结果是多行多列的:

    子查询结果只要是多列,肯定在FROM后面作为表,子查询可以作为一张虚拟表参与查询

    子查询作为表需要取别名,否则这张表没有名称则无法访问表中的字段

1
2
3
4
5
6
7
8
9
10
11
12
-- 1.查询员工入职日期是2011-11-11日之后的员工信息和部门信息
-- 1.1 在员工表中查询2011-1-1以后入职的员工
SELECT * FROM emp WHERE emp.`join_date` > '2011-11-11'
-- 1.2 查询所有的部门信息,与上面的虚拟表中的信息组合,找出所有部门id等于的dept_id
-- 子查询
SELECT * FROM dept t1 ,(SELECT * FROM emp WHERE emp.`join_date` > '2011-11-11') t2
WHERE t1.id = t2.dept_id;

-- 普通内连接
SELECT * FROM emp t1,dept t2 WHERE t1.`dept_id` = t2.`id` AND t1.`join_date` > '2011-11-11'
或者
SELECT * FROM emp INNER JOIN dept on emp.dept_id = dept.id WHERE emp.join_date>'2011-11-11';

子查询小结

  1. 子查询结果只要是单列,则在WHERE后面作为条件
  2. 子查询结果只要是多列,则在FROM后面作为表进行二次查询

多表查询练习

数据准备
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
-- 部门表
CREATE TABLE dept (
id INT PRIMARY KEY PRIMARY KEY, -- 部门id
dname VARCHAR(50), -- 部门名称
loc VARCHAR(50) -- 部门所在地
);
-- 添加4个部门
INSERT INTO dept(id,dname,loc) VALUES
(10,'教研部','北京'),
(20,'学工部','上海'),
(30,'销售部','广州'),
(40,'财务部','深圳');

-- 职务表,职务名称,职务描述
CREATE TABLE job (
id INT PRIMARY KEY,
jname VARCHAR(20),
description VARCHAR(50)
);
-- 添加4个职务
INSERT INTO job (id, jname, description) VALUES
(1, '董事长', '管理整个公司,接单'),
(2, '经理', '管理部门员工'),
(3, '销售员', '向客人推销产品'),
(4, '文员', '使用办公软件');
-- 员工表
CREATE TABLE emp (
id INT PRIMARY KEY, -- 员工id
ename VARCHAR(50), -- 员工姓名
job_id INT, -- 职务id
mgr INT , -- 上级领导
joindate DATE, -- 入职日期
salary DECIMAL(7,2), -- 工资
bonus DECIMAL(7,2), -- 奖金
dept_id INT, -- 所在部门编号
CONSTRAINT emp_jobid_ref_job_id_fk FOREIGN KEY (job_id) REFERENCES job (id),
CONSTRAINT emp_deptid_ref_dept_id_fk FOREIGN KEY (dept_id) REFERENCES dept (id)
);
-- 添加员工
INSERT INTO emp(id,ename,job_id,mgr,joindate,salary,bonus,dept_id) VALUES
(1001,'孙悟空',4,1004,'2000-12-17','8000.00',NULL,20),
(1002,'卢俊义',3,1006,'2001-02-20','16000.00','3000.00',30),
(1003,'林冲',3,1006,'2001-02-22','12500.00','5000.00',30),
(1004,'唐僧',2,1009,'2001-04-02','29750.00',NULL,20),
(1005,'李逵',4,1006,'2001-09-28','12500.00','14000.00',30),
(1006,'宋江',2,1009,'2001-05-01','28500.00',NULL,30),
(1007,'刘备',2,1009,'2001-09-01','24500.00',NULL,10),
(1008,'猪八戒',4,1004,'2007-04-19','30000.00',NULL,20),
(1009,'罗贯中',1,NULL,'2001-11-17','50000.00',NULL,10),
(1010,'吴用',3,1006,'2001-09-08','15000.00','0.00',30),
(1011,'沙僧',4,1004,'2007-05-23','11000.00',NULL,20),
(1012,'李逵',4,1006,'2001-12-03','9500.00',NULL,30),
(1013,'小白龙',4,1004,'2001-12-03','30000.00',NULL,20),
(1014,'关羽',4,1007,'2002-01-23','13000.00',NULL,10);
-- 工资等级表
CREATE TABLE salarygrade (
grade INT PRIMARY KEY, -- 级别
losalary INT, -- 最低工资
hisalary INT -- 最高工资
);
-- 添加5个工资等级
INSERT INTO salarygrade(grade,losalary,hisalary) VALUES
(1,7000,12000),
(2,12010,14000),
(3,14010,20000),
(4,20010,30000),
(5,30010,99990);

dept表

emp表

job表

salarygrade表

ER图

1. 查询所有员工信息。查询员工编号,员工姓名,工资,职务名称,职务描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- 需求:
-- 1.查询所有员工信息。查询员工编号,员工姓名,工资,职务名称,职务描述
/*
分析:
1.员工编号,员工姓名,工资,需要查询emp表
职务名称,职务描述 需要查询job表
2.查询条件 emp.job_id = job.id
*/
SELECT
t1.`id`, -- 员工编号
t1.`ename`, -- 员工姓名
t1.`salary`,-- 工资
t2.`jname`, -- 职务名称
t2.`description` -- 职务描述
FROM
emp t1, job t2
WHERE
t1.`job_id` = t2.`id`;

-- 或者
SELECT
emp.ename '姓名',
emp.salary '工资',
job.jname '职务名称',
job.description '职务描述'
FROM emp inner join job on job.id=emp.job_id;

2. 查询员工编号,员工姓名,工资,职务名称,职务描述,部门名称,部门位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- 2.查询员工编号,员工姓名,工资,职务名称,职务描述,部门名称,部门位置
/*
分析:
1. 员工编号,员工姓名,工资 emp
职务名称,职务描述 job
部门名称,部门位置 dept
2. 条件: emp.job_id = job.id and emp.dept_id = dept.id
*/
SELECT
t1.`id`, -- 员工编号
t1.`ename`, -- 员工姓名
t1.`salary`,-- 工资
t2.`jname`, -- 职务名称
t2.`description`, -- 职务描述
t3.`dname`, -- 部门名称
t3.`loc` -- 部门位置
FROM
emp t1, job t2,dept t3
WHERE
t1.`job_id` = t2.`id` AND t1.`dept_id` = t3.`id`;

3. 查询员工姓名,工资,工资等级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- 3.查询员工姓名,工资,工资等级
/*
分析:
1.员工姓名,工资 emp
工资等级 salarygrade
2.条件 emp.salary >= salarygrade.losalary and emp.salary <= salarygrade.hisalary
mp.salary BETWEEN salarygrade.losalary and salarygrade.hisalary
*/
SELECT
t1.ename ,
t1.`salary`,
t2.*
FROM emp t1, salarygrade t2
WHERE t1.`salary` BETWEEN t2.`losalary` AND t2.`hisalary`;

-- 或者
SELECT
e.ename 员工姓名,
e.salary 工资,
s.grade 工资登记
FROM salarygrade s INNER join emp e on e.salary>s.losalary AND e.salary<s.hisalary;

4. 查询员工姓名,工资,职务名称,职务描述,部门名称,部门位置,工资等级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- 4.查询员工姓名,工资,职务名称,职务描述,部门名称,部门位置,工资等级
/*
分析:
1. 员工姓名,工资 emp , 职务名称,职务描述 job 部门名称,部门位置,dept 工资等级 salarygrade
2. 条件: emp.job_id = job.id and emp.dept_id = dept.id and emp.salary BETWEEN salarygrade.losalary and salarygrade.hisalary
*/
SELECT
t1.`ename`,
t1.`salary`,
t2.`jname`,
t2.`description`,
t3.`dname`,
t3.`loc`,
t4.`grade`
FROM
emp t1,job t2,dept t3,salarygrade t4
WHERE
t1.`job_id` = t2.`id`
AND t1.`dept_id` = t3.`id`
AND t1.`salary` BETWEEN t4.`losalary` AND t4.`hisalary`;

5. 查询出部门编号、部门名称、部门位置、部门人数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
-- 5.查询出部门编号、部门名称、部门位置、部门人数  	
/*
分析:
1.部门编号、部门名称、部门位置 dept 表。
部门人数 emp表
2.使用分组查询。按照emp.dept_id完成分组,查询count(id) 多行多列的表
3.使用子查询将第2步的查询结果和dept表进行关联查询
*/
SELECT
d.id 部门编号,
d.dname 部门名称,
d.loc 部门位置,
total_number 部门人数
FROM dept d,
(SELECT
e.dept_id,
COUNT(e.dept_id) total_number
FROM emp e
GROUP BY e.dept_id) t
WHERE d.id=t.dept_id;

-- 或者
SELECT
d.id 部门编号,
d.dname 部门名称,
d.loc 部门位置,
COUNT(e.dept_id) 部门人数
FROM emp e, dept d
GROUP BY e.dept_id;

--或者
SELECT
d.id 部门编号,
d.dname 部门名称,
d.loc 部门位置,
COUNT(e.dept_id) 部门人数
FROM emp e INNER join dept d on e.dept_id= d.id
GROUP BY e.dept_id;

6.查询所有员工的姓名及其直接上级的姓名,没有领导的员工也需要查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-- 6.查询所有员工的姓名及其直接上级的姓名,没有领导的员工也需要查询
/*
分析:
1.姓名 emp, 直接上级的姓名 emp
emp表的id 和 mgr 是自关联
2.条件 emp.id = emp.mgr
3.查询左表的所有数据,和 交集数据
使用左外连接查询
*/
select
t1.ename,
t1.mgr,
t2.`id`,
t2.ename
from emp t1, emp t2
where t1.mgr = t2.`id`;

SELECT
t1.ename,
t1.mgr,
t2.`id`,
t2.`ename`
FROM emp t1
LEFT JOIN emp t2
ON t1.`mgr` = t2.`id`;

事务

事务的基本介绍

什么是事务:

  1. 在实际的开发过程中,一个业务操作如:转账,往往是要多次访问数据库才能完成的。转账是一个用户扣钱,另一个用户加钱。如果其中有一条SQL语句出现异常,这条SQL就可能执行失败。
  2. 事务执行是一个整体,所有的SQL语句都必须执行成功。如果其中有1条SQL语句出现异常,则所有的SQL语句都要回滚,整个业务执行失败
  3. 如果一个包含多个步骤的业务操作,被事务管理,那么这些操作要么同时成功,要么同时失败。

操作:

  1. 开启事务:start transaction;
  2. 回滚:rollback;
  3. 提交:commit;

例子:转账的操作

数据准备

1
2
3
4
5
6
7
CREATE TABLE account (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(10),
balance DOUBLE
);
-- 添加数据
INSERT INTO account (NAME, balance) VALUES ('zhangsan', 1000), ('lisi', 1000);

模拟操作

张三给李四转500元钱,一个转账的业务操作最少要执行下面的2条语句:

  1. 张三账号-500
  2. 李四账号+500
1
2
3
4
5
6
7
INSERT INTO account (name, balance) 	VALUES ('张三',1000), ('李四',1000);

-- 1.张三给李四转500
-- 1.1 张三账号-500
UPDATE account SET balance=balance-500 WHERE name='张三';
-- 1.2 李四账号+500
UPDATE account SET balance=balance+500 WHERE name='李四';

问题:

假设当张三账号上-500元,服务器崩溃了。李四的账号并没有+500元,数据就出现问题了。我们需要保证其中一条SQL语句出现问题,整个转账就算失败。只有两条SQL都成功了转账才算成功。这个时候就需要用到事务

事务提交的两种方式:

  • 自动提交
    • mysql就是自动提交的
    • 一条DML(增删改)语句会自动提交一次事务。
  • 手动提交
    • Oracle数据库默认是手动提交事务
    • 需要先开启事务,再提交

手动提交事务的SQL语句

手动提交事务使用过程
  1. 执行成功的情况: 开启事务-> 执行多条SQL语句 -> 成功提交事务
  2. 执行失败的情况: 开启事务-> 执行多条SQL语句 ->事务的回滚
案例演示1:事务提交

模拟张三给李四转500元钱(成功) 目前数据库数据如下:

  • 执行完 1.开启事务, 2.张三账号-500, 3.李四账号+500发现数据并没有变化

  • 执行commit提交事务之后,数据发生变化

案例演示2:事务回滚

模拟张三给李四转500元钱(失败) 目前数据库数据如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SELECT * FROM account;
UPDATE account SET balance = 1000;
-- 张三给李四转账 500 元
SELECT * FROM account;
UPDATE account SET balance = 1000;
-- 张三给李四转账 500 元
-- 0. 开启事务
START TRANSACTION;
-- 1. 张三账户 -500
UPDATE account SET balance = balance - 500 WHERE NAME = 'zhangsan';
-- 2. 李四账户 +500
-- 出错了...
UPDATE account SET balance = balance + 500 WHERE NAME = 'lisi';
-- 发现执行没有问题,提交事务
COMMIT;
-- 发现出问题了,回滚事务
ROLLBACK;
总结:

如果事务中SQL语句没有问题,commit提交事务,会对数据库数据的数据进行改变。 如果事务中SQL语句有问题,rollback回滚事务,会回退到开启事务时的状态。

自动提交事务

MySQL默认每一条DML(增删改)语句都是一个单独的事务,每条语句都会自动开启一个事务,语句执行完毕自动提交事务,MySQL默认开始自动提交事务

修改事务的默认提交方式:

  • 查看事务的默认提交方式:

    1
    SELECT @@autocommit; -- 1 代表自动提交  0 代表手动提交
  • 修改默认提交方式:

    1
    set @@autocommit = 0;

事务原理

事务开启之后, 所有的操作都会临时保存到事务日志中, 事务日志只有在得到commit命令才会同步到数据表中,其他任何情况都会清空事务日志(rollback,断开连接)

原理图:

事务的步骤:

  1. 客户端连接数据库服务器,创建连接时创建此用户临时日志文件
  2. 开启事务以后,所有的操作都会先写入到临时日志文件中
  3. 所有的查询操作从表中查询,但会经过日志文件加工后才返回
  4. 如果事务提交则将日志文件中的数据写到表中,否则清空日志文件。

回滚点

什么是回滚点

在某些成功的操作完成之后,后续的操作有可能成功有可能失败,但是不管成功还是失败,前面操作都已经成功,可以在当前成功的位置设置一个回滚点。可以供后续失败操作返回到该位置,而不是返回所有操作,这个点称之为回滚点。

回滚点的操作语句

具体操作:

  1. 将数据还原到1000
  2. 开启事务
  3. 让张三账号减3次钱,每次10块
  4. 设置回滚点:savepoint three_times;
  5. 让张三账号减4次钱,每次10块
  6. 回到回滚点:rollback to three_times;
  7. 分析执行过程

总结:

设置回滚点可以让我们在失败的时候回到回滚点,而不是回到事务开启的时

事务的四大特征:

  1. 原子性:是不可分割的最小操作单位,要么同时成功,要么同时失败。
  2. 持久性:当事务提交或回滚后,数据库会持久化的保存数据。
  3. 隔离性:多个事务之间。相互独立。
  4. 一致性:事务操作前后,数据总量不变

事务的隔离级别(了解)

概念:

多个事务之间隔离的,相互独立的。但是如果多个事务操作同一批数据,则会引发一些问题,设置不同的隔离级别就可以解决这些问题。

存在问题:

  1. 脏读:一个事务,读取到另一个事务中没有提交的数据

    脏读非常危险的,比如张三向李四购买商品,张三开启事务,向李四账号转入500块,然后打电话给李四说钱已经转了。李四一查询钱到账了,发货给张三。张三收到货后回滚事务,李四的再查看钱没了。

  2. 不可重复读(虚读):在同一个事务中,两次读取到的数据不一样。

    两次查询输出的结果不同,到底哪次是对的?不知道以哪次为准。 很多人认为这种情况就对了,无须困惑,当然是后面的为准。我们可以考虑这样一种情况,比如银行程序需要将查询结果分别输出到电脑屏幕和发短信给客户,结果在一个事务中针对不同的输出目的地进行的两次查询不一致,导致文件和屏幕中的结果不一致,银行工作人员就不知道以哪个为准了。

    结论:同一个事务中为了保证多次查询数据一致,必须使用repeatable read隔离级别

  3. 幻读:一个事务操作(DML)数据表中所有记录,另一个事务添加了一条数据,则第一个事务查询不到自己的修改。

隔离级别:

  1. read uncommitted:读未提交

    产生的问题:脏读、不可重复读、幻读

  2. read committed:读已提交 (Oracle)

    产生的问题:不可重复读、幻读

  3. repeatable read:可重复读 (MySQL默认)

    产生的问题:幻读

  4. serializable:串行化

    可以解决所有的问题

注意:隔离级别从小到大安全性越来越高,但是效率越来越低

数据库查询隔离级别select @@tx_isolation;

数据库设置隔离级别set global transaction isolation level 级别字符串;

演示:

1
2
3
4
5
set global transaction isolation level read uncommitted;
start transaction;
-- 转账操作
update account set balance = balance - 500 where id = 1;
update account set balance = balance + 500 where id = 2;