MYSQL基础语法
回顾
MySQL管理数据库
创建数据库
CREATE DATABASE IF NOT EXISTS 数据库名;
删除数据库
DROP DATABASE 数据库名;
表的管理
查看所有表
use 数据库名; 选中一个数据库
show tables;
创建表:student(整数id,字符串name,整数age)
CREATE TABLE sutdent (id int, name varchar(20), age int);
查看一个表结构
desc 表名;
查看创建表的sql语句
SHOW CREATE TABLE 表名;
修改表结构
ALTER TABLE 表名 ...
向学生表中添加1列remark varchar(100)
ALTER TABLE student ADD remark varchar(100)
删除学生表列remark
ALTER TABLE student DROP remark;
修改表student名字为student1
REANME TABLE sutdent TO student1;
删除表
DROP TABLE 表名;
管理数据:数据增删改DML
插入数据
INSERT INTO 表名 (字段名1, 字段名2..) VALUES (值1, 值2...);
修改数据
UPDATE 表名 SET 字段名=新的值 WHERE 条件;
删除表中的所有数据
DELETE FROM 表名;
1.排序:
select * from 表名 order by 字段名 asc(升序默认的)/desc(降序),字段名 asc(升序默认的)/desc(降序),..
2.聚合函数:
count(*) 根据列统计行数
sum(字段名)求某一列和值
max(字段名)某一列最大值
min(字段名)某一列最小值
avg(字段名)某一列平均值
3.分组:按照哪列分组,那么那一列的字段值相同就被分为一组
select * from 表名 where 条件 group by 字段名 having 条件
4.分页查询
select * from 表名 limit 起始索引,每页显示的行数;
起始索引 从0开始,对应第一行
5.约束:
1)主键约束:字段名 类型 primary key auto_increment
特点:唯一 非空
2)唯一约束:字段名 类型 unique
3)非空约束:字段名 类型 not null
4)默认值约束:字段名 类型 default 默认值
5)外键约束:约束外键,在多表中具有外键。
【constraint 外键约束名】 foreign key(外键对应的字段名) references 主表(主键);
6)多表关系:
1)一对多:将一方的主键作为多方的外键
2)多对多:创建从表(中间表)维护两张主表的关系,在从表中至少要有两列来自于主表的主键作为外键
3)一对一:将任意一方的主键作为另一方的外键
今日重点
1.约束
2.多表查询(最为重要)
3.事务(mysql如何控制事务)
第一部分(约束和表设计)
1、DQL查询语句-limit语句(掌握)
目标
能够掌握limit语句的使用
讲解
作用:
LIMIT是
限制的意思,所以
LIMIT`的作用就是限制查询记录的条数。
LIMIT语句格式:
select * from 表名 limit offset, row_count;
mysql中limit的用法:返回前几条或者中间某几行数据
-- 1 表示分页查询的索引,对应数据表是第二行数据,4表示每页显示4条数据
select * from 表名 limit 1,4。
1表示索引,注意这里的索引从0开始。对应表中第一行数据
4表示查询记录数。
上述就表示从第2条记录开始查询,一共查询4条,即到第5条。
具体步骤:
准备数据:
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条,显示4条
我们可以认为跳过前面1条,取4条数据
SELECT * FROM student3 LIMIT 1,4;
LIMIT的使用场景:分页
比如我们登录京东,淘宝,返回的商品信息可能有几万条,不是一次全部显示出来。是一页显示固定的条数。假设我们一每页显示5条记录的方式来分页。
假设我们一每页显示5条记录的方式来分页,SQL语句如下:
-- 每页显示5条
-- 第一页: LIMIT 0,5; 跳过0条,显示5条
-- 第二页: LIMIT 5,5; 跳过5条,显示5条
-- 第三页: LIMIT 10,5; 跳过10条,显示5条
SELECT * FROM student3 LIMIT 0,5;
SELECT * FROM student3 LIMIT 5,5;
SELECT * FROM student3 LIMIT 10,5;
注意:
- 如果第一个参数是0可以简写:
SELECT * FROM student3 LIMIT 0,5;
SELECT * FROM student3 LIMIT 5;
- LIMIT 10,5; -- 不够5条,有多少显示多少
小结
-
LIMIT语句的使用格式?
SELECT 字段 FROM 表名 LIMIT 索引, 显示条数; 索引:从0开始,一直变化 显示条数:每页显示的行数,固定不变的
-
SELECT 字段名(5) FROM 表名(1) WHERE 条件(2) GROUP BY 分组列名(3) HAVING 条件(4) ORDER BY 排序列名(6) LIMIT 跳过行数, 返回行数(7); 执行顺序:1234567
2、数据库约束的概述
目标
能够说出数据库约束的作用
讲解
数据库约束的作用
对表中的数据进行进一步的限制,保证数据的正确性、有效性和完整性。
约束种类
PRIMARY KEY
: 主键约束UNIQUE
: 唯一约束NOT NULL
: 非空约束DEFAULT
: 默认值 了解FOREIGN KEY
: 外键约束
小结
- 数据库约束的作用?
对表中的数据进行进一步的限制,保证数据的正确性、有效性和完整性。
3、主键约束(掌握)
目标
- 能够说出主键约束的作用
- 能够添加主键
讲解
主键的作用
用来唯一标识一条记录。
为什么需要主键约束
有些记录的 name,age,score 字段的值都一样时,那么就没法区分这些数据,造成数据库的记录不唯一,这样就不方便管理数据。
每张表都应该有一个主键,并且每张表只能有一个主键。
哪个字段作为表的主键
通常不用业务字段作为主键,单独给每张表设计一个id的字段,把id作为主键。主键是给数据库和程序使用的,不是给最终的客户使用的。所以主键有没有含义没有关系,只要不重复,非空就行。
创建主键
主键:PRIMARY KEY
主键的特点:
- 主键必须包含唯一的值
- 主键列不能包含NULL值
创建主键方式:
-
在创建表的时候给字段添加主键
字段名 字段类型 PRIMARY KEY
-
在已有表中添加主键(了解)
ALTER TABLE 表名 ADD PRIMARY KEY(字段名);
具体操作:
- 创建表学生表st5, 包含字段(id, name, age)将id做为主键
CREATE TABLE st5 (
id INT PRIMARY KEY, -- id是主键
NAME VARCHAR(20),
age INT
);
- 添加数据
INSERT INTO st5 (id, NAME,age) VALUES (1, '唐伯虎',20);
INSERT INTO st5 (id, NAME,age) VALUES (2, '周文宾',24);
INSERT INTO st5 (id, NAME,age) VALUES (3, '祝枝山',22);
INSERT INTO st5 (id, NAME,age) VALUES (4, '文征明',26);
- 插入重复的主键值
-- 主键是唯一的不能重复:Duplicate entry '1' for key 'PRIMARY'
INSERT INTO st5 (id, NAME,age) VALUES (1, '文征明2',30);
- 插入NULL的主键值
-- 主键是不能为空的:Column 'id' cannot be null
INSERT INTO st5 (id, NAME,age) VALUES (NULL, '文征明3',18);
小结
-
说出主键约束的作用?唯一,区分一条记录
-
主键的特点?唯一,不能为NULL
-
添加主键?
字段名 字段类型 PRIMARY KEYALTER TABLE 表名 ADD PRIMARY KEY(字段名);
4、主键自增
目标
能够设置主键为自动增长
讲解
主键如果让我们自己添加很有可能重复,我们通常希望在每次插入新记录时,数据库自动生成主键字段的值
字段名 字段类型 PRIMARY KEY AUTO_INCREMENT
AUTO_INCREMENT
表示自动增长(字段类型必须是整数类型)
具体操作:
- 创建学生表st6, 包含字段(id, name, age)将id做为主键并自动增长
CREATE TABLE st6 (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
age INT
);
- 插入数据
-- 主键默认从1开始自动增长
INSERT INTO st6 (NAME, age) VALUES ('唐僧', 22);
INSERT INTO st6 (NAME, age) VALUES ('孙悟空', 26);
INSERT INTO st6 (NAME, age) VALUES ('猪八戒', 25);
INSERT INTO st6 (NAME, age) VALUES ('沙僧', 20);
DELETE和TRUNCATE的区别
- DELETE 删除表中的数据,但不重置AUTO_INCREMENT的值。
- TRUNCATE 摧毁表,重建表,AUTO_INCREMENT重置为1
小结
- 设置主键为自动增长格式?
字段名 数据类型 PRIMARY KEY AUTO_INCREMENT
5、唯一约束
目标
- 能够说出唯一约束的作用
- 能够添加唯一约束
讲解
唯一约束的作用
在这张表中这个字段的值不能重复
唯一约束的基本格式
字段名 字段类型 UNIQUE
具体步骤:
- 创建学生表st7, 包含字段(id, name),name这一列设置唯一约束,不能出现同名的学生
CREATE TABLE st7 (
id INT,
NAME VARCHAR(20) UNIQUE
);
- 添加一些学生
INSERT INTO st7 VALUES (1, '貂蝉');
INSERT INTO st7 VALUES (2, '西施');
INSERT INTO st7 VALUES (3, '王昭君');
INSERT INTO st7 VALUES (4, '杨玉环');
-- 插入相同的名字出现name重复: Duplicate entry '貂蝉' for key 'name'
INSERT INTO st7 VALUES (5, '貂蝉');
-- 出现多个null的时候会怎样?因为null是没有值,所以不存在重复的问题
INSERT INTO st3 VALUES (5, NULL);
INSERT INTO st3 VALUES (6, NULL);
小结
- 说出唯一约束的作用?让这个字段的值不能重复,多个null不算重复
- 添加唯一约束格式?字段名 字段类型 UNIQUE
6、非空约束
目标
- 能够说出非空约束的作用
- 能够添加非空约束
讲解
非空约束的作用
这个字段必须设置值,不能是NULL
非空约束的基本语法格式
字段名 字段类型 NOT NULL
具体操作:
- 创建表学生表st8, 包含字段(id,name,gender)其中name不能为NULL
CREATE TABLE st8 (
id INT,
NAME VARCHAR(20) NOT NULL,
gender CHAR(2)
);
- 添加一些完整的记录
INSERT INTO st8 VALUES (1, '郭富城', '男');
INSERT INTO st8 VALUES (2, '黎明', '男');
INSERT INTO st8 VALUES (3, '张学友', '男');
INSERT INTO st8 VALUES (4, '刘德华', '男');
-- 姓名不赋值出现姓名不能为null: Column 'name' cannot be null
INSERT INTO st8 VALUES (5, NULL, '男');
小结
1.非空约束的格式:
字段名 数据类型 NOT NULL
2.问题:
主键约束和唯一约束并且非空的区别:
id primary key 主键约束 可以实现主键自增,一般数据表只有id是主键约束
name unique not null 唯一并且非空 不能实现主键自增 数据表中很多字段都可以是唯一 非空
7、默认值(了解)
目标
- 能够说出默认值的作用
- 能够给字段添加默认值
讲解
默认值的作用
往表中添加数据时,如果不指定这个字段的数据,就使用默认值
默认值格式
字段名 字段类型 DEFAULT 默认值
具体步骤:
- 创建一个学生表 st9,包含字段(id,name,address), 地址默认值是广州
CREATE TABLE st9 (
id INT,
NAME VARCHAR(20),
address VARCHAR(50) DEFAULT '广州'
);
- 添加一条记录,使用默认地址
INSERT INTO st9 (id, NAME) VALUES (1, '刘德华');
- 添加一条记录,不使用默认地址
INSERT INTO st9 VALUES (2, '张学友', '香港');
小结
- 说出默认值的作用?不添加这个字段,就会使用默认值
- 给字段添加默认值格式? 字段名 字段类型 DEFAULT 默认值
8、表关系的概念和外键约束
在真实的开发中,一个项目中的数据,一般都会保存在同一个数据库中,但是不同的数据需要保存在不同的数据表中。这时不能把所有的数据都保存在同一张表中。
那么在设计保存数据的数据表时,我们就要根据具体的数据进行分析,然后把同一类数据保存在同一张表中,不同的数据进行分表处理。
数据之间必然会有一定的联系,我们把不同的数据保存在不同的数据表中之后,同时还要在数据表中维护这些数据之间的关系。这时就会导致表和表之间必然会有一定的联系。这时要求设计表的人员,就需要考虑不同表之间的具体关系。
在数据库中,表总共存在三种关系,这三种关系如下描述:真实的数据表之间的关系:
多对多关系、一对多(多对一)、一对一(极少)。(一对一关系就是我们之前学习的Map集合的key-value关系)
多对多(掌握)
例如:程序员和项目的关系、老师和学生,学生和课程,顾客和商品的关系等
分析:
程序员和项目:
一个程序员可以参与多个项目的开发,一个项目可以由多个程序员来开发。这种关系就称为多对多关系。
当我们把数据之间的关系分析清楚之后,一般我们需要通过E(Entity)-R(relation)图来展示。 实体 关系 图
一个Java对象,可以对应数据库中的一张表,而Java中类的属性,可以对应表中的字段。
而在E-R图中:
一张表,可以称为一个实体,使用矩形表示,每个实体的属性(字段,表的列),使用椭圆表示。
表和表之间的关系,使用菱形表示。
实体(程序员):编号、姓名、薪资。
实体(项目):编号、名称。
程序员和项目存在关系:一个程序员可以开发多个项目,一个项目可以被多个程序员开发。
说明:如果两张表是多对多的关系,需要创建第三张表,并在第三张表中增加两列,引入其他两张表的主键作为自己的外键。
关系图总结:
创建表的sql语句:
-- 创建程序员表
create table coder(
id int primary key auto_increment,
name varchar(50),
salary double
);
-- 创建项目表
create table project(
id int primary key auto_increment,
name varchar(50)
);
-- 创建中间关系表
create table coder_project(
coder_id int,
project_id int
);
-- 添加测试数据
insert into coder values(1,'张三',12000);
insert into coder values(2,'李四',15000);
insert into coder values(3,'王五',18000);
insert into project values(1,'QQ项目');
insert into project values(2,'微信项目');
insert into coder_project values(1,1);
insert into coder_project values(1,2);
insert into coder_project values(2,1);
insert into coder_project values(2,2);
insert into coder_project values(3,2);
小结:
1.多对多关系,需要创建第三张表来维护两张主表的关系。在中间表中起码要有两列来自于主表的主键。
外键约束(掌握)
创建第三张关系表即中间表,来维护程序员表和项目表之间的关系。
使用中间表的目的是维护两表多对多的关系:
1 中间表插入的数据 必须在多对多的主表中存在。
2 如果主表的记录在中间表维护了关系,就不能随意删除。如果可以删除,中间表就找不到对应的数据了,这样就没有意义了。
上述是中间表存在的意义,可是我们这里所创建的中间表并没有起到上述的作用,而是存在缺点的:
缺点1: 我们是可以向中间表插入不存在的项目编号和程序员编号的。
说明:在程序员和项目表中是不存在编号是30和20的,但是这里依然可以插入不存在的编号。这样做是不可以的,失去了中间表的意义。
缺点2 : 如果中间表存在程序员的编号, 我们是可以删除程序员表对应的记录的。
在中间表中是存在编号为1的程序员的:
可是我们却可以删除程序员表coder中的编号为1的程序员:
编号为1的程序员张三已经被我们删除,但是在中间表coder_project中仍然还存在编号为1的程序员,这样对于中间表没有意义了。
说明:
创建第三张表的语句:
create table coder_project(
coder_id int ,--这个外键来自于coder表中的主键
project_id int--这个外键来自于project表中的主键
);
我们在创建第三张关系表时,表中的每一列,都是在使用其他两张表中的列,这时我们需要对第三张表中的列进行相应的约束。
当前第三张表中的列由于都是引用其他表中的列,我们把第三张表中的这些列称为引用其他表的外键约束。
给某个表中的某一列添加外键约束:
简化语法:
foreign key( 当前表中的列名 ) references 被引用表名(被引用表的列名);
foreign key( coder_id ) references coder(id);
注意:一般在开发中,被引用表的列名都是被引用表中的主键。
举例:
constraint [外键约束名称] foreign key(当前表中的列名) references 被引用表名(被引用表的列名)
举例:constraint coder_project_id foreign key(coder_id) references coder(id);
关键字解释:
constraint: 添加约束,可以不写
foreign key(当前表中的列名): 将某个字段作为外键
references 被引用表名(被引用表的列名) : 外键引用主表的主键
给第三张表添加外键约束有两种方式:
第一种方式:给已经存在的表添加外键约束:
-- 来自于程序员表
alter table coder_project add constraint c_id_fk foreign key(coder_id) references coder(id);
-- 来自于项目表
alter table coder_project add constraint p_id_fk foreign key(project_id) references project(id);
第二种方式:创建表时就添加外键约束:
create table coder_project(
coder_id int,
project_id int,
constraint c_id_fk foreign key(coder_id) references coder(id),
constraint p_id_fk foreign key(project_id) references project(id)
);
了解完如何给第三张表添加外键约束以后,我们就开始给上述创建好的第三张表添加外键约束。
在添加外键约束之前,由于刚才已经修改了表中的数据,所以我们先清空三张表,然后在添加外键约束。
操作步骤:
1、清空上述三张表:
2、增加外键约束:
3、添加完外键约束以后,就会在可视化工具中的架构设计器上查看表之间的关系
先选中表
然后点击右下角:
最后的表关系如下:
3、向三张表中分别插入数据:
-- 插入数据
insert into coder values(null,'张三',12000);
insert into coder values(null,'李四',15000);
insert into coder values(null,'王五',18000);
insert into project values(null,'QQ项目');
insert into project values(null,'微信项目');
insert into coder_project values(1,1);
insert into coder_project values(1,2);
insert into coder_project values(2,1);
insert into coder_project values(2,2);
insert into coder_project values(3,1);
4、测试外键约束是否起到作用:
A:执行以下语句:
B:执行以下语句:
再次执行上述语句的时候,发现报错了,而数据库表中的数据都没有改变,说明外键约束起到了作用。
小结:
1.为何引入外键约束?
让表的数据有效性,正确性。提高查询效率。
2.添加外键约束语法:
constraint 外键约束名 foreign key(当前表的字段名) references 主表(主键);
3.有了外键约束,那么要求添加数据需要先添加主表,然后添加从表。
4.有了外键约束,那么要求删除数据需要先删除从表,然后再删除主表。
外键的级联(掌握)
在修改和删除主表的主键时,同时更新或删除从表的外键值,称为级联操作
ON UPDATE CASCADE
-- 级联更新,主键发生更新时,外键也会更新
ON DELETE CASCADE
-- 级联删除,主键发生删除时,外键也会删除
具体操作:
- 删除三张表coder、coder_project、project表
- 重新创建三张表,添加级联更新和级联删除
-- 创建程序员表
create table coder(
id int primary key auto_increment,
name varchar(50),
salary double
);
-- 创建项目表
create table project(
id int primary key auto_increment,
name varchar(50)
);
create table coder_project(
coder_id int,
project_id int,
-- 添加外键约束,并且添加级联更新和级联删除
constraint c_id_fk foreign key(coder_id) references coder(id) ON UPDATE CASCADE ON DELETE CASCADE,
constraint p_id_fk foreign key(project_id) references project(id) ON UPDATE CASCADE ON DELETE CASCADE
);
再次添加数据到三张表:
-- 添加测试数据
insert into coder values(1,'张三',12000);
insert into coder values(2,'李四',15000);
insert into coder values(3,'王五',18000);
insert into project values(1,'QQ项目');
insert into project values(2,'微信项目');
insert into coder_project values(1,1);
insert into coder_project values(1,2);
insert into coder_project values(2,1);
insert into coder_project values(2,2);
insert into coder_project values(3,2);
需求1:修改主表coder表的id为3变为4.
需求2:删除主表coder表的id是4的行数据。
小结:
级联更新:ON UPDATE CASCADE 主键修改后,外键也会跟着修改
级联删除:ON DELETE CASCADE 主键删除后,外键对应的数据也会删除
一对多(掌握)
一对多的关系表:其中也有2个实体,但是其中A实体中的数据可以对应另外B实体中的多个数据,反过来B实体中的多个数据只能对应A实体中的一个数据。
例如:作者和小说关系,班级和学生,部门和员工,客户和订单
分析:
作者和小说:
一个作者可以写多部小说,但每一部小说,只能对应具体的一个作者。
具体的关系如下图所示:
注意:如果是一对多的关系,那么设计表的时候需要在多的一方增加一列,引入一的一方的主键作为自己的外键。
一对一
一对一关系表在实际开发中使用的并不多,其中也是2个实体,其中A实体中的数据只能对应B实体中的一个数据,同时B实体中的数据也只能对应A实体中的一个数据。例如:人和身份证对应关系,老公和老婆的对应关系。
而一对一在建表的时候,可以在任意一方的表中添加另外一方的主键作为外键即可。
建表原理如下图所示:
小结:
1.多对多:创建第三张表维护两张主表的关系,在第三张表中起码要有两列来自于两张主表的主键
2.一对多:在多方增加一列,来自于一方的主键作为外键
3.一对一:在任意一方将另一方的主键作为外键
9、表设计案例(练习)
目标
设计学生成绩管理系统数据表(按照给定需求设计即可)
1、每个教师可以教多门课程
2、每个课程由一个老师负责
3、每门课程可以由多个学生选修
4、每个学生可以选修多门课程
5、学生选修课程要有成绩
讲解
具体操作:
1)分析:
当我们拿到一个需求之后,首先应该分析这个需求中到底有多少名词,或者是当前这个需求中可以抽象出具体几个E-R图中的实体对象。
分析需求中存在的实体: 实体使用矩形表示。
学生、课程、老师。
当分析清楚具体的实体之后,接着要分析实体具备哪些属性?属性使用椭圆形表示。
学生:学号、姓名等。
课程:课程编号、课程名称等。
老师:工号、姓名等。
最后就要考虑实体和实体之间的关系问题:
老师和课程之间:一对多关系。一个老师可以教授多门课程,一个课程只能由一个老师负责。
学生和课程之间:多对多关系。每个学生可以选修多门课程,每门课程可以被多个学生来选修。
2)关于设计学生成绩管理系统的数据表的E-R图如下所示:
3)创建表sql语句:
画完E-R图之后,接下来我们就根据E-R图来创建具体的数据库表了。
学生选课管理系统的表创建:
思考:先创建哪张表?
不能先创建课程表,因为课程表需要有教师的工号,也不能创建中间表,因为中间表需要课程表和学生表的id,所以我们可以创建表的顺序如下:
-- 1、教师表
-- 2、课程表
-- 3、学生表
-- 4、学生课程表(中间关系表要有成绩属性)
创建表的语句如下:
-- 1、教师表
create table teacher(
id int primary key auto_increment,
name varchar(50)
);
-- 2、课程表
create table course(
id int primary key auto_increment,
name varchar(50),
teacher_id int,
foreign key(teacher_id) references teacher(id)
);
-- 3、学生表
create table student(
id int primary key auto_increment,
name varchar(50)
);
-- 4、学生课程表(中间关系表要有成绩属性)
create table studentcourse(
student_id int,
course_id int,
score double,
foreign key(student_id) references student(id),
foreign key(course_id) references course(id)
);
创建表之后的结构:
作业:多表练习
一对多关系练习
以下案例是我们JavaWeb课程最后的小项目.我们拿出其中一部分需求,根据需求来设计数据库表之间的关系
一个旅游线路分类中有多条旅游线路 , 一条旅游线路属于某一个分类 . 旅游线路表是多表 ,可以在多表上添加一个外键来执行分类表中的主键.
- 创建旅游线路分类表
CREATE TABLE tab_category (
cid INT PRIMARY KEY AUTO_INCREMENT, -- 旅游线路分类主键
cname VARCHAR(100) NOT NULL UNIQUE -- 旅游线路分类名称
);
- 添加旅游线路分类数据
INSERT INTO tab_category (cname) VALUES ('周边游'), ('出境游'), ('国内游'), ('港澳游');
- 创建旅游线路表
CREATE TABLE tab_route (
rid INT PRIMARY KEY AUTO_INCREMENT, -- 旅游线路主键
rname VARCHAR(100) NOT NULL UNIQUE, -- 旅游线路名称
price DOUBLE NOT NULL, -- 价格
cid INT NOT NULL, -- 所属分类
CONSTRAINT ro_cid_ref_cate_id FOREIGN KEY(cid) REFERENCES tab_category(cid)
);
- 添加旅游线路测试数据(自行添加)
多对多关系练习
一个用户可以收藏多个线路,一个线路可以被多个用户收藏 ,所以用户和线路之间是多对多的关系.对于多对多的关系我们需要增加一张中间表来维护他们之间的关系
- 创建用户表
CREATE TABLE tab_user (
uid INT PRIMARY KEY AUTO_INCREMENT, -- 用户id
username VARCHAR(100) NOT NULL UNIQUE -- 用户名
);
- 添加用户数据
INSERT INTO tab_user VALUES
(NULL, '老王'),
(NULL, '小王');
- 创建收藏表
CREATE TABLE tab_favorite (
fid INT PRIMARY KEY AUTO_INCREMENT, -- 收藏主键
rid INT NOT NULL, -- 旅游线路id
DATE DATE NOT NULL, -- 收藏时间
uid INT NOT NULL -- 用户id
);
注意 : 可以自己尝试着给收藏表添加外键约束
- 增加收藏表测试数据(自行添加)
第二部分(多表查询)
1、多表查询介绍(掌握)
目标
了解什么是多表查询,及多表查询的两种方式
讲解
什么是多表查询
同时查询多张表获取到需要的数据
比如:我们想查询水果的对应价格,需要将水果表和价格表同时进行查询
一种水果一种价格。
一种价格多种水果。
价格和水果属于一对多。
将一方即价格的主键作为多方即水果的外键。
多表查询的分类
小结
什么是多表查询?通过查询多张表获取我们想要的数据
2、笛卡尔积现象
目标
能够说出什么是笛卡尔积,以及如何消除笛卡尔积
讲解
准备数据
有两张表,一张是水果表fruit,一张是价格表price。
分析:
一种水果一种价格。
一个价格对应多种水果。
价格和水果是1对多
价格是1 水果是多
表设计原则:将价格的主键作为水果的外键
建表:
-- 价格 1
create table price(
id int primary key auto_increment,
price double
);
-- 水果 n
create table fruit(
id int primary key auto_increment,
name varchar(20) not null,
price_id int,
foreign key(price_id) references price(id)
);
insert into price values(1,2.30);
insert into price values(2,3.50);
insert into price values(4,null);
insert into fruit values(1,'苹果',1);
insert into fruit values(2,'橘子',2);
insert into fruit values(3,'香蕉',null);
-- 一种水果有一个价格 一个价格对应多种水果
-- 价格 1 水果 n 水果将价格主键作为外键
什么是笛卡尔积现象
需求:查询两张表中关于水果的信息,要显示水果名称和水果价格。
具体操作:
多表查询语法:select * from fruit,price;
查询结果:
产生上述查询结果的原因:
说明:
fruit表中的每一条记录,都和price表中的每一条进行匹配连接。所得到的最终结果是:fruit表中的条目数乘以price表中的数据的条目数。
将fruit表的每行记录和price表的每行记录组合的结果就是笛卡尔积。
笛卡尔积问题:把多张表放在一起,同时去查询,会得到一个结果,而这结果并不是我们想要的数据,这个结果称为笛卡尔积。
笛卡尔积缺点:查询到的结果冗余了,里面有很多错误的数据,需要过滤。
举例:上述的笛卡尔积结果中只有两行结果是正确的:
1 苹果 1 1 2.3
2 橘子 2 2 3.5
笛卡尔积的数据,对程序是没有意义的, 我们需要对笛卡尔积中的数据再次进行过滤。
对于多表查询操作,需要过滤出满足条件的数据,需要把多个表进行连接,连接之后需要加上过滤的条件。
如何清除笛卡尔积现象的影响
解决上述查询的方案:在查询两张表的同时添加条件进行过滤,比如fruit表的id和必须和price表的id相同。
小结
-
能够说出什么是笛卡尔积?
左表的每条记录和右表的每条记录会组合起来 -
如何消除笛卡尔积
只查询满足要求的数据,通常都是外键等于主键 -
给表起别名:
-- 别名写法:表名 as 别名 这里as可以省略 select * from fruit as f,price as p where f.price_id = p.id;
3、内连接
目标
能够掌握内连接的使用
讲解
什么是内连接
用左边表的记录去匹配右边表的记录,如果符合条件的则显示。内连接查询的结果:两表的公共部分。
隐式内连接
隐式内连接:看不到JOIN
关键字,条件使用WHERE
指定
语法:
select 列名 , 列名 .... from 表名1,表名2,.... where 表名1.列名 = 表名2.列名;
select * from fruit,price where fruit.price_id = price.id;
说明:在产生两张表的笛卡尔积的数据后,通过条件筛选出正确的结果。
显示内连接(掌握)
显示内连接:使用INNER JOIN ... ON
语句, 可以省略INNER
语法:
select * from 表名1 inner join 表名2 inner join 表名3 inner join 表名4 ..... on 多个条件;
或者
select * from 表名1 join 表名2 on 条件
具体操作:
- 使用显示内连接解决上述笛卡尔积问题
说明:显示的内连接,一般称为标准的内连接,有inner join,查询到的数据为两个表经过on条件过滤后的笛卡尔积。
- 应用
查询苹果的信息,显示苹果id,名字,和价格。
1.确定查询哪些表
2.确定表连接条件,水果表.price_id = 价格表.id 的数据才是有效的
3.确定查询条件,我们查询的是苹果的信息,水果表.name='苹果'
4.确定查询字段,查询苹果的信息,显示苹果id,名字,和价格
5.我们发现写表名有点长,可以给表取别名,显示的字段名也使用别名
-- 1.确定查询哪些表
select * from fruit f inner join price p;
-- 2.确定表连接条件
select * from fruit f inner join price p on f.price_id = p.id;
-- 3.确定查询条件,我们查询的是苹果的信息,水果表.name='苹果'
select * from fruit f inner join price p on f.price_id = p.id where f.name='苹果';
-- 4. 确定查询哪些列
select f.id,f.name,p.id,p.price from fruit f inner join price p on f.price_id = p.id where f.name='苹果';
小结
- 什么是隐式内连接和显示内连接?
隐式内连接:看不到JOIN:select 列名 , 列名 .... from 表名1,表名2 where 表名1.列名 = 表名2.列名;
显示内连接:看得到JOIN:select * from 表名1 inner join 表名2 on 条件; - 内连接查询步骤?
1.确定查询几张表
2.确定表连接条件
3.根据需要在操作
4、左外连接
目标
能够掌握左外连接查询
讲解
左外连接原理如下所示:
左外连接可以理解为:用左边表去右边表中查询对应记录,不管是否找到,都将显示左边表中全部记录。
举例:上述案例中虽然右表没有香蕉对应的价格,也要把他查询出来。
左外连接:使用LEFT OUTER JOIN ... ON
,OUTER
可以省略
select * from 表1 left outer join 表2 on 条件;
把left 关键字之前的表,是定义为左侧。 left关键字之后的表,定义右侧。
查询的内容,以左侧的表为主,如果左侧有数据,右侧没有对应的数据,仍然会把左侧数据进行显示。
具体操作:
- 不管能否查到水果对应的价格,都要把水果显示出来。
分析:香蕉是没有价格的,但是由于香蕉位于左边的表中,所以即使香蕉的价格是null,也会将香蕉的信息显示出来。
小结
-
掌握左外连接查询格式?
select * from 表1 left outer join 表2 on 条件; -- 表1看作为左表,表2看做为右表
-
左外连接查询特点?
在满足要求的基础上保证左表的数据全部显示
5、右外连接
目标
能够掌握右外连接查询
讲解
右外连接原理如下所示:
用右边表去左边表查询对应记录,不管是否找到,右边表全部记录都将显示。
举例:上述案例中不管在左方表能否找到右方价格对应的水果,都要把右方的价格显示出来。
右外连接:使用RIGHT OUTER JOIN ... ON
,OUTER
可以省略
语法:select * from 表1 right outer join 表2 on 条件;
说明:如果右侧有数据,左侧没匹配到,把右侧的数据显示出来。
right之前的是左侧,right之后的是右侧。
具体操作:
- 需求:不管能否查到价格对应的水果,都要把价格显示出来。
分析:在price表中id为4到fruit表中查询是没有对应水果描述的,但是使用右外连接也会将price表中的价格查询出来。
注意:其实关于左外连接和右外连接只记住一种就可以,只需将表的前后位置换一下就可以达到互换。
需求:使用左外连接达到上述右外连接的效果。
小结
-
掌握右外连接查询格式?
select * from 表1 right outer join 表2 on 条件;
-
右外连接查询特点?
在满足要求的基础上,保证右表的数据全部显示. -
其实对于左右外连接我们只需要掌握一种,即可,因为左右外可以互换。
-- 右外连接:查询right右边表以及两张表的公共部分 -- 需求:不管能否查到价格对应的水果,都要把价格显示出来。 -- 在right右边的表2是右表,以右表为主 select * from fruit f right outer join price p on f.price_id = p.id; -- 需求:使用左外连接使用上述功能: -- 不管能否查到价格对应的水果,都要把价格显示出来。 select * from price p left outer join fruit f on f.price_id = p.id;
6、子查询
目标
能够掌握子查询的概念
能够理解子查询的三种情况
讲解
准备数据:
-- 创建部门表 1
CREATE TABLE dept (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20)
);
INSERT INTO dept (NAME) VALUES ('开发部'),('市场部'),('财务部');
-- 创建员工表 n
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);
什么是子查询
一条查询语句结果作为另一条查询语法一部分。
SELECT 查询字段 FROM 表 WHERE 条件;
举例:
SELECT * FROM employee WHERE salary=(SELECT MAX(salary) FROM employee);
说明:子查询需要放在()中
子查询结果的三种情况
- 子查询的结果是单行单列的时候
- 子查询的结果是多行单列的时候
- 子查询的结果是多行多列
小结
- 什么是子查询?
一个查询的结果作为另一个查询语句的一部分 - 子查询结果的三种情况?
单行单列
多行单列
多行多列
7、子查询的结果是单行单列的时候
目标
能够掌握子查询的结果是单行单列的查询
讲解
子查询结果是单列
,在WHERE
后面作为条件
SELECT 查询字段 FROM 表 WHERE 字段=(子查询);
-
查询工资最高的员工是谁?
- 查询最高工资是多少
SELECT MAX(salary) FROM emp;
2.根据最高工资到员工表查询到对应的员工信息
SELECT * FROM emp WHERE salary=(SELECT MAX(salary) FROM emp);
-
查询工资小于平均工资的员工有哪些?
- 查询平均工资是多少
SELECT AVG(salary) FROM emp;
2.到员工表查询小于平均的员工信息
SELECT * FROM emp WHERE salary < (SELECT AVG(salary) FROM emp);
小结
子查询的结果是单行单列时父查询如何处理?
SELECT 查询字段 FROM 表 WHERE 字段=(子查询);
通常作为父查询的条件
8、子查询结果是多行单列的时候
目标
能够掌握子查询的结果是多行单列的查询
讲解
子查询结果是多行单列,结果集类似于一个数组,在WHERE
后面作为条件
,父查询使用IN
运算符
SELECT 查询字段 FROM 表 WHERE 字段 IN (子查询);
-- in表示在数值中
-
查询工资大于5000的员工,来自于哪些部门的名字
- 先查询大于5000的员工所在的部门id
SELECT dept_id FROM emp WHERE salary > 5000;
- 再查询在这些部门id中部门的名字
SELECT dept.name FROM dept WHERE dept.id IN (SELECT dept_id FROM emp WHERE salary > 5000);
-
查询开发部与财务部所有的员工信息
- 先查询开发部与财务部的id
SELECT id FROM dept WHERE NAME IN('开发部','财务部');
- 再查询在这些部门id中有哪些员工
SELECT * FROM emp WHERE dept_id IN (SELECT id FROM dept WHERE NAME IN('开发部','财务部'));
小结
1.子查询的结果是多行单列时父查询如何处理?
放在父查询的条件位置,使用in
2.in用法:in(值1,值2,....) 只要满足一个即可,或者的关系
9、子查询的结果是多行多列
目标
能够掌握子查询的结果是多行多列的查询
讲解
子查询结果是多列
,在FROM
后面作为表
SELECT 查询字段 FROM (子查询) 表别名 WHERE 条件;
注意:子查询作为表需要取别名,使用as,可以省略。否则这张表没用名称无法访问表中的字段
-
查询出2011年以后入职的员工信息,包括部门名称
- 在员工表中查询2011-1-1以后入职的员工
SELECT * FROM emp WHERE join_date > '2011-1-1';
- 查询所有的部门信息,与上面的虚拟表中的信息组合,找出所有部门id等于dept_id
SELECT * FROM dept d, (SELECT * FROM emp WHERE join_date > '2011-1-1') e WHERE e.dept_id = d.id;
使用表连接:
SELECT d.*, e.* FROM dept d INNER JOIN emp e ON d.id = e.dept_id WHERE e.join_date > '2011-1-1';
小结
三种子查询情况:单行单列,多行单列,多行多列
单行单列:作为父查询的条件
多行单列:作为父查询的条件,通常使用 IN
多行多列:作为父查询的一张表(虚拟表),起别名
10、多表查询案例(子查询)
我们在公司开发中,根据不同的业务需求往往需要通过2张及以上的表中去查询需要的数据。所以我们有必要学习2张及以上的表的查询。其实不管是几张表的查询,都是有规律可循的。
准备数据
重新新建一个数据库,然后在新建的数据库中创建如下数据表,并插入数据:
teacher 教师表
student 学生表
cource 课程表
studentcource 选课表 学生和课程的关系表
create table teacher (
id int(11) primary key auto_increment,
name varchar(20) not null unique
);
create table student (
id int(11) primary key auto_increment,
name varchar(20) NOT NULL unique,
city varchar(40) NOT NULL,
age int
) ;
create table course(
id int(11) primary key auto_increment,
name varchar(20) not null unique,
teacher_id int(11) not null,
foreign key(teacher_id) references teacher (id)
);
create table studentcourse (
student_id int NOT NULL,
course_id int NOT NULL,
score double NOT NULL,
foreign key (student_id) references student (id),
foreign key (course_id) references course (id)
);
insert into teacher values(null,'关羽');
insert into teacher values(null,'张飞');
insert into teacher values(null,'赵云');
insert into student values(null,'小王','北京',20);
insert into student values(null,'小李','上海',18);
insert into student values(null,'小周','北京',22);
insert into student values(null,'小刘','北京',21);
insert into student values(null,'小张','上海',22);
insert into student values(null,'小赵','北京',17);
insert into student values(null,'小蒋','上海',23);
insert into student values(null,'小韩','北京',25);
insert into student values(null,'小魏','上海',18);
insert into student values(null,'小明','广州',20);
insert into course values(null,'语文',1);
insert into course values(null,'数学',1);
insert into course values(null,'生物',2);
insert into course values(null,'化学',2);
insert into course values(null,'物理',2);
insert into course values(null,'英语',3);
insert into studentcourse values(1,1,80);
insert into studentcourse values(1,2,90);
insert into studentcourse values(1,3,85);
insert into studentcourse values(1,4,78);
insert into studentcourse values(2,2,53);
insert into studentcourse values(2,3,77);
insert into studentcourse values(2,5,80);
insert into studentcourse values(3,1,71);
insert into studentcourse values(3,2,70);
insert into studentcourse values(3,4,80);
insert into studentcourse values(3,5,65);
insert into studentcourse values(3,6,75);
insert into studentcourse values(4,2,90);
insert into studentcourse values(4,3,80);
insert into studentcourse values(4,4,70);
insert into studentcourse values(4,6,95);
insert into studentcourse values(5,1,60);
insert into studentcourse values(5,2,70);
insert into studentcourse values(5,5,80);
insert into studentcourse values(5,6,69);
insert into studentcourse values(6,1,76);
insert into studentcourse values(6,2,88);
insert into studentcourse values(6,3,87);
insert into studentcourse values(7,4,80);
insert into studentcourse values(8,2,71);
insert into studentcourse values(8,3,58);
insert into studentcourse values(8,5,68);
insert into studentcourse values(9,2,88);
insert into studentcourse values(10,1,77);
insert into studentcourse values(10,2,76);
insert into studentcourse values(10,3,80);
insert into studentcourse values(10,4,85);
insert into studentcourse values(10,5,83);
分析4张表的关系:
练习1
目标
查询获得最高分的学生信息。
讲解
具体操作:
分析:
1)在中间表中找最高分;
2)在中间表中找最高分对应的学生编号;
3)在学生表中根据学生编号找学生信息;
练习2
目标
查询编号是2的课程比编号是1的课程最高成绩高的学生信息。
讲解
具体操作:
课程编号和对应的成绩的部分数据:
分析:
1)在中间表中 找编号是1的课程的最高成绩;
2)在中间表中 找编号是2的成绩 > 编号1最高成绩 的学生id;
3)在学生表中 根据学生的编号 找对应的学生信息;
Sql语句如下所示:
-- 需求2:查询编号2课程 比 编号1课程最高成绩高学生信息:
-- 2.1 在中间表 找编号1课程的最高成绩
select max(score)
from studentcourse
where course_id=1;
-- 2.2 在中间表找 编号2的成绩 > 编号1最高成绩 的学生id
select student_id
from studentcourse
where course_id=2 and score>(select max(score)
from studentcourse
where course_id=1);
-- 2.3 在学生表 根据编号 找对应的学生信息
select *
from student
where id in (select student_id
from studentcourse
where course_id=2 and score>(select max(score)
from studentcourse
where course_id=1));
查询结果:
练习3
目标
查询编号是2的课程比编号是1的课程最高成绩高的学生姓名和成绩。
讲解
具体操作:
分析:
1)在中间表中 找编号是1的课程的最高成绩;
2)在中间表中 找编号是2的成绩 > 编号1最高成绩 的学生id和成绩;
3)将上述查询出来的内容作为临时表 和 学生表关联, 查询姓名和成绩
-- 需求3:查询编号2课程比编号1课程最高成绩高学生姓名和成绩 (临时表)
-- 2.1 在中间表 找编号1课程的最高成绩
select max(score)
from studentcourse
where course_id=1;
-- 2.2 在中间表 编号2的成绩 > 编号1最高成绩 的学生id,成绩
select student_id,score
from studentcourse
where course_id=2 and score>(select max(score)
from studentcourse
where course_id=1);
-- 2.3 将上述查询出来的内容作为临时表 和 学生表关联, 查询姓名和成绩
select student.name, temp.score
from student, (select student_id,score
from studentcourse
where course_id=2 and score>(select max(score)
from studentcourse
where course_id=1)) as temp
where student.id=temp.student_id;
练习4
目标
查询每个同学的学号、姓名、选课数、总成绩。
讲解
分析:
1)、在中间表中查询每个学生的选课数和总成绩,遇到每个,分组,按照学生学号进行分组;
2)、由于还得显示学号和姓名,并且姓名在student表中,所以我们将上述结果作为临时表和学生表关联。
目的是查找临时表和student表中学号相等时查找学号,姓名,选课数,总成绩。
-- 2、查询所有同学的学号、姓名、选课数、总成绩
-- 2.1 在中间表 查询每个学生的选课数和总成绩
select student_id,count(*),sum(score)
from studentcourse
group by student_id;
-- 2.2 将2.1的结果作为临时表和学生表关联,
-- 目的: 显示 学号、姓名、选课数、总成绩
select student.id,student.name,temp.cou,temp.sumScore
from student,(select student_id,count(*) as cou,sum(score) as sumScore
from studentcourse
group by student_id) as temp
where student.id=temp.student_id;
注意:
如果我们想使用聚合函数作为查找的结果,并且聚合函数存在子查询语句中,那么我们不能直接将聚合函数写在select后面,我们此时应该给聚合函数取别名。
11、 多表查询案例(连接查询)
我们在公司开发中,根据不同的业务需求往往需要通过2张及以上的表中去查询需要的数据。所以我们有必要学习2张及以上的表的查询。其实不管是几张表的查询,都是有规律可循的。
1.准备数据
-- 部门表
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);
分析4张表的关系:通过4张表可以查出一个员工的所有信息
说明:一个部门有多种职务,例如销售部有经理和销售员。一种职务可以在多个部门,例如文员可以在学工部和教研部。
2 练习
1.1 练习1
查询所有员工信息。显示员工编号,员工姓名,工资,职务名称,职务描述。
具体操作:
1.确定要查询哪些表:emp e, job j
SELECT * FROM emp e INNER JOIN job j;
2.确定表连接条件: e.job_id=j.id
SELECT * FROM emp e INNER JOIN job j ON e.job_id=j.id;
3.确定查询字段:员工编号,员工姓名,工资,职务名称,职务描述
SELECT e.id, e.ename, e.salary, j.jname, j.description FROM emp e INNER JOIN job j ON e.job_id=j.id;
1.2 练习2
查询所有员工信息。显示员工编号,员工姓名,工资,职务名称,职务描述,部门名称,部门位置
具体操作:
-
确定要查询哪些表,emp e, job j, dept d
SELECT * FROM emp e INNER JOIN job j INNER JOIN dept d;
2. 确定表连接条件 e.job_id=j.id and e.dept_id=d.id
SELECT * FROM emp e INNER JOIN job j INNER JOIN dept d ON e.job_id=j.id AND e.dept_id=d.id;
3. 确定查询字段:员工编号,员工姓名,工资,职务名称,职务描述,部门名称,部门位置
SELECT e.id, e.ename, e.salary, j.jname, j.description, d.dname, d.loc FROM emp e INNER JOIN job j INNER JOIN dept d ON e.job_id=j.id AND e.dept_id=d.id;
或者:
SELECT e.id,e.ename,e.salary,j.jname,j.description,d.dname,d.loc
FROM emp e INNER JOIN job j ON e.job_id = j.id INNER JOIN dept d on e.dept_id = d.id;
1.3 练习3
查询所有员工信息。显示员工姓名,工资,职务名称,职务描述,部门名称,部门位置,工资等级。
具体操作:
1. 确定要查询哪些表,emp e, job j, dept d, salarygrade s
SELECT * FROM emp e INNER JOIN job j INNER JOIN dept d INNER JOIN salarygrade s;
2. 确定表连接条件 e.job_id=j.id and e.dept_id=d.id and e.salary between s.losalary and s.hisalary
SELECT * FROM emp e INNER JOIN job j INNER JOIN dept d INNER JOIN salarygrade s ON e.job_id=j.id AND e.dept_id=d.id AND e.salary BETWEEN s.losalary AND s.hisalary;
3. 确定查询字段:员工姓名,工资,职务名称,职务描述,部门名称,部门位置,工资等级
SELECT e.ename, e.salary, j.jname, j.description, d.dname, d.loc, s.grade FROM emp e INNER JOIN job j INNER JOIN dept d INNER JOIN salarygrade s ON e.job_id=j.id AND e.dept_id=d.id AND e.salary BETWEEN s.losalary AND s.hisalary;
1.3.1 多表查询规律总结
- 不管我们查询几张表,表连接查询会产出笛卡尔积,我们需要消除笛卡尔积,拿到正确的数据。我们需要找到表与表之间通过哪个字段关联起来的(通常是
外键=主键
) - 消除笛卡尔积规律:2张表需要1个条件,3张表需要2个条件,4张表需要3个条件。(条件数量=表的数量-1),每张表都要参与进来
- 多表连接查询步骤:
3.1. 确定要查询哪些表
3.2. 确定表连接条件
3.3. 确定查询字段
1.4 练习4
查询经理的信息。显示员工姓名,工资,职务名称,职务描述,部门名称,部门位置,工资等级
具体操作:
1. 确定要查询哪些表,emp e, job j, dept d, salarygrade s
SELECT * FROM emp e INNER JOIN job j INNER JOIN dept d INNER JOIN salarygrade s;
2. 确定表连接条件 e.job_id=j.id and e.dept_id=d.id and e.salary between s.losalary and s.hisalary
SELECT * FROM emp e INNER JOIN job j INNER JOIN dept d INNER JOIN salarygrade s ON e.job_id=j.id AND e.dept_id=d.id AND e.salary BETWEEN s.losalary AND s.hisalary;
额外条件:只需要查询经理的信息(j.jname='经理')
3. 确定查询字段:员工姓名,工资,职务名称,职务描述,部门名称,部门位置,工资等级
SELECT e.ename, e.salary, j.jname, j.description, d.dname, d.loc, s.grade FROM emp e INNER JOIN job j INNER JOIN dept d INNER JOIN salarygrade s ON e.job_id=j.id AND e.dept_id=d.id AND e.salary BETWEEN s.losalary AND s.hisalary AND j.jname='经理';
1.5 练习5
查询出每个部门的部门编号、部门名称、部门位置、部门人数
具体操作:
1. 去员工表中找到每个部门的人数和部门id
SELECT dept_id, COUNT(*) FROM emp GROUP BY dept_id;
2. 再和部门表连接查询
SELECT * FROM dept d INNER JOIN (SELECT dept_id, COUNT(*) FROM emp GROUP BY dept_id) e ON e.dept_id=d.id;
3. 显示对应的字段
SELECT d.id, d.dname, d.loc, e.total 部门人数 FROM dept d INNER JOIN (SELECT dept_id, COUNT(*) total FROM emp GROUP BY dept_id) e ON e.dept_id=d.id;
最终效果:
学习总结
-
能够使用内连接进行多表查询
select 列名 from 左表,右表 where 条件 select 列名 from 左表 inner join 右表 on 条件
-
能够使用左外连接和右外连接进行多表查询
select 列名 from 左表 left join 右表 on 条件
select 列名 from 左表 right join 右表 on 条件
-
能够使用子查询进行多表查询
- 单行单列:父查询使用比较运算符
- 多行单列:父查询使用in
- 多行多列:将查询结果做成一张虚拟表,再次查询
-
能够理解多表查询的规律
第三部分(事务)
1、事务的概念
目标
能够理解事务的概念
讲解
事务的应用场景说明
关于事务在实际中的应用场景:
假设我在淘宝买了一部手机,然后当我付完款,钱已经从我的账户中扣除。正当此时,淘宝转账系统宕机了,那么此时淘宝还没有收到钱,而我的账户的钱已经减少了,这样就会导致我作为买家钱已经付过,而卖家还没有收到钱,他们不会发货物给我。这样做显然是不合理。实际生活中是如果淘宝出问题,作为用户的账户中钱是不应该减少的。这样用户就不会损失钱。
还有种情况,就是当我付完款之后,卖家看到我付款成功,然后直接发货了,我如果有权限操作,我可以撤销,这样就会导致我的钱没有减少,但是卖家已经发货,同样这种问题在实际生活中也是不允许出现的。
关于上述两种情况,使用数据库中的事务可以解决。具体解决方案如下图所示:
说明:在数据库中查询不会涉及到使用事务,都是增删改。
什么是事务
在实际的业务开发中,有些业务操作要多次访问数据库。一个业务要发送多条SQL语句给数据库执行。需要将多次访问数据库的操作视为一个整体来执行,要么所有的SQL语句全部执行成功。如果其中有一条SQL语句失败,就进行事务的回滚,所有的SQL语句全部执行失败。
简而言之,事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全都成功,要么全都失败。
事务作用:保证在一个事务中多次操作数据库表中数据时,要么全都成功,要么全都失败。
小结
什么是事务?多条SQL组合再一起完成某个功能.
2、手动提交事务
目标
能够使用手动的方式提交事务
讲解
MYSQL中可以有两种方式进行事务的操作:
- 手动提交事务:先开启,再提交
- 自动提交事务(默认的):即执行一条sql语句提交一次事务。
事务有关的SQL语句:
SQL语句 | 描述 |
---|---|
start transaction; | 开启手动控制事务 |
commit; | 提交事务 |
rollback; | 回滚事务 |
手动提交事务使用步骤
第1种情况:开启事务 -> 执行SQL语句 -> 成功 -> 提交事务
第2种情况:开启事务 -> 执行SQL语句 -> 失败 -> 回滚事务
准备数据:
# 创建一个表:账户表.
create database day04_db;
# 使用数据库
use day04_db;
# 创建账号表
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
# 初始化数据
insert into account values (null,'a',1000);
insert into account values (null,'b',1000);
案例演示1:需求:演示提交事务,a给b转账100元。
案例演示2:演示回滚事务,a给b转账100元。(失败)
注意:
事务是如何处理正常情况的呢?
a=1000 b=1000
开启事务(start transaction;)
update account set money = money -100 where name='a';
update account set money = money +100 where name='b';
提交事务(commit;) (事务提交之后,sql语句对数据库产生的操作才会被永久的保存)
事务是如何处理异常情况的呢?
a=1000 b=1000
开启事务(start transaction;)
update t_account set money = money -100 where name='a'; a=900
出现异常
update t_account set money = money +100 where name='b';
事务的回滚(rollback;)(撤销已经成功执行的sql语句,回到开启事务之前的状态)
a=1000 b=1000;
注意:只要提交事务,那么数据就会长久保存了,就不能回滚事务了。即提交或者回滚事务都是代表结束当前事务的操作。
小结
- 如何开启事务: start transaction;
- 如何提交事务: commit; 永久性改变
- 如何回滚事务: rollback; 回到开启事务最初的状态
3、自动提交事务
目标
了解自动提交事务
能够关闭自动提交事务
讲解
MySQL的每一条DML(增删改)语句都是一个单独的事务,每条语句都会自动开启一个事务,执行完毕自动提交事务,MySQL默认开始自动提交事务。自动提交,通过修改mysql全局变量“autocommit”进行控制。
1.通过以下命令可以查看当前autocommit模式:
show variables like '%commit%';
2.设置自动提交的参数为OFF:
set autocommit = 0; -- 0:OFF 1:ON
小结
1)MySql默认自动提交。即执行一条sql语句提交一次事务。
2)设置autocommit为off状态,只是临时性的,下次重新连接mysql,autocommit依然变为on状态。
3)如果设置autocommit为off状态,那么当我们执行一条sql语句,就不会自动提交事务,重新启动可视化工具,数据并没有改变。
4)如果设置autocommit为on状态,如果我们先执行 start transaction; 然后在执行修改数据库的语句:
update account set money = money-100 where name='a';
update account set money = money+100 where name='b';
那么此时就表示上述修改数据库的sql语句都在同一个事务中,此时必须手动提交事务,即commit;
换句话说,如果我们手动开启事务 start transaction; 那么此时mysql就不会自动提交事务,必须手动提交事务。
5)如果设置autocommit为on状态,如果我们不执行 start transaction; 直接执行修改数据库的语句:
update account set money = money-100 where name='a';
update account set money = money+100 where name='b';
那么此时mysql就会自动提交事务。即上述每条sql语句就是一个事务。
课堂代码演示:
-- 自动提交事务:每条sql语句就是一个事务,那么执行一条sql语句就会提交一次事务
-- mysql数据库就是自动提交事务
-- a给b转账100元
update account set money=money-100 where name='a';
update account set money=money+100 where name='b';
-- 查看mysql是否自动提交事务
-- autocommit的值是on表示自动提交事务,值是off表示关闭自动提交事务
show variables like '%commit%';
-- 我们可以使用命令临时设置mysql变为手动提交事务,即将自动提交事务关闭
-- 下次重新连接mysql依然是自动提交事务
set autocommit = 0; -- 0 表示关闭自动提交事务 1表示开启自动事务
update account set money=money-100 where name='a'
4、原理
原理说明
- 一个用户登录成功以后,服务器会创建一个临时日志文件。日志文件用来保存用户事务状态。
- 如果没有使用事务,则所有的操作直接写到数据库中,不会使用日志文件。
- 如果开启事务,将所有的写操作写到日志文件中。
- 如果这时用户提交了事务,则将日志文件中所有的操作写到数据库中。
- 如果用户回滚事务,则日志文件会被清空,不会影响到数据库的操作。
事务操作小结
说出事务原理?
开启事务后,SQL语句会放在临时的日志文件中,如果提交事务,将日志文件中SQL的结果放在数据库中
如果回滚事务清空日志文件.
事务的操作 | MySQL操作事务的语句 |
---|---|
手动开启事务 | start transaction |
手动提交事务 | commit |
手动回滚事务 | rollback |
查询事务的自动提交情况 | show variables like '%commit%'; |
设置事务的手动提交方式 | set autocommit = 0 -- 关闭自动提交 |
5、事务的四大特性(ACID)(面试)
目标
了解事务的四大特性
讲解
数据库的事务必须具备ACID特性,ACID是指 Atomicity(原子性)、Consistensy(一致性)、Isolation(隔离性)和Durability(持久性)的英文缩写。
1、隔离性(Isolation)
多个用户并发的访问数据库时,一个用户的事务不能被其他用户的事务干扰,多个并发的事务之间要相互隔离。
一个事务的成功或者失败对于其他的事务是没有影响。2个事务应该相互独立。
举例:
a 给b转账 -----》叫做事务A
c 给d 转账 -----》叫做事务B
事务A和事务B之间不会相互影响。
2、持久性(Durability)
指一个事务一旦被提交,它对数据库的改变将是永久性的,哪怕数据库发生异常,重启之后数据亦然存在。
举例:
a=1000、b=1000转账
开启事务
a-100
b+100
提交
结果: a 900 b 1100
即使事务提交以后再发生异常,a和b的数据依然不会变。a就是900 b就是1100。
3、原子性(Atomicity)
原子性是指事务包装的一组sql(一组业务逻辑)是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
4、一致性(Consistency)
一个事务在执行之前和执行之后 数据库都必须处于一致性状态。
如果事务成功的完成,那么数据库的所有变化将生效。
如果事务执行出现错误,那么数据库的所有变化将会被回滚(撤销),返回到原始状态。
事务的成功与失败,最终数据库的数据都是符合实际生活的业务逻辑。一致性绝大多数依赖业务逻辑和原子性。
举例1: a=1000、b=1000 转账 100
a - 100
b + 100
结果: a + b = 2000
如果a转账失败了,那么b也得失败。不能因为a失败了,a依然是1000.但是b却成功了,b却变成了1100.那么结果是2100了,这样是不符合事务的一致性的。
小结
事务四个特性?
原子性
一致性
隔离性
持久性
事务特性 | 含义 |
---|---|
一致性(Consistency) | 事务前后数据的完整性必须保持一致 |
原子性(Atomicity) | 事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 |
隔离性(Isolation) | 是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离,不能相互影响。 |
持久性(Durability) | 指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响 |
6、事务的并发访问引发的三个问题(面试)
目标
能够理解并发访问的三个问题
讲解
事务在操作时的理想状态:多个事务之间互不影响,如果隔离级别设置不当就可能引发并发访问问题。
并发访问的问题 | 含义 |
---|---|
脏读 | 一个事务读取到了另一个事务中尚未提交的数据。最严重,杜绝发生。 |
不可重复读 | 一个事务中两次读取的数据内容不一致,要求的是一个事务中多次读取时数据是不一致的,这是事务update时引发的问题 |
幻读(虚读) | 一个事务内读取到了别的事务插入或者删除的数据,导致前后读取记录行数不同。这是insert或delete时引发的问题 |
1.脏读:指一个事务读取了另外一个事务未提交的数据。(非常危险)
脏读具体解释如下图所示:注意脏读的前提是没有事务的隔离性。
说明:事务a首先执行转账操作,然后事务a还没有提交数据的情况下,事务b读取了数据库的数据。紧接着事务a执行回滚操作,导致事务b读取的结果和数据库的实际数据是不一样的。
一个事务读取了另一个事务未提交的数据叫做脏读。
举例:
a 转账 给b 100,未提交
b 查询账户多了100
a 回滚
b 查询账户那100不见了。
一个事务读取了另一个事务没有提交的数据,非常严重,必须避免脏读。
2.不可重复读:在一个事务内多次读取表中的数据,多次读取的结果不同。
说明:事务b首先读取数据库的数据,然后事务a对数据修改并提交。之后事务b对数据库再次进行读取。这时发现在事务b中2次读取的结果不一致。
一个事务内读取了另一个事务提交的数据。这个叫做不可重复读。
不可重复读和脏读的区别:
脏读:强调的是读取了未提交的数据。
不可重复读:一个事务内2次读取,其中一次读取了另一个事务提交了的数据。
例如: 银行想查询A账户的余额,第一次查询的结果是200元,A向账户中又存了100元。此时,银行再次查询的结果变成了300元。两次查询的结果不一致,银行就会很困惑,以哪次为准。
和脏读不同的是:脏读读取的是前一事务未提交的数据,不可重复度 读取的是前一事务已提交的事务。
很多人认为这有啥好困惑的,肯定是以后面的结果为准了。我们需要考虑这样一种情况,查询A账户的余额,一个打印到控制台,一个输出到硬盘上,同一个事务中只是顺序不同,两次查询结果不一致,到底以哪个为准,你会不会困惑呢?
当前事务查询A账户的余额为100元,另外一个事务更新余额为300元并提交,导致当前事务使用同一查询结果却变成了300元。
3.幻读(虚读):一个事务内读取到了别的事务插入或者删除的数据,导致前后读取记录行数不同
说明:事务b首先读取数据的数量,然后事务a添加了一条数据,并且提交了。接着事务b再次读取了数据的数量。2次读取不一致。
同一个事务内,2次读取的数据的数量不一致,叫做幻读或者虚读。
虚读(幻读)和不可重复读的区别:
不可重复读:强调的是数据内容的不一致。另一个事务是update操作。
虚读(幻读):强调的数据的数量(记录数)的不一致。另一个事务是insert或者delete操作。
注意:
指在一个事务中 读取 另一个事务 插入或删除 数据记录,导致当前事务读取数据的记录数前后不一致。
一个事务读取另一个事务已经提交的数据,强调的是记录数的变化,常用sql类型为 insert和 delete。
小结
- 能够理解并发访问的三个问题
赃读:一个事务读取另一个事务还没有提交的数据,一定避免。
不可重复读:一个事务读取多次数据内容不一样,主要是update语句。事务已经提交了。 可以发生的。
幻读:一个事务读取多次数量不一样,主要是delete或者insert语句。事务已经提交了。可以发生的。
7、事务的隔离级别
目标
能够说出mysql的四种隔离级别
讲解
1、通过以上问题演示,我们发现如果不考虑事务的隔离性,会遇到脏读、不可重复读和虚读等问题。所以在数据库中我们要对上述三种问题进行解决。MySQL数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。
上面的级别最低,下面的级别最高。“是”表示会出现这种问题,“否”表示不会出现这种问题。
级别 | 名字 | 隔离级别 | 脏读 | 不可重复读 | 幻读 | 数据库默认隔离级别 |
---|---|---|---|---|---|---|
1 | 读未提交 | read uncommitted | 是 | 是 | 是 | |
2 | 读已提交 | read committed | 否 | 是 | 是 | Oracle和SQL Server |
3 | 可重复读 | repeatable read | 否 | 否 | 是 | MySQL |
4 | 串行化 | serializable | 否 | 否 | 否 |
2、安全和性能对比
安全性:serializable > repeatable read > read committed > read uncommitted
性能 : serializable < repeatable read < read committed < read uncommitted
3、注意:其实三个问题,开发中最严重的问题就是脏读,这个问题一定要避免,而关于不可重复读和虚读其实只是感官上的错误,并不是逻辑上的错误。就是数据的时效性,所以这种问题并不属于很严重的错误。如果对于数据的时效性要求不是很高的情况下,我们是可以接受不可重复读和虚读的情况发生的。
小结
能够说出mysql的四种隔离级别
读未提交:read uncommitted
读已提交:read committed 可以避免脏读
可重复读:repeatable read mysql默认的,可以避免脏读 和不可重复读
串行化:serializable
8、脏读的演示(课下不用演示)
目标
- 能够设置mysql的隔离级别
- 能够解决赃读
讲解
查询和设置隔离级别
-
查询全局事务隔离级别
show variables like '%isolation%'; -- 或 select @@tx_isolation;
-
设置事务隔离级别,需要退出MSQL再进入MYSQL才能看到隔离级别的变化
set global transaction isolation level 隔离级别; -- 如: set global transaction isolation level read uncommitted; set global transaction isolation level repeatable read;
3.注意,mysql5.6后如果将隔离级别设置uncommitted,那么修改数据会报错,
所以我们在演示的时候,针对当前会话设置如下:
# 设置session级别的BINLOG
SET SESSION binlog_format = 'MIXED' ;
上述命令只是当前会话有效,重新连接失效,通过如下命令可以查看:
#查看binlog方式
select @@binlog_format;
脏读的演示
脏读:一个事务读取到了另一个事务中尚未提交的数据。
-
打开一个窗口,设置为A窗口,登录MySQL,设置全局的隔离级别为最低
-- 设置窗口名字A title A -- 登录mysql数据库 mysql -u root -p 1234 -- 设置事务隔离级别 set global transaction isolation level read uncommitted;
注意:设置事务隔离级别,需要重新打开一个窗口才能看到隔离级别的变化.
2.重新打开一个新的窗口,设置为B窗口,登录mysql
-- 设置窗口名字B
title B;
-- 登录mysql数据库
mysql -u root -p
1234
-- 查询隔离级别
select @@tx_isolation;
3.AB窗口都开启事务
use day05_db;
start transaction;
4.A窗口更新2个人的账户数据,未提交
update account set money=money-500 where id=1;
update account set money=money+500 where id=2;
5.B窗口查询账户
select * from account;
6.A窗口回滚
rollback;
7.B窗口查询账户,钱没了
脏读非常危险的,比如张三向李四购买商品,张三开启事务,向李四账号转入500块,然后打电话给李四说钱已经转了。李四一查询钱到账了,发货给张三。张三收到货后回滚事务,李四的再查看钱没了。
解决脏读的问题:将全局的隔离级别进行提升
-
在A窗口设置全局的隔离级别为
read committed
set global transaction isolation level read committed;
-
B窗口退出MySQL,B窗口再进入MySQL
-
AB窗口同时开启事务
-
A更新2个人的账户,未提交
update account set money=money-500 where id=1; update account set money=money+500 where id=2;
-
B窗口查询账户
-
A窗口commit提交事务
-
B窗口查看账户
结论:read committed的方式可以避免脏读的发生
小结
-
查询全局事务隔离级别?
show variables like '%isolation%';
select @@tx_isolation; -
设置全局事务隔离级别?
set global transaction isolation level 隔离级别字符串;set global transaction isolation level repeatable read;
-
如何解决赃读?
将隔离级别设置为:read committed