mysql详解

mysql命令

mysql官方文档(英文)

window安装mysql

mysql参考

b站老杜mysql参考视频

mysql简介和sql定义

  • mysql是一个轻量级关系型数据库管理系统,具有体积小,速度快,开源的优点
  • sql是一种结构化查询语言(Structured Query Language),专门用来管理和处理关系型数据库的标准化编程语言,mysql实现了SQL标准,使得用户能够使用SQL语言来与MySQL数据库进行交互和操作数据

sql语言分类

  • 数据定义语言DDL(data definition language),定义和管理数据库和数据表
  • 数据操作语言DML(data manipulation language),操作数据库数据(增删改)
  • 数据查询语言DQL(data query language),查看数据库数据(查)
  • 数据控制语言DCL(data control language),数据库权限管理
  • 事务控制语言TCL(transaction control language),数据库事务管理

连接数据库及数据库常用指令

这里的命令在sql可视化工具中可以直接操作,只有在linux或cmd中才会用到

  • 连接数据库常用选项

    -u用户名,-p密码 如果省略会在命令行隐藏式输入密码

    -P端口 不加默认3306,-h服务器地址 不加默认本地ip

    -D指定默认数据库 不加默认不指定,-D参数可以省略直接加数据库名

    # 连接本地mysql
    mysql -uroot -p
    # 连接远程mysql服务器,端口为3333,指定数据库为yilko
    mysql -h192.168.xx.xx -Dyilko -P3333 -uroot -p
    # 不加-D指定数据库,直接写数据库名也可以
    mysql -uroot -p yilko
    
  • 查看及选择数据库

    想知道自己在用哪个数据库,可以用database()函数,在下面mysql函数-系统信息函数可见

    show databases;
    use db_nme;
    
  • 查看表以及表结构

    show tables;
    desc tb_name;
    
  • 刷新数据库(属于dcl范畴)及退出mysql

    flush privileges;
    exit
    

数据类型

创建表时,表字段需要指定类型,先了解数据库常用数据类型,才能在ddl创建表中运用

数值类型

类型说明有符号范围无符号范围存储占用
tinyint很小的数据-27到27-10到2^8-1(255)1字节
smallint较小的数据-215到215-10到2^16-1(65535)2字节
mediumint中等数据-223到223-10到2^24-1(1677w)3字节
int标准整数-231到231-10到2^32-1(42.94e)4字节
bigint较大整数-263到263-10到2^64-1(20位)8字节
float单精度浮点数约7个有效数字(整数+小数)4字节
double双精度浮点数约15个有效数字(整数+小数)8字节
decimal字符串形式浮点数---

decimal根据不同的赋值有不同的存储大小,基本格式为decimal(m,d)

m指最大位数(小数点左边整数和右边小数加起来),m的范围是1-65

d指小数点右边位数(不能大于m),d的范围是0-30

如果d被省略,默认为0,m被省略,默认为10

上面文字来自mysql官方decimal解释

当某些场景需要高精度计算时不使用double,而是使用decimal类型

字符串类型

这里只提及字符长度,没有提及存储占用,是因为不同的编码方式,字节和字符的转换不同

想知道存储占用,可以先知道长度,编码方式,然后参考注意事项部分得出占用大小

固定长度的字符串,分配固定的空间,速度快

可变长度的字符串,根据实际长度动态分配空间,速度慢

enum类型属于一次性定义,不支持直接修改枚举值,要修改涉及到数据迁移,所以enum用的很少

类型说明最大长度
char固定长度字符串255个字符
varchar可变(长度)字符串65535个字符
tinytext可变字符串,存储很短文本255个字符
text可变字符串,存储较大文本65535个字符
longtext可变字符串,存储庞大文本,约4g2^32-1个字符
binary固定长度二进制255个字符
varbinary可变(长度)二进制65535个字符
longblob可变二进制,存储庞大二进制数据,约4g2^32-1个字符
enum枚举类型,从预定义集合中选一个值,存储效率高-
set集合类型,从预定义集合中选择多个值-

日期时间类型

类型说明取值范围存储占用
date日期格式yyyy-mm-dd1000-01-01到9999-12-313字节
time时间格式hh:mm:ss-838:59:59到838:59:593字节
datetime格式yyyy-mm-dd hh:mm:ssdate+time(24小时)的范围8字节
timestamp时间戳1970-01-01 00:00:01到现在7字节
year年份1901-21551字节

time、datetime、timestamp注意事项:

精度:默认精确到秒级,如需精确到毫秒级使用time(3),微秒级使用time(6),代表小数精度

时区:timestamp受服务器时区影响,date、time、datetime不受时区影响

null类型

代表该字段的数据缺失或未定义的状态,不是一个具体的值

与0、空字符串、空数组都不是一个意思

判断null不能使用表达式或运算符,而是用is [not] null判断该字段是否为空

null参与计算需要使用coalesce空处理函数把null改为指定默认值

分组函数计算时自动忽略null的那行

操作数据库(ddl)

对表的结构进行增删改,常见的有create、drop、alter、truncate指令

ddl的语句在sql可视化工具中是可以直接增删改,只有在linux或cmd中才会用到ddl命令

创建数据库

# 数据库存在会报错
create database yilko;

# 数据库存在不会报错也不会创建,沿用已存在的数据库
create dateabase if not exists yilko;

查看建库语句

show create database yilko;

创建表

表的创建涉及到列属性的定义,列属性中也存在约束,索引,引擎等相关知识

下面表格只简单描述列属性有哪些,简单的说明等(只提及约束、索引、引擎在表创建时添加)

更深入(如何使用,修改及删除等)的约束,索引,引擎查看下面的章节

列级约束写在字段里面,跟整列相关。表级约束是写在字段外表格内的,跟整个表相关

列属性说明
数据类型决定该列存储的类型,如int,varchar
长度/精度声明存储最大长度或精度,如varchar(255),time(3)
主键约束(表级)记录该行记录的唯一标识,相当于身份证号,不允许重复且不能为null
非空约束(列级)决定该字段能否存储null值,默认为null,使用not null可约束该字段不能为空
唯一约束(列级)字段的值在表中唯一,但可以有多个null
外键约束(表级)建立表的关联关系,子表中的字段引用父表中的主键字段
自增(列级)适用于整数类型字段,插入新行自动增加1,常用于主键,可定义起始值和步长
默认值(列级)插入时未指定值,使用默认值填充
无符号(列级)适用于整数类型字段,只能存储0或正整数的数值,增加整数型的取值范围
检查约束(表级)mysql8.0后支持,适用于各种字段,在检查约束里写一个判断条件
索引(表级)缩小扫描范围,提高查询效率,可理解为一本书的目录
注释(列/表级)字段添加备注
存储引擎(表级)默认innodb引擎,可以指定别的引擎,或者指定表级或列级引擎

基本格式:

create table [if not exists] '表名'(
  '字段名1' '数据类型'('长度/精度') ['列属性1'] ['列属性2'] ['列级约束'] ['注释'],
  '字段名2' '数据类型'('长度/精度') ['列属性1'] ['列属性2'] ['列级约束'] ['注释'],
  ...,
  ['表级约束'],
  ['索引'],
  ...
)['存储引擎'] ['字符集'] ['注释']

如果没有指定引擎和字符集,那么默认使用InnoDB引擎和utf8mb3字符集

举例使用上面所有列属性,数据类型列举大部分常用的

# 创建一个班级表用于了解外键的连接
CREATE TABLE class_table(
    # 列级主键约束不用加字段,默认指定当前列字段,而下面表级主键约束需要指定列字段
    # 注意建立外键的字段类型要一致,否则报incompatible错误
    `id` SMALLINT AUTO_INCREMENT PRIMARY KEY COMMENT '班级id'
);

CREATE TABLE my_table(
  # 常用数值类型举例,包含自增、无符号列属性
  `id` INT AUTO_INCREMENT COMMENT '用户id',
  `age` TINYINT UNSIGNED COMMENT '用户年龄(0-255)',
  `class_id` SMALLINT COMMENT '用户班级id 外键',
  `balance` DOUBLE COMMENT '用户余额',
  # 常用字符串类型举例,包含默认值、非空约束、唯一约束列属性
  `sex` CHAR(1) DEFAULT 0 COMMENT '用户性别 0未知 1男 2女',  
  `name` VARCHAR(20) NOT NULL COMMENT '用户名字',
  `email` VARCHAR(50) UNIQUE COMMENT '用户邮箱',
  `self_introduce` TEXT COMMENT '自我介绍',
  `self_photo` LONGBLOB COMMENT '用户自拍照片',
  # 常用时间类型举例
  `birthday` DATE COMMENT '生日',
  `create_time` DATETIME COMMENT '创建时间',
  `school_year` YEAR COMMENT '入学年份',
  # 主键外键索引注释
  PRIMARY KEY(id),
  FOREIGN KEY(class_id) REFERENCES class_table(id),
  INDEX idx_name(`name`)
  # 自定义存储引擎和字符集及表注释
) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT '个人信息表';

查看建表语句

show create table my_table;

改变表结构(改名和增删改字段)

下面所写的例子都是基于上面创建的表结构

  • 修改表名

    alter table 旧表名 rename as 新表名;
    alter table class_table rename as my_class_table;
    
  • 添加字段

    alter table 表名 add 字段名 字段属性(长度) [列属性] [约束] [注释];
    alter table my_class_table add person_sum tinyint;
    alter table my_class_table add name varchar(10) not null default '1班' comment '班级名字';
    
  • 修改字段属性

    alter table 表名 modify 字段名 字段属性;
    alter table my_class_table modify person_sum tinyint unsigned not null comment '班级总人数';
    
  • 修改字段名和属性

    只想改名字不改变属性,需要把旧字段的属性原封不动搬到修改语句,否则属性改变

    实际上字段属性可能很复杂,很难完全照搬,所以只改名字的情况下,尽量不改名

    alter table 表名 change 旧字段名 新字段名 字段属性;
    alter table my_class_table change name class_name varchar(5);
    
  • 删除表字段

    alter table 表名 drop 字段名;
    alter table my_class_table drop person_sum;
    

清空表

删除表全部数据(清空表)的情况下,truncate会比delete好

  • delete语句一行一行记录删,删除效率低,不会清空自增约束的数目

    数据还在真实存储空间,支持回滚(数据可恢复),属于DML语句

  • truncate一整张表清空,删除效率高,会把自增约束重置为1

    属于物理删除,不支持回滚,属于DDL语句

truncate 表名;
truncate my_table;

删除表

清空表和删除表都没有选择my_class_table是因为存在外键,清空或删除会报错

这里主要是了解如何删除和清空表,在下面约束章节中会提到如何解决外键的问题

drop table [if exists] 表名; 
drop table my_table;

操作数据表(dml)

对表的数据进行增删改,常见的有insert、update、delete指令

dml语句用的频率相对来说会高一些,需要熟悉掌握

下面使用到的dml语句都是来自于上面ddl创建的两个表

插入行

