【MySQL】基础部分(DQL,数据类型,约束等)

170 阅读21分钟

DB,DBMS,SQL

  • DB:全称Database,存储数据的仓库,实际上就是一堆文件,这些文件中存储了具有特定格式的数据。
  • DBMS:全称Database Management System,专门用来管理DB中数据的,DBMS可以对DB当中的数据进行增删改查。MySQL就是DBMS。
  • SQL:全程Structured Query Language,结构化查询语句,开发人员负责编写SQL语句,DBMS负责执行SQL语句,从而对DB中数据进行增删改查。SQL是一套标准,在MySQL可以使用,同时在Oracle中也可以使用。

常用命令

1. 查看数据库

show databases;

2. 使用数据库

use 表名;

3. 创建数据库

create database 表名;

4. 查看表结构

desc 表名;

5. 查看数据库版本号

select version();

6. 查看当前数据库

select database();

7. 终止一条SQL

\c

单表查询

1. 关键字顺序

书写顺序:

select
	...
form
	...
where
	...
group by
	...
having
	...
order by
	...

执行顺序:form ——> where ——> group by ——> having ——> select ——> order by

2. 简单查询

查询单个字段:

select 字段名 from 表名;

查询多个字段:

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

查询所有字段:

select * from 表名;

使用 * 查询效率是比较低的,因为MySQL还需要将 * 转换成表的所有字段,实际开发中不建议使用。

给字段起别名:

select 字段名1 as 别名1, 字段名2 as 别名2 from 表名;

使用as设置了别名查询出来的数据对应显示的字段也是别名,不是原字段名。

给字段做数学运算:

select 字段名*12 as 别名 from 表名;

做数学运算的字段类型必须是可计算的数值类型,但是如果不起别名,查询的数据对应显示的字段是字段名*12,因此一般还需要起别名。

3. 条件查询

基础结构:

select 字段名 from 表名 where 条件; 

条件一般有:

条件含义SQL实例
=,>,<,!=,>=,<=字面含义select * from user where age = 18;
between ... and ...字段 >= xx and 字段 <= xxselect * from user where age between 18 and 20;
is null,is not null字段是否为nullselect * from user where introduction is null;
and,or交集,并集select * from user where username = 'john' and age = 18;
in,not in字段是否是这几个值select * from user where username in ('john','peter');
like模糊查询(%任意字符,_一个字符)select * from user where username like '_o%';

如果and,or同时出现,优先级and > or。在执行SQL过程中,先执行and,后执行or

select * from user where age > 18 and dept_no = 10 or dept_no = 20; 

上面语句等同于

select * from user where (age > 18 and dept_no = 10) or dept_no = 20; 

遇到优先级问题,建议使用小括号。

4. 排序查询

升序排序:

select * from user order by age;

默认升序,等价于

select * from user order by age asc;

降序排序:

select * from user order by age desc;

可以参照多个字段进行排序

select * from user order by age asc, username desc;

上述命令表示按照年龄升序排序,在年龄一样时,再按照用户名降序排序。

4. 分组函数查询

分组函数,也叫做多行处理函数。

特点是:输入同一组的多行数据,统计出一行结果,来表示本组的状态。

函数含义SQL实例
count计数select count(id) from user;
sum求和select sum(age) from user;
avg平均值select avg(age) from user;
max最大值select max(age) from user;
min最小值select min(age) from user;

分组函数在使用前,必须先对数据进行分组,如果不分组,整张表默认为一组。

分组函数自动忽略NULL,不需要对NULL进行条件过滤。例如,如果使用count,则指定字段为NULL的条目将不会被计入总个数。(NULL不是一个数值,而是什么都没有)

因为count会忽略NULL,所以一般不会在count中传入一般字段作为参数,一般传 * 或者 主键,count(*)就是直接计算表的条目总数。

分组函数可以一起使用

select max(age), min(age), avg(age), count(*) from user;

分组函数不能直接出现在Where子句中。

比如说如下SQL运行会报错,为什么会报错?因为在分组函数使用前数据必须先分组,而where执行的时候,数据还没有分组(group by)。

select * from user where age > min(age);

5. 分组查询

分组查询可以先对表中数据进行分组,然后对每一组数据进行操作。

分组字段值一样的就会被分成一组。

