目录
[排序查询ORDER BY](#排序查询ORDER BY)
数据库中最基本的单元是:表(table)
行:被称为数据/记录,列:被称为 字段
命令不区分大小写
常用命令
本地登录 bin目录cmd,mysql -uroot -p密码, 隐藏密码 mysql -uroot -p换行
退出 exit
查看版本 select version();
终止输入 \c
备份
导出表 bin目录cmd:mysqldump 数据库名 [可加表名]>D:\数据库名.sql -uroot -p
导入表 use 数据库;source 表 路径(不要有引号、中文、分号)
数据类型
整型
INT 、BIGINT无符号在数据类型后加 unsigned 关键字,范围会变化。
浮点型
FLOAT、DOUBLE、DECIMAL (M, D),DEC
字符串
CHAR(M) 、VARCHAR(M) 、TEXT
日期和时间
类型名称 | 日期格式 | 日期范围 |
---|---|---|
DATE | YYYY-MM-DD | 年月日 |
DATETIME | YYYY-MM-DD HH:MM:SS | 年月日时分秒 |
TIMESTAMP | YYYY-MM-DD HH:MM:SS |
sql
create table t(
--TIMESTAMP自动更新时间,需要添加语句
login_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
日期、字符串转换
如果格式符合 %Y-%m-%d就可以自动转换成date,也可以自动转换成字符串,就不需要str_to_date和date_formate
%Y年、%m月、%d日、%h时、%i分、%s秒
短日期默认格式:%Y-%m-%d 长日期默认格式:%Y-%m-%d %h:%i:%s
str_to_date('字符串日期','日期格式'); str_to_date(('01-10-2002'),'%d-%m-%Y');date_formate(日期类型数据,'日期格式');
SQL语句分类
-
DDL: 数据定义语言,用来操作表的结构 (数据库、表、字段)(create,drop,alter)
-
DML: 数据操作语言,用来对表中的数据进行增删改(insert,delete,update)
-
DQL: 数据查询语言,用来查询数据库中表的记录(select)
-
DCL: 数据控制语言,用来创建数据库用户、控制数据库的控制权限(grant,revoke)
-
TCL: 事务控制语言,事务提交commit,事务回滚reooback
DDL(数据定义语言)
数据库操作
|--------------|--------------------------------------------------------------------------------------|
| 查询当前数据库 | SELECT DATABASE(); |
| 查询所有数据库 | SHOW DATABASES; |
| 使用数据库 | USE 数据库名; |
| 创建数据库 | CREATE DATABASE [ IF NOT EXISTS ] 数据库名 [ DEFAULT CHARSET 字符集] [COLLATE 排序规则 ]; |
| 查询创建数据库的定义信息 | SHOW CREATE DATABASE 数据库名; |
| 删除数据库 | DROP DATABASE [ IF EXISTS ] 数据库名; |
表操作
|--------------------|---------------------------------------------------------------|
| 查询当前数据库所有表 | SHOW TABLES; |
| 查询表结构 | DESC 表名 |
| 查询指定表的建表语句 | SHOW CREATE TABLE 表名; |
| 删除表 | DROP TABLE [IF EXISTS] 表名; |
| 删除表中的数据(不支持回滚) | TRUNCATE TABLE 表名; |
| 修改表名 | ALTER TABLE 表名 RENAME TO 新表名 |
| 删除字段 | ALTER TABLE 表名 DROP 字段名; |
| 添加字段 | ALTER TABLE 表名 ADD 字段名 类型(长度) [COMMENT 注释] [约束]; |
| 修改数据类型 | ALTER TABLE 表名 MODIFY 字段名 新数据类型(长度); |
| 修改字段名和字段类型 | ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型(长度) [COMMENT 注释] [约束]; |
创建表
sql
CREATE TABLE 表名(
字段1 字段1类型 [COMMENT 字段1注释],
字段2 字段2类型 [COMMENT 字段2注释],
字段3 字段3类型 [COMMENT 字段3注释],
...
字段n 字段n类型 [COMMENT 字段n注释]
)[ COMMENT 表注释 ];
--character set 字符集 collate 校对规则 engine 引擎;
--character set如不指定则为所在数据库字符集
--collate如不指定则为所在数据库校对规则;
--创建数据库或者表的名字是关键字,需要加上反引号
复制表的结构
sql
create table table like emp;
DML(数据操作语言)
INSERT
|------|------------------------------------------------------------|
| 指定字段 | INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...); |
| 省略字段 | INSERT INTO VALUES (值1, 值2, ...), (值1, 值2, ...); |
1、可以为表中的列设定默认值,默认值可以通过在建表中为列设置DEFAULT约束来设定。
2、每次执行一行数据的插入,省略INSERT语句中的列名,就会自动设定为该列的默认值(没有默认值时会设定为NULL)。
3、插入NULL时需要写入NULL,不能有NOT NULL约束。
使用INSERT INTO...SELECT可以从其他(自己)表中复制数据(使用GROUP BY无任何效果)。
sql
insert into table1 select * from table1;
--1,2,4,8,16,32...
sql
insert into table1 (字段1, 字段2...) select * from table2;
UPDATE
|-----|------------------------------------------------------------|
| 常用 | UPDATE 表名 SET 字段名1 = 值1, 字段名2 = 值2, ... [ WHERE 条件 ]; |
| 不常用 | UPDATE 表名 SET (字段名1,字段名2...) = (值1, 值2...) [ WHERE 条件 ]; |
1、UPDATE语句可以将列的值更新为NULL。
2、没有带WHERE条件,会修改所有记录。
DELETE
sql
DELETE FROM 表名 [ WHERE 条件 ];
1、DELETE语句的删除对象并不是表或者列,而是记录(行)。只能使用WHERE子句,。
2、没有带WHERE条件,会删除所有记录,效率低,支持回滚 。
3、不能删除某一列的值,可以使用update设为null 或 "
DQL(数据查询语言)
DQL执行顺序
FROM -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY -> LIMIT
语法
sql
SELECT
字段列表
FROM
表名字段
WHERE
条件列表
GROUP BY
分组字段列表
HAVING
分组后的条件列表
ORDER BY
排序字段列表
LIMIT
分页参数
DISTINCT
sql
SELECT DISTINCT 字段列表 FROM 表名;
distinct 出现在最前面,是多个字段联合(把多列看成一个整体)去重
基础查询SELECT
sql
SELECT 字段1 [ AS 别名1 ], 字段2 [ AS 别名2 ], 字段3 [ AS 别名3 ], ... FROM 表名;
1、as可以去掉,如果别名有空格,用单引号
2、字段可以参与运算
条件查询WHERE
sql
SELECT 字段列表 FROM 表名 WHERE 条件列表;
转义
sql
SELECT * FROM 表名 WHERE name LIKE '/_张三';
-- /之后的_不作为通配符
运算符
比较运算符 | 功能 |
---|---|
<> 或 != | 不等于 |
BETWEEN ... AND ... | 遵循左小右大,闭区间 |
IN(...) | 在in之后的列表中的具体的值,不是区间 |
LIKE 占位符 | 模糊匹配(_匹配单个字符,%匹配任意个字符),名字带下划线,用转义字符 |
NOT LIKE 占位符 | 模糊匹配(_匹配单个字符,%匹配任意个字符),名字带下划线,用转义字符 |
IS NULL | 是NULL |
IS NOT NULL | 不是NULL |
逻辑运算符 | 功能 |
---|---|
AND 或 && | 并且(多个条件同时成立) |
OR 或 || | 或者(多个条件任意一个成立) |
NOT 或 ! | 非,不是 |
排序查询ORDER BY
sql
SELECT 字段列表 FROM 表名 ORDER BY 字段1 排序方式1, 字段2 排序方式2;
排序方式:
-
ASC: 升序(默认)
-
DESC: 降序
ORDER BY可以使用SELECT子句中定义的列的别名,可以使用SELECT子句中未出现的列或者聚合函数
分组函数
sql
SELECT 聚合函数(字段列表) FROM 表名;
必须先分组才能使用否则整张表默认为一组
只有SELECT子句和HAVING子句以及ORDER BY子句中能够使用分组函数。
函数 | 功能 |
---|---|
count | 统计数量 |
max | 最大值 |
min | 最小值 |
avg | 平均值 |
sum | 求和 |
COUNT(*) 统计总行数 COUNT(具体字段) 统计该字段下非空的行数
1、分组函数会将NULL排除在外,但COUNT(*)例外
2、所有的分组函数都可以使用DISTINCT
3、分组函数不能直接用在WHERE子句中
分组查询
sql
SELECT 字段列表 FROM 表名 [ WHERE 条件 ] GROUP BY 分组字段名 [ HAVING 分组后的过滤条件 ];
--group by a,b; 先按a分组再按b分组
where 和 having 的区别:
执行时机不同:where是分组之前进行过滤,不满足where条件不参与分组;having是分组后对结果进行过滤。
判断条件不同:where不能对聚合函数进行判断,而having可以。
1、在select语句中,如果有group by语句的话,select后面只能跟:参加分组的字段,以及分组函数
2、having子句的3种要素: 常数 聚合函数 GROUP BY子句中指定的
分页查询
sql
LIMIT start, rows;
从start+1行开始取,取出rows行,start从0开始
注意事项
起始索引从0开始,起始索引(start) = (查询页码 - 1) * 每页显示记录数
如果查询的是第一页数据,起始索引可以省略,直接简写 LIMIT 10
结论:limit (pageNum - 1)*pageSize, pageSize
DCL
管理用户
|--------|------------------------------------------------------------------------|
| 查询用户 | USE mysql;ELECT * FROM user; |
| 创建用户 | CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码'; |
| 修改用户密码 | ALTER USER '用户名'@'主机名' IDENTIFIED WITH mysql_native_password BY '新密码'; |
| 删除用户 | DROP USER '用户名'@'主机名'; |
sql
-- 创建用户test,只能在当前主机localhost访问
create user 'test'@'localhost' identified by '123456';
-- 创建用户test,能在任意主机访问
create user 'test'@'%' identified by '123456';
create user 'test' identified by '123456';
-- 修改密码
alter user 'test'@'localhost' identified with mysql_native_password by '1234';
-- 删除用户
drop user 'test'@'localhost';
注意事项:
- 主机名可以使用 % 通配
权限控制
权限 | 说明 |
---|---|
ALL, ALL PRIVILEGES | 所有权限 |
SELECT | 查询数据 |
INSERT | 插入数据 |
UPDATE | 修改数据 |
DELETE | 删除数据 |
ALTER | 修改表 |
DROP | 删除数据库/表/视图 |
CREATE | 创建数据库/表 |
|------|------------------------------------------|
| 查询权限 | SHOW GRANTS FOR '用户名'@'主机名'; |
| 授予权限 | GRANT 权限列表 ON 数据库名.表名 TO '用户名'@'主机名'; |
| 撤销权限 | REVOKE 权限列表 ON 数据库名.表名 FROM '用户名'@'主机名'; |
注意事项
-
多个权限用逗号分隔
-
授权时,数据库名和表名可以用 * 进行通配,代表所有
函数
字符串函数
函数 | 功能 |
---|---|
CONCAT(s1, s2, ..., sn) | 字符串拼接,将s1, s2, ..., sn拼接成一个字符串 |
LOWER(str) | 将字符串全部转为小写 |
UPPER(str) | 将字符串全部转为大写 |
LPAD(str, n, pad) | 左填充,用字符串pad对str的左边进行填充,达到n个字符串长度 |
RPAD(str, n, pad) | 右填充,用字符串pad对str的右边进行填充,达到n个字符串长度 |
TRIM(str) | 去掉字符串头部和尾部的空格 |
SUBSTRING(str, start, len) | 返回从字符串str从start位置起的len个长度的字符串,第一个字符下标为1,缺省len就是到结尾 |
REPLACE(column, source, replace) | 替换字符串 |
LENGTH(ename) | 字符串长度 |
加密函数和系统函数
|----------------|------------------------------|
| USER() | 查询用户 |
| DATEBASE() | 数据库名称 |
| MD5() | 为字符串算出一个MD5 32位的字符串,(用户密码)加密 |
sql
create table users(
id int,
`name` varchar(32) not null,
pwd char(32) not null);
insert into users values(1, 'tom', MD5('abcd'));
select * from users where `name` = 'tom' and pwd = MD5('abcd');
流程函数
函数 | 功能 |
---|---|
IF(value, t, f) | 如果value为true,则返回t,否则返回f |
IFNULL(value1, value2) | 如果value1不为空,返回value1,否则返回value2 |
CASE WHEN [ val1 ] THEN [ res1 ] ... ELSE [ default ] END | 如果val1为true,返回res1,... 否则返回default默认值 |
CASE [ expr ] WHEN [ val1 ] THEN [ res1 ] ... ELSE [ default ] END | 如果expr的值等于val1,返回res1,... 否则返回default默认值 |
数值函数
函数 | 功能 |
---|---|
CEIL(x) | 向上取整 |
FLOOR(x) | 向下取整 |
MOD(x, y) | 返回x/y的模 |
RAND() | 返回[0,1]内的随机数 |
ROUND(x, y) | 求参数x(1234.123)的四舍五入值,保留y位小数,y是负数(-1)1230 |
日期函数
函数 | 功能 |
---|---|
CURDATE() | 返回当前日期 |
CURTIME() | 返回当前时间 |
NOW() | 返回当前日期和时间 |
YEAR(date) | 获取指定date的年份 |
MONTH(date) | 获取指定date的月份 |
DAY(date) | 获取指定date的日期 |
DATE_ADD(date, INTERVAL expr type) | 返回一个日期/时间值加上一个时间间隔expr后的时间值 |
DATEDIFF(date1, date2) | 返回起始时间date1和结束时间date2之间的天数 |
比较日期
sql
--查询1992-01-01之后入职的员工
select * from emp where hiredate > '1992-01-01'
子查询
操作符 | 描述 |
---|---|
IN | 在指定的集合范围内,多选一 |
NOT IN | 不在指定的集合范围内 |
ANY | 子查询返回列表中,有任意一个满足即可,有时可以用min代替 |
SOME | 与ANY等同,使用SOME的地方都可以使用ANY |
ALL | 子查询返回列表的所有值都必须满足,有时可以用max代替 |
单行子查询
sql
-- 查询与xxx的薪资及直属领导相同的员工信息
select *
from
employee
where (salary, manager)
= (select salary, manager from employee where name = 'xxx');
多行子查询
sql
-- 查询与xxx1,xxx2的职位和薪资相同的员工
select *
from
employee
where (job, salary)
in (select job, salary from employee where name = 'xxx1' or name = 'xxx2');
-- 查询商品表里各个类别中,价格最高的商品
select goods_id, e.cat_id, goods_name, shop_price
from ecs_goods e,
(select cat_id, max(shop_price) maxprice
from ecs_goods group by cat_id
) t
where
t.cat_id = e.cat_id and t.maxprice = e.shop_price;
多张表,可以写表名.* ,如果不写表名只写*,那么就会把多张表的所有字段显示出来。
联合查询
union, union all
把多次查询的结果合并,形成一个新的查询集
sql
SELECT 字段列表 FROM 表A ...
UNION [ALL]
SELECT 字段列表 FROM 表B ...
注意事项
-
UNION ALL 会有重复结果,UNION 不会
-
联合查询比使用or效率高,不会使索引失效
多表查询
笛卡尔积 两张表n*m次,表的连接次数越多,效率越差
外连接
左外连接
sql
SELECT 字段列表 FROM 表1 LEFT [ OUTER ] JOIN 表2 ON 条件 ...;
右外连接
sql
SELECT 字段列表 FROM 表1 RIGHT [ OUTER ] JOIN 表2 ON 条件 ...;
内连接
隐式内连接
sql
SELECT 字段列表 FROM 表1, 表2 WHERE 条件 ...;
显式内连接
sql
SELECT 字段列表 FROM 表1 [ INNER ] JOIN 表2 ON 连接条件 ...WHERE...;
显式性能比隐式高,带着inner可读性更好
自连接
当前表与自身的连接查询,自连接必须使用表别名,也可以给列起别名
自连接查询,可以是内连接查询,也可以是外连接查询
sql
SELECT 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件 ...;
sql
-- 查询员工及其所属领导的名字
select a.name, b.name from employee a, employee b where a.manager = b.id;
-- 没有领导的也查询出来
select a.name, b.name from employee a left join employee b on a.manager = b.id;
约束
约束是作用于表中字段上的,可以再创建表/修改表的时候添加约束。
约束 | 描述 | 关键字 |
---|---|---|
非空约束 | 限制该字段的数据不能为null | NOT NULL |
唯一约束 | 保证该字段的所有数据都是唯一、不重复的,NULL可以重复 | UNIQUE |
主键约束 | 主键是一行数据的唯一标识,要求非空(不能为null)且唯一 | PRIMARY KEY |
默认约束 | 保存数据时,如果未指定该字段的值,则采用默认值 | DEFAULT |
检查约束(8.0.1版本后) | 保证字段值满足某一个条件 | CHECK |
外键约束 | 用来让两张图的数据之间建立连接,保证数据的一致性和完整性 | FOREIGN KEY |
逻辑条件 | check(check条件),mysql15.7不支持,只做语法校验,不会生效 | CHECK |
自动增长 | AUTO_INCREMENT |
表级约束,给多个字段联合起来添加某一个约束,NOT NULL只有列级约束
两个字段联合唯一 unique(name, email)
主键
1、如果一个字段同时被unique和not null约束,自动变为主键字段,unique not null == primary key
2、 一张表最多只能有一个主键,但可以是复合主键3、复合主键: primary key(id, name) id 和 name 不能同时相同
4、主键的指定方式:1、字段名 primary key 2、在表定义最后写 primary key(列名)
5、DESC 表名 查看表的结果,显示约束情况
unique
如果没有指定 not null, 则 unique 字段可以有多个null (null, tom) (null, tom) (null, tom) 会插入3行,一张表可以有多个 nuique 字段。
auto_incream
sql
insert into value(字段1...) values (null...)
insert into values(null,...)
insert into xxx (字段2...) values (值1...)
1、一般和主键配合使用
2、单独使用需要unique
3、默认从1开始,可以通过以下命令修改 alter table 表名 auto_incream = xxx;
4、如果添加了具体的值,则以指定的值为准
外键
sql
CREATE TABLE 表名(
字段名 字段类型,
...
FOREIGN KEY(外键字段名) REFERENCES 主表(主表列名)
);
1、主表 必须有主键约束或unique约束
2、外键字段的值,必须在主键字段中出现过,或者为 null(前提字段允许为 null)
3、不能随意删掉主表数据,要先看是否有数据指向它,先删从表再删主表
4、外键和主键类型一致,长度可以不同
索引
分类 | 含义 | 特点 | 关键字 |
---|---|---|---|
主键索引 | 针对于表中主键创建的索引 | 默认自动创建,只能有一个 | PRIMARY KEY |
唯一索引 | 避免同一个表中某数据列中的值重复 | 默认自动创建,可以有多个 | UNIQUE |
常规索引 | 快速定位特定数据 | 可以有多个 | |
全文索引 | 全文索引查找的是文本中的关键词,而不是比较索引中的值 | 可以有多个 | FULLTEXT |
|----------|----------------------------------------------------------------------------|
| 创建索引 | CREATE [ UNIQUE | FULLTEXT ] INDEX 索引名称 ON 表名 (字段名, ...);,不加参数是普通索引 |
| 添加普通索引 | alter table 表名 ADD INDEX 索引名称 (字段名, ...); |
| 添加主键索引 | ALTER TABLE 表名 ADD PRIMARY KEY (字段名); |
| 查看索引 | SHOW INDEXES FROM 表名; SHOW KEYS FROM 表名; DES 表名;(显示MUL) |
| 删除索引 | DROP INDEX 索引名字 ON 表名; |
| 删除主键索引 | ALTER TABLE 表名 DROP PRIMARY KEY; |
考虑如何添加索引
1、数据大
2、字段经常出现在where中
3、字段有很少的DML操作,因为DML后需要重新排序
4、主键都会自动添加索引对象,unique也会自动添加索引对象
索引失效情况
在索引列上进行运算操作或函数,索引将失效。 如:
explain select * from tb_user where substring(phone, 10, 2) = '15';
字符串类型字段使用时,不加引号,索引将失效。如:
explain select * from tb_user where phone = 17799990015;
,此处phone的值没有加引号模糊查询中,如果仅仅是尾部模糊匹配,索引不会是失效;如果是头部模糊匹配,索引失效。如:
explain select * from tb_user where profession like '%工程';
,前后都有 % 也会失效。用 or 分割开的条件,如果 or 其中一个条件的列没有索引,那么涉及的索引都不会被用到。
使用复合索引,没有使用左侧的列查找,索引失效,
create index emp_job_sal on emp(job,sal);
select * from emp where job = "Manager";
select * from emp where sal = 800;失效
优点:
-
提高数据检索效率,降低数据库的IO成本
-
通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗
缺点:
-
索引列也是要占用空间的
-
索引大大提高了查询效率,但降低了更新的速度,比如 INSERT、UPDATE、DELETE
事务
事务是一个完整的业务逻辑,是最小的工作单元,不可再分,即这些操作要么同时成功,要么同时失败。
只有DML语句才会有事务
事务处理的终止指令包括COMMIT(提交处理)和ROLLBACK(取消处理)两种。
|--------|-----------------------------------------|
| 开启事务 | START TRANSACTION 或 set autocommit=off; |
| 设置保存点 | SAVEPOINT 保存点名字 |
| 回退事务 | ROLLBACK TO 保存点名字; |
| 回退全部事务 | ROLLBACK; |
| 提交事务 | COMMIT; |
11点 保存a 12点 保存b 如果回到a,则b就没了,不能再回到a
四大特性ACID
-
原子性(Atomicity):事务是最小的工作单元,不可再分
-
一致性(Consistency):要么全部成功,要么全部失败
-
隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行
-
持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的
并发事务
问题 | 描述 |
---|---|
脏读 | 一个事务读到另一个事务还没提交的数据 |
不可重复读 | 由于其他提交事务所做的修改和删除并提交,两次读取的数据不同,只想读取那一时刻的数据 |
幻读 | 由于其他提交事务所做的插入数据并提交,两次读取的数据不同,只想读取那一时刻的数据 |
隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 | 加锁读 |
---|---|---|---|---|
Read uncommitted(最低的隔离级别,读未提交) 事务A可以读取到事务B未提交的数据,脏读 | √ | √ | √ | × |
Read committed(读已提交) 事务A只能读取到事务B提交之后的数据,解决了脏读,但是不可以重复读取数据,开启后第一次3条,第二次4条 | × | √ | √ | × |
Repeatable Read(默认,可重复读) 事务A开启之后,不管多久,每一次读取的数据都是不变的(刚开始的那一刻数据),即使事务B将数据修改了 | × | × | × | × |
Serializable(最高的隔离级别,可串行化/序列化) 事务排队,不能并发 | × | × | × | × |
-
√表示在当前隔离级别下该问题会出现
-
Serializable 性能最低;Read uncommitted 性能最高,数据安全性最差
-
加锁,表的操作卡住,必须等待另一个表提交
|-------------------|-----------------------------------------------------------------------------------------------------------------------------------|
| 查看当前会话隔离级别 | SELECT @@tx_isolation; |
| 查询系统当前隔离级别 | SELECT @@global.tx_isolation; |
| 设置当前 会话 / 系统 隔离级别 | SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE }; |
SESSION 是会话级别,表示只针对当前会话有效,GLOBAL 表示对所有会话有效
视图
|----------|-----------------------------|
| 创建视图 | create view 视图名 as select语句 |
| 修改视图 | alter view 视图名 as select语句 |
| 显示创建视图命令 | show create view 视图名 |
| 删除多个视图 | drop view 视图名1,视图名2 |
1、视图是一个虚拟表,数据来自对应的真实表(基表)
2、通过视图可以修改基表的数据
3、基表的改变,也会影响视图的数据
4、视图中可以再使用视图,数据仍然来自基表
范式
第一范式:要求任何一张表必须有主键,每一个字段原子性不可再分
第二范式:在第一范式基础上,要求所有非主键字段完全依赖主键,不要产生部分依赖(多对多存在复合主键,三张表,关系表两个外键,主键不是复合主键,不会产生部分依赖)
第三范式:在第二范式基础上,要求所有非主键字段直接依赖主键,不要产生传递依赖(一对多,两张表,多的表加外键)
一对一,外键唯一