插入有两种方式:

  • 插入指定字段,字段顺序可以不跟table一样,但是字段和值要一一对应

    没有指定的使用默认值或赋予null,但遇到没有默认值且非空情况,需要指定该字段赋值

    insert into 表名(字段1,字段3...) values (值1,值3...);
    
    # 插入错误情况,name字段非空,需要指定
    insert into my_table(age,school_year) values(18,2022);
    # 正常插入
    insert into my_table(age,school_year,name) values(18,2022,'bueatiful girl');
    
  • 省略字段只写值,但必须所有字段值都写上

    不需要值的那个字段也需要写null,拥有自增的主键也需要写上

    values的个数必须和字段的个数对上,对不上就报错

    insert into 表名 values(值1,值2,值3...);
    insert into my_table values(2,22,null,100,1,'good girl','aaa@qq.com','自我介绍完毕',null,null,'2024-4-10 18:00:00',null);
    

可以一次插入多条记录,每条记录使用逗号隔开即可,上面两种方式都可

  • 指定字段一次插入多条

    有弊有利,利是字段少了看起来比起省略字段的要简洁

    弊是固定死了字段,如果第二条记录跟第一条记录不同字段,就没办法同时插入

    insert into my_table(age,school_year,name) values(19,2022,'bueatiful girl'),(33,2018,'green girl');
    
  • 省略字段一次插入多条

    insert into my_table values(3,22,null,100,1,'sexy girl',null,'自我介绍完毕',null,null,'2024-4-10 18:00:00',null),(7,25,null,100,1,'nice girl',null,'自我介绍完毕',null,null,'2024-4-10 18:00:00',null);
    
  • 上面两个方式看需求使用,插入多条时都是同一字段,就用第一种,否则就用第二种

更新行

update语句一定一定一定要有where限制条件!否则整张表数据全部更新!!!

update 表名 set 字段1=值1,字段2=值2... where 条件;

# 更新一条记录
update my_table set sex=2,email='qwe@qq.com' where id=2;

# 更新多条记录(大于小于或者in的方式都可以)
update my_table set balance=1000 where age>20;

# 更新整张表(很少情况会用到,谨慎使用)
update my_table set self_introduce=null;

删除行

delete语句一定一定一定要有where限制条件!否则整张表数据全部删除!!!

delete from 表名 where 条件;

# 删除一条记录
delete from my_table where id=4;

# 删除多条记录
delete from my_table where age in (18,19,20);

# 删除全部记录(不用delete语句,效率低,并且删除全部数据需谨慎)
truncate my_table;

数据库导入导出(备份)

sql文件和作业题百度云提取码: kjqv

下面所有的dql语句和mysql函数都会用到老杜的sql文件,这里先说如何对sql文件导入及导出

两种方式导入sql文件

  • 第一种是未登录mysql情况下,在cmd命令行或linux命令行界面直接导入

    这里导入本机数据库,如果是远程数据库需要再加-h指定ip

    如果sql文件中已经有指定数据库,可以不用在命令行中指定数据库

    该命令在cmd和linux下都可以使用(只是sql文件路径有一点差别)

    mysql -uroot -p yilko < e:\xxx\bjpowernode.sql
    
  • 第二种是登录mysql,在mysql里面使用命令导入sql文件

    mysql -uroot -p yilko
    source e:xxx\bjpowernode.sql
    

导出数据

  • 导出整个库

    基本格式:mysqldump -hxxx -uxxx -p 数据库 > 导出路径+sql文件命名

    mysqldump -uroot -p yilko > e:\xxx\yilko.sql
    
  • 导出某个表

    基本格式:mysqldump -hxxx -uxxx -p 数据库 数据表 > 导出路径+sql文件命名

    mysqldump -uroot -p yilko emp > e:\xxx\emp.sql
    

mysql函数

mysql函数使用官方文档(英文)

在了解dql语句前也需要先了解一些mysql常用的函数,在查询中可能会用到,先打个底了解下

以下只是提及到常用的,实际上mysql提供的函数有很多,没有提及到的可以去官方文档查

分组函数(聚合函数)

  • 分组函数定义:

    需要分组(group by)后才能使用的函数,总共有五个(count,sum,avg,max,min)

    根据某个字段group by后,原本一张表散乱的数据被分类为多组

    而分组函数可以对每个组进行操作,从而计算每个组中count,sum,avg,max,min的数据

    # 根据部门分组,找出每个部门中工资最高的人
    select deptno,max(sal) from emp group by deptno;
    
  • 分组函数一般都要配合group by使用,这里只是简单讲解

  • 详细使用请参考下面dql章节的分组查询

字符串函数

  • 字符串拼接

    基本格式:concat(字段或字符串1,字段或字符串2…)

    # 字段与字段拼接
    select ename,concat(ename,job) new_str from emp;
    
    # 字段与字符串拼接
    select ename,concat(ename,'congrats') new_str from emp;
    
  • 字符串长度

    基本格式:length(字段)

    经过测试字符串里面左右中的空格都会计算在内

    select job,length(job) len from emp;
    
  • 字符串截取

    基本格式:substr(字段,下标,长度)

    长度如果省略代表取到最后

    下标不是从0开始,而是从1开始

    下标正数代表从左往右数,下标负数代表从右往左数(与python切片类似)

    mysql提供left(),right()从左右边截取,但是substr都可实现

    # 从左边第二个开始截取,取两个长度
    select job,substr(job,2,2) from emp;
    
    # 如果截取长度超过原字段,超出部分忽略
    select job,substr(job,3,5) from emp;
    
    # 从右边第三个开始截取,截取两个长度
    select job,substr(job,-3,2) from emp;
    
    # 从左边第两个开始截取,一直截取到最后
    select job,substr(job,2) from emp;
    
  • 字符串大小写

    基本格式:lower(字段)

    select lower(job) my_job from emp;
    

    基本格式:upper(字段)

    select upper(job) my_job from emp;
    
  • 字符串去除左右空格

    基本格式:trim(字段)

    只能去掉左右空格,并不能去掉中间空格

    select trim(concat(ename,'-      ')) new_str from emp;
    
    # 与上面语句展示对比效果
    select concat(ename,'-      ') new_str from emp;
     
    # 展示不能去掉中间空格
    select loc,trim(loc) from dept;
    
  • 字符串替换

    基本格式:replace(被替换字段,被替换的字符串,新替换的字符串)

    如果字段中没有符合条件,则被跳过不替换

    select ename,replace(ename,'S','--') rep from emp;
    
  • 字符串反转

    基本格式:reverse(字段)

    select ename,reverse(ename) from emp;
    
  • 字符串小练习

    把emp表中ename改为只有首字母大写,其他小写

    mysql中没有提供首字母大写,其他小写的的函数

    因此需要自己截取,其他部分改小写,再拼接

    select ename,concat(substr(ename,1,1),lower(substr(ename,2))) name from emp;
    

日期时间函数

  • 获取当前系统日期和时间(datetime类型)

    select now();
    
  • 获取当前系统日期(date类型)

    select curdate();
    
  • 提取年月日时分秒

    接收一个date或datetime类型的参数,可以返回年、月、日、时、分、秒

    如果接收date类型,但是查询时分秒,默认为0时0分0秒,查出来的结果都是0

    select year(now());
    select month(now());
    select day(now());
    select hour(now());
    select minute(now());
    select second(now());
    
    # 传date类型,获取秒,返回0
    select second(curdate());
    
  • 计算时间差

    基本格式:timestampdiff(间隔类型,前一个日期,后一个日期)

    间隔类型:year,month,day,hour,minute,second,week,quarter(季度)

    前一个日期必须是小的,后一个日期必须是大的,否则得出的结果会是负数

    如果日期是date类型,但需要计算时分秒的间隔,会默认把date默认为0时0分0秒计算

    # emp表,计算每个员工入职年份距离现在有多少年
     select ename,hiredate,timestampdiff(year,hiredate,now()) year from emp;
     
     # emp表,计算每个员工入职年份距离现在是多少秒
     # hiredate字段没有时间,默认以0时0分0秒来算
     select ename,hiredate,timestampdiff(second,hiredate,now()) second from emp;
    
  • 字符串转date类型

    mysql具有默认日期格式,使用该格式,字符串类型的时间会自动转成时间类型

    而无需使用函数对字符串和日期进行转换

    mysql默认日期格式:

    date类型:‘%Y-%m-%d’

    datetime类型:‘%Y-%m-%d %H:%i:%s’

    # 单独创建一个表讲解字符串转date,date转字符串这两个函数
    CREATE TABLE teacher(
      id SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT '老师id',
      `name` VARCHAR(20) NOT NULL COMMENT '老师名字',
      birthday DATE COMMENT '老师生日',
      create_time DATETIME COMMENT '创建时间'
    );
    

    字符串转date场景,在插入记录时体现

    使用默认日期格式:

    写的时候是字符串,插入的时候自动把字符串转为date类型

    insert into teacher values(1,'xcvb','2000-1-1','2024-04-19 12:00:00');
    

    使用自定义格式:

    基本格式:str_to_date(‘字符串日期’,‘日期格式’)

    实际上很少用该函数,因为自定义日期格式插入记录后,

    实际上mysql存储的还是默认格式的日期,等同白搞,还不如就使用默认格式来的方便

    INSERT INTO teacher VALUES(2,'95511',STR_TO_DATE('2000/1/1','%Y/%m/%d'),STR_TO_DATE('2024_04_19_14_58_00','%Y_%m_%d_%H_%i_%s'));
    
  • date类型转字符串

    与上面同理,使用默认日期格式,mysql可以自动把时间类型的数据自动转为字符串类型

    date转字符串场景在查询数据时体现

    teacher表中的birthday和create_time字段从日期类型转为字符串类型展示

    使用默认日期格式:

    # 查找teacher表
    select name,create_time from teacher;
    

    使用自定义日期格式:

    无论插入的记录是否默认日期格式,最终mysql存储都是默认格式,所以mysql查询到的时间都是默认格式,而如果不想展示是默认格式,就需要用到date_format()函数

    基本格式:date_format(字段,‘日期格式’)

    # 查找teacher表,把create_time字段日期改为自定义格式
    select name,date_format(create_time,'%Y_%m_%d_%H_%i_%s') my_time from teacher;
    

