分享

【02】MySQL:SQL 基础

 印度阿三17 2019-08-13

写在前面的话

 

上一节主要谈谈 MySQL 是怎么安装的以及最简单的初始化我们应该做哪些配置。其中也用到了一些简单的用户操作 SQL,所以这一节主要学习常用的 SQL 使用。

 

 

SQL 介绍

 

在了解 SQL 之前,对于 SQL 需要有以下简单的认知:

1. 主流的来个标准,SQL92 和 SQL99,在 MySQL 5.7 以后采用的是 SQL92。

2. 在 5.7 中新增了 sql_mode,作用在于限制哪些 SQL 能够使用,一个很明显的例子是 group by 的使用。

 

在上一节简单说过常用的 SQL 分类(主要前 3 个):

DDL:数据定义语言

DCL:数据控制语言

DML:数据操作语言

DQL:数据查询语言

 

 

数据类型

 

这算是数据定义过程中的一个重点,针对不同的数据我们给定不同的数据类型,作用在于保证数据的准确性和标准性。

数值类型:

类型说明
tinyint 整数 很小,0 - 255
smallint 整数 较小,-2^15 - 2^15
mediumint 整数 中等,很少用
int 整数 常规,-2^31 - 2^31
bigint 整数 较大,-2^63 - 2^63
float 浮点数 小型单精度浮点数,四个字节
double 浮点数 常规单精度浮点数,八个字节
decimal 定点数 包含整数部分,小数部分或者同时包含二者精确数值
bit BIT 位字段值

 

字符类型:

类型说明
char 文本 固定长度字符串,最多 255 个字符
varchar 文本 可变长度字符串,最多 65535 个字符
tinytext 文本 可变长度字符串,最多 255 个字符
text 文本 可变长度字符串,最多 65535 个字符
mediumtext 文本 可变长度字符串,最多 1600万 个字符
longtext 文本 可变长字符串,最多 42亿 字符
enum 整数 一组固定合法值组成的枚举
set 整数 一组固定合法值组成的集

在生产中最容易出现的就是字符串字段长度不足的问题,所以在设计的时候注意选对类型。

char 之所以是定长,比如我们定义 char(10),那就意味着传的最大长度是 10,如果不够补充空格,反正就是占用 10 个字符。

varchar 相比之下,则是在指定的范围内按需分配,如 varchar(10),最大长度 10,不足就不足,不浪费。

至于 enum,则属于特别的使用,如本字段的值是指定范围,如 enum("北京", "上海", "广州", "深圳"),这样使用能够优化索引。但用的其实并不多。

 

时间类型:

类型格式示例
date YYYY-MM-DD 2019-08-08
time hh:mm:ss[.uuuuuu] 10:50:29.123456
datetime YYYY-MM-DD hh:mm:ss[.uuuuuu] 2019-08-08 10:50:29.123456
timestamp YYYY-MM-DD hh:mm:ss[.uuuuuu] 2019-08-08 10:50:29.123456
year YYYY 2019

timestamp 会受到时区的影响,且范围有限制。不是很建议。

 

二进制类型(这类不建议存到 MySQL):

类型说明
binary 二进制 类似 char 固定长度,但存储的是二进制
varbinary 二进制 类型 varchar
tinyblob blob 最大长度 255 的 blob 列
blob blob 最大长度 65535 的 blob 列
mediumblob blob 最大长度 1600万 的 blob 列
longblob blob 最大长度 42亿 的 blob 列

 

 

表属性

 

1. 列属性(主要关键字):

primary key:主键,非空唯一约束,一个表只能有一个,但是能由多个列组成。

not null:非空约束,属于设计规范,尽可能不要列空,可以使用默认值 0 替代空。

unique key:唯一键,值不能重复 。

unsigned:无符号,主要用于数字列,非负数。

key:索引,可以给某列建立索引来优化查询。

default:默认值,列没有值时默认填充。

auto_increment:自增,主要针对数字,顺序填充数据,默认 1 开始,可以设置起始值和偏移量。

comment:注释。

 

2. 表属性:

存储引擎:在 5.7 默认是 innodb,在老版本中可能是 MyISAM。

字符集:常见的 utf8,utf8mb4 等。

校对(排序)规则:如 utf8_general_ci,utf8_bin 这种。主要用于大小写是否敏感。

可以通过以下 SQL 查看系统支持:

# 查看编码
show charset;

# 查看排序规则
show collation;

 

 

数据库操作(DDL)

 

1. 创建数据库并指定查看字符集:

# 默认创建
create database a;
show create database a;

# 指定字符集创建
create database b charset utf8mb4;
show create database b;

# 指定字符集和排序规则创建
create database c charset utf8mb4 collate utf8mb4_bin;
show create database c;

结果如下:

可以看到,MySQL 默认不指定字符集创建数据库的时候,创建的数据库的字符集为拉丁(latin1)

排序规则 ci 结尾的都是大小写不敏感的。bin 大小写敏感。

当然,创建数据库还可以使用:

create schema d;

这样也是能够创建数据库的。

建库规范:

a. 库名不应该包含大小写。

b. 库名不该以数字开头。

c. 建库一定要加字符集。

d. 库名要有意义。

 

特别注意:

禁止生产种执行 drop database xxx;

 

2. 修改数据库字符集:

alter database a charset utf8mb4;

查看:

但是值得注意的是,修改后的字符集必须比之前的字符集范围更大。原因是数据的兼容性。

同时,不到万不得已一般不要修改。

 

 

数据表操作(DDL)

 

语法格式:

create table students(
列1 属性(数据类型, 约束, 其它),
列2 属性,
...
)

 

1. 创建一个名为学校的测试库,创建一张名为学生的用户表:

# 创建库
create database school charset utf8mb4 collate utf8mb4_bin;

# 指定库
use school;

# 创建表
create table students (
id int not null primary key auto_increment comment "学号",
sname varchar(255) not null comment "姓名",
sage tinyint unsigned not null default 0 comment "年龄",
sgender enum("m", "f", "n") not null default "n" comment "性别",
id_card char(18) not null unique comment "身份证",
add_time timestamp not null default now() comment "入学时间"
) engine=innodb charset=utf8mb4 comment "学生表";

建表规范:

a. 表名小写,不能数字开头且具有意义。

b. 选择合适的数据类型,字符集,存储引擎。

c. 每个列都需要有注释说明且非空,如果为空选择 0 代替。

 

特别注意:

禁止生产种执行 drop table xxx;

 

2. 查看表结构:

desc students;

结果如图:

也可以查看建表语句:

show create table students\G

 

3. 添加列:

a. 直接添加手机号列:

alter table students add mobile varchar(20) not null unique comment "手机号";

如图:

默认添加列加到最后面。

 

b. 在 id_card 后面添加微信列:

alter table students add wechat varchar(20) not null unique comment "微信" after id_card;

如图:

 

c. 在最前面加个列:

alter table students add school_id tinyint not null comment "学校编号" first;

如图:

 

4. 修改列:

a. 添加 qq 列,然后删除它:

# 添加
alter table students add qq varchar(20) not null unique comment "QQ";
desc students;

# 删除
alter table students drop qq;
desc students;

添加:

删除:

 

b. 修改列属性:

alter table students modify mobile varchar(15);

如图:

可以发现,虽然只是修改了 varchar,但是 null 也修改了。所以修改的时候建议多以属性都加一遍。

 

c. 修改列名和数据类型:

alter table students change sgender sg char(1) not null default 'n';

我们这里将 sgender 改为 sg,并修改类型:

 

在过去的版本中,我们应该避免在业务高峰期修改表结构,因为这会导致数据库锁表。

但可以使用 pt-osc 工具(Percona 的),可以在线修改,不再锁表,原理在于创建一个新表。

当然,在 MySQL 8.0 以后的版本以及自身集成了该工具。

 

5. 复制表结构建立一张新表:

create table t1 like students;

对于 DCL,主要就两个,一个是 grant,一个是 revoke。

 

 

数据增删改(DML)

 

1. 插入数据:

a. 最标准的 insert 语法:

insert into students(school_id,id,sname,sage,sg,id_card,wechat,add_time,mobile) values (11,1,'张三',18,'m','511123199311111214','13290909801',now(),'13290909801');

 

b. 省事写法:

由于我们每个字段都按照顺序写,所以没必须要把字段列出来。

insert into students values (11,2,'李四',19,'f','511123199311111124','13290222201',now(),'13290222201');

 

c. 部分插入:

因为有些字段是由默认值的,所以我们可以就使用默认值:

insert into students(school_id,sname,sage,id_card,wechat,mobile) values (11,'王五',18,'511123199311112224','13290909221','13290909221');

 

d. 同时插入多个:

insert into students(school_id,sname,sage,id_card,wechat,mobile) values
(11,'老赵',12,'511123133311112224','13233909221','13233909221'),
(11,'老钱',16,'511333133311112224','13333909221','13333909221'),
(12,'老孙',25,'511113133311112224','13111909221','13111909221');

 

e. 查看插入结果:

select * from students;

结果如图:

 

2. 修改数据:

a. 把张三的年龄改为 20:

update students set sage=20 where sname='张三';

  

b. 把所有名字老开头的性别改为 m:

update students set sg='m' where sname like '老%';

结果如图:

 

c. 同时修改多个值:

update students set sage=25,sg='f' where sname='张三';

结果如图:

 

3. 删除数据:

delete from students where sname="老孙";

不推荐使用!!!

 

清空表的方法:

delete from students;

delete 逐行全部删除,属于 DDL 操作,速度慢!!!

同时,我们可以从上面的 id 可以看到,由于发生了 delete 导致 id 不连续,确实的那一部分仍然占据着磁盘,这将导致可能数据量不大,但是磁盘占用很大的情况。这就是磁盘碎片。

truncate table students;

truncate 全部清空数据页,干干净净,属于 DML 操作,速度快。

都不推荐!!!

特别注意:

update / delete 一定要记得 where,否则原地爆炸。

 

在实际生产中,我们都是使用伪删除的方式,也就是新加数据状态字段,如可用为 1,不可用为 0,我们删除就将状态由 1 改为 0。

alter table students add status tinyint not null default 1 comment "数据状态";

删除就将改行数据 status 改为 0。

 

 

查询 DQL(Data Query Language)

 

1. 单独使用,查看系统参数:select @@xxx

select @@port;
select @@basedir;
select @@datadir;
select @@socket;
select @@server_id;

结果如下:

有些复杂的我们可以使用 show 来模糊查询:

show variables like 'log%';

如图:

 

2. 单独使用,调用函数:select 函数(); 

# 显示当前信息
select now();
select database();
select user();

# 打印输出
select concat("hello world");

# 定制化输出
select concat(user,"@",host) from mysql.user;

# 一行输出
select group_concat(user,"@",host) from mysql.user;

结果:

更多的函数可以查看官方文档:

https://dev./doc/refman/5.7/en/func-op-summary-ref.html

 

3. 单表子句,FROM

select * from students;

不建议在生产中使用,如果表数据太大会造成卡死。

查询指定列:

select sname,sage,mobile from students;

如图:

 

4. 单表子句,WHERE

在使用之前,MySQL 官方提供了专门用于学习的一个数据库:world,可以前往官网下载:

https://dev./doc/index-other.html

如图:

将示例的数据库导入 MySQL 中,当然也可以下载 world_x,那个是新数据,只是测试没必要:

包含三张表,城市,国家,国家语言。可以使用 desc 了解每张表的数据结构。

a. 等值查询:查询中国(CHN)的城市

select * from city where CountryCode="CHN";

结果:

也可以单独查询深圳:

select * from city where Name="shenzhen";

如图:

 

b. 比较查询:>,<,>=,<=,<>

查询世界人口小于 100 的城市:

select * from city where population<100;

如图:

 

c. 逻辑查询:and,or

 查询中国人口大于 500 万的:

select * from city where countrycode="CHN" and population>=5000000;

如图:

查询中国或美国的城市:

select * from city where countrycode="CHN" or countrycode="USA";

 

d. 模糊查询:%

查询 bei 开头的:

select * from city where name like "bei%";

查询名字中包含 bei 的(不走索引,性能极差,不推荐):

select * from city where name like "�i%";

如图:

 

e. 列表匹配:in(类似 or)

select * from city where countrycode in ("CHN","USA");

如图:

 

f. 查询范围:between and(类似 >= and <=)

查询人口在 830000 和 840000 之间的城市:

select * from city where population between 830000 and 840000;

换成 and 写为:

select * from city where population>=830000 and population<=840000;

结果:

 

5. GROUP BY 聚合函数

常用的聚合函数有以下一些:

最大值:max()
最小值:min()
平均值:avg()
求和:sum()
统计:count()
列转行:group_concat()

 

a. 统计每个国家的人口数量:

select countrycode,sum(population) from city group by countrycode;

结果:

 

b. 统计中国各省总人口:

select District,sum(population) from city where countrycode="CHN" group by District;

结果:

 

3. 统计世界上每个国家城市数量:

select CountryCode,count(name) from city group by CountryCode;

结果:

 

6. 单表子句:HAVING

例如统计中国各省总人口数,只显示小于 100 万的。

select district,sum(population) from city where countrycode="CHN" group by district having sum(population)<1000000;

如图:

之所以使用 having,是因为 where 有顺序要求,分别是 where -- group by -- having

在 group by 之后只能使用 having 不能再用 where 了。另外 having 后条件不走索引。

 

7. 排序和限制:ORDER BY LIMIT

a. 查询中国的城市信息,并按照人口升序排序。

select * from city where countrycode="CHN" order by population;

如图:

 

b. 统计各省总人口数并按照降序排列。

select district,sum(population) from city where countrycode="CHN" group by district order by sum(population) desc;

默认升序,降序需要 desc

 

c. 统计全国各省人口大于 500 万的按照降序排列并取前三。

select district,sum(population) from city where countrycode="CHN" group by district order by sum(population) desc limit 3;

如图:

当然,limit 也可做限制,如:

select district,sum(population) from city where countrycode="CHN" group by district order by sum(population) desc limit 2,3;

limit n,m,这意味着跳过前面的 n 行,然后显示 m 行,于是结果为:

当然也可以另外的写法:limit m offset n,一个意思。

 

8. 去重复:DISTINCT

查询所有国家:

select distinct(countrycode) from city;

如果只是查询 countrycode 会有很多重复数据,可以通过 distinct 去掉重复:

 

9. 联合查询:union all

之前查询中国和美国使用了 and 和 in 的方法,但这并不是性能最优的方法。最好的是使用 union all:

select * from city where countrycode="CHN" union all select * from city where countrycode="USA";

值得注意的是 union 会去掉重复数据,而 union all 不会去重复。 

 

10. 多表连接查询:join

首选准备 4 张表,关系是这样的:

建表语句:

-- 删掉旧数据新建数据库
drop database school;
create database school charset utf8mb4 collate utf8mb4_bin;
use school;

-- 学生表
create table student(
sno int not null primary key auto_increment comment "学号",
sname varchar(20) not null comment "姓名",
sage tinyint unsigned not null comment "年龄",
sgender enum("m","f","n") not null default "n" comment "性别"
) engine=innodb charset=utf8mb4;

-- 课程表
create table course(
cno int not null primary key auto_increment comment "课程编号",
cname varchar(20) not null comment "课程名称",
tno int not null comment "教师编号"
) engine=innodb charset=utf8mb4;

-- 学生成绩表
create table sc(
sno int not null comment "学号",
cno int not null comment "课程编号",
score tinyint not null default 0 comment "成绩"
) engine=innodb charset=utf8mb4;

-- 教师表
create table teacher(
tno int not null primary key auto_increment comment "教师编号",
tname varchar(20) not null comment "教师名字"
) engine=innodb charset=utf8mb4;

 

基础数据:

-- 学生信息
INSERT INTO student VALUES
(1,'张三',18,'m'),
(2,'李四',18,'m'),
(3,'王五',18,'m'),
(4,'老赵',19,'f'),
(5,'老钱',20,'m'),
(6,'老孙',20,'f'),
(7,'老李',25,'m');

-- 教师信息
INSERT INTO teacher VALUES
(101,'李老师'),
(102,'张老师'),
(103,'王老师');

-- 课程信息
INSERT INTO course VALUES
(1001,'linux',101),
(1002,'python',102),
(1003,'mysql',103);

-- 学生成绩
INSERT INTO sc VALUES
(1,1001,80),
(1,1002,59),
(2,1002,90),
(2,1003,100),
(3,1001,99),
(3,1003,40),
(4,1001,79),
(4,1002,61),
(4,1003,99),
(5,1003,40),
(6,1001,89),
(6,1003,77),
(7,1001,67),
(7,1003,82); 

 

最后效果:

 

多表查询测试:

a. 统计张三学习了几门课:

select st.sname,count(sc.cno) as course_nums from student as st join sc on st.sno=sc.sno where st.sname="张三";

这里用到的知识有:

1. 通过 as 可以对字段就行取别名,便于后面书写使用。

2. 在一开始不知道这么写的时候可以选择将 select 和 from 之间的内容替换为 *,然后再根据需求修改。

结果:

 

b. 查询张三学习的课程名称:

同样可以先查出所有信息:

select * from student as st join sc on st.sno=sc.sno join course as co on co.cno=sc.cno where st.sname="张三";

结果:

然后我们只需要姓名列和课程名称列:

select st.sname,co.cname from student as st join sc on st.sno=sc.sno join course as co on co.cno=sc.cno where st.sname="张三";

结果:

 

c. 查询李老师教的学生名字:

select te.tname,co.cname,st.sname from teacher as te join course as co on co.tno=te.tno join sc on sc.cno=co.cno join student as st on st.sno=sc.sno where te.tname="李老师";

结果:

 

d. 计算李老师所教课程的平均分:

select avg(sc.score) from teacher as te join course as co on te.tno=co.tno join sc on co.cno=sc.cno where te.tname="李老师";

结果:

 

e. 计算每个老师的平均分,并降序排列:

select te.tname,avg(sc.score) from teacher as te join course as co on te.tno=co.tno join sc on co.cno=sc.cno group by te.tname order by avg(sc.score) desc;

结果:

 

f. 查询李老师80分以下的学生:

select * from teacher as te join course as co on te.tno=co.tno join sc on sc.cno=co.cno join student as st on st.sno=sc.sno where te.tname="李老师" and sc.score<80;

结果:

 

g. 查询所有老师成绩不及格的:

select * from teacher as te join course as co on co.tno=te.tno join sc on sc.cno=co.cno join student as st on sc.sno=st.sno where sc.score<60;

结果:

 

h. 查询平均成绩大于 60 的学生:

select st.sname,avg(score) from sc join student as st on st.sno=sc.sno group by sc.sno having avg(score)>60;

结果:

这里值得注意的是,由于 group by 后面不能使用 where,所以筛选条件变成 having

 

i. 显示各门成绩的最高分最低分:

select co.cname,max(sc.score) as "最高分",min(sc.score) as "最低分" from sc join course as co on co.cno=sc.cno group by sc.cno;

结果:

 

11. information_schema 视图库:

在说明这个之前,可以简单了解以下视图。可以这样理解,视图就是对复杂 SQL 的封装,举个例子:

select co.cname,max(sc.score) as "最高分",min(sc.score) as "最低分" from sc join course as co on co.cno=sc.cno group by sc.cno;

这是上面一条复杂的查询,如果每次用这个就写一次,这么长肯定很麻烦,这就可以将它保存为视图:

create view mytest_view as select co.cname,max(sc.score) as "最高分",min(sc.score) as "最低分" from sc join course as co on co.cno=sc.cno group by sc.cno;

可以在前面增加创建视图:create view 视图名字 as

此时就可以直接使用:

select * from mytest_view;

结果:

 

在 MySQL 5.7 中,有三个库用于存储视图:information_schema,performance_schema,sys