注意:使用group by分完组之后,查询结果是以组为单位呈现,而不是原来的行,因此这就是为什么select之后只能写分组字段和聚合函数的原因。如果一个表为一组,则还是以行为单位呈现。

比方说一些场景:

  • 计算每个部门的平均薪资

    select department, avg(salary) from employee group by department;
    
  • 计算每种岗位的平均薪资

    select job, avg(salary) from employee group by job;
    
  • 计算每个部门不同岗位的最高薪资(多字段联合分组)

    select department, job, max(salary) from employee group by department, job;
    
  • 计算每个部门的最高薪资且最高薪资大于3000(对分完组的数据进行过滤)

    select department, max(salary) from employee group by department having max(salary) > 3000;
    

在分组查询的select子句中,只能写group by子句中参加分组的字段和分组函数,如果写其他字段,则会出现数据不匹配错误。

having不能单独使用,必须和group by联合使用,不能代替where。但是使用having进行过滤的SQL效率会比较低,因为有大量的前置数据会被group by计算。因此一般都会优先使用where代替having进行过滤,这样group by处理起来就会轻松很多。如果where实在完成不了,再选择having。

having过滤的是使用group by分完组之后以组为单位的数据。

比如优化上面使用having的SQL

select department, max(salary) from employee where salary > 3000 group by department;

比如,计算每个部门平均工资且平均薪资大于3000,此时where就不能代替having

select department, avg(salary) from employee group by department having avg(salary) > 3000;

6. 去重查询

使用到一个关键字:distinct

比如查询一个单位所有的职位种类

select distinct job from emp;

distinct还可以和分组函数连用,比如查询一个单位中职位种类的数量

select count(distinct job) from emp;

连接查询

1. 分类

  • 根据语法年代分类:

    • SQL92:1992年的SQL语法。

    • SQL99:1999年的SQL语法。

    SQL92语法没有SQL99清晰,尤其在连接查询时可以凸显出来,SQL99将表连接条件和筛选条件分离(join..on...),结构更加清晰,SQL92现在已经很少有人使用了。

  • 根据表连接方式分类:

    • 内连接
      • 等值连接
      • 非等值连接
      • 子连接
    • 外连接
      • 左外连接
      • 右外连接
    • 全连接(很少使用,不讲述)

2. 笛卡尔积现象

在MySQL,Oracle,DB2等一系列DBMS中,如果若干张表在没有任何条件限制下进行连接查询,那么就会出现笛卡尔积现象。

20211101172225.png

笛卡尔积现象是一个数学现象,但是它阐述了DBMS查询的底层逻辑。

笛卡尔积在DBMS中的具体表现是:假如有两张表,A表有 M 条记录,B表有 N 条记录,A表和B表在没有任何条件限制下进行连接查询,那么查询出来的数据总条数为 M×N ,因为DBMS会将A表中的每一条记录去和B表的所有记录进行匹配。

select * from A, B;

如何避免笛卡尔积现象?

连接时添加合适的条件,在A表匹配B表的每一条记录时,只有符合条件的才能匹配成功,最终被查询出来。

select * from A, B where ...

注意:

  • 虽然添加合适的条件会让查询出来的数据条数减少,但是查询过程中匹配的次数是不会减少的,如上案例还是 M × N 次。

  • 通过底层查询逻辑可以了解到,连接查询时连接的表越多,查询的效率越低,尽量避免表的连接次数。

3. 表起别名

在连接查询中,给表起一个别名很重要,有两个原因:

  • 表名很长,书写不方便。
  • 使用表的别名来引出表的字段比单独写一个字段的效率要高,因为如果SQL中只写一个字段,DBMS会去每一个表中找对应的字段。

例如(SQL92语法):

select 
	e.ename, d.dname 
from 
	emp e, dept d
where 
	e.deptno = d.deptno;

起的别名只是在SQL中会使用到,查出来的数据的对应字段还是表中的原字段。

4. 内连接

4.1 特点

20211101210637.png

在内连接中,表与表之间的关系是平等的,只有完全能匹配上条件的数据才能被查询出来。

4.2 等值连接

表与表之间连接的条件是一个等量关系。

例如 (SQL92语法,不推荐):查询每个员工和其对应的部门。

select e.ename, d.dname from emp e, dept d where e.deptno = d.deptno;

SQL99语法(推荐):