控制流程函数

  • if函数

    当需要基于某个字段的布尔结果(筛选条件)来动态展示数据时就会用到if函数,例如数据筛选

    简单理解为不是想展示该字段数据,而是根据筛选条件分为符合、不符合两种结果

    基本格式:if(字段限制条件,满足条件输出的内容,不满足条件输出的内容)

    # emp表,sal字段大于等于1000视为高工资,小于1000为低工资
    select ename,sal,if(sal>=1000,'high','low') sal_level from emp;
    
    # emp表,comm字段为空或为0,显示无奖金,其他显示奖金数目
    select ename,comm,if(comm is null or comm=0,'无奖金',comm) has_comm from emp;
    
  • case when函数

    有两种格式:

    case ‘字段’ when ‘字段值(不能判断空)’ then ‘结果’ when…then…else ‘结果’ end

    case when ‘布尔表达式’ then ‘结果’ when…then…else ‘结果’ end

    第一种适用于对某个字段进行枚举判断,第二种适用于范围判断(跟if类似但分支更多)

    没有else不符合条件的字段默认显示为null

    case when涉及很长语句,本质上还是一个字段,展示一列数据(只是多了筛选过程)

    # emp表,deptno枚举10,20,30,分别对应会计,研究,销售
    # 可以自己尝试一下去掉30,那么30部门对应的就会显示null
    SELECT ename,deptno,
      CASE deptno
        WHEN 10 THEN '会计'
        WHEN 20 THEN '研究'
        WHEN 30 THEN '销售'
      END dept_name
    FROM emp;
    
    # emp表,hiredate在1980及之前为元老,1981是开荒员工,1982是老油条,1982以后是新人
    # 如果不加else字段,那么1982年以后的都显示null
    SELECT ename,hiredate,
      CASE
        WHEN hiredate < '1980-12-31' THEN '元老'
        WHEN hiredate BETWEEN '1981-1-1' AND '1981-12-31' THEN '开荒员工'
        WHEN hiredate BETWEEN '1982-1-1' AND '1982-12-31' THEN '老油条'
        ELSE '新人'
      END people_level
    FROM emp;
    

逻辑函数

  • 空处理函数

    空字段进行数学运算(加减乘除取余)时都为空

    使用空处理函数可把空字段赋予某个值进行运算,大多数情况是把空字段当成0

    空处理函数有两种,第一种为ifnull,第二种为coalesce

    ifnull只能在mysql中使用,coalesce在其他数据库中是通用的空处理函数

    基本格式:coalsece(expression1, expression2, …, expressionN)

    空字段当成0参与计算

    # 计算emp表每个人的工资+奖金总数
    select ename,sal+coalesce(comm,0) sum_sal from emp;
    

    空字段赋值其他字段(一个一个查找不为空的字段)

    字段1为空找字段2,字段2为空找字段3,以此类推,最后找个默认值兜底

    老杜的表没有该函数的使用场景,自己创建一个场景

    假设有一个学生表,表中有固话,爸爸手机号码,妈妈手机号码

    现需要找到某个学生的家属联系方式

    联系方式优先找妈妈,妈妈找不到找爸爸,爸爸找不到找固话,固话找不到返回null

    # 创建表
    CREATE TABLE student(
      id SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT '学生id',
      `name` VARCHAR(20) NOT NULL COMMENT '学生名字',
      dad_contact VARCHAR(11) COMMENT '父亲电话',
      mom_contact VARCHAR(11) COMMENT '母亲电话',
      landline VARCHAR(8) COMMENT '固定电话'
    );
    
    # 插入数据
    INSERT INTO student VALUES(1,'where','12311112222','12333334444','84608888'),
    (2,'tyuio','12355556666','12377778888',NULL),
    (3,'where','12399998888',NULL,'84621234'),
    (4,'ltsz',NULL,'12312341234','84601111'),
    (5,'grlt',NULL,NULL,'84662222'),
    (6,'gofy',NULL,'12345671111',NULL),
    (7,'dfgh','12312346789',NULL,NULL),
    (8,'hjkl',NULL,NULL,NULL);
    
    # 使用coalesce查询上面场景结果
    SELECT `name`,COALESCE(mom_contact,dad_contact,landline,NULL) contact FROM student;
    

数学函数

  • 四舍五入

    基本格式:round(值,取几位小数)

    第二个参数为0代表取整

    第二个参数为负数,代表往整数位取整(-1代表个位,-2代表十位以此类推)

    select round(12.45,0); # 12
    select round(12.45,1); # 12.5
    select round(12.45,-1); # 10
    select round(12.45,-2); # 0
    
  • 向上取整

    select ceiling(10.1);
    
  • 向下取整

    select floor(10.9);
    
  • 生成[0,1)区间随机浮点数

    基本格式:rand()

    无参数,但有很多个小数点,一般需要配合round指定位数

    select rand();
    select round(rand(),2);
    
  • 数学函数小练习

    emp表comm字段加上一个1-100以内的随机整数

    rand()的范围是[0-1),所以乘100就变成[0-100)

    地板除可以保证rand()*100最大为99,再加1可以实现[1-100]区间

    select ename,comm,coalesce(comm,0)+floor(rand()*100)+1 add_comm from emp;
    

    emp表comm字段加上一个1-100以内的随机两位小数

    rand()的范围是[0-1),所以乘100就变成[0-100)

    但是round()会四舍五入,rand()>0.995时,rand()*100变成99.5,再四舍五入就变100

    如果再加1,最终范围就会变成[1-101],不符合要求

    所以可以得出rand()*99就可以保证最大为98.x,再四舍五入最大为99,再加1符合范围要求

    select ename,comm,coalesce(comm,0)+round(rand()*99+1,2) add_comm from emp;
    

系统信息函数

  • 查看当前mysql数据库版本号

    select version();
    
  • 查看当前使用哪个数据库

    在cmd时可能不知道自己选择了哪个数据库,可以用该函数查看

    select database();
    
  • 查看当前用户信息

    select user();
    

查询数据(dql)

使用mysql,用的最多的是查询语句,ddl和dml这些都相对少,因此dql是学mysql中最重要的

下面使用到的sql语句都是来自上面数据库导入导出章节的sql文件

select基本格式

select [all | distinct] [字段 | * | 字面值]
from [database_name].table_name [as 别名]
[(left | right | inner) join table_name2 [as 别名] on 表连接条件]
[where 条件(可多个)]
[group by 字段(可多个) having 条件(可多个)]
[order by 字段(可多个) asc | desc]
[limit 偏移量,返回行数]
  • all代表查所有,实际查询可省略all,默认查所有,如果不查所有则需要加distinct去重

  • 查询的内容可以是字段,字面值或者*(代所有字段),同时字段支持算术运算

  • 当没有指定库或者指定A库但要用B库的表时,需要表名前面加库名

  • 表和字段都可以加别名,可以是as+空格+别名,也可以省略as直接空格+别名

  • 表连接有三种,其中inner join可省略inner,表连接需要给表起别名(要识别字段是哪个表)

  • where可以筛选多个条件,筛选方式有多种,下面会讲解

  • group by按照字段分组,分组后筛选使用having

  • order by按照字段排序,支持多个字段排序

  • limit限制返回条数,一般用于分页,如果limit后只有一个数字代表返回多少行

  • 执行顺序:

    1. 查表(from)和连表(join)

    2. 过滤(where)

    3. 分组(group by)

    4. 分组后过滤(having)

    5. 查询结果(select)

    6. 排序(order by)

    7. 限制行数(limit)

去重查询(distinct )

  • distinct有去重的功能,支持多个字段联合去重(多个作为整体,全部字段一样才认为重复)

  • 可以与count()函数结合使用计算去重后的总数

    # 查找emp表有哪些职位
    select distinct job from emp;
    
    # 查找emp表部门有哪些职位
    select distinct job,deptno from emp;
    
    # 统计emp表有多少个职位
    select count(distinct job) job_nums from emp;
    

字段算术运算与字面值

  • 加减乘除取余

    字段可以使用数学表达式参与运算(加减乘除取余)

    如果字段为空,进行数学运算也是为空,使用空处理函数把null当成0参与计算

    # 计算员工的年薪,包括工资和奖金,sal每个月一次,comm一年一次
    select ename,sal*12+coalesce(comm,0) year_sal from emp;
    
    # 计算每个员工日薪,月工资/21.75为日薪,并保留两位小数
    select ename,round(sal/21.75,2) day_sal from emp;
    
    # 计算工资最高和工资最低的相差多少
    select max(sal)-min(sal) diff_value from emp;
    
    # 计算员工的编号是是单数还是双数,单数显示单数,双数显示双数
    select empno,if(empno%2=0,'双数','单数') '单双数',ename from emp;
    
  • 字面值

    字面值定义:直接写入的、表示固定数据值,不随运行时动态变化,是硬编码

    select后面除了写字段,还可以跟字面值,查询的数据返回几条,就返回几个字面值

    字面值可以是数字,字符串,字符串形式的日期格式,布尔值,null值

    布尔值默认会转为01,false代表0,true代表1

    # 数字
    select 1 as num from emp where deptno=10;
    
    # 字符串
    select 'peppa pig' as '粉色吹风机' from emp;
    select '中文也ok' as chinese from emp where comm is null;
    select '2000-1-1' as str_date from emp group by job;
    
    # 布尔值
    select false as bool from emp where mgr is not null;
    select true as bool from emp where empno%2=0;
    
    # null值
    select null as '空值' from emp where length(ename)>5;
    

库名

  • 查询时没有指定库名(use xxx),查询的表需要加库名

  • 指定A库,但使用B库的表时,查询需要加库名

    # 登录mysql,查看所选数据库
    select database();
     
    # 没有选库的情况下查询emp表(提示没选数据库)
    select * from emp;
    
    # 选了yilko库的情况下查询mysql库的user表
    use yilko;
    # 会查yilko的user表,但yilko库没有,会报错
    select * from user;
    # 不查yilko库,去查mysql库,可以找到,不报错
    select * from mysql.user;
    

起别名(alias)

  • 可以给表或字段起别名

  • 别名可省略as,空格+名字即可

  • 别名可以是中文,最好加单引号(实际试过不加也可以)

    # 给emp表起名为e,查询ename,job,deptno字段都加上表别名前缀
    select e.ename,e.job,e.deptno from emp e;
    
    # 给emp的mgr字段起别名叫领导
    select mgr '领导' from emp;
    
    # 统计总人数,并起别名为nums,使用as起别名
    select count(*) as nums from emp;
    

