我是 ABin-阿斌:写一生代码,创一世佳话,筑一揽芳华。 如果小伙伴们觉得我的文章有点 feel ,那就点个赞再走哦。
上一篇:MySQL从入门到精通:基本语法介绍与使用_01
下一篇:MySQL从入门到精通:多表查询实战案例_03
文章目录
第1章 DQL语句1.1 条件查询1.1.1 准备数据1.1.2 比较运算符1.1.3 逻辑运算符1.1.4 范围区间查询1.1.5 like:模糊查询1.1.6 排序1.1.6.1 单列排序1.1.6.2 组合排序1.1.7 聚合函数1.1.8 分组1.1.9 limit语句第2章 数据库备份2.1 备份的应用场景2.2 source命令备份与还原第3章 数据库约束3.1主键3.1.1 主键的作用:3.1.2 创建主键3.1.3 删除主键3.1.4 主键自增3.2 唯一:3.2.1 唯一约束的基本格式3.2.2 实现唯一约束3.3 非空:3.3.1 非空约束的基本语法格式3.3.2 默认值3.4 外键3.4.1 单表的缺点3.4.2 什么是外键约束:3.4.3 创建外键3.4.4 外键的级联第4章 表关系4.1 表关系的概念:4.2 一对多4.3 多对多4.4 一对一第5章 三范式5.1 什么是范式5.2 范式的基本分类5.3 第一范式:5.4 第二范式:5.5 第三范式:第6章 多表查询6.1 什么是多表查询6.2 笛卡尔积现象6.2.1 什么是笛卡尔积现象6.2.2 如何清除笛卡尔积现象的影响6.3 内连接6.3.1 隐式内连接6.3.2 显示内连接6.4 左外连接6.5 右外连接6.6 子查询6.6.1 子查询的结果是一个值的时候6.1.2 子查询结果是单例多行的时候6.1.3 子查询的结果是多行多列6.1.4总结:第1章 DQL语句
1.1 条件查询
在上一篇文章中我们的查询都是将所有数据都查询出来,但是有时候我们只想获取到满足条件
的数据怎么操作呢?语法格式:SELECT 字段名 FROM 表名 WHERE 条件;
流程:取出表中的每条数据,满足条件的记录就返回,不满足条件的记录不返回1.1.1 准备数据
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.2 比较运算符
>
大于<
小于<=
小于等于>=
大于等于=
等于<>
、!=
不等于具体操作:
查询 math 分数大于 80 分的学生
SELECT * FROM student3 WHERE math>80;
查询 english 分数小于或等于 80 分的学生
SELECT * FROM student3 WHERE english<=80;
查询 age 等于 20 岁的学生
SELECT * FROM student3 WHERE age=20;
查询 age 不等于 20 岁的学生
SELECT * FROM student3 WHERE age!=20;SELECT * FROM student3 WHERE age<>20;
1.1.3 逻辑运算符
and
多个条件同时满足or
多个条件其中一个满足not
不满足具体操作:
查询 age 大于 35 且性别为男的学生(两个条件同时满足)
SELECT * FROM student3 WHERE age>35 AND sex='男';
查询 age 大于 35 或性别为男的学生(两个条件其中一个满足)
SELECT * FROM student3 WHERE age>35 OR sex='男';
查询 id 是 1 或 3 或 5 的学生
SELECT * FROM student3 WHERE id=1 OR id=3 OR id=5;
in 关键字:
语法格式:SELECT 字段名 FROM 表名 WHERE 字段 in (数据1, 数据2...);
in
里面的每个数据都会作为一次条件,只要满足条件的就会显示
具体操作:
查询 id 是 1 或 3 或 5 的学生
SELECT * FROM student3 WHERE id IN (1,3,5);
查询id不是1或3或5的学生
SELECT * FROM student3 WHERE id NOT IN (1,3,5);
1.1.4 范围区间查询
BETWEEN 值1 AND 值2
表示从值1到值2范围,包头又包尾比如:age BETWEEN 80 AND 100
相当于:age>=80 and age<=100
具体操作:
查询 english 成绩大于等于 75,且小于等于 90 的学生
SELECT * FROM student3 WHERE english>=75 AND english<=90;SELECT * FROM student3 WHERE english BETWEEN 75 AND 90;
1.1.5 like:模糊查询
LIKE
表示模糊查询
SELECT * FROM 表名 WHERE 字段名 like '通配符字符串'; 满足
通配符字符串规则的数据就会显示出来
MySQL通配符有两个:
%:表示0个或多个字符(任意个字符)
_:表示一个字符
具体操作:
查询姓【马】的学生
SELECT * FROM student3 WHERE NAME LIKE '马%';
查询姓名中包含【德】字的学生
SELECT * FROM student3 WHERE NAME LIKE '%德%';
查询姓【马】,且姓名有三个字的学生
SELECT * FROM student3 WHERE NAME LIKE '马__';
1.1.6 排序
通过ORDER BY
子句,可以将查询出的结果进行排序(排序只是显示方式,不会影响数据库中数据的顺序)SELECT 字段名 FROM 表名 WHERE 字段=值 ORDER BY 字段名 [ASC|DESC];
ASC:升序, 默认是升序DESC:降序
1.1.6.1 单列排序
单列排序就是使用一个字段排序具体操作:
查询所有数据,使用年龄降序排序
SELECT * FROM student3 ORDER BY age DESC;
1.1.6.2 组合排序
组合排序就是先按第一个字段进行排序,如果第一个字段相同,才按第二个字段进行排序,依次类推。上面的例子中,年龄是有相同的,当年龄相同再使用math进行排序SELECT 字段名 FROM 表名 WHERE 字段=值 ORDER BY 字段名1 [ASC|DESC], 字段名2 [ASC|DESC];
具体操作:
查询所有数据,在年龄降序排序的基础上,如果年龄相同再以数学成绩降序排序
SELECT * FROM student3 ORDER BY age DESC, math DESC;
1.1.7 聚合函数
之前我们做的查询都是横向查询,它们都是根据条件一行一行的进行判断,而使用聚合函数查询是纵向查询,它是对一列的值进行计算,然后返回一个结果值,另外聚合函数会忽略空值。
五个聚合函数:
count
:统计指定列记录数,记录为NULL的不统计sum
:计算指定列的数值和,如果不是数值类型,那么计算结果为0max
:计算指定列的最大值min
:计算指定列的最小值avg
:计算指定列的平均值,如果不是数值类型,那么计算结果为0
聚合函数的使用:写在 SQL语句SELECT
后字段名
的地方
SELECT 字段名... FROM 表名;
SELECT COUNT(age) FROM 表名;
具体操作:
SELECT COUNT(*) FROM student3;
查询年龄大于40的总数
SELECT COUNT(*) FROM student3 WHERE age>40;
查询数学成绩总分
SELECT SUM(math) FROM student3;
查询数学成绩平均分
SELECT AVG(math) FROM student3;
查询数学成绩最高分
SELECT MAX(math) FROM student3;
查询数学成绩最低分
SELECT MIN(math) FROM student3;
1.1.8 分组
分组查询是指使用GROUP BY
语句对查询信息进行分组,相同数据作为一组
SELECT 字段1,字段2... FROM 表名 GROUP BY 分组字段 [HAVING 条件];
GROUP BY怎么分组的?将分组字段结果中相同内容作为一组
将sex相同的数据作为一组
SELECT * FROM student3 GROUP BY sex;
GROUP BY
将分组字段结果中相同内容作为一组,并且返回每组的第一条数据,所以单独分组没什么用处。分组的目的就是为了统计,一般分组会跟聚合函数一起使用。
分组后聚合函数的作用?不是操作所有数据,而是操作一组数据。
SELECT SUM(math), sex FROM student3 GROUP BY sex;
效果如下:
实际上是将每组的math进行求和,返回每组统计的结果
注意事项:
当我们使用某个字段分组,在查询的时候也需要将这个字段查询出来,否则看不到数据属于哪组的查询的时候没有查询出分组字段
查询的时候查询出分组字段
具体步骤:
按性别分组
SELECT sex FROM student3 GROUP BY sex;
查询男女各多少人
1.查询所有数据,按性别分组。 2.统计每组人数SELECT sex, COUNT(*) FROM student3 GROUP BY sex;
查询年龄大于25岁的人,按性别分组,统计每组的人数
1.先过滤掉年龄小于25岁的人。2.再分组。3.最后统计每组的人数SELECT sex, COUNT(*) FROM student3 WHERE age > 25 GROUP BY sex;
查询年龄大于25岁的人,按性别分组,统计每组的人数,并只显示性别人数大于2的数据有时候我们可能会将SQL语句写出这样:
SELECT sex, COUNT(*) FROM student3 WHERE age > 25 GROUP BY sex WHERE COUNT(*) >2;
注意:并只显示性别人数 >2 的数据属于分组后的条件,对于分组后的条件需要使用 HAVING 句
SELECT sex, COUNT(*) FROM student3 WHERE age > 25 GROUP BY sex HAVING COUNT(*) >2;只有分组后人数大于2的`男`这组数据显示出来
注意:
having 与 where 的区别:having 是在分组后对数据进行过滤.where 是在分组前对数据进行过滤having 后面可以使用聚合函数where 后面不可以使用聚合函数
准备数据:
INSERT INTO student3(id,NAME,age,sex,address,math,english) VALUES (9,'唐僧',25,'男','长安',87,78),(10,'孙悟空',18,'男','花果山',100,66),(11,'猪八戒',22,'男','高老庄',58,78),(12,'沙僧',50,'男','流沙河',77,88),(13,'白骨精',22,'女','白虎岭',66,66),(14,'蜘蛛精',23,'女','盘丝洞',88,88);
1.1.9 limit语句
LIMIT
是限制
的意思,所以LIMIT
的作用就是限制查询记录的条数。SELECT *|字段列表 [as 别名] FROM 表名 [WHERE子句] [GROUP BY子句][HAVING子句][ORDER BY子句][LIMIT子句];
思考:limit子句为什么排在最后?因为前面所有的限制条件都处理完了,只剩下显示多少条记录的问题了!
LIMIT语法格式:
LIMIT offset,length; 或者limit length;
offset
是指偏移量,可以认为是跳过的记录数量,默认为0:从哪开始length
是指需要显示的总记录数:显示几条
具体步骤:
查询学生表中数据,从第三条开始显示,显示6条
我们可以认为跳过前面2条,取6条数据SELECT * FROM student3 LIMIT 2,6;
LIMIT的使用场景:分页
比如我们登录京东,淘宝,返回的商品信息可能有几万条,不是一次全部显示出来。是一页显示固定的条数。假设我们一每页显示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 条,有多少显示多少
第2章 数据库备份
2.1 备份的应用场景
在服务器进行数据传输、数据存储和数据交换,就有可能产生数据故障。比如发生意外停机或存储介质损坏。这时,如果没有采取数据备份和数据恢复手段与措施,就会导致数据的丢失,造成的损失是无法弥补与估量的。2.2 source命令备份与还原
备份格式:
mysqldump -u用户名 -p密码 数据库 > 文件的路径
还原格式:
SOURCE 导入文件的路径;
注意:还原的时候需要先登录MySQL,并选中对应的数据库
具体操作:
备份 day22 数据库中的数据
mysqldump -uroot -proot day22 > D:\技术栈\\MySQL\code\bak.sql
数据库中的所有表和数据都会导出成SQL语句
还原day22数据库中的数据
删除day22数据库中的所有表
登录MySQL
mysql -uroot -proot
选中数据库
use day22;select database();
使用 SOURCE 命令还原数据
source D:\技术栈\\MySQL\code\bak.sql
第3章 数据库约束
对表中的数据进行进一步的限制,保证数据的正确性、有效性和完整性。
约束种类:
PRIMARY KEY
:主键
UNIQUE
: 唯一
NOT NULL
:非空
DEFAULT
: 默认
FOREIGN KEY
: 外键
3.1主键
3.1.1 主键的作用:
用来唯一标识一条记录,每个表都应该有一个主键,并且每个表只能有一个主键。有些记录的name,age,score字段的值都一样时,那么就没法区分这些数据,造成数据库的记录不唯一,这样就不方便管理数据哪个字段应该作为表的主键?
通常不用业务字段作为主键,单独给每张表设计一个id的字段,把id作为主键。主键是给数据库和程序使用的,不是给最终的客户使用的。所以主键有没有含义没有关系,只要不重复,非空就行。
3.1.2 创建主键
主键:PRIMARY KEY
主键的特点:
主键必须包含唯一的值主键列不能包含NULL值
创建主键方式:
在创建表的时候给字段添加主键
字段名 字段类型 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) VALUES (1, '唐伯虎');INSERT INTO st5 (id, NAME) VALUES (2, '周文宾');INSERT INTO st5 (id, NAME) VALUES (3, '祝枝山');INSERT INTO st5 (id, NAME) VALUES (4, '文征明');
插入重复的主键值
-- 主键是唯一的不能重复:Duplicate entry '1' for key 'PRIMARY'INSERT INTO st5 (id, NAME) VALUES (1, '文征明2');
插入NULL的主键值
-- 主键是不能为空的:Column 'id' cannot be nullINSERT INTO st5 (id, NAME) VALUES (NULL, '文征明3');
3.1.3 删除主键
ALTER TABLE 表名 DROP PRIMARY KEY;
具体操作:
删除 st5 表的主键
ALTER TABLE st5 DROP PRIMARY KEY;
3.1.4 主键自增
主键如果让我们自己添加很有可能重复,我们通常希望在每次插入新记录时,数据库自动生成主键字段的值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);
扩展:
默认地 AUTO_INCREMENT 的开始值是1,如果希望修改起始值,请使用下列 SQL 语法:
ALTER TABLE 表名 AUTO_INCREMENT=起始值;
DELETE 和 TRUNCATE 的区别:
DELETE 删除表中的数据,但不重置 AUTO_INCREMENT 的值。
TRUNCATE 摧毁表,重建表,AUTO_INCREMENT重置为1
3.2 唯一:
在这张表中这个字段的值不能重复3.2.1 唯一约束的基本格式
字段名 字段类型 UNIQUE
3.2.2 实现唯一约束
具体步骤:
创建学生表 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);
3.3 非空:
这个字段必须设置值,不能是 NULL3.3.1 非空约束的基本语法格式
字段名 字段类型 NOT NULL
具体操作:
创建表学生表 st8,包含字段(id,name,gender)其中 name 不能为 NULL
CREATE TABLE st8 (id INT,NAME VARCHAR(20) NOT NULL,gender Varchar(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 nullINSERT INTO st8 VALUES (5, NULL, '男');
3.3.2 默认值
往表中添加数据时,如果不指定这个字段的数据,就使用默认值默认值格式:
字段名 字段类型 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, '张学友', '香港');
疑问:如果一个字段设置了非空与唯一约束,该字段与主键的区别
一张表中只有一个字段可以设置为主键一张表中可以多个字段非空与唯一约束主键可以自动增长,非空与唯一约束的字段不能自动增长
3.4 外键
3.4.1 单表的缺点
创建一个员工表包含如下列(id,name,age,dep_name,dep_location),id主键并自动增长,添加5条数据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, '销售部', '深圳');
缺点:表中出现了很多重复的数据(数据冗余),如果要修改研发部的地址需要修改3个地方。
解决方案:将一张表分成2张表(员工表和部门表)
-- 创建部门表CREATE TABLE department (id INT PRIMARY KEY AUTO_INCREMENT,dep_name VARCHAR(20),dep_location VARCHAR(20));-- 创建员工表CREATE TABLE employee (id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(20),age INT,dep_id INT);-- 添加2个部门INSERT INTO department (dep_name, dep_location) VALUES ('研发部', '广州'), ('销售部', '深圳');-- 添加员工,dep_id表示员工所在的部门INSERT INTO employee (NAME, age, dep_id) VALUES ('张三', 20, 1), ('李四', 21, 1), ('王五', 20, 1), ('老王', 20, 2),('大王', 22, 2),('小王', 18, 2);
问题:
当我们在employee 的 dep_id里面输入不存在的部门,数据依然可以添加。但是并没有对应的部门,不能出现这种情况employee的 dep_id中的内容只能是department 表中存在的 id
目标:需要约束dep_id只能是department表中已经存在id
解决方式:使用外键约束
3.4.2 什么是外键约束:
一张表中的某个字段引用另一个表的主键主表:约束别人副表/从表:使用别人的数据,被别人约束3.4.3 创建外键
新建表时增加外键:
[CONSTRAINT] [外键约束名称] FOREIGN KEY(外键字段名) REFERENCES 主表名(主键字段名)
关键字解释:
CONSTRAINT:约束关键字FOREIGN KEY(外键字段名) :某个字段作为外键REFERENCES :主表名(主键字段名) 表示参照主表中的某个字段
具体操作:
副表/从表:被别人约束,表结构添加外键约束
CREATE TABLE employee (id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(20),age INT,dep_id INT,-- 添加一个外键-- 外键取名公司要求,一般fk结尾CONSTRAINT emp_depid_ref_dep_id_fk FOREIGN KEY(dep_id) REFERENCES department(id));
正常添加数据
INSERT INTO employee (NAME, age, dep_id) VALUES('张三', 20, 1),('李四', 21, 1),('王五', 20, 1),('老王', 20, 2),('大王', 22, 2),('小王', 18, 2);
部门错误的数据添加失败
INSERT INTO employee (NAME, age, dep_id) VALUES ('二王', 20, 5);
3.4.4 外键的级联
要把部门表中的 id 值 2,改成 5,能不能直接修改呢?UPDATE department SET id=5 WHERE id=2;
不能直接修改:Cannot delete or update a parent row: a foreign key constraint fails 如果副表(员工表)中有引用的数据,不能直接修改主表(部门表)主键
要删除部门 id 等于 1 的部门,能不能直接删除呢?
DELETE FROM department WHERE id=1;
不能直接删除:Cannot delete or update a parent row: a foreign key constraint fails 如果副表(员工表)中有引用的数据,不能直接删除主表(部门表)数据
什么是级联操作:
在修改和删除主表的主键时,同时更新或删除副表的外键值,称为级联操作ON UPDATE CASCADE
:级联更新,主键发生更新时,外键也会更新ON DELETE CASCADE
:级联删除,主键发生删除时,外键也会删除
具体操作:
删除employee表重新创建employee表,添加级联更新和级联删除
CREATE TABLE employee (id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(30),age INT,dep_id INT,-- 添加外键约束,并且添加级联更新和级联删除CONSTRAINT employee_dep_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);
把部门表中 id 等于1的部门改成 id 等于 10
UPDATE department SET id=10 WHERE id=1;
删除部门号是 2 的部门
DELETE FROM department WHERE id=2;
第4章 表关系
4.1 表关系的概念:
现实生活中,实体与实体之间肯定是有关系的,比如:老公和老婆,部门和员工,老师和学生等。那么我们在设计表的时候,就应该体现出表与表之间的这种关系!分成三种:一对一一对多多对多4.2 一对多
一对多(1:N)例如:班级和学生,部门和员工,客户和订单,分类和商品一对多建表原则:在从表(多方)创建一个字段,字段作为外键指向主表(一方)的主键4.3 多对多
多对多(M:N)例如:老师和学生,学生和课程,用户和角色多对多关系建表原则:需要创建第三张表,中间表中至少两个字段,这两个字段分别作为外键指向各自一方的主键。4.4 一对一
一对一(1:1)在实际的开发中应用不多,因为一对一可以创建成一张表两种建表原则:外键唯一:主表的主键和从表的外键(唯一),形成主外键关系外键是主键:主表的主键和从表的主键,形成主外键关系一对多关系练习:
一个旅游线路分类中有多个旅游线路,根据需求来设计数据库表之间的关系
具体操作:
创建旅游线路分类表
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, -- 价格routeIntroduce VARCHAR(200), -- 线路介绍rflag CHAR(1) NOT NULL, -- 是否上架rdate VARCHAR(19) NOT NULL, -- 上架时间isThemeTour CHAR(1) NOT NULL, -- 是否主题旅游acount INT DEFAULT 0, -- 收藏数量cid INT NOT NULL, -- 所属分类rimage VARCHAR(200) NOT NULL, -- 缩略图地址CONSTRAINT ro_cid_ref_cate_id FOREIGN KEY(cid) REFERENCES tab_category(cid));
添加旅游线路数据
INSERT INTO tab_route VALUES(NULL, '【厦门+鼓浪屿+南普陀寺+曾厝垵 高铁3天 惠贵团】尝味友鸭面线 住1晚鼓浪屿', 1499, '春节国内游优惠:即日起至1月31号,5-9人同时报名立减¥100/人,10人及以上同时报名立减¥150/人。仅限2月10-22日春节期间出发享优惠,自由行及特价产品不参与!', 1, '-01-27', 0, 100, 3, 'c:\1.png'),(NULL, '【浪漫桂林 阳朔西街高铁3天纯玩 高级团】城徽象鼻山 兴坪漓江 西山公园', 699, '春节国内游优惠:即日起至1月31号,5-9人同时报名立减¥100/人,10人及以上同时报名立减¥150/人。仅限2月10-22日春节期间出发享优惠,自由行及特价产品不参与!', 1, '-01-27', 0, 66, 3, 'c:\2.png'),(NULL, '【爆款¥1699秒杀】泰国 曼谷 芭堤雅 金沙岛 杜拉拉水上市场 双飞六天【含送签费 泰风情 广州往返 特价团】', 1699, '1月15日至2月11日官网特卖!①出境全线正价线路(特价线除外)满2人立减¥60!满4人立减¥200!满5人立减¥500!', 1, '-01-27', 0, 15, 2, 'c:\123.png'),(NULL, '【经典·狮航 ¥2399秒杀】巴厘岛双飞五天 抵玩【广州往返 特价团】', 2399, '官网特卖!2-3月出发,前10名网付立享¥2399/人!', 1, '-01-27', 0, 22, 2, 'c:\3.png'),(NULL, '香港迪士尼乐园自由行2天【永东跨境巴士广东至迪士尼去程交通+迪士尼一日门票+香港如心海景酒店暨会议中心标准房1晚住宿】', 799, '永东巴士提供广东省内多个上车地点,购买后需自行致电永东巴士客服电话4008861668预约车位', 1, '-01-27', 0, 38, 4, 'c:\4.png');
多对多关系练习
一个用户收藏多个线路,一个线路被多个用户收藏
对于多对多的关系我们需要增加一张中间表来维护他们之间的关系
具体操作:
创建用户表
CREATE TABLE tab_user (uid INT PRIMARY KEY AUTO_INCREMENT, -- 用户idusername VARCHAR(100) NOT NULL UNIQUE, -- 用户名PASSWORD VARCHAR(30) NOT NULL, -- 密码NAME VARCHAR(100), -- 真实姓名birthday DATE, -- 生日sex CHAR(1), -- 性别telephone VARCHAR(11), -- 手机号email VARCHAR(100), -- 邮箱STATUS CHAR(1) NOT NULL, -- 是否激活状态CODE VARCHAR(32) NOT NULL UNIQUE -- 激活码);
添加用户数据
INSERT INTO tab_user VALUES(NULL, 'cz110', 123456, '老王', '1977-07-07', '男', '13888888888', '66666@', '是', '1386'),(NULL, 'cz119', 654321, '小王', '1999-09-09', '男', '13999999999', '99999@', '是', '9898');
创建收藏表
CREATE TABLE tab_favorite (rid INT NOT NULL, -- 旅游线路idDATE DATE NOT NULL, -- 收藏时间uid INT NOT NULL -- 用户id);
增加收藏表数据
INSERT INTO tab_favorite VALUES(NULL, 1, '-01-01', 1), -- 老王选择厦门(NULL, 1, '-01-01', 2), -- 老王选择桂林(NULL, 1, '-01-01', 3), -- 老王选择泰国(NULL, 2, '-01-01', 2), -- 小王选择桂林(NULL, 2, '-01-01', 3), -- 小王选择泰国(NULL, 2, '-01-01', 5); -- 小王选择迪士尼
第5章 三范式
5.1 什么是范式
范式是指:设计数据库表的规则(Normal Form)好的数据库设计对数据的存储性能和后期的程序开发,都会产生重要的影响。建立科学的,规范的数据库就需要满足一些规则来优化数据的设计和存储5.2 范式的基本分类
目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。满足最低要求的范式是第一范式(1NF)。在第一范式的基础上进一步满足更多规范要求的称为第二范式(2NF),其余范式以次类推。一般说来,数据库只需满足第三范式(3NF)就行了。5.3 第一范式:
即数据库表的每一列都是不可分割的原子数据项,而不能是集合、数组等非原子数据项。即实体中的某个属性有多个值时,必须拆分为不同的属性。在符合第一范式(1NF)表中每个列的值只能是表的一个属性或一个属性的一部分。简而言之,第一范式每一列不可再拆分,称为原子性。、第一范式:每一列不能再拆分
总结:如果不遵守第一范式,查询出数据还需要进一步处理(查询不方便)。遵守第一范式,需要什么字段的数据就查询什么数据(方便查询)。
5.4 第二范式:
第二范式(2NF)要求数据库表中的每个实例或记录必须可以被唯一地区分。选取一个能区分每个实体的属性或属性组,作为实体的唯一标识。例如在员工表中的身份证号码即可实现每个员工的区分,该身份证号码即为候选键,任何一个候选键都可以被选作主键。在找不到候选键时,可额外增加属性以实现区分。第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性。如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的唯一标识。简而言之,第二范式就是在第一范式的基础上属性完全依赖于主键。第二范式:
一张表只描述一件事情表中的每一个字段都依赖于主键
总结:如果不准守第二范式,数据冗余,相同数据无法区分。遵守第二范式减少数据冗余,通过主键区分相同数据。
5.5 第三范式:
在 2NF 基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)第三范式(3NF)是第二范式(2NF)的一个子集,即满足第三范式(3NF)必须满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个关系中不包含已在其它关系已包含的非主关键字信息。第三范式:要求一个数据库表中不包含已在其它表中已包含的非[主关键字]信息(使用主外键的方式关联,而不是使用非主属性关联)
总结:如果不遵守第三范式,可能会有相同数据无法区分,修改数据的时候多张表都需要修改(不方便修改)。遵守第三范式通过id可以区分相同数据,修改数据的时候只需要修改一张表(方便修改)。
第6章 多表查询
6.1 什么是多表查询
同时查询多张表获取到需要的数比如:我们想查询到开发部有多少人,需要将部门表和员工表同时进行查询多表查询的分类:
准备数据:
-- 创建部门表CREATE TABLE dept (id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(20));INSERT INTO dept (NAME) VALUES ('开发部'),('市场部'),('财务部');-- 创建员工表CREATE TABLE emp (id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(10),gender CHAR(1), -- 性别salary DOUBLE, -- 工资join_date DATE, -- 入职日期dept_id INT);INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('孙悟空','男',7200,'-02-24',1);INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('猪八戒','男',3600,'-12-02',2);INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('唐僧','男',9000,'-08-08',2);INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('白骨精','女',5000,'-10-07',3);INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('蜘蛛精','女',4500,'-03-14',1);
6.2 笛卡尔积现象
6.2.1 什么是笛卡尔积现象
多表查询时左表的每条数据和右表的每条数据组合,这种效果成为笛卡尔积
需求:查询每个部门有哪些人
具体操作:
SELECT * FROM dept, emp;
以上数据其实是左表的每条数据和右表的每条数据组合。左表有 3 条,右表有 5 条,最终组合后 3 * 5 = 15条数据
左表的每条数据和右表的每条数据组合,这种效果称为笛卡尔乘积
6.2.2 如何清除笛卡尔积现象的影响
我们发现不是所有的数据组合都是有用的,只有员工表 dept_id = 部门表 id 的数据才是有用的。所以需要通过条件过滤掉没用的数据。SELECT * FROM dept, emp WHERE emp.`dept_id`=dept.`id`;
6.3 内连接
用左边表的记录去匹配右边表的记录,如果符合条件的则显示6.3.1 隐式内连接
隐式内连接:看不到JOIN
关键字,条件使用WHERE
指定SELECT 字段名 FROM 左表, 右表 WHERE 条件;
6.3.2 显示内连接
显示内连接:使用INNER JOIN ... ON
语句, 可以省略INNER
SELECT 字段名 FROM 左表 INNER JOIN 右表 ON 条件;
具体操作:
查询唐僧的信息,显示员工id,姓名,性别,工资和所在的部门名称,我们发现需要联合 2 张表同时才能查询出需要的数据,我们使用内连接确定查询哪些表
SELECT * FROM dept INNER JOIN emp;
确定表连接条件,员工表.dept_id = 部门表.id 的数据才是有效的
SELECT * FROM dept INNER JOIN emp ON emp.`dept_id`=dept.`id`;
确定表连接条件,我们查询的是唐僧的信息,员工表.name=唐僧
SELECT * FROM dept INNER JOIN emp ON emp.`dept_id`=dept.`id` AND emp.`NAME`='唐僧';
确定查询字段,查询唐僧的信息,显示员工id,姓名,性别,工资和所在的部门名称
SELECT emp.`id`, emp.`NAME`, emp.`gender`, emp.`salary`, dept.`NAME` FROM dept INNER JOIN emp ON emp.`dept_id`=dept.`id` AND emp.`NAME`='唐僧';
我们发现写表名有点长,可以给表取别名,显示的字段名也使用别名
SELECT e.`id` 员工编号, e.`NAME` 员工姓名, e.`gender` 性别, e.`salary` 工资, d.`NAME` 部门名称 FROM dept d INNER JOIN emp e ON e.`dept_id`=d.`id` AND e.`NAME`='唐僧';
总结内连接查询步骤:
确定查询哪些表确定表连接条件确定查询字段
6.4 左外连接
左外连接:使用LEFT OUTER JOIN ... ON
,OUTER
可以省略SELECT 字段名 FROM 左表 LEFT OUTER JOIN 右表 ON 条件;
用左边表的记录去匹配右边表的记录,如果符合条件的则显示;否则,显示NULL可以理解为:在内连接的基础上保证左表的数据全部显示具体操作:
在部门表中增加一个销售部
INSERT INTO dept (NAME) VALUES ('销售部');
使用内连接查询
SELECT * FROM dept INNER JOIN emp ON emp.`dept_id`=dept.`id`;
使用左外连接查询
SELECT * FROM dept LEFT OUTER JOIN emp ON emp.`dept_id`=dept.`id`;
6.5 右外连接
右外连接:使用RIGHT OUTER JOIN ... ON
,OUTER
可以省略SELECT 字段名 FROM 左表 RIGHT OUTER JOIN 右表 ON 条件;
用右边表的记录去匹配左边表的记录,如果符合条件的则显示;否则,显示NULL可以理解为:在内连接的基础上保证右表的数据全部显示具体操作:
在员工表中增加一个员工
INSERT INTO emp(NAME,gender,salary,join_date,dept_id) VALUES('沙僧','男',6666,'-02-24',NULL);
使用内连接查询
SELECT * FROM dept INNER JOIN emp ON emp.`dept_id`=dept.`id`;
使用右外连接查询
SELECT * FROM dept RIGHT OUTER JOIN emp ON emp.`dept_id`=dept.`id`;
6.6 子查询
一条SELECT语句结果作为另一条SELECT语法一部分SELECT 查询字段 FROM 表 WHERE 查询条件;
SELECT * FROM employee WHERE salary=(SELECT MAX(salary) FROM employee);
子查询需要放在()中
子查询结果的三种情况:
子查询的结果是一个值的时候
子查询结果是单例多行的时候
子查询的结果是多行多列
说明:
子查询结果只要是单列
,肯定在WHERE
后面作为条件
子查询结果只要是多列
,肯定在FROM
后面作为表
6.6.1 子查询的结果是一个值的时候
子查询结果只要是单列
,肯定在WHERE
后面作为条件
SELECT 查询字段 FROM 表 WHERE 字段=(子查询);
查询工资最高的员工是谁?
查询最高工资是多少
SELECT MAX(salary) FROM emp;
根据最高工资到员工表查询到对应的员工信息
SELECT * FROM emp WHERE salary=(SELECT MAX(salary) FROM emp);
![在这里插入图片描述](https://img-/img_convert/c5fb07608ee853724c316aa58c0cfb3c.png#pic_center)
查询工资小于平均工资的员工有哪些?
查询平均工资是多少
SELECT AVG(salary) FROM emp;
员工表查询小于平均的员工信息
SELECT * FROM emp WHERE salary < (SELECT AVG(salary) FROM emp);
6.1.2 子查询结果是单例多行的时候
子查询结果只要是单列
,肯定在WHERE
后面作为条件
子查询结果是单例多行,结果集类似于一个数组,父查询使用IN
运算符SELECT 查询字段 FROM 表 WHERE 字段 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('开发部','财务部'));
6.1.3 子查询的结果是多行多列
子查询结果只要是多行多列
,肯定在FROM
后面作为表
SELECT 查询字段 FROM (子查询) 表别名 WHERE 条件;
子查询作为表需要取别名,否则这张表无法访问表中的字段
查询出以后入职的员工信息,包括部门名称
在员工表中查询 -1-1 以后入职的员工
SELECT * FROM emp WHERE join_date > '-1-1';
查询所有的部门信息,与上面的虚拟表中的信息组合,找出所有部门 id 等于的 dept_id
SELECT * FROM dept d, (SELECT * FROM emp WHERE join_date > '-1-1') e WHERE e.dept_id = d.id;
6.1.4总结:
子查询结果只要是单列
,肯定在WHERE
后面作为条件
SELECT 查询字段 FROM 表 WHERE 字段=(子查询);
子查询结果只要是多列
,肯定在FROM
后面作为表
SELECT 查询字段 FROM (子查询) 表别名 WHERE 条件;