select 
	e.ename, d.dname 
from 
	emp e 
inner join 
	dept d 
on 
	e.deptno = d.deptno;

在内连接中inner是可以省略掉的,只写一个join,但是不建议这么做。

4.3 非等值连接

表与表之间连接的条件不是一个等量关系。

例如:查询每个员工的薪资和其对应的薪资等级。

select 
	e.ename, e.sal, s.grade 
from 
	emp e 
inner join
	salgrade s 
on 
	e.sal between s.losal and s.hisal;

4.4 内自连接

自己与自己连接进行查询。

例如:查询每个员工的上级领导。

select 
	e1.ename as '员工名', e2.ename as '领导名' 
from 
	emp e1
inner join
	emp e2 
on 
	e1.leaderno = e2.empno;

这道题的技巧就是将emp表看成逻辑上的两张表,一张是员工表,一张是领导表。

5. 外连接

5.1 特点

20211101210624.png

在外连接中,表与表之间存在主次关系,因此不仅仅能够查询出完全匹配条件的数据,还可以将主表中其他不匹配条件的数据也一并查询出来。

注意:不匹配的数据查询出来时,只有主表的字段有数据,次表对应的字段全部为null。

任何一个左外连接都有对应的一个右外连接的写法,反之成立。

外连接的查询结果条数一定大于等于内连接查询结果条数。

5.2 左外连接

将join左边的表看成主表,在查询出主表全部数据的同时,捎带着连接join右边的表查询出完全匹配条件的数据。

例如:查询出所有员工和其对应的部门,已知部门表中有一个部门一个员工都没有,现在要将要将这个空部门也查询出来

select 
	e.ename, d.dname
from 
	dept d
left outer join
	emp e
on
	d.deptno = e.deptno;

如果主表中的一些数据没有匹配条件,则某些关联字段就是NULL。

5.3 右外连接

将join右边的表看成主表,在查询出主表全部数据的同时,捎带着连接join左边的表查询出完全匹配条件的数据。

例如:查询出所有员工和其对应的部门,已知部门表中有一个部门一个员工都没有,现在将要将这个空部门也查询出来

select
	e.ename, d.dname
from
	emp e
right outer join
	dept d
on 
	e.deptno = d.deptno;

在外连接中outer是可以省略掉的,只写一个right join,但是不建议这么做。

5.4 外自连接

外自连接一定是左外连接或者右外连接中的一种,只是主表和副表都是自己。

例如:查询每个员工的上级领导,已知有一个员工没有领导,现在要将这个员工也查询出来。

select 
	e1.ename as '员工名', e2.name as '领导名'
from 
	emp e1
left outer join
	emp e2
on
	e1.leaderno = e2.empno;

这道题的技巧就是将emp表看成逻辑上的两张表,一张是员工表,一张是领导表。

如果使用内连接来做此题,这个没有领导的员工就查不出来。

6 三张以上表连接

一条SQL中,内连接和外连接可以一起出现。

select
	...
from
	a
inner join
	b
on
	a表和b表的连接条件
right outer join
	c
on 
	a表和c表的连接条件
inner join
	d
on 
	a表和d表的连接条件

上述语句可以理解成:a表先和b表进行内连接,然后将连接的结果再和c表进行右外连接,然后将连接的结果最后和d表进行内连接。

例如:查询每个部门的员工和该员工的工资等级

select
	e.ename, e.sal, d.dname, s.grade
from
	emp e
inner join 
	dept d
on 
	e.deptno = d.deptno
inner join
	salgrade s
on
	e.sal between s.losal and s.hisal;

分页查询

1. limit关键字

limit在order by之后执行,作用就是将查询结果集的一部分取出来,通常使用在分页查询当中。

limit的用法:

  • 标准用法:startIndex是起始下标,length是长度。

    limit startIndex, length
    
  • 缺省用法:如果不写startIndex,默认 就是0。

    limit length
    

例如:

(1)按照工资降序,查询排名在前5名的员工

select ename, sal from emp order by sal desc limit 5;

完整写法:

select ename, sal from emp order by sal desc limit 0, 5;

(2)按照工资降序,查询排名在3~5名的员工

select ename, sal from emp order by sal desc limit 2, 3;

2. 标准分页

假设每一页有 3 条数据:

  • 第一页:[ 0,1,2 ]
  • 第二页:[ 3,4,5 ]
  • 第三页:[ 6,7,8 ]