连表查询(join)

  • 交叉连接(cross join)

    也就是笛卡尔积,两张表连接查询没有条件限制情况下出现,查询结果为两张表条数乘积

    只要是表连接就一定会出现笛卡尔积现象,增加条件过滤后(内/外连接)也无法避免

    条件过滤是出现笛卡尔积后才过滤,并不是在笛卡尔积之前过滤

    因此展示的结果实际是笛卡尔积后的结果再过滤来展示的有效数据

    无效数据并不是没有,只是不展示而已

    交叉连接实际上用的不多,更多的是内连接和外连接,但是要知道交叉连接的

    # 查看emp表和salgrade表总条数
    select count(*) from emp;
    select count(*) from salgrade;
    
    # 交叉连接
    select * from emp join salgrade;
    
    左表右表
    aA
    bB
    cC
    dD

    a对ABCD,b对ABCD,c对ABCD,d对ABCD一共是4*4=16条记录

  • 内连接(inner join)

    可以省略inner,一般写sql语句只写join

    内连接是在交叉连接基础上,筛选左表和右表同时都有的数据进行展示

    简单理解就是两个圆取交集

    # 取emp表和dept表都是deptno相等的数据,不相等的数据被过滤不展示
    select * from emp e join dept d on e.deptno=d.deptno;
    
    # 取emp表和salgrade表薪资范围在对应区间的数据,不在区间的被过滤不展示
    select * from emp e join salgrade s on e.sal between s.losal and s.hisal;
    
    左表右表
    anull
    bB
    cC
    dD
    nullE

    b对B,c对C,d对D,在笛卡尔积中只有3条是两表交集

  • 外连接(outer join)

    • 左外连接(left outer join)

      可以省略outer,一般写sql语句只写left join

      左外连接是在交叉连接基础上,展示左表全部数据+筛选左表和右表同时都有的数据

      以左表为基准,右表与左表一一匹配,匹配上的正常显示,匹配不上用null填充

      简单理解就是左边的圆全部显示

      # dept做左表,emp做右表,dept的全部数据都会展示出来
      # dept有一个部门40还没有人,但是也会显示出来,但是使用null填充
      select * from dept d left join emp e on d.deptno=e.deptno;
      
      左表右表
      anull
      bB
      cC
      dD
      nullE

      a对null,b对B,c对C,d对D,在笛卡尔积中只有3条是两表交集+1条左表数据

      左外连接还有一种情况,可能有场景只需要右表没有的数据

      也就是左边的圆,不要交集那部分,只要无交集部分

      # dept做左表,emp做右表
      # 左表有数据但右表没有,对应的连接字段右边为null,使用null筛选可达到目的
      select * from dept d left join emp e on d.deptno=e.deptno 
      where e.deptno is null;
      
      左表右表
      anull
      bB
      cC
      dD
      nullE

      a对null,在笛卡尔积中只有1条左表数据是右表为null的

    • 右外连接(right outer join)

      可以省略outer,一般写sql语句只写right join

      跟左外连接差不多,只是反过来,是以右表为基准,这里不再重复描述

      # emp做左表,dept做右表
      select * from emp e right join dept d on e.deptno=d.deptno;
      
      左表右表
      anull
      bB
      cC
      dD
      nullE

      b对B,c对C,d对D,null对E在笛卡尔积中只有3条是两表交集+1条右表数据

      右外连接也有场景可能只需要左表没有的数据

      同样是使用null筛选达到目的,但是使用左表的字段

      # emp做左表,dept做右表
      select * from emp e right join dept d on e.deptno=d.deptno 
      where e.deptno is null;
      
      左表右表
      anull
      bB
      cC
      dD
      nullE

      null对E,在笛卡尔积中只有1条右表数据是左表为null的

    • 全连接(full outer join)

      mysql不支持全连接,但可以通过union实现,左连接 union 右连接

      左表有右表没有的会展示,左表没有右表有的都会展示,就是两个连接的联合

      并且union会把完全相同的行去除(去重)

      因为两个表联合实际上交集部分是有两次记录,存在重复的

      全连接可以简单理解为两个圆的并集

      # 左连接联合右连接
      select * from dept d left join emp e on d.deptno=e.deptno
      union
      select * from dept d right join emp e on d.deptno=e.deptno;
      
      左表右表
      anull
      bB
      cC
      dD
      nullE

      a对null,b对B,c对C,d对D,null对E在笛卡尔积中只有3条是两表交集+2条非交集数据

      全连接也有场景可能只需要非交集那部分

      select * from dept d left join emp e on d.deptno=e.deptno where e.deptno is null
      union
      select * from dept d right join emp e on d.deptno=e.deptno where d.deptno is null;
      
      左表右表
      anull
      bB
      cC
      dD
      nullE

      a对null,null对E,在笛卡尔积中只有2条是两表非交集的部分

  • 自连接(self join)

    同一张表自己进行连接,左表和右表都是自己

    在以下场景中可能会用到自连接:

    • 字段中存在层级关系

      如领导和员工、菜单层级、文件目录结构等

      # 查找emp表每个员工对应的领导名字,展示员工编号,员工名字,领导名字
      # 因为king没有领导,也得输出他的信息,需要用到左连接
      select a.empno,a.ename '员工',b.ename '领导' from emp a left join emp b on a.mgr=b.empno;
      
    • 比较表自身行(与下面查找字段重复项类似,都可归为一类)

      如某行A字段值为B,查找还有哪些行的A字段也是B

      可能首先会想到用where A=B,但这种情况基于知道值为B才能用

      如果不知道A字段的值,就无法用where,可以用子查询或自连接实现

      # 查找与allen相同职位的员工,显示名字+职业
      # 通过job自连接,就是对job的笛卡尔积,如果同职位有5个,总共有5*5=25条记录
      # 如果过滤a表的allen,就要展示b表的名字,反之过滤b表就要展示a表名字
      # 如果过滤a表还展示a表,就全是allen名字
      # 同时需要过滤自身allen
      select a.ename,a.job from emp a join emp b on a.job=b.job where b.ename='allen' and a.ename!='allen';
      
      
      # 子查询也能实现,相对来说比自连接好理解一些
      # 先找到allen的职位
      select job from emp where ename='allen';
      # 把子查询当成一个值去查其他人的职位,并排除自己
      select ename,job from emp where job=(select job from emp where ename='allen') and ename!='allen';
      
    • 查找字段重复项

      如查找A字段中相同的值都有哪些行

      与上面最大的差别是一个是针对行相同的值,这个是针对表相同的值

      也与distinct有不同,是去掉重复,而这个是查找重复

      这个其实很好理解,查找重复项从数学角度来看就是

      A跟其他人比工资,B跟其他人比工资,以此类推

      每个人都跟所有人比一下,遇到一样的就记录,本质上就是表自身的笛卡尔积

      # 查找具有相同工资的人,显示名字和工资
      select a.ename,a.sal from emp a join emp b on a.sal=b.sal and a.empno!=b.empno;
      
  • 三表查询

    三表或以上其实和两个表查询是一样的

    可以理解为A跟B组合后,生成临时表t,临时表t再和C组合

    # 查看每个人的部门名称和薪资水平,显示名字,部门编号+名字,薪资+薪资水平
    select e.ename,e.deptno,d.dname,e.sal,s.grade 
    from emp e join dept d on e.deptno=d.deptno 
    join salgrade s on e.sal between s.losal and s.hisal;
    
    # 还可以在上面基础上对grade排序,二级排序等
    select e.ename,e.deptno,d.dname,e.sal,s.grade 
    from emp e join dept d on e.deptno=d.deptno 
    join salgrade s on e.sal between s.losal and s.hisal
    order by grade desc,deptno,ename;
    
  • union联合

    union合并要求查询的列数相同,列名相同,列的数据类型相同

    union是把两个表相加,因此要求列完全一致

    union合并时会默认去重,如果不需要去重可以使用 union all

    在实际中很少使用到union,知道使用条件,什么场景用即可

    # 上面连表查询有一个全连接,就用到union字段
    
    # 因为三个表没有相同的字段,没有办法展示union使用
    # 这里只展示基本格式
    select field1,field2... from table1
    union [all]
    select field1,field2... from talbe2;
    
  • 表连接时的一些注意点或小知识

    1. 表连接时建议加表名,有以下两个原因:

      第一不加表名时查字段会从两个表一起查,加了表名只查一个表,会提高查询效率

      第二两个表有同个名字的字段,查询时用到该字段,mysql因不知道使用哪个而报错

      # emp表和dept表同时有deptno字段,不加表名连表查就会报错
      select * from emp join deptno on deptno=deptno;
      
    2. 在sql当中,表和表之间连接次数越多,效率越低,因为笛卡尔积。根据实际需求可以用数据冗余换取执行速度(不分表)

    3. union和join有本质区别,union是对表做加法,把两个表的数据加起来展示,要求列数列名列类型一模一样,总共有m+n条记录。而join是对表做乘法,进行笛卡尔积,总共有m*n条记录。从效率上来说,union比join高,但前提是支持使用union

    4. 多表查询时,可以内连接、外连接、自连接混合使用,但实际使用过程中需要知道哪个是主表,哪个表的数据是一定要展示的。

条件查询(where过滤)

  • 逻辑运算符

    and或者&&,逻辑与,两者为真才为真

    or或者||,逻辑或,只要一个真就是真

    not或者!,逻辑非,条件为假,结果返回真

    如果同时存在多个逻辑运算符,and的优先级比or高,如果想or部分先执行需要加小括号

    如果多个逻辑运算符下不知道运行顺序,把自己想要先执行的部分加小括号即可

    # 查找职业为clerk同时部门为20的人
    select ename,job,deptno from emp where job='clerk' and deptno=20;
    
    # 查找工资大于2000或者奖金不为0和空的人
    # 第一种写法利用空处理函数,把空当成0,然后筛选非0条件
    select ename,sal,comm from emp where sal>2000 or coalesce(comm,0)!=0;
    # 第二种是判断非空和判断非0,但两者是并列关系要用and
    select ename,sal,comm from emp where sal>2000 or (comm is not null and comm!=0);
    
  • 比较运算符(单词)

    is [not] null,判断某个字段是否为空

    [not] in,判断某个字段的值是否在集合内(可以是字符串或者数字)

    between A and B,判断某个字段是否在b和c之间(左右都是闭区间且左小右大)

    like,模糊查询,%匹配0或多个字符,_匹配任意一个字符,匹配字符本身需要\转义

    # 查找哪些人的奖金不为空
    select ename,comm from emp where comm is not null;
    
    # 查找哪些人的领导为空
    select * from emp where mgr is null;
    
    # 查找哪些人的职业是clerk或manager
    select ename,job from emp where job in ('clerk','manager');
    
    # 查找哪些人的工资在1000-1500范围内
    select ename,sal from emp where sal between 1000 and 1500;
    
    # 查询哪些人的名字是四个字母的
    select ename from emp where ename like '____';
    
    # 查询哪些人的名字是j开头,s结尾的
    select ename from emp where ename like 'j%s';
    
    # 查询哪些人的名字第二个字母是l,中间或最后包含e的
    select ename from emp where ename like '_l%e%';
    
    # 查询哪些人的名字是a开头或n结尾的(多个条件时需要写明 字段 like 'xxx')
    select ename from emp where ename like 'a%' or ename like '%n';
    
    # 查询哪些人的名字是n结尾并且长度是5的
    select ename from emp where ename like '%n' and length(ename)=5;
    
  • 比较运算符(符号)

    大于、小于、等于、大于等于、小于等于、不等于

    # 查询入职年份大于1981年,并且工资小于2000,名字长度等于4的人
    select * from emp where hiredate>'1981-1-1' and sal<2000 and length(ename)=4;
    # 查找年份的另一种写法
    select * from emp where year(hiredate)>1980 and sal<2000 and length(ename)=4;
    
    # 查找工资大于等于2000,名字长度小于等于4位,入职年份不为12月的人
    select * from emp where sal>=2000 and length(ename)<=4 and month(hiredate)!=12;
    

