database-notes

本文最后更新于:12 小时前

全局变量

@snum 表示全局变量

容易语法错误的地方

  1. update不要table关键字,直接跟表名
  2. insert不要table关键字,直接跟表名
  3. 想要切换database时,直接use其他的。比如user在mysql数据库中,直接use mysql 然后再select host,user from user;

数据库命令

delimiter

1
2
3
delimiter //
-- 将数据库的结束符号改变为//,这样可以处理多;的sql语句。
-- 用完记得改回来 delimiter ;

启动/关闭数据库命令

1
2
net stop mysql57
net start mysql57

进入数据库命令

1
2
mysql -u root -p
# root指管理员身份

创建data文件夹

创建文件夹时一定要以管理员身份打开文件夹

在C:\Program Files\MySQL\MySQL Server 5.7\bin路径下调用如下命令:

1
mysqld --initialize-insecure --user=root

显示所有的仓库

1
show databases;

创建数据库

1
2
3
4
5
6
7
8
9
# 普通
create database student;
# 也可以加上反引号
create database `student`;
# 当使用关键字当作数据库的名字时,必须使用反引号,但是很显然,不符合规范
create database `database`;
# 有逼格的创建方法
create database if not exists student;
create database if not exists `student`;

删除数据库

1
2
3
drop database student;
drop database if exists student;
drop database if exists `database`;

获取仓库创建的语句

1
show create database student;

字符编码

1
2
3
4
5
# 创建数据库时设置字符编码,win下改成gbk编码(简体中文),但是实际开发中一律utf8
create database if not exists `student` charset=gbk;

# 可以使用如下命令查看数据库的字符编码
show create database student;

更改数据库

1
alter database student charset=gbk;

表的命令

进入一个数据库,进行操作

1
2
3
use database frank_school;
# 显示该数据库中的所有表
show tables;

创建表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 下面的操作在 use database 之后
create table student(
id int,
name varchar(), # 注意此处varchar一定要跟上数值
age int
);
show tables;


# 有B格的创建方式
create table student(
id int auto_increment primary key comment'主键id',
name varchar(30) not null comment'老师的名字',
phone varchar(20) comment'电话号码',
address varchar(100) default '暂时未知' comment '地址',
)engine=innodb;


id name phone address # 字段
auto_increment # 自动增长 自动增长的一定是主键,主键不一定自动增长
# 一旦写了auto_increment, 删除了主键为3的,就不能再添加主键为3的了

primary key # 主键 最重要的 靠他来区分学生
comment # 注释
not null # 不能为空
default # 默认值
engine # 数据库引擎

查看表的结构

1
2
desc student;
desc teacher;

删除表

1
drop table if exists iii,uuu,kkk;

有限制地删除

1
drop table <表名> [restrict|cascade];
  • restrict表明删除表是有限制的,欲删除的基本表不能被其他表的约束所引用。如果存在依赖该表的对象,则此表不能被删除。
  • cascade表明删除该表没有限制。在删除基本表的同时,相关的依赖对象一起删除。

更改表

alter + table + 表名 +add/drop + 字段名

增加字段

1
2
3
alter table student add gender varchar(1) after id;# 好像没有before??
# 也可以直接放在第一个 使用first
# 不写增加的位置,表明在最后增加

增加约束条件

1
2
alter table course add unique(Cname);
# 给course表的Cname列增加unique属性

更改字段名

1

更改字段类型

1
2
# 将字段类型改为varchar(20)且可以为空(插入数据之后还可以使用)
alter table teacher modify tel_phone varchar(20) null;

不能同时更改两个字段名,用逗号隔开。

删减

1
alter table student drop gender;

数据操作

插入数据insert

基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 先选择一个数据库database使用
use frank_school;
insert into teacher (id, name, phone, address) values (1, 'shenchen', '15890702212', 'Zhumadian');

# 插入数据使用insert 时,表名前不必加tableS
# 也可以不写前面的,那么后面的就是默认顺序
insert into teacher values (2, 'jeff', '122332465', 'BeiJing');

# 也可以不写auto_increment的id,只是前面不能省略,要一一对应
insert into teacher (name, tel_phone, address) values ('Alice', null, default);

# 顺序也可以乱,只是前后要一一对应
insert into teacher (name,phone, address, id) values ('Tom', '874651661', 'NanJing', 3);


# 查询所有数据
select * from teacher;

插入数据之后,如何更改数据类型?

一次性插入多条数据

1
2
insert into teacher values () # 这样写要写id
insert into teacher (name, tel_phone, address) values (), ();

插入子查询结果

子查询不仅可以嵌套在select语句中,用以构造父查询的条件,也可以嵌套在insert语句中,用以生成要插入的批量数据。

例题:对于每一个系,求学生的平均年龄,并把结果存入数据库。

1
2
3
4
5
6
7
8
-- 创建新表
create table sdeptaAge(
sdept varchar(20),
age smallint
);

-- 插入子查询
insert into sdeptAge select sdept,avg(sage) from s group by sdept;

删除数据

delete语句的功能是,从指定表中删除满足where子句条件的所有元组。若省略where子句,则表明删除表中所有元组,但表的定义还在字典中。所以delete删除的是表中的数据,而不是表的定义。

1
2
3
4
5
6
7
8
9
delete from teacher where id = 7;
where id > 4;

# 删除所有的数据
delete from teacher; # 这种删除数据的方式是 for语句遍历删除,效率比较低,不推荐
# 若使用delete删除数据,下次再插入数据时,数据id不是从1开始的

# 删除原先的表,再创建一个一模一样的表,但是没有数据
truncate table teacher;

更新表数据

1
2
update teacher set name = 'James', tel_phone = '15836637628' where id = 4;# where A or B
# 若没有where,则所有数据的字段都会变化,十分悲惨

更新表时不需要写table!!!!!!!!!!

查询表数据

1
2
3
# 两种基本方法
select name, address from teacher;
select * from teacher;

SQL语句区分

  • DDL data definition language 数据库定义语言 create alter drop show
  • DML data manipulation language 数据操纵语言 insert update delete select
  • DCL data control language GRANT 和 REVOKE

grant与revoke

基本格式

