目录
前言
数据库约束类型
1️⃣not null 非空约束
2️⃣unique 唯一约束
3️⃣default 默认值约束
4️⃣primary key 主键约束
5️⃣foreign key 外键约束
6️⃣check 限制约束
新增(insert + select)
查询(进阶)
聚合查询
聚合函数
1️⃣count - 查询行数
2️⃣sum - 求和
3️⃣avg - 求平均值
4️⃣max - 求最大值 / min - 求最小值
分组聚合查询 (group by 操作)
联合查询
1️⃣内连接
2️⃣外连接
3️⃣自连接
4️⃣子查询
5️⃣合并查询
小结ending
书接上回, 在认真学习本人的SQL入门文章后, 想必各位同学对SQL的操作已经基本拿捏了, 进来我们来学习一下SQL的进阶操作.
而本文的主要内容是, 在有一定SQL语言基础下, 学习数据库的约束类型(以MySQL数据库为例), 数据库中表及其数据的进阶操作.
本文篇幅较长, 关注收藏, 耐心食用吧
此为SQL系列第二篇, 如果还未学习基础SQL语言的同学, 可以移步系列第一篇文章, 即可快速上手↓
【SQL武林秘籍】零基础带你快速上手SQL语言http://t.csdn.cn/dX6Nd
简单来说, 约束其实就是数据库针对里面的数据能写什么, 给出的一组检验规则.
当我们在学习desc描述表结构操作时, 有没有同学对里面的结构产生疑问?
表结构中, Field代表字段名, Type代表其字段类型, 那后面的Null, Key, Default, Extra是代表什么意思呢? 这就需要讲到我们数据库的约束, 接下来给大家解答这些疑问.
先给大家看一下, 在创建表时, 该如何指定他的约束类型
注意: 当我们不指定约束类型时, Null默认为YES, Default默认为Null.
常见的约束有以下几种:
约束类型
|
说明
|
示例
|
---|---|---|
null | 使用not null指定列不为空 | name varchar(20) not null, |
unique | 指定列为唯一的, 不重复的 | name varchar(20) unique, |
default | 指定列为空时的默认值 | age int default 20, |
primary key | not null 和 unique 的结合 | id int primary key, |
foreign key | 关联其他表的主键或唯一键 | foreign key .. references .. |
check | 保证列中的值符合指定的条件 | check(sex='男' or sex='女') |
语法:
create table 表名(字段名 字段类型 约束类型, ...);
not null是用来指定某列不能存储null值, 表示该字段始终包含一个不为空的值, 如果插入数据时, 不指定一个确定值或指定值为空, 那么就会插入失败, 也无法使用update将确定值更新为null
下面我们来创建一个图书表, 将图书id设置为not null约束.
drop table if exists book;
create table book(id int not null, name varchar(20));
如图, 创建成功, 我们设置了两个字段, 一个是图书id, 一个是图书名称, 并将图书id用not null约束, 我们看一下约束后的表结构与普通表有什么区别.
可以看到字段id的Null属性, 是NO, 与默认的YES不同, 现在我们试着在表里插入一个id为空值的图书.
insert into book (id) value (null);
插入失败, 报错了, 意思是id列的值不能为null(空值)
unique约束是用来保证某列的每行必须有唯一值, 不能有重复的数据, 如果插入值时, 表中已经存在一个相同值时, 会导致插入失败, 更新也是一个道理.
我们还是来拿book表举例.
drop table if exists book;
create table book(id int unique, name varchar(20));
创建成功, 我们再查看一下book表结构是什么样子的.
可以看到字段id的Key属性是UNI(unique), 意味着这个字段存储的数据不可以重复, 我们现在试着插两个相同id的图书.
insert into book values (1, '图书一'), (1, '图书二');
插入失败, 报错了, 意思是重复的给id列插入1
default用来规定, 还没被赋值的列的初始值, 我们还是举一个例子来说明.
给book插入数据时, name列为空, 默认值设置为unknow.
drop table if exists book;
create table book(id int, name varchar(20) default 'unknow');
创建成功, 我们再查看一下book表结构是什么样子的.
可以看到字段name的Default属性写着unknow, 不赋值时字段的默认值是unknow.
现在我们插入一条空名图书.
可以看到, 即使我们没有给name赋值, 也有一个默认值unknown.
我们可以将 primary key 理解为 not null 和 unique 的结合, 确定某列有唯一标识, 不能为空, 也不能有重复数据. 主键 = unique + not null
drop table book;
create table book(id int primary key, name varchar(20));
创建成功, 我们来看一下表结构是什么样子.
可以看到字段id的Key属性是PRI(primary key), 意味着这个字段存储的数据不可以为空, 也不可以重复, 我们就不插入数据来验证了, 与之前unique + not null的效果一致, 我们讲一下primary key的特殊用法.
自增主键(primary key auto_increment)
对于整数类型的主键, 我们可以搭配自动增长auto_increment来使用, 插入数据对应字段不给值时, 给默认赋值最大值 + 1
我们来创建一张有自增主键的book表
drop table book;
create table book(id int primary key auto_increment, name varchar(20));
我们再看一下表结构
可以看到, 相较于普通主键, 自增主键的Extra额外属性中, 写着auto_increment, 代表着这个字段是可以自动增长的.
在使用时, 我们只需要在insert语句中, 不给id赋值即可.
现在我们再插入三个图书数据进去, 看一下是什么样的效果.
insert into book (name) values ('图书一'), ('图书二'), ('图书三');
可以看到, 即使我们没有设置字段id的值, 带有自增主键的字段id, 可以自动生成值, 并且是递增的.
如果我们手动加入一个id进去, 自增就会自动缩印以上所有数据中的最大值, 并且+1赋给新的id.
外键通常用来在两个表之间建立关系, 外键的主要用途是维持实体的两个独立实例之间的数据完整性, 下面我来用一张 class 表与一张 student 表来带大家简单了解一下外键的使用. 在设定时, 我们让 student 受到 class 的约束, 那么 class 就叫做 student 的父表(parent), student 就是 class 的子表(child).
# 创建class表
create table class(classId int primary key auto_increment, className varchar(20));
# 创建student表, 将classId与class表中classId关联
# 语法结构 foreign key (子表列名) references 父表名(父表列名);
create table student(studentId int primary key auto_increment, name varchar(20), classId int,foreign key (classId) references class(classId));
我们再来看一下两个表的结构
那么外键具体是帮助我们怎么约束呢? 接下来我们进行一个 insert 插入操作, 在 student 表中添加姓名为张三, 班级为1班的同学, 你们猜会发生什么呢?
可以看到, 这里出现错误了, 我们可以简单看一下报错信息, Cannot add or update a child row, 意思是不能添加或修改子表的行, a foreign key constraint fails, 意思是外键约束失败.
那么为什么会发生这样的错误呢? 原因是我们对 student 表中的 classId 进行了外键约束, 只有父表内的 classId 有其插入对应的数据时, 才可以正确进行插入.
接下来, 我们在 class 表中插入一个班级信息.
我们再尝试一遍上面对 student 的插入张三的操作.
可以看到, 这次插入成功了, 可能有细心的同学会注意到, 明明是第一次成功插入数据, 为什么主键Id的值为2, 那是前面尝试添加过一次数据, 但是失败了, 不过自增主键还是增加了.
约束是父表与子表双方的
在进行外键约束操作时, 我们要注意约束其实是相对的, 父表在约束子表的同时, 其实子表凡响的约束了父表, 大家可以想一想, 当有子表依赖父表时, 我们可以直接将父表中的数据删除吗?
可以看到, 删除操作失败了, Cannot delete or updata a parent row, 不能删除或修改父表数据.
所以当子表对父表有依赖时, 父表内被依赖的数据是无法直接删除的, 只有子表中依赖其的数据被删除后, 才可以删除父表中被依赖数据, 这就是子表对父表的约束.
check限制约束是 MySQL 8.0 之后新增的约束类型, 在之前的版本内使用该约束会被忽略, 但也不会报错. 大家简单认识一下即可.
下面给大家一个check约束使用的案例, 此案例表示列 sex 的值只能为 男 或者 女.
create table test_user (
id int,
name varchar(20),
sex varchar(1),
check (sex ='男' or sex='女')
);
可以看到, 当我们在尝试插入一个名为张三, 性别为 '?' 的人时, 检测到插入数据是违法的, 所以无法插入.
这里我要讲的进阶内容的新增, 就是将新增和查询联合起来, 其实就是 insert + select, 可以实现将查询到的结果作为新增的数据插入到表中.
接下来我们建立两个 student 学生表 student1 和 student2 , 并且在 student1 中插入三条数据.
接下来, 我们进行一个新增操作, 目的是将查询到 student1 表中的所有数据添加到 student2 表中.
insert into student2 select * from student1;
可以由结果看到新增数据成功了, 即向 student2 中插入了 student1 中的全部数据.
注意: 在进行这种方式的插入时, 查询的表的结果, 得到的列数, 类型需要和插入的表的列数, 类型相匹配.
聚合查询, 就是在查询过程中, 将表的行和行之间进行一定的运算, 聚合查询依赖于聚合函数, 这些函数是 SQL 中提供的库函数.
常见的聚合函数有以下几种:
函数 | 说明 |
---|---|
count |
返回查询到的数据的 数量
|
sum |
返回查询到的数据的 总和,不是数字没有意义
|
avg |
返回查询到的数据的 平均值,不是数字没有意义
|
max |
返回查询到的数据的 最大值,不是数字没有意义
|
min |
返回查询到的数据的 最小值,不是数字没有意义
|
例: 查询 student1 表中所有行的数量
select count(*) from student1;
例: 查询 student1 表中 id 列数据的数量
select count(id) from student1;
例: 当表中有 null 数据时, 查询 name 列数据的数量
在进行查询操作前, 我们先给 student1 添加一个 id 为 4 , name 为 null 的数据.
然后按照 id 和 name 来分别进行 count 的聚合查询
# 插入数据
insert into student1 values(4, null);
# 按id进行count聚合查找
select count(id) from student1;
# 按name进行count聚合查找
select count(name) from student1;
可以看到, 如果查询的单列中, 有 null 数据时, 会将其省略.
sum求和的聚合查找, 只是针对数字列有效, 如果是字符串列, 则无法进行计算.
例: 查询 student1 中 id 列的值的和
select sum(id) from student1;
例: 查询 student1 中 name 列的值的和 (字符串列)
select sum(name) from student1;
我们可以看到, 当计算字符串列时, 结果会出现 0 , 并且会提示 warnings 信息, 在这里我们可以使用 show warnings; 来进行查看.
可以看到, 报错信息显示, 将张三, 李四, 王五字符串类型给截断了.
注: 当查询数据中有null值时, 会自动跳过当前行, 不进行计算
例: 查询 student1 中 id 列的值的平均值
select avg(id) from student1;
avg求平均值的操作就不具体展开讲了, 其实是与上面sum操作注意点是一致的.
由于 max 与 min 是相反的, 其逻辑也比较简单, 我就把他放在一个小点里来说.
例: 查询 student1 中 id 列的最大值和最小值
select max(id),min(id) from student1;
select 中使用 group by 子句可以对指定列进行分组查询, 需要满足: 使用 group by 进行分组查询时, select 指定的字段必须是“分组依据字段”, 其他字段若想出现在 select 中则必须包含在聚合函数中.
select column1, sum(column2), .. from table group by column1,column3;
让我们来准备一个测试表以及插入一些测试数据: 职员表, 有 id , name , role , salary.
create table emp(
id int primary key auto_increment,
name varchar(20) not null,
role varchar(20) not null,
salary numeric(11,2)
);
insert into emp(name, role, salary) values
('马云','服务员', 1000.20),
('马化腾','游戏陪玩', 2000.99),
('孙悟空','游戏角色', 999.11),
('猪无能','游戏角色', 333.5),
('沙和尚','游戏角色', 700.33),
('隔壁老王','董事长', 12000.66);
例: 查询每个角色的最高工资、最低工资和平均工资 (group by role)
select role,max(salary),min(salary),avg(salary)from emp group by role;
以上便求出了每个角色的平均薪资
- 利用having和where来进行条件筛选
1. group by 分组前可使用 where 条件来进行筛选, 我们可以试着求一下每个角色的平均薪资, 但是需要刨除掉孙悟空.
select role,avg(salary) from emp where name != '孙悟空' group by role;
2. group by 分组后可使用 having 条件来进行筛选, 试着求每个岗位的平均薪资, 但是要刨除掉董事长.
select role,avg(salary) from emp group by role having role != '董事长';
3. 我们也可以同时使用 where 和 having 在分组前和分组后来进行筛选
select role,avg(salary) from emp where name != '孙悟空' group by role having role != '董事长';
实际开发中, 很多数据往往来自于不同的表, 而联合查询就是把多个表联合到一起进行查询, 多表查询是对多张表的数据取笛卡尔积:
笛卡尔积得到的表的列数, 为两个表的列数之和, 行数为之积, 但由于笛卡尔积是排列组合后的结果, 这里面的有些数据是无效的.
让我们来创建一些测试表及其数据:
drop table if exists classes;
drop table if exists student;
drop table if exists course;
drop table if exists score;
create table classes(id int primary key auto_increment,name varchar(20), `desc`varchar(100));
create table student(id int primary key auto_increment,sn varchar(20),name varchar(20),qq_mail varchar(20),classes_id int);
create table course(id int primary key auto_increment,name varchar(20));
create table score(score decimal(3,1),student_id int,course_id int);
insert into classes(name, `desc`) values
('计算机系2019级1班', '学习了计算机原理、C和Java语言、数据结构和算法'),
('中文系2019级3班','学习了中国传统文学'),
('自动化2019级5班','学习了机械自动化');
insert into student(sn, name, qq_mail, classes_id) values
('09982','黑旋风李逵','[email protected]',1),
('00835','菩提老祖',null,1),
('00391','白素贞',null,1),
('00031','许仙','[email protected]',1),
('00054','不想毕业',null,1),
('51234','好好说话','[email protected]',2),
('83223','tellme',null,2),
('09527','老外学中文','[email protected]',2);
insert into course(name) values
('Java'),('中国传统文化'),('计算机原理'),('语文'),('高阶数学'),('英文');
insert into score(score, student_id, course_id) values
-- 黑旋风李逵
(70.5, 1, 1),(98.5, 1, 3),(33, 1, 5),(98, 1, 6),
-- 菩提老祖
(60, 2, 1),(59.5, 2, 5),
-- 白素贞
(33, 3, 1),(68, 3, 3),(99, 3, 5),
-- 许仙
(67, 4, 1),(23, 4, 3),(56, 4, 5),(72, 4, 6),
-- 不想毕业
(81, 5, 1),(37, 5, 5),
-- 好好说话
(56, 6, 2),(43, 6, 4),(79, 6, 6),
-- tellme
(80, 7, 2),(92, 7, 6);
共创建四张表:
看一下course表中数据:
看一下score表中数据:
看一下student表中数据:
联合查询中, 主要有以下几种方式, 让我来给大家一一讲解.
例1: 查询"许仙"同学的成绩
想要查找学生以及其成绩, 我们需要用到两个表, sutdent 和 score, 这种情况, 我们就需要进行联合查询
# 两种连接方式
# 1. 使用 ',' 来连接两表
select * from student, score;
# 2. 使用 "join ... on ..." 来连接两表
# 外连接时必须使用 "join on" , 外连接我们在之后讲解.
select * from student join score;
由于查询结果有160行数据, 我们给大家截取一部分来看, 160行数据就是两个表的行数相乘得到的积, 20 * 8 = 160.
我们可以看到, 如果我们不额外进行操作的话, 笛卡尔积得到的联合表, 是有很多无效数据的, 我们需要加一些修饰来筛选出有效数据, 在这个例子中, 我们需要筛选出一个学生的课程成绩, 要求两表中的学生ID是相同的, 我们可以使用 " 表名.列名 " 的形式来进行选择, student.id = score.student_id.
# 两种连接方式
# 1. 使用 "where" 搭配 ',' 来筛选有效信息
select * from student, score where student.id = score.student_id;
# 2. 使用 "join ... on ..." 来筛选有效信息
select * from student join score on student.id = score.student_id;
可以看到经过筛选后, 160行数据只剩下了20行, 并且都是有效数据, 接下来我们就可以利用学过的基础查询语句来实现查询"许仙"同学的成绩.
在这里我们就使用 "join on" 来进行查询.
这样我们便查询到了许仙同学的成绩.
例2: 查询"许仙"同学的成绩并要求显示课程名称
我们再思考一下这个问题, 如果要求要显示其课程的名称该怎么办呢? 这就需要查询三个表, student, score, course, 才可以实现该操作, 那么我们该如果连接三个表呢? 在有了之前一个案例的经验后, 我们话不多说, 直接上代码来看.
select
student.name,
score.score,
course.name as course_name # 给course.name取别名
from
student,
score,
course
where
student.id = score.student_id
and
score.course_id = course.id
and
student.name = '许仙';
由于本条语句有点复杂, 我将代码分开给大家看
可以看到, 当我们连接三张表时, 需要有两个连接条件:
例3: 查询每个同学的总成绩, 及其个人信息.
在本案例中, 我们需要查找每个学生的总成绩, 说到总成绩, 大家熟悉吗? 有没有想到我们之前讲到的聚合查询, 聚合查询中有一个聚合函数是 sum 求和函数, 我们可以利用 sum 再搭配联合查询 student 表和 score 表来实现本操作. 当然要使用 sum 求和函数, 我们还需要利用 "group by" 按照 student.name 来进行分组.
select
student.name,
sum(score.score)
from
student,score
where
student.id = score.student_id
group by
student.name;
总结一下:
在完成三个案例后, 你肯定对联合查询有了一个基本的了解, 我们在这里总结一下联合查询的实现步骤: 看, 求, 筛, 简
上面讲到的联合查询, 其实就是最基本的, 也是最常用的内连接, 此外我们还有一些连接方式, 我们先讲一下外连接.
外连接和内连接在本质上都是进行笛卡尔积, 但是在细节中还是有所差别的.
而外连接又分为左外连接和右外连接两种情况.
由于我们之后要进行外连接, 所以我们采用 "join on" 内连接方法来查询一下数据.
例: 查询所有同学的成绩, 及其个人信息.
select * from student join score on student.id = score.student_id
这是我们内连接查询到的结果. 接下来我们尝试进行一下外连接, 来看一下外链接的作用是什么.
例: 查询所有同学的成绩, 及其个人信息, 如果该同学没有考试成绩, 也需要显示
左外连接
select * from student left join score on student.id = score.student_id;
可以看到 "老外学中文" 并没有考试成绩, 但是也显示出来了, 这就是外连接的作用, 当我们进行左外连接时, 就是以左侧的表为准, 左侧表其中的数据均能体现出来, 即使是与其对应的右侧表中的值为空值, 甚至数据无法对应时, 也能体现左侧表中的所有数据. 右外连接与其相反.
此操作对应的右外连接为
select * from score right join student on student.id = score.student_id;
总结一下: 相较于内连接来说, 外连接的使用是比较少的, 一般只用于一些特定场景, 同学认识该操作即可, 等真需要使用时, 回来再看一下, 回想起来基本的操作就OK了
自连接大家听名字应该就知道它是如何进行连接的, 没错, 就是自己和自己连接, 自己和自己做笛卡尔积, 自连接就是指在同一张表连接自身进行查询, 这是特殊情况下的特殊操作, 并非是一般用法, 这个操作本质上是将 "行" 转换成 "列"
例1: 显示所有“计算机原理”成绩比“Java”成绩高的成绩信息
在本案例中, 我们需要找到哪些同学的 "计算机原理" 比 "Java" 高, 如果我们直接查询 score 成绩表
可以看到:
当前的课程, 与其分数是行关系, 这样我们不方便查找, 这时我们就需要将行转换为列, 需要用到自连接. 在使用自连接时, 我们需要注意一个问题, 在连接时, 两个表必须指定一个别名, 否则两个都是 score 表, 会出现语法错误.
给大家看一下自连接的实现步骤:
# 先查询“计算机原理”和“Java”课程的id
select id,name from course where name='Java' or name='计算机原理';
# 再查询成绩表中,“计算机原理”成绩比“Java”成绩 好的信息
select
s1.*
from
score s1, # 给score取别名为s1
score s2 # 给score取别名为s2
where
s1.student_id = s2.student_id
and
s1.score < s2.score
and
s1.course_id = 1
and
s2.course_id = 3;
# 也可以使用join on 语句来进行自连接查询
select
s1.*
from
score s1 # 给score取别名为s1
join
score s2 # 给score取别名为s2
on
s1.student_id = s2.student_id
and s1.score < s2.score
and s1.course_id = 1
and s2.course_id = 3;
经过自连接筛选之后, 得到了我们想要的结果.
思考一下: 其实以上查询, 只显示了成绩信息, 并且是分布执行的, 如果要显示学生及成绩信息,并在一条语句显示, 该怎么做呢?
这就需要将自连接, 与我们之前学的内连接相结合起来了.
select
stu.*,
s1.score Java,
s2.score 计算机原理
from
score s1
join score s2 on s1.student_id = s2.student_id
join student stu on s1.student_id = stu.id
join course c1 on s1.course_id = c1.id
join course c2 on s2.course_id = c2.id
and s1.score < s2.score
and c1.name = 'Java'
and c2.name = '计算机原理';
总结一下: 学到这里, 大家应该可以发现, SQL的逻辑表达能力是有限的, 很多时候业务的逻辑是很难实现的, 不是做不了, 而是代价太大, 即使是实现了一些比较复杂的逻辑, 也需要很复杂的语句来完成, 代码可读性是很差的, 相比之下, 使用Java这样的语言来实现复杂逻辑是更加合适的.
子查询是指嵌入在其他 SQL 语句中的 select 语句, 也叫作嵌套查询, 大家可以把他想象为 "套娃", 一层套一层, 把多个查询语句合并成一个.
而子查询也分为两种查询方法: 单行子查询和多行子查询
1. 单行子查询
例1: 查询与"不想毕业"同学的同班同学
我们不使用子查询的话, 用之前学的查询知识, 应该如何去做呢? 是不是应该把这个操作分为两步, 先查询 "不想毕业" 同学是在哪个班, 然后再查询, 和查询到的班级号相同的同学.
# 1. 查询'不想毕业'同学的班级号
select classes_id from student where name = '不想毕业';
# 2. 第一步查询结果为1, 查询1班的所有同学
select * from student where classes_id = 1;
我们可以将两步操作嵌套起来, 变为一步操作, 这就是子查询.
select * from student where classes_id = (select classes_id from student where name = '不想毕业');
可以看到, 与分步查询的结果是一样的.
2. 多行子查询
在上述查询中, 我们嵌套的查询结果只有一条记录, 如果我们需要嵌套一个返回多行记录的查询结果, 该怎么办去做?
例2: 查询"语文"或"英文"课程的成绩信息
我们还是先进行基本的分步操作, 先查询到两个课程的id, 再拿着课程id去分数表中查找.
# 1. 查询两个课程的id
select id from course where name = '语文' or name = '英文';
# 2. 拿着课程id去分数表中查找, 还记着 "in" 运算符吗?
select * from score where course_id in (4, 6);
那么如何将两步操作嵌套起来呢? 我们只需要将 in 运算符中的(4, 6), 替换为多行查询结果即可.
注意一下: 子查询在实际开发当中使用的时候一定要谨慎, 一旦其中嵌套的层次多了, 那么对于代码的可读性将会是毁灭性的打击.
在实际应用中, 为了合并多个select的执行结果, 可以使用集合操作符 union, union all.
使用 union 和 union all 时, 前后查询的结果集中, 字段需要保持一致.
1. union
该操作符用于取得两个结果集的并集. 当使用该操作符时, 会自动去掉结果集中的重复行.
例1: 查询id小于3, 或者名字为"英文"的课程
# 使用 union 来进行合并查询
select * from course where id < 3
union
select * from course where name = '英文';
我们使用之前学过的运算符 or 来实现也可以, 但是要注意 or 只能针对一个表中的数据, 而 union 能针对多个表.
查询得到的结果与 union 是一样的
2. union all
该操作符用于取得两个结果集的并集. 但是与 union 有些不同, 当使用 union all 时, 不会自动去掉结果集中的重复行.
例2: 查询id小于3, 或者名字为"Java"的课程
select * from course where id < 3
union all
select * from course where name = 'Java';
可以看到结果集中出现重复数据Java, 如果我们使用 union 来操作, 就可以避免了
union运算符则没有出现重复数据Java.
✨感谢你们的耐心阅读, 博主本人也是一名学生, 也还有需要很多学习的东西. 写这篇文章是以本人所学内容为基础, 日后也会不断更新自己的学习记录, 我们一起努力进步, 变得优秀, 小小菜鸟, 也能有大大梦想, 关注我, 一起学习.
✨数据库SQL语言的知识就先告一段落了, 学习完初阶和进阶(本篇)两篇文章后, 你一定可以实现轻松操作表数据, 之后我还会有章节给大家讲一下数据库中表的简单设计, 请关注插眼, 带你继续学习数据库的知识.
再次感谢你们的阅读, 你们的鼓励是我创作的最大动力!!!!!