这里主要谈谈 information_schema 中的 tables 表,其中主要的字段包括:

字段名称作用
TABLE_SCHEMA 库名
TABLE_NAME 表名
ENGINE 引擎
TABLE_ROWS 表的行数
AVG_ROW_LENGTH 表中行的平均大小(字节)
DATA_LENGTH 数据占用空间大小(字节)
INDEX_LENGTH 索引占用空间大小(字节)

可以直接查询:

select TABLE_SCHEMA,TABLE_NAME,ENGINE,TABLE_ROWS,AVG_ROW_LENGTH,DATA_LENGTH,INDEX_LENGTH from tables;

 

示例:

a. 显示每个库都有哪些表:

select TABLE_SCHEMA,group_concat(TABLE_NAME) from information_schema.tables group by TABLE_SCHEMA;

结果类似:

| world              | countrylanguage,country,city|

 

b. 统计所有库下表的个数:

select TABLE_SCHEMA,count(TABLE_NAME) from information_schema.tables group by TABLE_SCHEMA;

结果:

 

c. 统计 world 下面每张表所占的磁盘:

select TABLE_NAME,concat((DATA_LENGTH INDEX_LENGTH)/1024, " KB") as LENGTH from information_schema.tables where TABLE_SCHEMA="world";

结果:

当然会发现小数位数太多,没啥意义,可以设置小数:保留两位

select TABLE_NAME,concat(round((DATA_LENGTH INDEX_LENGTH)/1024, 2)," KB") as LENGTH from information_schema.tables where TABLE_SCHEMA="world";

结果:

 

d. 统计所有库占用大小:

select TABLE_SCHEMA,concat(round(sum(DATA_LENGTH   INDEX_LENGTH)/1024,2)," KB") as DB_LENGTH from information_schema.tables group by TABLE_SCHEMA;

结果:

 

e. 统计整个库占用的空间:

select concat(sum(DATA_LENGTH   INDEX_LENGTH)/1024," KB") as TOTAL_LENGTH from information_schema.tables;

结果:

 

f. 假设现在有 1000 张表,需要单独生产备份语句,就需要用到 SQL 拼接:

mysqldump -uroot -p123 world city >/tmp/world_city.sql

这是备份单个表的语句,如果需要备份 1000 个,可以使用 SQL 来生成:

select concat("mysqldump -uroot -p123 ",TABLE_SCHEMA," ",TABLE_NAME," >/tmp/",TABLE_SCHEMA,"_",TABLE_NAME,".sql") from information_schema.tables where TABLE_SCHEMA="world";

结果如下:

但这只是单纯的 SQL 结果,可以将其输出到文件:前提是在 /etc/my.cnf 中指定安全目录 secure-file-priv=/tmp

select concat("mysqldump -uroot -p123 ",TABLE_SCHEMA," ",TABLE_NAME," >/tmp/",TABLE_SCHEMA,"_",TABLE_NAME,".sql") from information_schema.tables where TABLE_SCHEMA="world" into outfile '/tmp/1.sh'

 

12. show 命令如下表

命令作用
show databases; 查看数据库
show tables; 查看表
show tables from information_schema; 查看指定库的表
show create database world; 查看建库语句
show create table city; 查看建表语句
show grants for root@'%'; 查看用户授权
show charset; 查看支持的编码
show collation; 查看数据库支持的排序规则
show processlist; 查看数据库连接情况
show index from city; 查看表索引情况
show status; 查看数据库情况
show status like '%lock%'; 模糊查询数据库状态
show variables; 查看数据库配置信息
show variables like "%timeout%"; 模糊查询配置信息
show engines; 查看存储引擎
show engine innodb status\G 查看 innodb 相关信息
show binary logs; 列举所有二进制日志
show master status; 查看数据库日志位置
show binlog evnets in xxx 查看二进制日志事件
show slave status\G 查看从库状态
show relaylog events; 查看从库 relaylog

 

 

小结 

 

增删查改的核心语句都在这里,内容非常多!

来源:https://www./content-2-389751.html

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多