选择哪一页显示pageNo,每页显示的数据条数为pageSize,那么startIndex = (pageNo - 1) * pageSize,length = pageSize。

使用limit标准分页写法为

limit (pageNo - 1) * pageSize, pageSize;

数据类型

1. 常见数据类型

MySQL中有很多数据类型,只需掌握几个常见的即可。

  • int:整数
  • bigint:长整数
  • char:定长字符串
  • varchar:可变长度字符串
  • float:单精度浮点数
  • double:双精度浮点数
  • decimal:定点数 / 精确数
  • date:短日期,只包括年、月、日信息
  • datetime:长日期,包括年、月、日、时、分、秒信息
  • clob:字符大对象,全称:Character Large Object,最多可以存储4G的字符串,字符数超过255的字符串建议使用clob存储
  • blob:二进制大对象,全称:Binary Large Object,专门用来存储视频、音频和视频等流媒体数据。只能用IO流来插入数据,不能用命令

2. int和bigint

MySQL中常见的整形类型:

类型占用字节有符号范围无符号范围
tinyint1-2^7 ~ (2^7) - 10 ~ 2^8
smallint2-2^15 ~ (2^15) - 10 ~ 2^16
int4-2^31 ~ (2^31) - 10 ~ 2^32
bigint8-2^63 ~ (2^63) - 10 ~ 2^64

MySQL整形可以使用Type(M)进行表示,在Type(M)中,M表示最大显示宽度,M的值和Type(M)所占多少存储空间没有关系,比如int(4)、bigint(10)只是用于显示的位数。

3. char和varchar

MySQL字符串类型可以使用Type(M)进行标识,在Type(M)中,M表示最大字符串最大字符数。

char和varchar的存储机制:

  • char类型不会根据数据动态分配存储空间。比如定义类型为char(10)而实际存储的数据只有4个字符,那么底层仍然占用10个空间来存储。
  • varchar类型会根据实际存储的数据动态分配存储空间。比如定义类型为varchar(10)而实际存储的数据只有4个字符,那么底层就会动态占用4个空间来存储,而不是10个空间。

在MySQL 4 之前,char和varchar的长度是按照字节来计算,而MySQL5之后,则是按照字符来计算。

例如:varchar(6)在MySQL4中占用6个字节,在MySQL5中占用6各个字符。

而字符和字节的换算,则与编码方式有关:

  • 在UTF-8中,1 个英文字符占 1 个字节,1个汉字字符占 3 个字节。

  • 在GBK / ASCII中,1 个英文字符占 1 个字节,1 个汉字字符占 2 个字节。

  • 在Unicode中,1 个英文字符占 2 个字节,1 个汉字字符占 2 个字节。

char和varchar的异同

  • char的最大长度为255(M <= 255)。
  • varchar的整体最大长度为65532(M <= 65532),具体有效长度由最大行大小和使用的字符集来确定。
  • char浪费内存,但是处理速度较快,相当于空间换时间。
  • varchar节省内存,但是处理速度较慢,相当于时间换空间。

4. float和double

类型占用字节有符号范围无符号范围
float4(-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38)0,(1.175 494 351 E-38,3.402 823 466 E+38)
double8(-1.797 693 134 862 315 7 E+308,-2.225073858507 2014E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308)0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308)

MySQL浮点型可以用Type(M,D)来表示:

  • M为精度,表示该数据的总位数。
  • D为标度,表示小数点后的位数。

float和double在指定了精度和标度的情况下,如果存储的数据的整数部分大于M - D,则会报错;如果小数位超出标度的范围,则进行四舍五入存储。

例如:

  • float(7,4)在存储-1999.9001时会报错。
  • float(7,4)在存储-999.90009时会将-999.90009会被转化为-999.9001来存储。

float和double在不指定精度和标度的情况下,默认会按照实际环境的精度(由实际的硬件和操作系统决定)来存储。

从MySQL 8.0.17开始不再支持浮点型使用Type(M,D)来表示,原因有两点:

  • 浮点值在计算机中存储永远都是近似值,因为计算机所使用的二进制无法正确表示某些带有小数位的十进制数据,比如0.1在二进制中是没有办法保存真实值的。在将float和double当作精确值处理是会产生问题的。
  • 由于float和double都有一个明确的范围,所以如果定义的精度超出了原存储范围,就会出现错误。比如float(16,2),由于float是4个字节存储,小数点后有效位为7位,因此根本达不到16位的精度,最多只能保证6~7位的精度。而double小数点后有效位为16位,因此double(16,2)就没有问题。