分组查询(group by&having)

  • 分组查询基本格式:

    select 分组字段,分组函数(返回一个值的子查询) from 表 group by 分组字段 having 条件;

  • 分组查询注意点:

    1. 使用分组函数时,一般需要先分组(group by)再使用

      分为一组就返回一个数据,两组返回两个,以此类推

      # 根据deptno分组,每一组统计工资最高的
      # 分为三组,返回三个数据
      select deptno,max(sal) from emp group by deptno;
      
    2. 特殊情况:不分组的时候使用分组函数,会默认把整张表当一组(可省略group by)

      此时只有一组,只会返回一个数据

      # 不加group by,查询表中最高工资
      # 一张表作为一组,只返回一个数据
      select max(sal) from emp;
      
    3. 分组函数支持组合使用

      使用分组函数后,每一组只有一个值,每个分组函数互不影响(一对一不影响,一对多不行)

      分组后使用count(字段)需要注意,遇到null不统计,需要coalesce改为非空再统计

      # 根据deptno分组后,统计工资最高,工资最低,平均工资,工资总和,总条数
      select deptno,max(sal),min(sal),avg(sal),sum(sal),count(sal) from emp group by deptno;
      
    4. 分组函数支持统计不同字段

      都是针对分组后的数据进行聚合,都是一组返回一个值,每个字段其实互不影响

      # 根据deptno分组后,统计平均薪资,最近入职日期
      select deptno,avg(sal),max(hiredate) from emp group by deptno;
      
    5. 分组字段支持多个

      简单理解为AB两个字段都相同时作为一组,联合分组

      实际场景中也会遇到,比如:查询每个xxx下的每个xxx

      # 查询每个部门下都有哪些职位
      select deptno,job from emp group by deptno,job;
      
    6. 分组字段与查询字段

      select 分组字段,分组函数(子查询)[可多个] from 表 group by 分组字段;

      分组后每一组只有一行数据,因此要求查询字段(select后面跟的字段)只能是单个值

      如果分组后返回一行数据,但是查某个字段时返回多行,mysql就会报错

      查询字段返回单个值的场景有:

      • 分组函数(最常见)

        # 单字段分组
        # 根据deptno分组后,统计平均薪资,最近入职日期
        # 分组函数会把每一组的数据都聚合成单个值,因此根据四个组输出四个不同的数据
        select deptno,avg(sal),max(hiredate) from emp group by deptno;
        
        # 联合分组
        # 根据部门分组,查询每个部门最近入职的人的时间,并且显示部门名字
        # 第一步先根据deptno连表查,第二步联合deptno和dname分组查最近入职时间
        select d.deptno,d.dname,max(e.hiredate) lastest_date from emp e join dept d on e.deptno=d.deptno group by d.deptno,d.dname;
        
      • 字面值

        # 根据deptno分组后,实际分为四组
        # 查询的字面值本身就是返回单个值,因此跟着四组输出四次共四行
        select deptno,'xdnhx' as '兄弟你好香' from emp group by deptno;
        
      • 子查询(返回单个值)

        # 查询每个职位最近入职日期的人的工资
        
        # job分组之后的值会传给子查询,有多少组子查询就执行几次
        # 子查询根据外部查询的值过滤条件(比如第一次过滤clerk,第二次是salesman)
        # 过滤后根据日期倒叙取第一个,即该职位最近入职时间,然后查它的工资
        select job,(select sal from emp b where b.job=a.job order by hiredate desc limit 1) max_date_sal from emp a group by job;
        

      分组字段(group by后面跟的字段)并不一定要写在查询字段那里

      但是分组后查询的字段一定要是单个值

      # 上面的字面值例子去掉查询deptno
      # 可以去掉查询deptno分组字段,但是还是四组
      # 字面值字段根据四组输出四次共四行
      select 'xdnhx' as '兄弟你好香' from emp group by deptno;
      
      # 一个表作为一组
      # 也可以不需要分组字段,但查询字段要单个值
      # 因为只有一组,所以就只有一行
      select count(*),max(hiredate),avg(sal),sum(comm),min(deptno) from emp;
      
    7. 分组函数自动忽略null,但不同情况需不同处理方式

      如果使用sum,avg,max,min等,碰到null不需要空函数处理,会降低执行效率

      如果使用count时,碰到null需要加空函数,否则该条记录被忽略不统计在内

      # 分组计算comm总工资
      select sum(comm),comm from emp group by comm;
      
      # 分组计算comm条数
      # 错误做法,因为统计到comm为null的字段条数显示0,因为null被忽略
      select count(comm),comm from emp group by comm;
      
      # 正确做法,改为非null状态,记录到空的条数为10条
      select count(coalesce(comm,0)) count,comm from emp group by comm;
      
    8. 分组函数不能用在where子句中

      从执行顺序讲,分组函数要在语句执行分组后才能使用(group by),而where的执行顺序在group by字段之前,所以where执行时还没分组,不能使用分组函数,强行使用会报错

      而select执行在group by后面,所以可以使用

      但实际上确实会有这种场景,处理方案是把分组函数放到子查询

      # 计算哪些人员的工资高于平均工资
      # 错误做法
      select ename,sal from emp where sal > avg(sal);
      # 正确做法
      select ename,sal from emp where sal > (select avg(sal) from emp);
      
    9. where和having的使用

      where和having都有过滤的功能,但where用于分组前过滤,而having用于分组后过滤

      having需要配合group by一起使用,不能单独使用

      having的查询效率比较低,确认所需字段必须使用分组后的数据,才用having

      如果所需字段在分组前就能过滤的,优先使用where

      # 统计部门中超过5人的部门有哪些
      # 题目中的5人是分组后的数据才过滤,使用having
      select deptno,count(*) from emp group by deptno having count(*) >= 5;
      
      # 统计哪些职位的最低工资大于1500,并且统计这些职位对应的人数
      # 这个题目并不能where过滤,题目要求职位每个人最低工资都要大于1500
      # 如果where筛选,假如A职位有两个人分别是1000和2000工资
      # 按题目是要求A职业不符合,但是会被where过滤出来,因此where不适用
      # 应该先按职业分组求每个组最小值
      select job,min(sal) min_sal from emp group by job;
      # 因为涉及到分组后的数据过滤,所以用having
      select job,min(sal) min_sal from emp group by job having min(sal)>1500;
      # 最后加上每个职位人数
      select job,min(sal) min_sal,count(*) nums from emp group by job having min(sal)>1500;
      
    10. 分组函数不支持嵌套使用,想实现该效果只能通过子查询(mysql不支持,oracle支持)

      某些场景需要用到嵌套分组函数,比如基于分组后的结果,需要再一次分组

      例如一共x个部门,算出了每个部门平均工资,但是还需要知道哪个部门平均工资最低

      # 计算平均工资最低的部门(展示部门+工资)
      
      # 第一步先得出每个部门的平均工资
      select avg(sal) avg_sal from emp group by deptno;
      
      # 第二步取平均工资最低
      select min(t.avg_sal) min_avg_sal from
      (select avg(sal) avg_sal from emp group by deptno) t;
      
      # 第三步获取工资最低的部门及工资
      select deptno,avg(sal) min_avg_sal from emp group by deptno 
      having avg(sal)=(select min(t.avg_sal) min_avg_sal from (select avg(sal) avg_sal from emp group by deptno) t);
      
    11. count(1),count(*),count(字段)三者区别与比较

      参考文章

      官方分组函数文档,查找count部分

      mysql官方文档中有解释到,存储引擎使用innodb的情况下

      count(*)和count(1)没有性能差异,因此不存在说count(1)比count(*)快的说法

      count(*)和count(1)都会计算包含null在内的行数

      而count(字段)会忽略null行,除非主键字段

      从效率上来说count(*)=count(1)>count(主键)>count(非主键列)

排序查询(order by)

  • 对结果集排序,不指定排序默认使用asc升序

  • asc(ascend)代表升序,desc(descend)代表降序

  • 排序支持使用字段或数字来指定列

  • 字符串排序默认使用一个个字母对比方式,如果想根据长度排序,可以使用length(字段)

  • 支持多个字段排序,使用英文逗号分割,第二个字段用于二次排序(最多排16个字段)

    当第一个字段的数据相同时,比较第二个字段,以此类推

    多个字段排序时,每个字段都要指定升降序,只写一个降序只对最后一个字段生效

    # emp表工资升序排序
    select * from emp order by sal;
    select * from emp order by sal asc;
    
    # emp表入职时间降序排序
    select * from emp order by hiredate desc;
    
    # 指定第2列降序排序
    select * from emp order by 2 desc;
    
    # 比较职位,职位相同情况下比较工资,工资相同按照入职时间,降序排列
    select * from emp order by job desc,sal desc,hiredate desc;
    
    # ename根据长度降序排序,长度相等情况下再用默认的一个个字母对比方式排序(升序)
    select * from emp order by length(ename) desc,ename;
    

分页查询(limit)

  • 基本格式 limit 下标,长度

  • 下标从0开始,代表第一条数据

  • 如果只有一个数字,代表取的长度,忽略下标。等同于 limit 0,长度

  • 如果限制条数大于实际有的条数,多的忽略不会报错

  • 通用公式:limit (page_num -1)*page_size,page_size

  • page_num代表第几页,page_size代表每页多少条

  • 按照公式,假如想知道第20页的数据(5条一页算),可以很快算出应该展示limit 95,5的数据

    # 查看第1-5条数据(第一页)
    # 两种写法结果一样,但一般从第一条开始拿数据都用第一种省略0下标方式
    select * from emp limit 5;
    select * from emp limit 0,5;
    
    # 查看第6-10条数据(第二页)
    select * from emp limit 5,5;
    
    # 查看11-15条数据(第三页)
    # 因为表实际只有14条,这里最多就显示4条,限制15不会报错
     select * from emp limit 10,5;
    