1
mysql> grant all privileges on *.* to 'yangxin'@'%' identified by 'yangxin123456' with grant option;
  • all privileges:表示将所有权限授予给用户。也可指定具体的权限,如:SELECT、CREATE、DROP等。
  • on:表示这些权限对哪些数据库和表生效,格式:数据库名.表名,这里写“*”表示所有数据库,所有表。如果我要指定将权限应用到test库的user表中,可以这么写:test.user
  • to:将权限授予哪个用户。格式:”用户名”@”登录IP或域名”。%表示没有限制,在任何主机都可以登录。比如:”yangxin”@”192.168.0.%”,表示yangxin这个用户只能在192.168.0IP段登录
  • identified by:指定用户的登录密码
  • with grant option:表示允许用户将自己的权限授权给其它用户

可以使用GRANT给用户添加权限,权限会自动叠加,不会覆盖之前授予的权限,比如你先给用户添加一个SELECT权限,后来又给用户添加了一个INSERT权限,那么该用户就同时拥有了SELECT和INSERT权限。

grant使用时,可以一条语句中添加多个权限,但是只能对一张表,不能同时对多张表添加。

revoke也是这样。

刷新权限

对用户权限做了变更之后,一定记得重新加载一下权限,将权限信息从内存中写入数据库。

1
mysql> flush privileges;

查看用户权限

1
2
mysql> grant select,create,drop,update,alter on *.* to 'yangxin'@'localhost' identified by 'yangxin0917' with grant option;
mysql> show grants for 'yangxin'@'localhost';

回收权限

1
2
mysql> revoke create on *.* from 'yangxin@localhost';
mysql> flush privileges;

注意,回收权限之后也要flush privileges

注意

回收权限时建议加一个 cascade 级联回收。因为从某个用户回收权限时,该用户可能将权限赋予了其他用户,要一并收回。否则会拒绝执行该命令(restrict)

删除用户

1
mysql> drop user 'yangxin'@'localhost';

显示字符编码

1
2
3
4
5
6
7
8
show variables like 'character_set_%;
# 改变客户端字符编码
set character_set_client=gbk;

# 有影响的两个,在win上要改成gbk,但实际开发一定是utf8
character_set_client;
character_set_results;

mysql数据类型

数据类型没有一个统一的标准,但应该符合业务项目的逻辑标准

数值类型

类型 大小 范围(有符号) 范围(无符号) 用途
TINYINT 1 byte (-128,127) (0,255) 小整数值
SMALLINT 2 bytes (-32 768,32 767) (0,65 535) 大整数值
MEDIUMINT 3 bytes (-8 388 608,8 388 607) (0,16 777 215) 大整数值
INT或INTEGER 4 bytes (-2 147 483 648,2 147 483 647) (0,4 294 967 295) 大整数值
BIGINT 8 bytes (-9,223,372,036,854,775,808,9 223 372 036 854 775 807) (0,18 446 744 073 709 551 615) 极大整数值
FLOAT 4 bytes (-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) 单精度 浮点数值
DOUBLE 8 bytes (-1.797 693 134 862 315 7 E+308,-2.225 073 858 507 201 4 E-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) 双精度 浮点数值
DECIMAL 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 依赖于M和D的值 依赖于M和D的值 小数值

注:若是无符号,则后面加上unsigned

整数类型注意事项

定义为tinyint类型,则数据不能超过128;但是若定义为int(6), 则数据位数可以超过6,即可以为99999999,也不报错(因为int类型上限数据宽度是11)

浮点类型注意事项

1
2
3
4
5
create table t_1(
number_1 float(3,1),# float(总位数,小数位数)
number_2 double(5,2),# double 同理
);
# 小数位数超越时,会采取四舍五入的方式

当输入的数据超出限定位数时,浮点数会产生精度丢失,所以不能用来存储

1
2
3
4
5
6
7
8
9
10
11
create table t_1(
number_1 float(20,19),
number_2 double(20,19)
);
insert into t_1 values (2.111111111111111111111111111111,5.6666666666666666666666666666666);

# 结果如下所示
select * from t_1;

2.1111111640930176000 | 5.6666666666666670000
# 产生了精度丢失!!!!!

为什么会产生精度丢失?