使用float和double存储数据,无论精度和标度是指定还是不指定,float和double在DB中保存的都是近似值,从而一定会引起精度丢失问题,在数据对于精度要求比较高的时候(如货币,科学数据)不建议使用float和double。

float和double的优缺点:

  • float在一些处理器上比double更快,而且只占用double一半的空间。
  • double精度比float高,但是占用内存比float大,处理速度比float慢。

5. decimal

类型占用字节有符号范围无符号范围
decimal如果M>D为M+2,否则为D+2依赖于M和D的值依赖于M和D的值

decimal的范围取决于M和D的值,具体表现为:decimal(5,2)的有符号范围是-999.99 ~ 999.99。

MySQL定点型decimal推荐用Type(M,D)来表示:

  • M为精度,表示该数据的总位数(1 ~ 65)。
  • D为标度,表示小数点后的位数(0 ~ 30,D <= M)。

decimal在指定了精度和标度的情况下,如果存储的数据的整数部分大于M - D,则会报错;如果小数位超出标度的范围,则进行四舍五入存储。

例如:

  • decimal(7,4)在存储-1999.9001时会报错。
  • decimal(7,4)在存储-999.90009时会将-999.90009会被转化为-999.9001来存储。

decimal在不指定精度和标度的情况下,默认会按照decimal(10,0)来存储,也就是说只能存储整形数据。如果存储的数据的整数部分大于M - D,则会报错;如果小数位超出标度的范围,同样进行四舍五入存储。

如果使用decimal存储的数据小数位还没有达到指定标度,则自动补0;如果数据首位数字为0,则自动忽略。

为什么float和double不能存储精确数而decimal就可以呢?

这就要说明decimal的存储形式了。

MySQL使用二进制格式存储decimal类型的数据,并将decimal数据的整数部分和小数部分分别计算存储,每9位十进制数占用4字节空间。

如果没到9位或者超过9位,官方还提供了如下转换表:

Leftover DigitsNumber of Bytes
00
1 ~ 21
3 ~ 42
5 ~ 63
7 ~ 94

该表格的意思是,从小数点向左或者向右,如果有1 ~ 2位的十进制数,则占用1字节;如果有3 ~ 4位的十进制数,则占用2字节;如果有5 ~ 6位的十进制数,则占用3字节;如果有7 ~ 9位的十进制数,则占用4字节;当超过9位,从0位开始继续计算,占用空间累加。

例如:

  • decimal(18,9),小数点左边和右边各有9位十进制数,则占用4 + 4 = 8字节,

  • decimal(20,6),小数点左边有14位十进制数,小数点右边有6位十进制数,则占用 4 + 3 + 3 = 10字节。

约束

1. 约束介绍

约束对应的英文单词:constraint。

在创建表的时候,我们可以给表中的字段加上一些约束,来保证这个表中的数据的完整性,有效性。

2. 约束种类

  • 非空约束:not null,约束的字段的值不能为NULL。
  • 唯一性约束:unique,约束的字段的值不能重复,可以为NULL。支持同时约束多个字段实现联合唯一性。
  • 主键约束:primary key(简称PK),约束的主键字段的值既不能为NULL也不能重复。每一张表都必须添加主键约束,否则该表处于read-only状态无法操作。支持同时约束多个主键字段实现复合主键。
  • 外键约束:foreign key(简称FK),约束的外键字段去引用另一张表的某个字段,从而实现数据的一致性。

3. 外键约束

只要使用了外键约束,那么表与表之间就有了父子关系。

如果B表中的某个字段设置了外键约束去引用了A表中的某个字段,那么A表是父表,B表是子表。

创建表时,先创建父表,再创建子表。

删除表时,先删除子表,再删除父表。

插入数据时,先插入父表,再插入子表。

删除数据时,先删除子表,再删除父表。

存储引擎

1. 概念

存储引擎是MySQL中特有的一个术语,其他数据库中没有。

存储引擎实际上是一个表存储和组织数据的方式。不同存储引擎,表存储和组织数据的方式都不同。