子查询

  • 子查询从本质上来说就是select语句嵌套select语句,可以多层嵌套

  • 子查询在实际场景的复杂查询中用的不少,需要看懂并理解

  • 子查询的结果可以返回一个值、一行记录、一列记录、一个表等

  • 子查询效率比表连接查询低,实际中优先连表查,不行再考虑子查询

  • 从出现的位置上来分类,常用的大概分为三种

    1. where后面出现子查询(出现最多)

      where接的子查询可以是一个值或者是一列记录(一组值)

      # emp表,统计哪些人高于平均工资
      # 第一步,计算平均工资,返回一个值
      select avg(sal) from emp;
      # 第二步,计算哪些人大于平均工资
      select * from emp where sal>(select avg(sal) from emp);
      
      # emp表,统计与职位为clerk相同领导的其他员工的姓名、职位、领导
      # 第一步,查找职位为clerk的领导列表,返回一列(多个值)
      select distinct mgr from emp where job='clerk';
      # 第二步,查找除职位clerk外,哪些职位的领导也在这些选项中
      select ename,job,mgr from emp where job!='clerk' and mgr in (select distinct mgr from emp where job='clerk');
      
    2. from后面出现子查询(join临时表)

      from接的子查询可以是一行记录或者是一个表

      其实返回一行和一个表,本质上都是当成临时表进行连接查询

      # 查找哪些员工的工资在1档
      # 第一步,找到1档的记录(返回一行记录)
      select * from salgrade where grade=1;
      
      # 第二步,与第一步的行记录结合筛选哪些人的工资是1档
      select e.ename,e.sal,t.grade from emp e join (select * from salgrade where grade=1) t on e.sal between t.losal and t.hisal;
      
      # 别的思路,先连表查,再筛选1档工资(不需要用到子查询)
      select e.ename,e.sal,s.grade from emp e join salgrade s on e.sal between s.losal and s.hisal where s.grade=1;
      
      # 展示部门编号、部门名字、每个部门员工最近的入职日期(涉及emp和dept表)
      # 第一步,查找每个部门最近的入职日期(返回一张临时表)
      select deptno,max(hiredate) from emp group by deptno;
      
      # 第二步,查找部门名字和编号,与第一步结合成一张表
      select d.deptno,d.dname,t.lastest_date, from dept d join (select deptno,max(hiredate) lastest_date from emp group
      by deptno) t on t.deptno=d.deptno;
      
      # 别的思路,先连表查再筛选最近的入职日期(不需要用到子查询)
      select d.deptno,d.dname,max(e.hiredate) lastest_date from emp e join dept d on e.deptno=d.deptno group by d.deptno,d.dname;
      
    3. select后面出现子查询

      select接的子查询只能是一个值(sql规范要求)

      如果返回多行多列,就会出现表格嵌套表格情况

      # 查找每个职位最近入职的人的工资,展示职位、入职时间、工资
      
      # job分组之后的值会传给子查询,有多少组子查询就执行几次
      # 子查询根据外部查询的值过滤条件(比如第一次过滤clerk,第二次是salesman)
      # 过滤后根据日期倒叙取第一个,即该职位最近入职时间,然后查它的工资
      select job,max(hiredate) lastest_date,
      (select sal from emp b where b.job=a.job order by hiredate limit 1) lastest_sal 
      from emp a group by job;
      

权限管理(dcl)

对数据库账户以及访问权限的控制,常见的有create、drop、alter、grant、revoke指令

查看用户

  • 用户存放在mysql库user表,直接查表即可

    user表中,host字段代表记录哪个主机或ip地址可以连接mysql

    如果为%代表该用户可以用任何主机连接,如果为localhost代表只能服务器本地连接

    # 查看当前用户
    select user();
    
    # 查看所有用户
    select user,host from mysql.user;
    

创建用户

  • 使用create user语句,不建议在mysql库user表直接插入记录,需要手动配置其他东西

  • 基本格式:create user 用户名[@主机名] identified by 密码;

  • 用户名可省略主机名,密码需要单引号括起来(纯数字也要)

  • 创建用户需要已登录用户拥有CREATE USER权限,否则报错

  • 新创建的用户只有usage权限,该权限仅能登录连接数据库,增删改查无权限

    # 不设置密码(默认空密码,直接回车登录即可)
    create user yilko;
    
    # 设置密码
    create user yilco identified by '123456';
    
    # 查看新用户权限(只有usage权限)
    show grants for yilko;
    

删除用户

  • 基本格式:drop user 用户名

  • 删除用户需要已登录用户拥有CREATE USER权限,否则报错

  • 如果删除的用户不存在会报错

    drop user yilko;
    

修改用户

  • 修改用户名

    基本格式:rename 旧用户名 to 新用户名

    rename user yilco to yilko;
    
  • 修改用户密码

    基本格式:

    第一种:set password for 用户名=‘密码’

    第二种:alter user 用户名 identified by ‘密码’

    哪种都可以看个人喜好,但我个人偏向第二种因为和创建用户很像,比较好记

    # set passowrd方式
    set password for yilko='qwe123';
    
    # alter方式
    alter user yilko identified by 'qwer1234';
    

    如果是给当前用户修改密码,上面命令还可以进一步简化

    set passwrd可以省略用户名

    但alter不可以省略,必须指定用户名,可以用user()代替当前用户名

    # set passwrd当前用户修改密码
    set password='112233';
    
    # alter方式当前用户修改密码
    alter user user() identified by 'qqwwee';
    

授权用户

  • 权限一般分为以下几类

    1. 系统权限:

      涉及到mysql服务器级别的操作,允许执行特定的管理功能

      但是因为也不是很常用,这里不列举,详细了解可查资料或问ai

    2. 数据库/表操作权限:

      库的操作例如create、alter、drop

      表的操作例如insert、delete、update、select

      索引的操作例如index,允许创建和删除索引

    3. 全局权限:all [privileges],授予除grant option之外所有简单权限

    4. 管理权限(grant option):与其他权限独立开,它可以赋予用户可以将权限授权给其他人。(类似于成员和管理员区别,被授予管理权限就是管理员,否则就是普通成员)

  • 授权

    基本格式:grant 权限列表 on 库名.表名 to 用户名

    如果不指定库名和表名使用*.*所有库所有表,但并不建议使用

    如果再配上all,会把默认核心数据库的权限也授予

    此时该用户就拥有高级系统管理权限,差不多有了等同root的权限

    防止用户权限过大,一般指定数据库授予权限(database.*),排除默认数据库的授权

    # 给yilko用户授予yilko库所有表的增删改查权限和索引权限
    grant insert,delete,update,select,index on yilko.* to yilko;
    
    # 给yilko用户授予yilko库emp表增删改表的权限
    grant create,alter,drop on yilko.emp to yilko;
    
    # 给yilko用户授予yilko库所有权限
    grant all on yilko.* to yilko;
    

    授权时是需要已知该用户权限的情况下(show grants for 用户名)进行调整的

    因为grants只会一直往上叠加权限,而不会出现权限覆盖的情况

    假设A用户A库已经有all权限,此时再给他授权select权限是无作用的

    因为本来all权限已经包含select权限,也不会把select权限覆盖all权限

    但如果真的有这个需求(从all权限改为只有select权限),应该是先撤回权限再授予select权限

    # 授予yilko库所有表select权限
    grant select on yilko.* to yilko;
    # 授予yilko库所有表update和delete权限,授权更新
    grant update,delete on yilko.* to yilko;
    # 授予yilko库所有表所有权限,授权更新
    grant all on yilko.* to yilko;
    # 授予yilko库的所有表insert权限,授权不更新,因为all已经包含select权限
    grant insert on yilko.* to yilko;
    

撤销授权用户

  • 基本格式:revoke 权限列表 on 库名.表名 from 用户名

  • 权限修改一般根据实际情况

    如果权限复杂就全部撤回,然后重新授权

    如果不复杂根据已有权限进行grant或者revoke

    # 撤回yilko库所有表的所有权限
    revoke all on yilko.* from yilko;
    
    # 撤回yilko库所有表的增删改权限,只留select权限(假设原本有增删改查四个权限)
    revoke insert,delete,update on yilko.* from yilko;
    

查看用户授予的权限

  • 查看单个用户权限

  • 基本格式:show grants for 用户名@主机

    # 不指定主机,默认所有主机都显示
    show grants for root;
    
    # 指定主机
    show grants for root@localhost;
    
    # 查看当前用户权限
    show grants;
    
  • 查看所有用户权限

    需要自己查看mysql库的user表(基本权限和全局权限)、db表(用户在某个库上的权限)、table_priv表(用户在某个表上的权限),如果想直观展示,需要这三个表进行连表查询。

刷新权限

  • 修改用户权限后,理论上mysql会更新权限设置

    但刷新权限可以确保权限缓存和权限实际设置保持同步

  • 如果不是通过命令修改权限而是直接在权限表改权限,该情况一定要刷新权限

  • 删除用户后再创建同名用户,也需要刷新权限确保权限被正确处理

    flush privileges;
    

事务(tcl)

常见的指令有commit、rollback、start transaction

事务的介绍

  • 定义:一个完整的业务逻辑,是最小的工作单元,不可再拆分,要么同时成功,要么同时失败
  • 本质:多条DML语句同时成功或者同时失败

事务的使用场景

  • 为什么需要事务:某些场景需要多条dml语句作为一个整体被执行,需要保证数据的一致性
  • 举例银行转钱。A给B转钱,从操作上看A执行update扣钱,B执行update加钱。这是一个整体的动作。如果某条dml不成功这个场景就视为失败,回滚重来。两个dml都成功,该场景才成功。
  • 涉及数据变化(dml的增删改)才会有事务的需求,事务就是为了保证数据的安全

事务的执行过程

  • innodb引擎提供一组用来记录事务性活动的日志文件,事务执行时,每一条DML语句都会记录到事务性活动的日志文件中
  • 提交事务:清空事务性活动的日志文件,将数据持久化到数据库表中。提交事务标志着事务以成功结束
  • 回滚事务:将之前DML操作全部撤销,清空事务性活动的日志文件,提交事务标志着事务以失败结束

事务的使用

  • mysql默认使用innodb引擎,该引擎支持事务

  • 默认情况下mysql支持自动提交事务,每执行一条dml语句就会提交一次事务,当需要事务是多条是需要用start transaction说明开启事务,此时会关闭自动提交事务

  • 事务格式:start transaction; 各种dml语句; commit/rollback;

    # 伪代码,实际没有表可操作
    start transaction;
    update accout set money=money-1000 where id='A';
    update accout set money=money+1000 where id='B';
    commit;
    

    事务使用起来很简单,多个dml语句用start transaction和commit包起来即可

    但实际上事务都是配合开发语言来使用

    当检查到两个语句都修改成功,不抛出异常,正常commit提交,事务执行成功

    当检查到两个语句任意一个不成功,抛出异常,捕捉异常处写rollback,事务执行失败回滚

    # 以pyhton举个例子
    # 连接数据库...
    try:
        # 开始事务
        cursor.execute("START TRANSACTION;")
        # 减少用户A的账户余额
        sql_A = "UPDATE accounts SET balance=balance-1000 WHERE user='A'"
        cursor.execute(sql_update_A)
        if cursor.rowcount == 0:  # 检查是否有行受到影响,可以根据业务逻辑调整
            raise ValueError("User A's balance update failed.")
        # 增加用户B的账户余额
        sql_B = "UPDATE accounts SET balance=balance+1000 WHERE user='B'"
        cursor.execute(sql_update_B)
        if cursor.rowcount == 0:
            raise ValueError("User B's balance update failed.")
        # 提交事务
        connection.commit()
        print("Transaction committed successfully.")
    except Error as e:
        # 如果有错误发生,回滚事务
        connection.rollback()
        print(f"Transaction failed. Error: {e}")
    finally:
        # 关闭游标和连接
        cursor.close()
    

事务的四个特性(ACID)

这四个特性是事务中的核心概念,确保了数据可靠性和一致性

  • 原子性(Atomicity):保证事务内的操作是不可分割的最小工作单元
  • 一致性(Consistency):事务执行前后,数据库的状态保持合法,确保数据的正确性有效性
  • 隔离性(Isolation):并发环境中,多个事务之间互不干扰。提供了四种隔离级别
  • 持久性(Durability):事务提交后永久性保存在数据库,系统故障也不会丢失

