外键+查询关键字
目录
自增特性
# 被delete语句删除的id值,除非sql中将id重新插入,否则前面空余的id不会复用。
create table t1(
id int primary key auto_increment,
name varchar(32)
);
insert into t1(name) values('jason'),('kevin'),('tony');
insert into t1(name) values('oscar'); # id=4
delete from t1 where id=4;
insert into t1(name) values('oscar'); # id=5
'''自增不会随着数据的删除而回退'''
delete from t1; # 删除数据但无法重置主键
insert into t1(name) values('jason'),('kevin'),('tony');
truncate t1; # 删除数据并重置主键值
insert into t1(name) values('jason'),('kevin'),('tony');
外键
外键的使用条件
- 外键只适用于InnoDB引擎,MyISAM不支持。
- 外键列必须建立了索引,MySQL 4.1.2以后的版本在建立外键时会自动创建索引,但如果在较早的版本则需要显式建立;
- 外键关系的两个表的列必须是数据类型相似,也就是可以相互转换类型的列,比如int和tinyint可以,而int和char则不可以;
# 外键前戏
创建一张员工表
id name age dep_name dep_desc
缺陷
1.表的重点不清晰(可以忽略)
既可以说是员工表也可以说是部门表
2.表中某些字段对应的数据一直在重复(可以忽略)
浪费存储空间
3.表的扩展性极差 牵一发而动全身(不能忽略)
耦合度太高 不利于维护
解决
将一张表一分为二
员工表 部门表
id name age id dep_name dep_desc
# 拆表之后上述的三个缺陷解决
但是迎来了一个致命问题 >>> 员工与部门之间没有了绑定关系
在员工表中添加一个部门编号字段与部门表中的主键字段对应
"""
该字段其实就是外键字段
外键字段就是用来记录表与表之间数据的关系
"""
外键关系
总共就四种
1.一对多
2.多对多
3.一对一
4.没有关系
"""
关系的判断记住四字口诀:换位思考
"""
# 一对多
1.以员工表与部门表为例
先站在员工表的角度
问:一个员工能否对应多个部门
答:不可以
再站在部门表的角度
问:一个部门能否对应多个员工
答:可以
结论:换位思考之后得出的答案是一个可以一个不可以
所以关系是"一对多" 部门是'一'员工是'多'
'''关系表达只能用一对多 不能用多对一'''
一对多关系 外键字段建在"多"的一方(员工表)
外键SQL语句
"""
如果表中有外键字段 那么建议你先编写普通字段 最后再考虑外键字段
"""
# 在创建表字段的时候也可以给每个字段添加中文注释
# comment : 注释
create table emp(
id int primary key auto_increment comment '编号',
name varchar(32) comment '姓名',
age int comment '年龄',
dep_id int comment '部门编号',
foreign key(dep_id) references dep(id)
);
create table dep(
id int primary key auto_increment comment '编号',
dep_name varchar(32) comment '部门名称',
dep_desc varchar(32) comment '部门描述'
);
# foreign key的约束效果
1.创建表的时候 应该先创建被关联表(没有外键字段的表)
2.插入数据的时候 应该先插入被关联表(没有外键字段的表)
外键字段填入的值只能是被关联表中已经存在的值
3.修改、删除被关联表数据都会出现障碍
update dep set id=200 where id=2;
delete from dep where id=2;
级联操作
CASCADE
:级联操作,如果父表中的一行被删除或更新,子表会自动跟着删除或更新。
# 级联更新级联删除
create table emp1(
id int primary key auto_increment comment '编号',
name varchar(32) comment '姓名',
age int comment '年龄',
dep_id int comment '部门编号',
foreign key(dep_id) references dep1(id)
on update cascade # 级联更新
on delete cascade # 级联删除
);
create table dep1(
id int primary key auto_increment comment '编号',
dep_name varchar(32) comment '部门名称',
dep_desc varchar(32) comment '部门描述'
);
"""
在实际工作中外键也可能不会使用 因为外键会消耗额外的资源
并且会增加表的复杂度
表很多的情况下 我们也可以通过SQL语句的形式建立逻辑意义上的表关系
"""
多对多关系
以图书与作者表为例
1.先站在图书表的角度
问:一本书籍能否对应多名作者
答:可以
2.再站在作者表的角度
问:一名作者能否对应多本书籍
答:可以
结论:换位思考之后两边都可以 那么就是"多对多"关系
create table book(
id int primary key auto_increment,
title varchar(32),
price float(10,2),
author_id int,
foreign key(author_id) references author(id)
on update cascade # 级联更新
on delete cascade # 级联删除
);
create table author(
id int primary key auto_increment,
name varchar(32),
gender enum('male','female','others'),
book_id int,
foreign key(book_id) references book(id)
on update cascade # 级联更新
on delete cascade # 级联删除
);
"""
针对多对多关系 需要单独开设第三张表专门存储关系
"""
create table book(
id int primary key auto_increment,
title varchar(32),
price float(10,2)
);
create table author(
id int primary key auto_increment,
name varchar(32),
gender enum('male','female','others')
);
create table book2author(
id int primary key auto_increment,
author_id int,
book_id int,
foreign key(author_id) references author(id)
on update cascade # 级联更新
on delete cascade, # 级联删除
foreign key(book_id) references book(id)
on update cascade # 级联更新
on delete cascade # 级联删除
);
# 针对多对多表关系
两张基表内的数据没有在第三张表内绑定关系的情况下随意新增修改删除
一对一关系
针对qq用户表 其实里面的数据可以分成两类
热数据:经常需要使用的数据
eg:qq号码 座右铭 个人简介 爱好
冷数据:不怎么经常需要使用的数据
eg:邮箱 电话 学校 ...
为了节省资源并降低数据库压力 会将表一分为二
用户表
存使用频率较高的数据字段
用户详情表
存使用频率较低的数据字段
1.先站在用户表的角度
问:一个用户数据能否对应多个用户详情数据
答:不可以
2.再站在用户详情表的角度
问:一个用户详情数据能否对应多个用户数据
答:不可以
结论:换位思考之后两边都不可以 那么关系可能有两种
'没有关系':用膝盖都能判断出来
'一对一关系'
针对'一对一关系'外键字段建在任意一方都可以,但是推荐建在查询频率较高的较好的一方
create table User(
id int primary key auto_increment,
name varchar(32),
gender enum('male','female','others'),
user_detail_id int unique, # 好好体会为什么加unique
foreign key(user_detail_id) references UserDetail(id)
on update cascade # 级联更新
on delete cascade, # 级联删除
);
create table UserDetail(
id int primary key auto_increment,
phone bigint,
age int
);
表查询关键字
# 数据准备
create table emp(
id int primary key auto_increment,
name varchar(20) not null,
sex enum('male','female') not null default 'male', #大部分是男的
age int(3) unsigned not null default 28,
hire_date date not null,
post varchar(50),
post_comment varchar(100),
salary double(15,2),
office int, #一个部门一个屋子
depart_id int
);
#插入记录
#三个部门:教学,销售,运营
insert into emp(name,sex,age,hire_date,post,salary,office,depart_id) values
('jason','male',18,'20170301','张江第一帅形象代言',7300.33,401,1), #以下是教学部
('tom','male',78,'20150302','teacher',1000000.31,401,1),
('kevin','male',81,'20130305','teacher',8300,401,1),
('tony','male',73,'20140701','teacher',3500,401,1),
('owen','male',28,'20121101','teacher',2100,401,1),
('jack','female',18,'20110211','teacher',9000,401,1),
('jenny','male',18,'19000301','teacher',30000,401,1),
('sank','male',48,'20101111','teacher',10000,401,1),
('哈哈','female',48,'20150311','sale',3000.13,402,2),#以下是销售部门
('呵呵','female',38,'20101101','sale',2000.35,402,2),
('西西','female',18,'20110312','sale',1000.37,402,2),
('乐乐','female',18,'20160513','sale',3000.29,402,2),
('拉拉','female',28,'20170127','sale',4000.33,402,2),
('僧龙','male',28,'20160311','operation',10000.13,403,3), #以下是运营部门
('程咬金','male',18,'19970312','operation',20000,403,3),
('程咬银','female',18,'20130311','operation',19000,403,3),
('程咬铜','male',18,'20150411','operation',18000,403,3),
('程咬铁','female',18,'20140512','operation',17000,403,3);
select 标准用法(配合其他子句使用)
# 单表
前提:
select
1.from 表1,表2,...
2.where 过滤条件1,过滤条件2...
3.group by 条件列1 条件列2...分组字段
4.select_list 列名
5.having 过滤条件1 过滤条件2...
6.order by 条件列1 条件列2...排序字段
7.limit 分页限制
select配合from子句使用
select配合from子句使用
语法:
select 列 from 表;
例子:
#查询表中所有列所有行,*谨用!
select * from city;
#查询部分列值
select name,population from city;
select+from+where配合使用(筛选)
where配合 # 比较判断符 = ,< ,> ,>= ,<= ,!=
# 逻辑判断符 and,or,not
# 存在判断 in
# 是否判断 is
# 1.查询id大于等于3 小于等于6的数据
select * from emp where id>=3 and id<=6;
select * from emp where id between 3 and 6;
# 2.查询薪资是20000或者18000或者17000的数据
select * from emp where salary=20000 or salary=18000 or salary=17000;
select * from emp where salary in (20000,18000,17000);
# 3.查询id小于3或者大于6的数据
select * from emp where id not between 3 and 6;
# 4.查询薪资不在20000,18000,17000的数据
select * from emp where salary not in (20000,18000,17000);
# 5.查询岗位描述为空的数据
select * from emp where post_comment=null; # 不可以
'''针对null只能用is不能用等号'''
select * from emp where post_comment is null; # 可以
select+from+where+like配合使用,模糊查询,针对字符串
select+from+where+like配合使用,模糊查询
# 1.查询员工姓名中包含字母o的员工姓名和薪资
"""
查询条件如果不是很明确的情况下 我们统一称之为'模糊查询'
关键字
like:开启模糊查询的关键字
关键符号
%:匹配任意个数的任意字符
_:匹配单个个数的任意字符
"""
select name,salary from emp where name like '%o%';
# 2.查询员工姓名是由四个字符组成的数据
select * from emp where name like '____';
select * from emp where char_length(name)=4;
select+from+where+group by+聚合函数(分组)
# 什么是分组?
按照指定的条件将单个单个的个体组织成一个个整体
eg:按照性别分组 按照部门分组 按照年龄分组 按照国家分组...
# 为什么需要分组?
分组的好处在于可以快速统计出某些数据
eg:最大薪资 平均年龄 最小年龄 总人数
# 如何分组
'''按照部分分组'''
select * from emp group by post;
"""
mysql5.7及以上版本默认自带sql_mode=only_full_group_by
该模式要求分组之后默认只可以直接获取分组的依据不能直接获取其他字段
原因是分组的目的就是按照分组的条件来管理诸多数据 最小单位应该是分组的依据而不是单个单个的数据
如果是MySQL5.6及以下版本 需要自己手动添加
"""
# 聚合函数
专门用于分组之后的数据统计
max 统计最大值
min 统计最小值
sum 统计求和
count 统计计数
avg 统计平均值
ps:是否需要分组 我们可以在题目或者需求中发现
# 1.统计每个部门的最高薪资
select post,max(salary) from emp group by post;
# 2.统计每个部门的平均薪资
select post,avg(salary) from emp group by post;
# 3.统计每个部门的员工人数
select post,count(id) from emp group by post;
# 4.统计每个部门的月工资开销
select post,sum(salary) from emp group by post;
# 5.统计每个部门最小的年龄数
select post,min(age) from emp group by post;
"""间接获取分组以外其他字段的数据"""
group_concat(xxx) #是将分组中括号里对应的字符串进行连接.如果分组中括号里的参数xxx有多行,那么就会将这多行的字符串连接,每个字符串之间会有特定的符号进行分隔。
# 1.统计每个部门下所有员工的姓名
select post,group_concat(name) from emp group by post;
# 2.统计每个部门下所有员工的姓名和年龄
select post,group_concat(name,age) from emp group by post;
select post,group_concat(name,'|',age) from emp group by post;
"""字段起别名"""
select post,group_concat(name) as '姓名' from emp group by post;
select id as '序号',name as '姓名' from emp;
# as关键字也可以不写 但是语义不明确 建议加上
select id '序号',name '姓名' from emp;
select+group by+having(过滤)
having与where的功能是一模一样的 都是对数据进行筛选
where用在分组之前的筛选
having用在分组之后的筛选
为了更好的区分 所以将where说成筛选 havng说成过滤
# 统计每个部门年龄在30岁以上的员工的平均薪资并且保留平均薪资大于10000的部门
# 1.先获取每个部门年龄在30岁以上的员工的平均薪资
先筛选出30岁以上的员工数据 然后再对数据进行分组
select post,avg(salary) from emp where age>30 group by post;
# 2.在过滤出平均薪资大于10000的数据
针对分组之后的数据再次筛选 需要使用having而不是where
select post,avg(salary) from emp
where age>30
group by post
having avg(salary) > 10000
;
'''针对聚合函数 如果还需要在其他地方作为条件使用 可以先起别名'''
# 这段代码如果在only_full_group_by模式下运行,因为 SQL-92里 SELECT、HAVING、ORDER后的非聚合字段必须和GROUP BY后的字段保持完全一致,会报错:ERROR 1463 (42000): Non-grouping field 'avg_salary' is used in HAVING clause
select post,avg(salary) as avg_salary from emp
where age>30
group by post
having avg_salary > 10000
;
distinct去重
# 去重的前提 数据必须是一模一样的才可以(如果数据有主键肯定无法去重)
select distinct age from emp;
"""
等我们学到django orm之后 数据会被封装成对象
那个时候主键很容易被我们忽略 从而导致去重没有效果!!!
"""
order by排序
# 1.按照薪资高低排序
默认升序:asc
降序:desc
select * from emp order by salary; # 默认是升序(从小到大)
select * from emp order by salary asc; # 关键字asc 可以省略
select * from emp order by salary desc; # 降序(从大到小)
# 2.先按照年龄升序排序 如果年龄相同 则再按照薪资降序排序
select * from emp order by age asc,salary desc;
# 3.统计各部门年龄在10岁以上的员工平均工资 并且保留平均工资大于1000的部门并按照从大到小的顺序排序
select post,avg(salary) as avg_salary from emp
where age > 10
group by post
having avg_salary > 1000
order by avg_salary desc;
limit分页
# 分页即限制展示条数
# 1.限制只展示五条数据
select * from emp limit 5;
# 2.分页效果
select * from emp limit 5,5;
# 3.查询工资最高的人的详细信息
select * from emp order by salary desc limit 1;
"""
当数据特别多的时候 经常使用limit来限制展示条数 节省资源 防止系统崩溃
"""
regexp正则
模式 | 描述 |
---|---|
^ | 匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 '\n' 或 '\r' 之后的位置。 |
$ | 匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 '\n' 或 '\r' 之前的位置。 |
. | 匹配除 "\n" 之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用像 '[.\n]' 的模式。 |
[...] | 字符集合。匹配所包含的任意一个字符。例如, '[abc]' 可以匹配 "plain" 中的 'a'。 |
[^...] | 负值字符集合。匹配未包含的任意字符。例如, '[abc]' 可以匹配 "plain" 中的'p'。 |
p1|p2|p3 | 匹配 p1 或 p2 或 p3。例如,'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 则匹配 "zood" 或 "food"。 |
* | 匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。 |
+ | 匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。 |
n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。 |
select * from emp where name regexp '^j.*(n|y)$';
"""
补充说明:我们目前所讲的是MySQL查询关键字中使用频率较高的一些
其实还有一些关键字目前无需讲解 并且SQL语句里面同样还支持流程控制语法
如果感兴趣的话 课后可以自行百度查看 非常简单!!!
"""
多表查询思路
# 多表查询的思路总共就两种
1.子查询
就相当于是我们日常生活中解决问题的方式(一步步解决)
将一条SQL语句的查询结果加括号当做另外一条SQL语句的查询条件
eg:以昨天的员工表和部门表为例 查询jason所在的部门名称
子查询的步骤
1.先查jason所在的部门编号
2.根据部门编号去部门表中查找部门名称
2.连表操作
先将多张表拼接到一起 形成一张大表 然后基于单表查询获取数据
eg:以昨天的员工表和部门表为例 查询jason所在的部门名称
连表操作
1.先将员工表和部门表按照某个字段拼接到一起
2.基于单表查询
# 实际演练
create table dep(
id int primary key auto_increment,
name varchar(32)
);
create table emp(
id int primary key auto_increment,
name varchar(32),
gender enum('male','female','others') default 'male',
age int,
dep_id int
);
insert into dep values(200,'技术'),(201,'人力资源'),(202,'销售'),(203,'运营'),(205,'安保');
insert into emp(name,age,dep_id) values('jason',18,200),('tony',28,201),('oscar',38,201),('jerry',29,202),('kevin',39,203),('jack',48,204);
# 使用子查询 获取jason所在的部门名称
# 1.先获取jason的部门编号
select dep_id from emp where name='jason';
# 2.将结果加括号作为查询条件
select name from dep where id=(select dep_id from emp where name='jason');
# 使用连表操作 获取jason所在的部门名称
笛卡尔积(了解知识)
select * from emp,dep; # 会讲所有的数据全部对应一遍
select * from emp,dep where emp.dep_id=dep.id; # 效率低下
"""
1.一条SQL语句的查询结果 我们也可以看成是一张虚拟表
2.如果一条SQL语句中设计到多张表的字段名称编写 建议使用表名前缀做区分
"""
连表操作有四个关键字
inner join 内连接
select * from emp inner join dep on emp.dep_id=dep.id;
'''只连接两张表中有对应关系的数据'''
left join 左连接
select * from emp left join dep on emp.dep_id=dep.id;
'''以左表为基准 展示所有的数据 没有对应项则用NULL填充'''
right join 右连接
select * from emp right join dep on emp.dep_id=dep.id;
'''以右表为基准 展示所有的数据 没有对应项则用NULL填充'''
union 全连接
select * from emp left join dep on emp.dep_id=dep.id
union
select * from emp right join dep on emp.dep_id=dep.id;
'''左右两表数据全部展示 没有对应项则用NULL填充'''
答案求解
select dep.name from emp
inner join dep on emp.dep_id=dep.id
where emp.name='jason'
;
"""
了解
我们学会了连表操作之后 其实就可以将N多张表拼接到一起
思路:我们可以将两张表拼接之后的结果起别名当做一张表使用
然后再去跟另外一张表拼接
select * from emp inner join
(select emp.id as epd,emp.name,dep.id from emp inner join dep on emp.dep_id=dep.id) as t1
on emp.id=t1.epd;
"""