2. 设置存储引擎

可以在建表时给表指定存储引擎。

create table 'xxx' {
	...
} engine=InnoDB

如果不设置,默认的存储引擎是InnoDB。

3. 常用存储引擎

可以使用以下命令查看MySQL中支持哪些存储引擎:

show engines \G

MySQL中各个版本一共支持的存储引擎有9个,各个版本对于9个存储引擎的支持都不相同,比如MySQL 5.5.36中支持8个存储引擎。

MySQL中常用的存储引擎有:MyISAM,InnoDB,MEMORY。

4. MyISAM引擎

MyISAM引擎是MySQL 5.1之前的默认存储引擎。

MyISAM引擎使用三个文件表示每一张表:

  • 格式文件:存储表结构的定义(表名.frm)。
  • 数据文件:存储表的数据(表名.MYD)。
  • 索引文件:存储表的索引(表名.MYI)。

使用该存储引擎创建的每一张表都对应以上三个文件。

优点:由于格式、数据和索引都是使用固定的文件存储,因此表可以被压缩或者转换成只读模式来节省空间。

缺点:不支持事务。

5. InnoDB引擎

InnoDB引擎是MySQL5.1之后的默认存储引擎,同时也是一个重量级的存储引擎。

InnoDB引擎使用两个文件表示每一张表:

  • 格式文件:存储表结构的定义(表名.frm)。
  • 数据 + 索引文件:存储表的数据和索引(表名.ibd

InnoDB支持事务,支持数据库崩溃后自动恢复机制。

优点:支持事务,从而保证数据非常安全。

缺点:效率不高。

6. MEMORY引擎

MEMORY引擎以前被称作HEAP引擎(堆引擎)。

使用MEMORY引擎存储的表,其数据和索引都被存储在内存中,断电即丢失,但会让MEMORY处理速度出奇的快。

MEMORY引擎不能存储text,blob数据类型。

优点:查询效率最高。

缺点: 不安全,关机之后数据数据丢失。

设计三范式

1. 设计范式

数据库设计范式就是数据库表的设计依据,只有按照三范式设计表,数据才不会出现大量冗余,导致空间过度浪费。

一共有三个设计范式:

  • 第一范式:要求任何一张表必须有主键,每一个字段原子性不可再分。
  • 第二范式:建立在第一范式的基础上,要求所有非主键字段完全依赖主键,不要产生部分依赖。
  • 第三范式:建立在第二范式的基础上,要求所有非主键字段直接依赖主键,不要产生传递依赖。

2. 第一范式

最核心,最重要的范式,所有表的设计必须满足第一范式。

分析下面表符合不符合第一范式?

学生编号学生姓名联系方式
1001张三123@qq.com,13909709989
1002李四455@qq.com,13909709911
1001王五198@qq.com,19955009002

不符合,原因有两点:

  • 没有主键,学生编号字段数据重复。
  • 联系方式字段数据还能再拆分,不符合原子性。

3. 第二范式

分析下面表符合不符合第二范式?

学生编号(pk)学生姓名教师编号(pk)教师姓名
1001张三001华老师
1002李四001华老师
1001张三003高老师

不符合,原因是:本张表是学生编号和教师编号联合做主键,学生姓名依赖学生编号而不依赖教师编号;教师姓名依赖于教师编号而不依赖学生编号。因此表中产生部分依赖。

产生部分依赖会导致数据的冗余。

如何将该表改成符合第二范式?

一般不符合第二范式的表都是有复合主键的表,是多对多关系。因此可以将该表拆成三表独立的表,分别是学生表、教师表和学生—教师关系表,其中关系表要加两个外键。

4. 第三范式

分析下面表符合不符合第三范式?

学生编号(pk)学生姓名班级编号班级名称
1001张三01一年一班
1002李四02一年二班
1003王五03一年三班
1004赵六03一年三班

不符合,原因是:本张表虽然没有复合主键,没有产生部分依赖,满足第二范式。但是一年一班依赖01,而01依赖1001,因此出现传递依赖。

产生传递依赖也会导致数据的冗余。

如何将该表改成符合第三范式?

一般不符合第三范式的表是一对多关系。因此可以将该表拆成两表独立的表,分别是学生表和班级表。多关系的是学生,因此学生表要加一个外键。