事务的四种隔离级别

  • 读未提交(Read Uncommitted):

    最宽松的隔离级别

    事务A可以读到事务B还未提交的数据,但会出现读到不对的数据,也叫脏读

  • 读已提交(Read Committed):

    oracle的默认级别

    事务A只能读取事务B已提交的数据,但会出现每次读的数据都不同,也叫不可重复读

  • 可重复读(Repeatable Read):

    mysql的默认级别

    事务A只能读取事务B已提交的数据并且多次读取数据的结果一致

    但事务B的操作改了,A还是读B旧的数据,会出现幻读

  • 序列化(Serializable):

    最高的隔离级别,可以处理脏读、不可重复读、幻读的问题。但并发的性能最低

    事务只能排队进行(同步),不能并发,只能执行完事务A,才能再进行事务B

    # 查看mysql的隔离级别
    select @@global.transaction_isolation;
    
    # 设置mysql的隔离级别
    set global transaction isolation level 隔离英文;
    

约束

约束一般在创建表定义字段的时候已经写好。但也支持创建表后对约束添加或修改。创建表后再改约束已经会有数据,可能会导致添加约束失败,一般需要先处理数据。最好先备份数据再操作

常见的八种约束

  • 八种约束在ddl的创建表章节中有做解释说明,这里不再详细叙述
  • 主键约束(primary key)、非空约束(not null)、唯一约束(unique),外键约束(foreign key)
  • 自增长约束(auto_increment),默认约束(default),无符号约束(unsigned),检查约束(check)
  • 检查约束需要mysql从8.0版本才开始支持

列级约束和表级约束

  • 在列属性后面直接加的为列级约束,不在列属性加而是另起一行的为表级约束

  • 列级约束常见于主键、非空、唯一、自增长、默认、无符号约束

  • 表级约束常见于外键、检查、联合约束(联合主键或联合唯一)

    create table teacher(
      # 下面四个列属性都跟了约束,为列级约束
      id int auto_increment primary key comment '教师id',
      name varchar(20) not null comment '教师名字',
      id_card char(18) unique comment '身份证号码',
      class_id int comment '班级id',
      sex tinyint UNSIGNED default 0 COMMENT '用户性别 0未知 1男 2女',  
      # 不跟列属性写一起的就是表级约束
      # 其中外键的class表没有创建,只是示例
      foreign key (class_id) references class(id),
      check (length(id_card)=18)
    ) COMMENT '教师信息表';
    

创建表时定义约束

  • 上面已有例子说明,这里不再重复叙述

创建表后添加约束

  • 测试表准备

    # 创建一个表用来增删改约束,下面的增删改约束都用到这个表
    create table restraint(
      id int,name varchar(20),sex tinyint,age tinyint,id_card char(18),frg_id int
    ) comment '约束测试表';
    
    # 演示添加外键的表
    create table restraint_frg(id int primary key) comment '外键约束测试表';
    
  • 添加主键约束

    alter table 表名 add primary key(字段);
    alter table restraint add primary key(id);
    
  • 添加非空约束

    # 其实就是把允许nll改为不允许null,该命令与修改字段属性一样,要把原有属性都写上
    alter table 表名 modify 字段 原字段属性 [not] null;
    alter table restraint modify name varchar(20) not null;
    
  • 添加唯一约束

    alter table 表名 add unique(字段);
    alter table restraint add unique(id_card);
    
  • 添加外键约束

    alter table 表名 add foreign key(字段) references 外键表(外键表字段);
    alter table restraint add foreign key(frg_id) references restraint_frg(id);
    
  • 添加自增长约束

    不支持添加自增长,需要在创建表时定义好

  • 添加默认约束

    alter table 表名 alter 字段 default 默认值;
    alter table restraint alter sex set default 0;
    
  • 添加无符号约束

    # 与添加非空约束一样,都是与修改字段属性一样,要把原有属性都写上
    alter table 表名 modify 字段 原字段属性 unsigned;
    alter table restraint modify id int unsigned;
    
  • 添加检查约束

    # 约束名是可以随意起,但最好见名知义,条件就是一个返回布尔值的表达式
    alter table 表名 add constraint 约束名 check(条件);
    alter table restraint add constraint check_id_card check(length(id_card)=18);
    

创建表后删除约束

  • 删除主键约束

    alter table 表名 drop primary key;
    alter table restraint drop primary key;
    
  • 删除非空约束

    # 其实就是把不允许nll改为允许null,该命令与修改字段属性一样,要把原有属性都写上
    alter table 表名 modify 字段 原字段属性 null;
    alter table restraint modify name varchar(20) null;
    
  • 删除唯一约束

    # 约束名通过show create table来获取
    alter table 表名 drop constraint 约束名;
    show create table restraint;
    alter table restraint drop constraint id_card;
    
  • 删除外键约束

    # 外键名通过show create table来获取
    alter table 表名 drop foreign key 外键名;
    show create table restraint;
    alter table restraint drop foreign key `restraint_ibfk_1`;
    
    # 删除外键后,还需要删除字段的索引
    alter table 表名 drop index 字段
    alter table restraint drop index fgn_id;
    
  • 删除自增长约束

    # 该命令与修改字段属性一样,要把原有属性都写上,写少自增即可
    alter table 表名 modify 字段 原字段属性(少自增长);
    # 假设id自增长,现在改没
    alter table restraint modify id int unsigned;
    
  • 删除默认约束

    # 其实就是把有默认值改为默认null,该命令与修改字段属性一样,要把原有属性都写上
    alter table 表名 modify 字段 原字段属性 default null;
    alter table restraint modify sex tinyint default null;
    
  • 删除无符号约束

    # 该命令与修改字段属性一样,要把原有属性都写上,写少无符号即可
    alter table 表名 modify 字段 原字段属性(少无符号);
    alter table restraint modify id int;
    
  • 删除检查约束

    # 约束名通过show create table来获取
    alter table 表名 drop check 约束名;
    show create table restraint;
    alter table restraint drop check `check_id_card`;
    

创建表后修改约束

  • 通常是删除约束再添加约束来实现修改效果

创建表后查看约束

# 其实就是查看表结构,以下两种方式都可以
# 但如果要看检查约束使用show create table,desc无法看到
desc 表名
show create table 表名

约束补充说明

  1. 联合约束

    • 联合主键约束

      两个或多列组合起来作为主键,两个列值完全一样才是主键,单独一列可重复可为null

      # 创建时添加联合主键约束
      create table union_test(
        id int,name varchar(20),primary key(id,name)
      );
      
      # 添加和删除联合主键约束与单个主键约束一样,只是字段多了
      alter table 表名 add primary key(字段1,字段2...);
      alter table 表名 drop primary key;
      
    • 联合唯一约束

      两个或多列组合起来作为唯一,两个列值完全一样才是唯一,单独一列可重复

      # 创建时添加联合唯一约束
      create table union_test1(
        name varchar(20),email varchar(255),unique(name,email)
      );
      
      # 添加和产出联合唯一约束与单个唯一约束一样,只是字段多了
      alter table 表名 add unique(字段1,字段2...);
      alter table 表名 drop constraint 约束名;
      
  2. 外键约束

    • 外键指子表中的字段引用父表中的主键字段
    • 成立条件:子表不是主键字段,父表要主键字段,这两个字段的数据类型要一致
    • 有外键约束时,父子表的创建、插入和删除都有顺序要求。创建和插入都是先父后子,删除是先子后父
    • 外键在开发中很少使用
  3. 每一张表都应该有主键,没有主键,表无效

  4. 一张表中主键约束只能有一个,大于一个会报错

  5. mysql中某个字段同时添加not null和unique约束会自动变为主键字段(oracle不会)

  6. 主键建议使用int,bigint,char类型,主键值一般是数字、定长的,不建议用varchar类型

  7. 不建议使用复合主键,建议使用单一主键,主键意义在于唯一性,联合起来就不是唯一了

索引

索引介绍

索引是数据库中用于提高数据检索速度的数据结构,通过创建指向表中数据的指针或引用,可以更快地定位到所需的数据行,从而大幅提高查询效率。可以简单理解为一本书的目录

索引分类

  1. 按照字段特性分类,可以分为四类索引
    • 主键索引:设置为主键的字段默认添加索引
    • 唯一索引:设置为unique的字段默认添加索引
    • 普通索引:没有唯一性的要求,是最基本的索引类型,用于快速定位特定数据
    • 全文索引:用于全文搜索,针对大文本字段的模糊匹配
  2. 按照字段个数分类,可以分为两类索引
    • 单列索引:索引只包含一个字段
    • 复合索引:一个索引中包含多个字段

索引添加时机

  1. 数据量很大的字段
  2. 属于高频查询的字段,经常在select语句或where子句出现
  3. 属于高频连表查询的字段,经常在连表时用该字段连接
  4. 频繁更新(增删改)的字段,不建议添加索引(字段修改后索引需要重新排序)
  5. 数据量少的字段,不建议添加索引,全表扫描会比索引扫描还快
  6. 不建议随意添加索引(不是越多越好),索引本身也需要维护,添加太多会影响mysql的性能

索引使用

  • 创建索引

    • 创建表时添加索引

      基本格式:index 索引名(字段)

      create table index_test(
          id int primary key,
          name varchar(20),
          index name_index(name)
      ) comment '索引测试表';
      
    • 创建表后添加索引

      create index 索引名 on 表名(字段);
      
      # 给emp表的员工名字添加索引
       create index name_index on emp(ename);
       
      # 给emp表的员工名字和部门编号添加复合索引
      create index composite_index on emp(ename,deptno);
      
  • 删除索引

    # 索引名可以查看索引来获取
    drop index 索引名 on 表名;
    drop index name_index on emp;
    
  • 查看索引

    1. 查看表中哪个字段添加了索引

      show indexes from 表名;
      show indexes from emp;
      
    2. 查看sql语句是否有使用索引

      当key字段显示为null时说明没有使用索引

      当type字段显示为all代表全表扫描,没有使用索引

      除了all以外其他的如index、range、ref、const、system都有利用索引优化查询效率

      explain select语句
      explain select empno from emp;
      explain select * from emp;
      

索引失效

以下举例的几种场景都不是必然导致索引失效,只是有可能失效,具体还得explain分析

  1. 索引字段参与计算(加减乘除取余)

    # 已经给emp表sal添加索引
    # key返回null,没有使用索引
    explain select ename from emp where sal*12 > 10000;
    
  2. 索引字段使用了函数

    # 已经emp表给hiredate字段添加索引
    # key返回null,没有使用索引
    explain select * from emp where year(hiredate) > 1987;
    
  3. 索引字段使用like模糊查询并且以%开头

    # 已经给emp表name字段添加索引
    # key返回null,没有使用索引
    explain select * from emp where ename like '%a';
    
  4. 索引字段使用or连接

    当or连接左右两边的字段都有添加索引,才会走索引

    如果任意一边没有索引,都会出现索引失效

    # mgr没有添加索引,sal已添加索引
    # key返回null,没有使用索引
    explain select * from emp where mgr > 7800 or sal > 2000;
    
  5. 索引字段为复合索引

    多个字段联合添加一个索引,只有最左字段才会走索引,其他字段不走索引