为了避免精度丢失,我们使用decimal数据类型进行存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
show create table t_3;
+-------+-----------------------------------------------------------------------------+
| Table | Create Table |
+-------+-----------------------------------------------------------------------------+
| t_3 | CREATE TABLE `t_3` (
`money` decimal(20,19) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=gbk |
+-------+-----------------------------------------------------------------------------+

insert into t_3 values (2.111111111111111111111111111);

# 结果如下、

select * from t_3;
+-----------------------+
| money |
+-----------------------+
| 2.1111111111111111111 |
+-----------------------+
1 row in set (0.00 sec)
# 没有精度丢失!!

字符串与文本类型

类型 大小 用途
CHAR 0-255 bytes 定长字符串
VARCHAR 0-65535 bytes 变长字符串
TINYBLOB 0-255 bytes 不超过 255 个字符的二进制字符串
TINYTEXT 0-255 bytes 短文本字符串
BLOB 0-65 535 bytes 二进制形式的长文本数据
TEXT 0-65 535 bytes 长文本数据
MEDIUMBLOB 0-16 777 215 bytes 二进制形式的中等长度文本数据
MEDIUMTEXT 0-16 777 215 bytes 中等长度文本数据
LONGBLOB 0-4 294 967 295 bytes 二进制形式的极大文本数据
LONGTEXT 0-4 294 967 295 bytes 极大文本数据

注:1. char()与varchar()中的数字是指字符的个数,而不是字节个数

​ 2. 总结起来,有几点:

  • 经常变化的字段用 varchar
  • 知道固定长度的用 char
  • 尽量用 varchar
  • 超过 255 字符的只能用 varchar 或者 text
  • 能用 varchar 的地方不用 text

布尔类型

1
2
3
4
5
6
7
8
9
10
11
create table t_4(
fff boolean
);
insert into t_4 values (true);

select * from t_4;
+------+
| fff |
+------+
| 0 |
+------+

枚举类型(单选)

只能取这几个中的一个,不能取别的

1
2
3
4
5
6
7
8
9
10
11
12
create table t_5(
gender enum ('man','woman','?','nothing','it')
);

insert into t_5 values ('man');
select * from t_5;
# 结果如下
+--------+
| gender |
+--------+
| man |
+--------+

枚举类型的存储方式

1
2
3
4
5
6
7
8
9
10
11
12
# 利用整数进行存取,第一个是1而不是0
# 接上
insert into t_5 values (2);
select * from t_5;
# 结果如下

+--------+
| gender |
+--------+
| man |
| woman |
+--------+

好处:快,每次存取数字,存储节省空间

set类型(多选)

相当于可以插入多个数据的枚举类型

插入多个数据时,只能使用一个单引号,在一个单引号中使用逗号隔开

1
2
3
4
5
6
7
8
9
10
create table t_6(
hobby set('哲学','经济学','文学','IT','数学','绘画')
);
select * from t_6;
# 结果如下
+---------+
| hobby |
+---------+
| 数学,IT |
+---------+

日期和时间类型

类型 大小 ( bytes) 范围 格式 用途
DATE 3 1000-01-01/9999-12-31 YYYY-MM-DD 日期值
TIME 3 ‘-838:59:59’/‘838:59:59’ HH:MM:SS 时间值或持续时间
YEAR 1 1901/2155 YYYY 年份值
DATETIME 8 1000-01-01 00:00:00/9999-12-31 23:59:59 YYYY-MM-DD HH:MM:SS 混合日期和时间值
TIMESTAMP 4 1970-01-01 00:00:00/2038结束时间是第 2147483647 秒,北京时间 2038-1-19 11:14:07,格林尼治时间 2038年1月19日 凌晨 03:14:07 YYYYMMDD HHMMSS 混合日期和时间值,时间戳
1
2
3
4
5
6
7
8
9
10
11
12
13
create table t_7 (
createdTime datetime
);
insert into t_7 values ('2021-02-23 19:03:45'); # 这里不要忘了加引号

# 结果如下
select * from t_7;
+---------------------+
| createdTime |
+---------------------+
| 2021-02-23 19:03:45 |
+---------------------+
1 row in set (0.00 sec)

列属性完整性

主键(primary key)

唯一的,必须要有,用来标识唯一

一个表里只有一个主键,但一个主键可以由多个字段组成

一个表里的主键可能和其他表还有关系

添加主键

1
2
# 给id添加主键
alter table t_8 add primary key (id);

消除主键

1
2
# 删除表中某个字段的主键
alter table t_8 drop primary key;

组合键

两个(多个)字段做成了一个主键

作用不大

1
2
3
# 可以创建的时候给多个primary key
# 也可以像下面这样添加
alter table t_8 add primary key (id, name);

作用:id,昵称都是唯一的,可以用这种方法

但是,这种方法的扩展性不是太好

唯一键(unique)

可以与表里的其他字段没有关系,可以为空

一个表里可以有多个唯一键

唯一键的用处:保证数据不能重复

唯一键只在这一张表里凑热闹

添加唯一键

1
2
3
4
5
6
# 创建时直接添加
create table t_9 (
id int primary key auto_increment,
phone varchar(20) unique
);

查看唯一键

1
2
desc t_9;
show create table t_9;

唯一键扩展(一般不用)MUL

1
2
3
4
5
# 创建时添加多个
# alter添加多个
alter table t_9 add unique (name, phone);

关键字 MUL

删除唯一键

1
2
# 删除了phone的唯一键
alter table t_9 drop index phone;

主键不能用此方式删除!!

主键和唯一键的区别

  • 主键
    • 不能重复
    • 不能空
    • 主键可能被其他的表引用
  • 唯一键
    • 不能重复
    • 可以为空
    • 唯一键只在自己的表中折腾

SQL语句注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 单行注释

# ......

-- ......

# 多行注释

/* ......
.....
*/

# SQL内注释,给管理员看的
comment '...'

设计数据库

  1. 应当有一个主键,可能用自动增长。(保证实体完整性);保证数据类型是对的;有些字段可以为空,有些字段不能为空,要有一个约束
  2. 考虑default的应用。比如考试学生没来,应该是默认缺考,而不是零分;或者原因未知等。
  3. 可能需要外部引用

外键

主表和从表之间有联系的关键字时,创建从表时要声明外键

实际开发,并发处理,禁止使用外键

外键对主表和从表的约束

  1. 对主表:当主表的外键在从表中被引用时,不能写(更改和删除)主表中的外键相关的字段,但是其他的字段可以写。(如学生表中的学号和食堂记录表中的学号捆绑为外键,当学号为3的学生在食堂记录表中被引用三时,不能更改学生表中学号为3学生的学号,但是可以更改其姓名)
  2. 对从表:不能添加主表中没有的外键数据(主表中学生编号就到5,那么从表中不能添加编号为6的学生的购买记录)

严格操作

创建表时添加外键

1
2
3
4
5
6
7
8
9
10
11
12
13
# 主表创建
create table stu(
stuId int(4) primary key,
name varchar(20)
);

# 从表创建
create table eatery(
id int primary key,
money decimal(10,4),
stuId int(4),
foreign key (stuId) references stu(stuId)); # 创建表的时候添加外键

创建之后添加外键(维护时才用到)

1
2
3
4
5
6
create table eatery_2 (
id int(4) primary key,
money decimal(10,4),
stuId int(4)
);
alter table eatery_2 add foreign key (stuId) references stu (stuId);

删除外键

1
2
3
4
5
6
# 首先要查看外键在表中的名称,使用以下命令
show create table eatery_2 ;

# 根据表中显示的外键的名称进行删除操作
alter table eatery_2 drop foreign key eatery_2_ibfk_1;

置空操作

主表中的绑定外键的数据被干掉了,那么从表中于其绑定为外键的关键字都置为null,但是从表中的数据保持不变。(置空是不可逆的)

级联操作

主表中被绑定的外键被干掉了,那么从表中与其相关的字段和数据都删除了,而不是置为null。比较狠

一般情况下,置空操作是留给外界删除数据的;级联操作是留给外界更新数据的。

一般都是在创建表是设置其是否有置空和级联操作

置空操作和级联操作的使用

1
2
3
4
5
6
7
create table eatery(
id int(11) primary key,
money decimal(10,4),
stuId int(4),
foreign key (stuId) references stu (stuId) on delete set null on update cascade
);
-- 此时就创建了一个具有置空操作和级联操作的表

数据库设计思维

关系?行?列?

关系? 关系型数据库 两张表的共有字段去确定数据的完整性

行? 一条数据 一条数据记录 实体

列? 一个字段 属性

OOP

实体和实体之间的关系

一对一

多对一

一对多

多对多

规范约束(Codd三范式)

  1. 确保每列的原子性:确保每一列不能再分了。(如持续时间2018-2019,这是不对的,应该再分两个字段:开始时间2018,结束时间2019)
  2. 在保证是第一范式的条件下,非键字段只能依赖于键字段,一张表只能描述一件事情,不能扯淡
  3. 保证是第二范式的基础上,消除传递依赖(如当表里有语文、数学、英语成绩时,没必要再设计一个总分栏目了,但高考成绩表这种特殊情况除外,因为有很多人都会查这个总成绩)
    将一个第二范式的关系分解为多个第三范式的关系,可以在一定程度上解决原第二范式中存在的插入异常,删除异常,数据冗余度大,修改复杂等问题。

这三个都是从非主属性的角度入手。

另外的范式

  1. BC范式

    每一个决定属性因素都包含码。

  2. 多值依赖

  3. 第四范式

https://gitee.com/dutsc/cloud-images/raw/master/images/202212061627229.png

查询语句

sql语句的执行顺序

开始->FROM子句->WHERE子句->GROUP BY子句->HAVING子句->ORDER BY子句->SELECT子句->LIMIT子句->最终结果

sql语句的顺序

1
<SELECT clause> [<FROM clause>] [<WHERE clause>] [<GROUP BY clause>] [<HAVING clause>] [<ORDER BY clause>] [<LIMIT clause>] 

一个SELECT语句中,子句的顺序是固定的。例如GROUP BY子句不会位于WHERE子句的前面。熬出

执行顺序示例:

1
2
3
4
5
6
select 考生姓名, max(总成绩) as max总成绩 
from tb_Grade
where 考生姓名 is not null
group by 考生姓名
having max(总成绩) > 600
order by max总成绩

在上面的示例中 SQL 语句的执行顺序如下:

   (1). 首先执行 FROM 子句, 从 tb_Grade 表组装数据源的数据

   (2). 执行 WHERE 子句, 筛选 tb_Grade 表中所有数据不为 NULL 的数据

   (3). 执行 GROUP BY 子句, 把 tb_Grade 表按 “学生姓名” 列进行分组(注:这一步开始才可以使用select中的别名,他返回的是一个游标,而不是一个表,所以在where中不可以使用select中的别名,而having却可以使用)
   (4). 计算 max() 聚集函数, 按 “总成绩” 求出总成绩中最大的一些数值

   (5). 执行 HAVING 子句, 筛选课程的总成绩大于 600 分的.

   (7). 执行 ORDER BY 子句, 把最后的结果按 “Max 成绩” 进行排序.

单表查询

select

1
2
3
4
5
6
select 2*7 as result; -- as可以省略。起别名,表中只有一个字段时可以这么写
+--------+
| result |
+--------+
| 14 |
+--------+

查询时可以使用列别名,可以加as也可以不加as(省略as)

escape ‘ \ ‘ 表示 \ 为转码符

1
select cno,ccredit from course where cname like 'DB\_design' escape '\';

from

1
2
select * from ...
select * from t_1,t_2; -- 返回两表t_1,t_2的笛卡尔积

dual

1
2
# 伪表  完整的语句应当如下
select 2*7 as result from dual;

where

1
2
-- 条件选择  就是一个按条件查询
select * from teacher where = != and or not >= <=

and的优先级高于or

where是一条一条地去查的

where子句中不能用聚集函数作为条件表达式

in

1
2
-- 在其中,不在其中 in   not in 
select * from stu where name in ('shenchen','xiaohuixin');

between…and…

1
2
-- 在两者之间(包括两者)     前面可以加上not
select * from stu where stuId between 2 and 4;

左闭右闭

is null

1
2
-- 某个字段为null,不能用=null, 而应当用关键字is null    前面可以加not
select * from stu where name is null;

聚合函数

1
2
3
4
5
6
7
8
-- 求某一列数据的和,最大值,最小值,平均值,计数等等
select sum(chinese) from score;
sum([distinct|all] <列名>) -- 统计一列值的总和 (此列必须是整数型)
max()
min()
avg([distinct|all]<列名>) -- 计算一列值的平均值(此列值必须是整数类型)
count([distinct|all] *) -- 统计元组个数
count([distinct|all]<列名>) -- 统计一列中值的个数

在聚合函数遇到空值时,除了count(*) 之外,都跳过空值而只处理非空值

模糊查询

1
2
3
-- %代表多个字符  _代表一个字符
select * from student where name like 'j%';
-- 不区分大小写

模糊查询时,必须使用like,不能使用=

order by 排序

1
2
-- asc升序   desc降序
select * from student order by chinese asc;

缺省的时候,默认为升序

  • asc:排序列为空值的元组最后显示
  • desc:排序列为控制的元组最先显示

问题:求各个课程号及相应的选课人数,并按照选课任务降序排列

1
select cno,count(cno) as num from sc group by cno order by num desc;

group查询

group by 后面跟的是查询后面的字段之一

使用group by,查询的字段必须是分组字段聚合函数

如要查询这张表里,男性的平均年龄和女性的平均年龄

1
2
select avg(age) as '年龄', gender as '性别' from info group by gender desc;
# desc或者asc可以不要

在组内使用聚合函数。

注意:

  • 未对查询结果分组,聚集函数将作用与整个查询结果
  • 对查询结果分组后,聚集函数将分别作用于每个组,即每一组都有一个函数值。相当于细化了聚集函数的作用对象。
  • 按指定的一列或多列值分组,值相等的为一组。

group_concat() 查询

1
2
select group_concat(name),gender from student group by gender;
# 查询所有男生,女生的名字,合起来显示

having

两个条件语句:where having

比如,我们想查一下根据地区计算年龄平均值后,查找平均值大于24的地区,这时我们已经查出了一个虚拟的表,表里是地区和该地区的平均年龄。这时我们若再使用where age > 24这条语句,查询的结果是原始表中age>24 的人的地区,这不是我们想要的

where 是在原本的表中去进行筛选,

having 是在查询之后的结果的表中(虚拟表),再进行筛选,此时的字段,要使用虚拟表中的字段,因此查询虚拟表的时候,最好不要使用中文名。having 后面可以直接跟原名avg(age)若起了别名可以跟别名,也可以跟原名。这里就凸显出来起别名的好处,所以要起一个别名。

limit

限定查找的范围

可以结合order by 使用

查找从某一名开始,连续的几名。若只给一个数字,则默认从0开始。(也就是从第一位开始查,和C语言中数组的第一个元素下标为0相对应)

1
2
select * from info order by age desc limit 3 # 把info表中的数据俺age降序排序,并查找前三项
# 也就是查找该表中年龄最大的三个人
两个参数的limit

LIMIT 子句可以被用于强制 SELECT 语句返回指定的记录数。LIMIT 接受一个或两个数字参数。参数必须是一个整数常量。
如果给定两个参数,第一个参数指定第一个返回记录行的偏移量,第二个参数指定返回记录行的最大数目。初始记录行的偏移量是 0(而不是 1): 为了与 PostgreSQL 兼容,MySQL 也支持句法: LIMIT # OFFSET #。

1
2
SELECT * FROM table LIMIT 5,10
-- 检索记录行6-15

这些都是常用的条件限制语句:where,having,limit

distinct

去重,在筛选的结果中去掉重复的

显示这张表里所有出现的城市,但是不能出现两个上海,即不能重复

查谁之前写一个distinct 代表去重

查询该表中不同的地址有多少个:

1
select count(distinct address) from info;

默认情况下其实是all ,只不过一般默认情况都忽略了

union取并集

联合查询只要求字段一样,对应项的数据类型也必须相同

将两个表查询的数据联合显示

1
2
3
4
5
select id,addrid,sex,score 
from student
union all
select sex,addrid,id,score
from student;

union 自动去重

union all 保留所有

intersect 取交集

1
select * from Student where sdept = 'CS' intersect select * from student where sage<=19;

except取差集

1
select * from student where sdept = 'CS' except select * from student where sage<=19;

多表查询

left join right join inner join 都要用on 不能用where

inner join(内连接)

两张表使用相同的公共字段进行联合查询

两表联合查询,如Student表中存储了学生的信息,表中的id为学生的学号;表score中存储了学生的成绩信息,该表中的id为score表中的编号,而stuid才是学生的学号,所以使用如下语句对名字和成绩进行联合查询

1
select name,score from score inner join student on score.stuid=student.id;

让公共字段相等

注意:

  • id和stuid前面必须要加是哪一张表,不能省去

  • 若有三张表,则后面继续加inner join

    1
    2
    3
    4
    select name,score
    from student
    inner join score on student.id=score.stuid
    inner join ...
注意

给表起别名的时候,不能加引号

left join

左连接,以左表为基准 以左排头为基准,向左看齐

就是把inner join换成left join

左表:from 后面的第一张表,就是左表,(也是left join左边的那张表)以它为基准,即使辅表中没有相关的数据,也要将左表中要查询的信息给出来

1
2
3
4
5
6
7
8
9
select name,score from student left join score on score.stuid=student.id;
+-------+-------+
| name | score |
+-------+-------+
| James | 89 |
| Curry | 80 |
| Jiuo | 75 |
| Jerry | NULL |
+-------+-------+

所以inner join以项较少的那一个为基准

right join

以右排头为基准,向右看齐

以right join右边的表为基准

1
2
3
4
5
6
7
8
9
select name,score from student right join score on score.stuid=student.id;
+-------+-------+
| name | score |
+-------+-------+
| James | 89 |
| Curry | 80 |
| Jiuo | 75 |
| NULL | 86 |
+-------+-------+

内连接和向左看齐 比较实用

cross join

交叉连接 ,返回两表的笛卡尔积 和省略cross join返回的结果是一样的,都是笛卡尔积

1
select * from student cross join score;
1
2
select * from student cross join score where student.sno=score.sno;
# 就相当于inner join

逗号,就相当于交叉连接?也不一定好像

natural join

自然连接? 自然内连接

类似于内连接,但是同时查询两个表的公共字段,保证两张表中公共的字段名一模一样,如两张表中都叫id 根据同名字段来判断的

1
select * from student natural join score;

只是简单地合起来

在等值连接中,把目标列中重复的属性列去掉则为自然连接

注意

  1. 若查询的两表中没有相同的字段名称,则返回两表的笛卡尔积

  2. 若查询的两表中有多个相同的字段名称,则返回

    using

    当连接的表有多个字段相同时,用来指定连接的字段(唯一的字段)。两张表字段都是一样的,指定谁来作为连接字段

    此时处理方式和natural相同 只不过由于有多个相同字段,所以不能使用natural来处理

    其实就是,两个表连接时,完全相同的字段只保留一个

还会有自然左连接和自然右连接,分别向左看齐,向右看齐

在实际使用时,一般都写全,很少用自然连接,using,这样可读性也不好

写全:谁=谁 score.stuid=student.id

自身连接

连接操作不仅可以在两个表之间进行,也可以是一个表与其自身进行连接。

自身连接时,给要查询的那个表命两个不同的名字(在from中,因为select语句会先执行from

例题:查询每一门课程的间接先修课(先修课的先修课)

1
2
select first.cno,second.cpno from course as first,course as second
where first.cpno=second.cno;

子查询

in&not in

内层的查询只查询出外层需要的字段

先给一个子查询的实例

查score表中成绩大于等于80分的学生的信息(储存在student表中)

1
2
3
4
5
6
7
select * from student where id in (select stuid from score where score>=80);
+------+-------+--------+------+-----------+
| id | name | gender | age | phone |
+------+-------+--------+------+-----------+
| 1 | James | 男 | 35 | 1565465 |
| 3 | Curry | 女 | 30 | 245344565 |
+------+-------+--------+------+-----------+

括号里的查询只能返回一个字段stuid,而不能是*,否则对不上,因为我们只需要根据分数大于80的学生的学号,而不需要其他的信息(如成绩,考试编号等)

若返回值(stuId)不止一个,则要使用in,若返回值只有一个,则可以使用= 一般情况下都使用in

in就有not in

子查询可以用连接运算替代

有的子查询可以用连接运算替代,而有的不可以。下面给出一个子查询可以由连接运算替代的例子:

例题:查询选修了课程名为“信息系统”的学生的学号和姓名。

1
2
3
select s.sno,s.sname from s where sno in(
select sno from sc where cno in(
select cno from c where cname="信息系统"));

这是嵌套查询的版本,用连接运算替代如下:

1
select s.sno,s.sname from s,sc,c where s.sno=sc.sno and sc.cno=c.cno and c.cname="信息系统";
子查询的执行顺序

一般先执行子查询,再执行父查询,但是相关子查询例外。相关子查询,先执行父查询,再执行子查询。

相关子查询的例题: 找出每个学生超过他选修课程平均成绩的课程号。

1
2
select sno,cno from sc x where grade>(
select avg(grade) from sc where x.sno=sc.sno);

执行相关子查询时,不能像求解不相关子查询那样,一次将子查询求解出来,然后求解父查询。内层查询由于与外层查询有关,因此必须反复求值。每次从外层查询中取出sc的一个元组x,将元组x的sno值传送给内层查询,然后执行内层查询,得到值之后,用该值替代内层查询,得到外层查询。执行这个外查询,得到一部分结果。然后外层查询取出下一个元组重复做上述步骤处理,直到外层的sc元组全部处理完毕。

注意
  1. 子查询不能使用order by子句,order by子句只能对最终查询结果排序。但子查询可以使用group by子句
  2. 层层嵌套方式反映了sql语言的结构化
  3. 有些嵌套查询可以用连接运算替代

exists&not exists

放在where后面,格式如下

1
select ....... where exists(子查询)

如果子查询有任意数据返回,exists就返回true,子查询外的查询语句执行

如果子查询没有数据返回,exists就返回false,子查询外的查询语句就不执行

若查出括号里有值,则进行括号外面的查找操作

比如,这个班里有一个人考了一百分,则这个班里每个人都发一个红包(扯淡需求)

1
select * from student where exists (select stuid from score where score>=80);

in与exists的区别

我们以A、B表为例,两表通过 id 字段进行关联。
1、当 B 表的数据集小于 A 表时,用 in 优化 exist;使用 in ,两表执行顺序是先查 B 表,再查 A 表

1
select * from A where id in (select id from B)

2、当 A 表的数据集小于 B 表时,用 exist 优化 in;使用 exists,两表执行顺序是先查 A 表,再查 B 表

1
select * from A where exists (select 1 from B where B.id = A.id)

简单一句话:in后面放数据量小的表,exits后面放数据量大的表
这就是小表驱动大表,即小的数据集驱动大的数据集的思想。

高级部分

视图

作用:

  1. 防止用户看到敏感数据(比如某人在银行账户上存了多少钱)
  2. 有意地隐藏表的结构
  3. 降低了sql的复杂度

视图和函数有类似的意思,但是它不是函数

视图是从一个或几个基本表(或视图)导出的表,他与基本表不同,是一个虚表,数据库中只存放视图的定义,不存放视图对应的数据,这些数据仍然存放在原来的基本表中。所以基本表中的数据发生变化,从视图中查询出来的数据也就随之改变了。

对视图的更新操作可能会受到限制:增,删,改

创建视图

1
2
3
create view vw_stu as select name,phone from student;

create view vw_stu_all as select name,phone,score from student inner join score on student.id=score.stuid;
  • 后面的select语句不允许有distinct和order by子句,因为它没有数据

  • 执行create view 时只是把视图定义存入数据字典,并不执行其中的select语句;只是在对视图查询时,才按视图的定义从基本表中将数据查出。

  • 最后可以加上 with check option WITH CHECK OPTION:透过视图进行增删改操作时,不得破坏视图定义中的谓词条件(即子查询中的条件表达式)
    例题:建立信息系学生的视图,并且进行修改和插入操作时仍需保证该视图只有信息系的学生。

    1
    2
    create view ISstudent as 
    select sno,sname,sage from s where sdept='IS' with check option;

行列子集视图

若一个视图是从单个基本表导出的,并且只是去掉了基本表的某些列和某些行,但保留了主码,我们称这类视图为行列子集视图。ISstudent视图就是一个行列子集视图。

带表达式的视图

就是带虚拟列的视图。

定义视图时可以根据应用的需要,设置一些派生属性列。这些派生属性由于在基本表中不实际存在也称他们为虚拟列。带虚拟列的视图也称为带表达式的视图。

例题:定义一个反映学生出生年份的视图

1
2
create view BT_s(sno,Sname,Sbirth)
as select sno,sname,2022-sage from s;

分组视图

用带有聚集函数和group by 子句的查询来定义视图。

例题:将学生的学号和他的平均成绩定义成一个视图

1
create view S_G(sno,avg) as select sno,avg(grade) from sc group by sno;

使用视图

1
2
select * from vw_stu;
select * from vw_stu_all;

在可视化窗口中,创建视图后,直接双击视图就可以查询,这和select * from vw_stu是等价的

视图消解

RDBMS执行对视图地查询时,首先进行有效性检查。检查查询中涉及的表、视图等是否存在。如果存在,则从数据字典中取出视图的定义,把定义中的子查询和用户的子查询结合起来,转换成等价的对基本表的查询,然后再执行修正了的查询。这一转换过程称为视图消解。

显示视图

在终端中显示当前视图

1
show tables;

默认显示视图

所以如果要区分table和view的话,view前面一定要加上前缀vw

还可以使用如下语句:

1
2
desc vw_stu;
show create view vw_stu;

还有一种赋予b格的显示方式

1
show table status where comment='view' \G

更改视图

create改成alter,后面跟create的语句一样,赋给它新的功能即可。

更新视图

更新视图本质上就是更新表。一般只有行列子集视图可以被更新。

删除视图

create 改成drop,删去as及其后面的语句即可

注意,基本表删除后,由该基本表导出的所有视图(定义)没有被删除,但均已经无法使用了。删除这些视图(定义)需要显式地使用drop view语句。

视图的两种算法

有两种算法:MERGE, TEMPTABLE

把子查询用到视图里,发现结果和想象的不一样??与视图算法有关

1
2
3
4
create view vw_stu_zi as select * from student where id in (select stuid from score where score>=85);
select * from view_stu_zi;
# 此时却显示
ERROR 1146 (42S02): Table 'frank_school.view_stu_zi' doesn't exist

此时要设置一个temptable 的视图算法

在创建视图时设计视图算法

1
2
3
4
5
6
7
8
create algorithm=temptable view vw_stu_zi as select * from student where id in (select stuid from score where score>=85);
mysql> select * from vw_stu_zi;
# 此时就可以正常显示了
+------+-------+--------+------+---------+
| id | name | gender | age | phone |
+------+-------+--------+------+---------+
| 1 | James | 男 | 35 | 1565465 |
+------+-------+--------+------+---------+

触发器

  • 触发器(Trigger)是用户定义在关系表上的一类由事件驱动的特殊过程
    • 由服务器自动激活
    • 可以进行更为复杂的检查和操作,具有更精细和更强大的数据控制能力

定义触发器

1
2
3
CREATE TRIGGER <触发器名> <触发时机 BEFORE | AFTER>
<触发事件 INSERT | UPDATE | DELETE >
ON <表名> FOR EACH Row <触发器主体>

1 <触发器名>

触发器的名称,触发器在当前数据库中必须具有唯一性。如果要在某个特定数据库中创建,名称前面应该加上数据库的名称. 如 xxx_trigger_xxx 命名

2 <触发时机> BEFORE | AFTER

触发时机 BEFORE 和 AFTER,触发器被触发的时刻,表示触发器是在激活它的语句之前或之后触发。若希望验证新数据是否满足条件,则使用 BEFORE 选项;若希望在激活触发器的语句执行之后完成几个或更多的改变,则通常使用 AFTER 选项。

3 <触发事件> INSERT | UPDATE | DELETE

触发事件,用于指定激活触发器的语句的种类。
注意:三种触发器的执行时间如下。

触发器类型 触发器功能 激活触发器的语句
INSERT触发器 将新行插入表时激活触发器。 INSERT ,LOAD, DATA ,REPLACE
DELETE触发器 从表中删除某一行数据时激活触发器。 DELETE ,REPLACE
UPDATE触发器 更改表中某一行数据时激活触发器。 DELETE

4 <表名>

与触发器相关联的表名,此表必须是永久性表,不能将触发器与临时表或视图关联起来。在该表上触发事件发生时才会激活触发器。同一个表不能拥有两个具有相同触发时刻和事件的触发器。例如,对于一张数据表,不能同时有两个 BEFORE UPDATE 触发器,但可以有一个 BEFORE UPDATE 触发器和一个 BEFORE INSERT 触发器,或一个 BEFORE UPDATE 触发器和一个 AFTER UPDATE 触发器

5 <关键字 FOR EACH ROW>

一般是指行级触发,对于受触发事件影响的每一行都要激活触发器的动作。例如,使用 INSERT 语句向某个表中插入多行数据时,触发器会对每一行数据的插入都执行相应的触发器动作。

6 <触发器主体>

触发器动作主体,包含触发器激活时将要执行的 MySQL 语句。如果要执行多个语句,可使用 BEGIN…END 复合语句结构。

注意:每个表都支持 INSERT、UPDATE 和 DELETE 的 BEFORE 与 AFTER,因此每个表最多支持 6 个触发器。每个表的每个事件每次只允许有一个触发器。单一触发器不能与多个事件或多个表关联。

NEW和OLD的使用:

触发器类型 NEW和OLD的使用:
INSERT触发器 NEW表示将要或者已经新增的数据。
DELETE触发器 OLD用来表示将要或者已经被删除的语句。NEW表示将要或者已经被修改的语句
UPDATE触发器 OLD表示将要或者已经被删除的数据。

注意

  1. 不能在触发器中使用开始或结束事务的语句。
  2. MySQL的触发器是按照BEFORE触发器、行操作、AFTER触发器的顺序执行的,其中任何一步发生错误都不会继续执行剩下的操作,如果对事务表进行的操作,如果出现错误,那么将会被回滚,如果是对非事务表进行操作,那么就无法回滚了,数据可能会出错。

定义触发器的语法说明

  1. 创建者:表的拥有者
  2. 触发器名
  3. 表名:触发器的目标表
  4. 触发事件:INSERT、DELETE、UPDATE
  5. 触发器类型
    行级触发器(FOR EACH ROW)
    语句级触发器(FOR EACH STATEMENT)
1
2
3
4
5
6
7
8
9
10
TEACHER表定义如下:
CREATE TABLE TEACHER
(Eno NUMERIC(4) PRIMARY KEY,
Ename CHAR(10),
Job CHAR(8),
Sal NUMERIC(7,2),
Deduct NUMERIC(7,2),
Deptno NUMERIC(2),
CONSTRAINT EMPFKey FOREIGN KEY (Deptno) REFERENCES DEPT(Deptno),
CONSTRAINT C1 CHECK (Sal + Deduct >= 3000)

在TEACHER表上创建了一个AFTER UPDATE触发器。如果表TEACHER有1000行,执行如下语句:
UPDATE TEACHER SET Deptno=5;
如果该触发器为语句级触发器,那么执行完该语句后,触发动作只发生一次
如果是行级触发器,触发动作将执行1000次

image-20220419093828817

1
2
3
4
5
6
7
8
9
10
11
[例18]  定义一个BEFORE行级触发器,为教师表Teacher定义完整性规则“教授的工资不得低于4000元,如果低于4000元,自动改为4000元”。
CREATE TRIGGER Insert_Or_Update_Sal
BEFORE INSERT OR UPDATE ON Teacher
/*触发事件是插入或更新操作*/
FOR EACH ROW /*行级触发器*/
AS BEGIN /*定义触发动作体,是PL/SQL过程块*/
IF (new.Job='教授') AND (new.Sal < 4000) THEN
new.Sal :=4000;
END IF;
END;

触发器删除

触发器必须是一个已经创建的触发器,并且只能由具有相应权限的用户删除。

1
DROP TRIGGER <触发器名> ON <表名>;
1
2
[例21] 删除教师表Teacher上的触发器Insert_Sal
DROP TRIGGER Insert_Sal ON Teacher;

事务

操作比较严谨的,跟钱打交道的

点立即购买,没付款,钱去哪了?

转账,最后再提醒一次,是不是这个卡号,点了确定,才把钱转出去

难道每一次买东西都给阿里增加一条数据吗?你买一个东西,马上给人家增加50块钱?再退款,再update?

总有一个步骤,确定一定以及肯定之后,才能更新这个数据,我们管它叫做事务,transaction

开启事务

1
2
3
4
5
6
7
start transaction;
update wallet set balance=balance-50 where id=1;
update wallet set balance=balance+50 where id=2;


# 提交事务,相当于 你确定吗?
commit;

只要你commit,就不能再rollback

从事务开始的时候,随时随地可以rollback,但是一但commit,就不能再rollback了。最好的例子就是,淘宝发货,你付款,收货。

所以在公司里,一般遇到金额转账,跟钱有关的,一般都用transaction

rollback to

设置回滚点,使用savepoint操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
start transaction;
insert into wallet values(4,1000);
savepoint four;
insert into wallet values(5,19999);
rollback to four;
commit;
select * from wallet;
+----+-----------+
| id | balance |
+----+-----------+
| 1 | 56.00 |
| 2 | 883.00 |
| 3 | 888888.00 |
| 4 | 1000.00 |
+----+-----------+
# 整个过程就像虚拟机里面的还原到某个点

事务的四个特性ACID

  • atomicity 原子性 不能再分了,事务是一个整体,要死一起死,要活一起活
  • consistency 一致性 事务一但commit,数据也一定要达到一致的状态,所有的数据都应该是正常的
  • isolation 隔离性 所有的事务都是相互隔离的
  • durability 持久性 事务一但commit,就不能再更改了,在数据库中永久的写存

注意:必须保证引擎是innodb,才能使用事务

索引(index)

优点:查询速度快

缺点:

  1. 一旦设置索引,数据的增删改,效率都会变低,而且变得不是一般的低。
  2. 索引还占空间

所以有些东西设置成索引,有些东西强制不能设置成索引,有些大公司明确要求

四种类型

  1. 主键索引:primary key
  2. 唯一键索引:
  3. 普通索引
  4. 全文索引:搜索引擎使用,mysql不支持中文的全局索引,但是可以使用sphinx

设置索引

添加表时设置 不常用

创建一个普通索引 常用

1
create index balance_index on wallet(balance);

设置一个唯一索引

随便设的

1
create unique index myindex on stu(name);

可以为多个列创建索引

不说的话,默认是按照升序排列(前两个)

设置一个主键索引

1
create primary key index myindex on stu(id);

修改索引

1
alter table stu add index myindex1 (stuId);

删除索引

1
drop index balance_index on wallet;

为什么要创建索引?

  • 经常要查这一列,比如高考总分
  • 共用字段,可能创建索引

注意:

  • 表里数据非常少,千万不要创建索引 神经病行为
  • 还有性别,这种,种类很少,不要创建索引

聚簇索引

1
create cluster index Stusname on Student(Sname);

在Student表的Sname列上建立一个聚簇索引

  • 在最经常查询的列上建立聚簇索引以提高查询效率
  • 一个基本表上最多只能建立一个聚簇索引。
  • 经常更新的列不宜建立聚簇索引

问题:

索引怎么用???

存储过程

一般开发用不到

Introduction

用来模块化设计 DBA用的

可以用来增删改查,可以支持事务,相当于sql里的函数

给一个案例,双十一了,淘宝想把所有人的账户都给发一个红包,把所有人的头像都改了,这操作用视图肯定不行,因为视图只能查,明显用存储过程。这个存储过程,双十一之前DBA先写好,然后回家睡觉。双十一了,马云打电话说,赶紧发红包,改头像23:59,打开笔记本,执行存储过程,他就会默认执行之前设置过的sql,就不用再一点一点去写了,就相当于调用一个函数一样。

mysql中,每一个;就相当于一个语句,执行完直接发送服务器了,有点类似matlab 中的命令行。但是执行下述语句之后

1
delimiter //

分号此时没用了,只有使用//才能把语句发出去,去执行它

用完存储过程之后,再进行还原也可以

1
delimiter ;

存储过程里肯定有多个sql语句,之间用;隔开

在存储过程中,最好使用transaction,这样比较符合常规

建立存储过程

1
2
3
4
5
6
7
mysql> create procedure proc()
-> begin
-> start transaction;
-> update wallet set balance=balance+50;
-> commit;
-> end //
# 此存储过程的功能是给账户上增加50块钱

调用存储过程

1
call proc();

删除存储过程

1
drop procedure proc;

查看存储过程

1
show create procedure proc;

显示数据库里的所有存储过程

1
show procedure status \G

有趣的函数

number

rand()

生成一个随机数

1
select rand();

抽奖

1
2
select * from student order by rand() limit 3;
# 抽三个人

向上取整

1
select ceil(3.1);

四舍五入

1
select round(3.1);

向下取整

1
select floor(3.1);

截取数字

1
select truncate(3.141592654,2);

随机排序

1
select * from student order by rand();

string

转换成大写

1
select ucase('fuck!');

转换成小写

1
select lcase('FUCK!');

截取字符串

1
2
3
4
5
select left('FUCK!',2);
select right('FUCK!',2);

# 从中间截取
select substring('FUCK!',2,3);

拼接字符串

1
select concat('FUCK!','ASDFSDF');

补充:concat的有趣用法

1
2
3
4
5
6
7
8
9
select concat(name,'|',age) from student;
+----------------------+
| concat(name,'|',age) |
+----------------------+
| James|35 |
| Jerry|34 |
| Curry|30 |
| Jiuo|30 |
+----------------------+

数据库角色

数据库角色是被命名的一组与数据库操作相关的权限,角色是权限的集合。因此,可以为一组具有相同权限的用户创建一个角色,使用角色来管理数据库权限可以简化授权的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
create role r1;

grant select,update,insert
on table student
to r1;

-- 将这个角色授予王平,张明,赵玲,使他们具有角色r1所包含的全部权限
grant r1 to 王平,张明,赵玲

-- 当然,也可以一次性通过r1来回收王平的这三个权限
revoke r1 from 王平;

-- 角色的权限修改
grant delete on table student to r1;

-- 使r1减少了select权限
revoke select on table student from r1;

问题

  1. 使用sql脚本文件写入数据时,出现command /2 错误的原因:

    文件的路径只能用正斜杠/,不能用反斜杠\。

  2. ERROR 1406 (22001): Data too long for column ‘Ssex’ at row 1

    更改命令行的编码格式 从gbk到utf8

    1
    chcp 65001

database-notes
http://example.com/2024/03/17/database-notes/
作者
dutsc
发布于
2024年3月17日
许可协议