存储过程

什么是存储过程

  • 一组预编译的sql语句集合,可以实现复杂的业务逻辑和代码复用
  • 可以简单理解为业务需求从编程语言上实现转移到以sql语句实现

存储过程的作用

  • 提高性能:存储过程只编译一次,之后调用不用再编译,比每次执行sql语句效率高
  • 减少网络流量:sql语句存在数据库服务器,客户端只需发请求不用发sql语句,减少传输量
  • 代码重用:可以被多个服务共享使用,减少重复编码
  • 处理复杂逻辑:支持流程控制语句、循环语句

存储过程的缺点

  • 业务需求使用编程语言实现要比用存储过程简单
  • 复杂的业务逻辑需要深厚的sql知识和存储过程语法(下面例子没有讲语法)
  • 存储过程调试困难,没有图形化工具调试,通常需要依赖日志
  • 需要额外时间学习,相当于又多学了一门编程语言

存储过程基本格式

delimiter //
create procedure 函数名([in|out|inout] 参数名 参数类型(长度))
begin
... # sql语句和逻辑
end //
delimiter ;
  • delimiter把语句结束标志从;改为//,存储过程内部会有多个sql语句,都会带有;

    改变结束标志才能使整个存储过程执行,否则遇到第一个;就会执行

    存储过程执行完后需要把结束标志改为;使其他语句正常执行(遇到;作为结束标志)

  • in代表入参,out代表输出,inout代表既是入参也是输出

  • 起名的参数名不能和sql的字段名字一致,会出问题

存储过程的使用

  • 创建存储过程

    # emp表查询工资大于2000的人名字、部门名字、工资
    delimiter //
    create procedure s_dept_name(in gt_sal int,out ename varchar(255),out dname varchar(255))
    begin
    select e.ename,d.dname from emp e join dept d on e.deptno=d.deptno where sal > gt_sal limit 1;
    end //
    delimiter ;
    
  • 修改存储过程

    mysql8.x开始才支持修改存储过程,其他版本需要先删除存储过程再创建

    基本格式:create or replace procedure 存储过程名字(参数1,参数2) begin … end

    可以改变参数输入输出的类型、个数

    也可以改变存储过程的内容

    delimiter //
    create or replace procedure s_dept_name(in gt_sal int)
    begin
    select e.ename,d.dname from emp e join dept d on e.deptno=d.deptno where sal > gt_sal;
    end //
    delimiter ;
    
  • 查询存储过程

    1. 两次show获取存储过程的内容

      # 获取指定库的存储过程(不指定库会有很多条),可以获取存储过程的名字
      show procedure status where db='yilko';
      # 通过存储过程名字获取它的内容
      show create procedure s_dept_name;
      
    2. 查询information_schema库的routines表

      routine_schema代表数据库名字、routine_name代表存储过程名字

      routine_definition代表存储过程的内容

      若不知道存储过程名字,可以通过查库再查名字,可以得到内容

      两种方式第一种可能简单一些

      select routine_definition from information_schema.routines where routine_name='s_dept_name';
      
  • 调用存储过程

    基本格式:call 存储过程名字(参数)

    类似python函数调用即可

    call s_dept_name(2000);
    
  • 删除存储过程

    基本格式:drop procedure if exists 存储过程名字;

    drop procedure [if exists] s_dept_name;
    

视图

什么是视图

  • 视图是一个虚拟表,不直接存储数据
  • 视图基于一个或多个表(其他视图)构建
  • 视图中的行和列实际上是来自表的查询结果

视图的作用

  • 简化sql查询,某个sql语句经常被查询但很复杂,视图可以达到简化查询的作用
  • 隐藏表中的部分列数据,限制敏感数据访问,仅暴露所需信息,提高数据安全性
  • 可以复用相同的查询逻辑,减少代码冗余

视图的缺点

  • 视图上不能直接创建索引
  • 复杂的视图查询影响性能

视图的使用

  • 创建视图

    基本格式:create view 视图名 as dql语句;

    # 三表查询,输出员工编号、名字、工资水平、部门名字
    create view name_dept_sal as
    (select e.empno,e.ename,s.grade,d.dname from emp e 
    join dept d on e.deptno=d.deptno
    join salgrade s on e.sal between s.losal and s.hisal
    order by grade desc,dname,ename);
    
  • 修改视图

    • 修改视图的dql语句

      基本格式:alter view 视图名 as dql语句

      # 多加一列薪资
      alter view name_dept_sal as
      (select e.empno,e.ename,e.sal,s.grade,d.dname from emp e 
      join dept d on e.deptno=d.deptno
      join salgrade s on e.sal between s.losal and s.hisal
      order by grade desc,dname,ename);
      
    • 修改视图dql语句涉及表的数据

      视图内修改数据会影响到原表的内容,修改前最好先了解视图的dql语句并且做好备份

      视图更多的是体现查询的结果,如果要改表的数据,最好是直接操作表而不是视图

      视图修改数据与普通表修改一致,都是用insert、update、delete实现

  • 查询视图

    1. 查看视图的dql语句

      show create view 视图名

      show create view name_dept_sal;
      
    2. 查看视图列信息

      desc 视图名

      desc name_dept_sal;
      
    3. 查询视图数据

      把视图当成一个表来查询即可,select * from 视图名

      select * from name_dept_sal;
      
    4. 查找已创建视图名

      出现忘记视图名的情况,通过information_schema库的views表查找,需要知道数据库名字

      table_name=视图名,view_definition=视图的dql语句,table_schema=数据库

      select table_name from information_schema.views where table_schema='yilko';
      
  • 删除视图

    基本格式:drop view 视图名;

    drop view name_dept_sal;
    

引擎

引擎介绍

  • 存储引擎是mysql表存储数据的一种方式,是数据库中的关键部分
  • mysql默认引擎为InnoDB

常见的引擎

  • 常见的有三种引擎
  • myisam:不支持事务,支持全文索引,但读取速度快,占用资源少
  • InnoDB:重量级存储引擎,支持事务,支持数据库崩溃后自动恢复机制,不支持全文索引
  • memory:数据存储在内存中,查询极快,但数据非持久化,服务器重启后数据丢失

引擎使用

  • 使用InnoDB引擎

    create table engine_test1(id int primary key);
    create table engine_test2(id int primary key) engine=InnoDb;
    
  • 使用memory引擎

    create table engine_test3(id int primary key) engine=memory;
    
  • 使用myisam引擎

    create table engine_test4(id int primary key) engine=myisam;
    
  • 查看当前数据库支持的引擎(有九种引擎)

    show engines;
    
  • 查看当前表使用的引擎

    show create table 表名;
    

sql注入

什么是sql注入

输入内容时故意输入sql代码,篡改或查询数据库的数据,从而达到攻击的目的

sql注入怎么产生

  • 在代码层面,sql通过命令和变量拼接方式组合
# 以emp表举例,查询emp表中deptno为10且名字为king的人
# 以python的pymysql为例子(代码不完整,sql连接和查询语句部分没有放出来)
mu = MysqlUtil("localhost", "root", "***", 3306, database="yilko");
deptno = "10 or 1=1";
s_sql = f"select * from emp where deptno={deptno} and ename='king'";
print(mu.dql(s_sql));
  • 正常应该输出部门编号为10且名字为king的人
  • 而现在因为注入绝对成立的sql代码,只会执行前面的代码,后面的被忽略
  • 整条sql语句就等同于select ename from emp where deptno=10;

如何防止sql注入

  • 不要使用命令和变量拼接方式组合
  • 使用%s作为占位符,将参数以元组形式传入
deptno = "10 or 1=1";
s_sql = "select ename from emp where deptno=%s and ename='king'";
print(mu.dql(s_sql, (deptno)));

为什么占位符可以防止注入

  • 占位符会对特殊字符进行转义,确保在sql上失去特殊意义
  • 例如单引号或sql有特殊意义的单词,都会被转义从而失去注入能力

设计三范式

数据库表设计的依据,可以避免表中数据冗余,避免空间的浪费

第一范式

任何一张表中必须要有主键,每一个字段都是原子性不可再分

举例:一般是主键都是id,字段如联系方式(邮箱+手机),联系方式字段可以再次拆分邮箱字段和手机字段

第二范式

建立第一范式基础上,所有非主键字段完全依赖主键,不要产生部分依赖

部分依赖的定义:复合主键(A+B),某个字段依赖A,某个字段依赖B,称为部分依赖

第三范式

第三范式:建立第二范式基础上,所有非主键字段直接依赖主键,不要产生传递依赖

传递依赖定义:某字段A依赖某字段B,某字段B又依赖主键

举例:如学生教师都写到一张表,学生学号是主键,但是班级编号和班级名称是依赖关系然后又依赖学生学号

一对一设计

  • 一对一,加外键再加唯一性约束
  • 正常来说一张表就搞定,但是如果一张表中的字段太多,可以根据实际情况对字段进行分类,拆为两张表

一对多设计

  • 一对多,两张表,多的表加外键
  • 一对多的关系时使用两张表来设计,1、学生表(加班级编号做外键) 2、班级表

多对多设计

  • 多对多,三张表,关系表两个外键
  • 多对多的关系时使用三张表来设计,1、学生表 2、教师表 3、学生教师关系表(对应学生和老师的外键)

知识点和注意事项

  1. 字符串使用单引号’',双引号""在mysql中可用,但oracle不支持

  2. 字符串比较大小:如果字符串里全部是数字,则直接比较数字里面的内容。如果是数字字母混合,则先判断字符串总长度,如果长度相等则比较同位置字符,按照ascii码排序

  3. 表字段的数据类型长度需要注意:

    对于可变长度类型来说,如varchar(66),假如说插入的记录长度超过66也不会报错,因为实际长度是255,只要小于这个数都没问题

    对于固定长度来说,如char(2),假如说插入超过两位数就会报错,因为固定长度2位就不可变

    但是声明为2不是固定2位数,而是最大两位数,也就是说可以存1位数或2位数

    声明的类型针对可变长度字符串或者数值类型都是无作用的,因为实际是根据定义的类型最大范围来限制的

    但是varchar不能不限制长度,如果只写varchar但不写长度,创建表时会报错

  4. 数据类型章节中涉及到字节和字符的概念,不同的编码方式对应的不同

    ASCII码:1个字符=1个字节,其中包含(大小写英文,0-9数字,英文特殊符号)

    utf-8:ASCII码部分为1字符=1字节,中文大多数1字符=2或3字节,极少数占用4字节

    gbk:ASCII码部分为1字符=1字节,中文1字符=2字节(固定2字节,不存在大于2的情况)

  5. tinytext和varchar选择

    有索引的varchar字段通常提供更好的查询性能。

    tinytext更适合存储那些长度不确定但总体较短,且不需要频繁查询或索引的文本内容

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值