01-MySql的前戏
MySql的前戏
在学习Mysql之前,我们先来想一下一开始做的登录注册案例,当时我们把用户的信息保存到一个文件中:
#用户名 |密码
root|123321 alex|123123
上面文件内容的规则是我自己定义的,你要想用我这个程序,必须按照我的规则去执行,但凡不是这个规则,就没有交流的余地。
在一开始的时候文件格式的规定是没有规范的,后面学到模块的时候逐渐知道了目录规则,我们会把文件放到db目录下。
类似下面目录结构:
对于上面db目录中,是可以存放多个文件的,并且文件中可以有多行数据。
那么问大家一个问题,如果说上面那个软件是我写好的,在一台服务器或者一台机器上安装了我写好的软件。
现在有张三和李四两个人都安装了我这个软件,张三注册了一个用户,李四也注册了一个用户,那么自己注册的用户应该是自己用的。那么这两个用户是不共享的,
所以这个软件证明我写的不太好,它不能共享,这种类似单机的软件是不好的。
然后呢,我现在将我写的这个软件中目录修改一下,将db目录移除。现在将单机程序(自己的DB)改变成也是单机程序(公用DB)。
那么之前给大家介绍过,凡是交互操作,是离不开scoket的,那么现在我这边有个公用DB和一个scoket服务端,然后在张三和李四那边有scoket客户端。张三和李四此时输入用户名和密码,现在我这边scoket服务端某个目录接收用户输入的用户名和密码,看一下该用户名存在不存在,如果存在返回true,如果不存在返回false。也就是说重点的内容我们把db目录放到某台服务器上。
那么上面这个例子呢,其实就给大家慢慢的引出来mysql了,mysql呢其实就一个软件,这个软件安装到某台电脑上或者某台服务器上,那么现在只要我告诉它创建一个文件目录或者创建文件,那么mysql就自动帮我们创建了,比如说我们再在文件中添加一条数据,那么它也帮我们完成。
总结一句话:其实mysql就是用于管理我们的文件的一个软件。
那么对于mysql这个软件来说有两个软件
---服务器软件 - socket服务端 - 本地文件操作
- 解析指令(mysql语句) ---客户端软件 - socket客户端 - 发送指令
- 解析指令(mysql语句)
解释:
对于服务器软件中的socket服务器是一直开着,客户端得需要连接,并且还有创建文件、删除文件等等的操作
对于客户端软件中的scoket客户端,我们得需要发送指令去命令scoket服务端对文件进行操作。
大家应该知道ftp的项目,上传项目和下载项目的命令是不一样的。那么对于mysql中的客户端和服务端也会有些指令的操作,那么在这两个端中应该是有解析指令的过程,这个指令只有mysql这个软件知道,这种指令就是mysql语句。
再想想我们写的html、css、js.这些好比就是我们客户端写好的语法,然后浏览器充当了服务端的角色去解析我们的的语法,最后来渲染出来结果。
接下来我们要学习这些技能:
- 安装 服务端和客户端 - 连接 - 学习SOL语句规则:命令服务端做任何的操作
02-数据库概述
什么是数据(Data)
描述事物的符号记录称为数据,描述事物的符号既可以是数字,也可以是文字、图片,图像、声音、语言等,数据由多种表现形式,它们都可以经过数字化后存入计算机 在计算机中描述一个事物,就需要抽取这一事物的典型特征,组成一条记录,就相当于文件里的一行内容,如:
1 xiaomage,male,18,1999,山东,计算机系,2017,oldboy
单纯的一条记录并没有任何意义,如果我们按逗号作为分隔,依次定义各个字段的意思,相当于定义表的标题
这样通过表格我们很清楚的知道了某人的详细信息。
什么是数据库(DataBase,简称DB)
数据库即存放数据的仓库,只不过这个仓库是在计算机存储设备上,而且数据是按一定的格式存放的
过去人们将数据存放在文件柜里,现在数据量庞大,已经不再适用
数据库是长期存放在计算机内、有组织、可共享的数据即可。
数据库中的数据按一定的数据模型组织、描述和储存,具有较小的冗余度、较高的数据独立性和易扩展性,并可为各种 用户共享
什么是数据库管理系统(DataBase Management System)
在了解了Data与DB的概念后,如何科学地组织和存储数据,如何高效获取和维护数据成了关键
这就用到了一个系统软件---数据库管理系统
如MySQL、Oracle、SQLite、Access、MS SQL Server
mysql主要用于大型门户,例如搜狗、新浪等,它主要的优势就是开放源代码,因为开放源代码这个数据库是免费的,他现在是甲骨文公司的产品。
oracle主要用于银行、铁路、飞机场等。该数据库功能强大,软件费用高。也是甲骨文公司的产品。
sql server是微软公司的产品,主要应用于大中型企业,如联想、方正等。
数据库服务器、数据管理系统、数据库、表与记录的关系(重点理解!!!)
记录:1 xxx 324245234 22(多个字段的信息组成一条记录,即文件中的一行内容)
表:student,scholl,class_list(即文件)
数据库:oldboy_stu(即文件夹)
数据库管理系统:如mysql(是一个软件)
数据库服务器:一台计算机(对内存要求比较高)
总结:
数据库服务器-:运行数据库管理软件
数据库管理软件:管理-数据库
数据库:即文件夹,用来组织文件/表
表:即文件,用来存放多行内容/多条记录
03-MySql安装和基本管理
本节掌握内容:
-
mysql的安装、启动
-
mysql破解密码
-
统一字符编码
MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下公司。MySQL 最流行的关系型数据库管理系统,在 WEB 应用方面MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。
mysql是什么?
mysql就是一个基于socket编写的C/S架构的软件
数据库管理软件分类
分两大类: 关系型:如sqllite,db2,oracle,access,sql server,MySQL,注意:sql语句通用 非关系型:mongodb,redis,memcache 可以简单的理解为: 关系型数据库需要有表结构 非关系型数据库是key-value存储的,没有表结构
mysql的下载安装
想要使用MySQL来存储并保存数据,则需要做几件事情:
a. 安装MySQL服务端
b. 安装MySQL客户端
b. 【客户端】连接【服务端】
c. 【客户端】发送命令给【服务端MySQL】服务的接受命令并执行相应操作(增删改查等)
下载地址:
window版本
1、官网去下载
2、针对操作系统的不同下载不同的版本
3.解压
如果想要让MySQL安装在指定目录,那么就将解压后的文件夹移动到指定目录,如:C:mysql-5.6.40-winx64
4.添加环境变量
【右键计算机】--》【属性】--》【高级系统设置】--》【高级】--》【环境变量】--》【在第二个内容框中找到 变量名为Path 的一行,双击】 --> 【将MySQL的bin目录路径追加到变值值中,用 ; 分割】
5.初始化
1
|
mysqld - - initialize - insecure |
6.启动mysql服务
mysqld #启动MySQL服务
7.启动mysql客户端并连接mysql服务端(新开一个cmd窗口)
mysql -u root -p # 连接MySQL服务器
上一步解决了一些问题,但不够彻底,因为在执行【mysqd】启动MySQL服务器时,当前终端会被hang住,那么做一下设置即可解决此问题,即将MySQL服务制作成windows服务
注意:--install前,必须用mysql启动命令的绝对路径 # 制作MySQL的Windows服务,在终端执行此命令: "c:mysql-5.6.40-winx64inmysqld" --install # 移除MySQL的Windows服务,在终端执行此命令: "c:mysql-5.7.16-winx64inmysqld" --remove 注册成服务之后,以后再启动和关闭MySQL服务时,仅需执行如下命令: # 启动MySQL服务 net start mysql # 关闭MySQL服务 net stop mysql
windows下登录设置密码
打开终端,输入mysql
输入mysql提供的函数:select user(); # 查看当前登录的账号
当前登录的默认账号为ODBC@localhost
如果想切到root账号登录
执行命令:
mysql -uroot -p
再查看当前用户:
select user();
管理员为root(拥有最高权限,管理员账号),密码为空,以无密码的方式登录了管理员账号,是非常危险的一件事情,所以要为管理员账号设置密码
设置管理员root账号密码为123
C:Usersmjj>mysqladmin -uroot -p password "123" #设置初始密码 由于原密码为空,因此-p可以不用
ps:⚠️不用管它。翻译为:在命令行界面使用密码不安全,暴露在终端当中。
然后再登录账号,不输入密码则会出现如下结果:
再次执行如下操作:
再查看一下当前登录的账号:
如果想将原始密码123,设置新密码为456
C:Usersmjj>mysqladmin -uroot -p"123" password "456" #修改mysql密码,因为已经有密码了,所以必须输入原密码才能设置新密码
依次执行如下操作:
mysql -uroot -p456 select user();
密码忘记——破解密码
跳过授权方式,直接登录!!
0.以管理员身份打开cmd
2.停掉mysql服务端
C:WINDOWSsystem32>net stop mysql MySQL 服务正在停止. MySQL 服务已成功停止。
3.执行如下命令跳过授权表
#跳过授权表 C:WINDOWSsystem32>mysqld --skip-grant-tables 2018-06-09 17:12:38 0 [Warning] Insecure configuration for --secure-file-priv: Current value does not restrict location of generated files. Consider setting it to a valid, non-empty path. 2018-06-09 17:12:38 0 [Note] mysqld (mysqld 5.6.40) starting as process 6052 ...4.
4.再次查看
5. 现在可以任意的更改密码,执行如下命令
6.刷新权限,执行命令
flush privileges;
7.退出mysql。执行命令:exit,
8.让用户去加载权限,以管理员身份进入cmd,查看当前mysql进程
1
|
tasklist |findstr mysql #查看当前mysql的进程 |
9.杀死当前的进程,执行如下命令
taskkill /F /PID 6052 # 杀死当前的进程pid
10.再次执行如下操作,还原
统一字符编码
进入mysql客户端,执行s
为了统一字符编码,请执行如下操作:
(1)my.ini文件是mysql的配置文件,
在C:mysql-5.6.40-winx64文件下创建my.ini文件
(2)将如下代码拷贝保存。
#mysql5.5以上:修改方式为 [mysqld] character-set-server=utf8 collation-server=utf8_general_ci [client] default-character-set=utf8 [mysql] default-character-set=utf8
(3)以管理员身份重启服务,执行如下命令
C:Windowssystem32>net stop MySQL MySQL 服务正在停止.. MySQL 服务已成功停止。 C:Windowssystem32>net start MySQL MySQL 服务正在启动 . MySQL 服务已经启动成功。
(4)在cmd中输入mysql进入mysql环境,执行s,显示如下信息,表示成功
04-初始mysql语句
本节课先对mysql的基本语法初体验。
操作文件夹(库)
增
create database db1 charset utf8;
查
# 查看当前创建的数据库 show create database db1; # 查看所有的数据库 show databases;
改
alter database db1 charset gbk;
删
drop database db1;
操作文件(表)
use db1; #切换文件夹 select database(); #查看当前所在文件夹
增
create table t1(id int,name char);
查
#查看当前的这张t1表 show create table t1; # 查看所有的表 show tables; # 查看表的详细信息 desc t1;
改
# modify修改的意思 alter table t1 modify name char(6); # 改变name为大写的NAME alter table t1 change name NAMA char(7);
删
# 删除表 drop table t1;
操作文件内容(记录)
增
# 插入一条数据,规定id,name数据leilei insert t1(id,name) values(1,"mjj01"),(2,"mjj02"),(3,"mjj03");
查
select id from db1.t1; select id,name from db1.t1; select * from db1.t1;
改
update db1.t1 set name='zhangsan'; update db1.t1 set name='alex' where id=2;
删
delete from t1; delete from t1 where id=2;
05-库的操作
本节重点:
- 掌握库的增删改查
一、系统数据库
执行如下命令,查看系统库
show databases;
nformation_schema: 虚拟库,不占用磁盘空间,存储的是数据库启动后的一些参数,如用户表信息、列信息、权限信息、字符信息等
performance_schema: MySQL 5.5开始新增一个数据库:主要用于收集数据库服务器性能参数,记录处理查询请求时发生的各种事件、锁等现象
mysql: 授权库,主要存储系统用户的权限信息
test: MySQL数据库系统自动创建的测试数据库
二、创建数据库
1、求救语法:
help create database;
2、创建数据库语法
CREATE DATABASE 数据库名 charset utf8;
3、数据库命名规则:
可以由字母、数字、下划线、@、#、$ 区分大小写 唯一性 不能使用关键字如 create select 不能单独使用数字 最长128位 # 基本上跟python或者js的命名规则一样
三、数据库相关操作
#查看数据库 show databases; #查看当前库 show create database db1; #查看所在的库 select database(); #选择数据库 use 数据库名 #删除数据库 DROP DATABASE 数据库名; # 修改数据库 alter database db1 charset utf8;
四、了解内容
SQL语言主要用于存取数据、查询数据、更新数据和管理关系数据库系统,SQL语言由IBM开发。SQL语言分为3种类型: 1、DDL语句 数据库定义语言: 数据库、表、视图、索引、存储过程,例如CREATE DROP ALTER 2、DML语句 数据库操纵语言: 插入数据INSERT、删除数据DELETE、更新数据UPDATE、查询数据SELECT 3、DCL语句 数据库控制语言: 例如控制用户的访问权限GRANT、REVOKE
06-表的操作
本节掌握
- 存储引擎介绍(了解)
- 表的增删改查
一、存储引擎(了解)
前几节我们知道mysql中建立的库===》文件夹,库中的表====》文件
现实生活中我们用来存储数据的文件有不同的类型,每种文件类型对应各自不同的处理机制:比如处理文本用txt类型,处理表格用excel,处理图片用png等
数据库中的表也应该有不同的类型,表的类型不同,会对应mysql不同的存取机制,表类型又称为存储引擎。
ps: 存储引擎说白了就是如何存储数据、如何为存储的数据建立索引和如何更新、查询数据等技术的实现方法。因为在关系数据库中数据的存储是以表的形式存储的,所以存储引擎也可以称为表类型(即存储和操作此表的类型)
在Oracle 和SQL Server等数据库中只有一种存储引擎,所有数据存储管理机制都是一样的。而MySql
数据库提供了多种存储引擎。用户可以根据不同的需求为数据表选择不同的存储引擎,用户也可以根据
自己的需要编写自己的存储引擎
SQL 解析器、SQL 优化器、缓冲池、存储引擎等组件在每个数据库中都存在,但不是每 个数据库都有这么多存储引擎。MySQL 的插件式存储引擎可以让存储引擎层的开发人员设 计他们希望的存储层,例如,有的应用需要满足事务的要求,有的应用则不需要对事务有这 么强的要求 ;有的希望数据能持久存储,有的只希望放在内存中,临时并快速地提供对数据 的查询。
二、mysql支持的存储引擎
mysql> show enginesG;# 查看所有支持的引擎 mysql> show variables like 'storage_engine%'; # 查看正在使用的存储引擎
1、InnoDB 存储引擎
支持事务,其设计目标主要面向联机事务处理(OLTP)的应用。其
特点是行锁设计、支持外键,并支持类似 Oracle 的非锁定读,即默认读取操作不会产生锁。 从 MySQL 5.5.8 版本开始是默认的存储引擎。
InnoDB 存储引擎将数据放在一个逻辑的表空间中,这个表空间就像黑盒一样由 InnoDB 存储引擎自身来管理。从 MySQL 4.1(包括 4.1)版本开始,可以将每个 InnoDB 存储引擎的 表单独存放到一个独立的 ibd 文件中。此外,InnoDB 存储引擎支持将裸设备(row disk)用 于建立其表空间。
InnoDB 通过使用多版本并发控制(MVCC)来获得高并发性,并且实现了 SQL 标准 的 4 种隔离级别,默认为 REPEATABLE 级别,同时使用一种称为 netx-key locking 的策略来 避免幻读(phantom)现象的产生。除此之外,InnoDB 存储引擎还提供了插入缓冲(insert buffer)、二次写(double write)、自适应哈希索引(adaptive hash index)、预读(read ahead) 等高性能和高可用的功能。
对于表中数据的存储,InnoDB 存储引擎采用了聚集(clustered)的方式,每张表都是按 主键的顺序进行存储的,如果没有显式地在表定义时指定主键,InnoDB 存储引擎会为每一 行生成一个 6 字节的 ROWID,并以此作为主键。
InnoDB 存储引擎是 MySQL 数据库最为常用的一种引擎,Facebook、Google、Yahoo 等 公司的成功应用已经证明了 InnoDB 存储引擎具备高可用性、高性能以及高可扩展性。对其 底层实现的掌握和理解也需要时间和技术的积累。如果想深入了解 InnoDB 存储引擎的工作 原理、实现和应用,可以参考《MySQL 技术内幕:InnoDB 存储引擎》一书。
2、MyISAM 存储引擎
不支持事务、表锁设计、支持全文索引,主要面向一些 OLAP 数 据库应用,在 MySQL 5.5.8 版本之前是默认的存储引擎(除 Windows 版本外)。数据库系统 与文件系统一个很大的不同在于对事务的支持,MyISAM 存储引擎是不支持事务的。究其根 本,这也并不难理解。用户在所有的应用中是否都需要事务呢?在数据仓库中,如果没有 ETL 这些操作,只是简单地通过报表查询还需要事务的支持吗?此外,MyISAM 存储引擎的 另一个与众不同的地方是,它的缓冲池只缓存(cache)索引文件,而不缓存数据文件,这与 大多数的数据库都不相同。
3、NDB 存储引擎
年,MySQL AB 公司从 Sony Ericsson 公司收购了 NDB 存储引擎。 NDB 存储引擎是一个集群存储引擎,类似于 Oracle 的 RAC 集群,不过与 Oracle RAC 的 share everything 结构不同的是,其结构是 share nothing 的集群架构,因此能提供更高级别的 高可用性。NDB 存储引擎的特点是数据全部放在内存中(从 5.1 版本开始,可以将非索引数 据放在磁盘上),因此主键查找(primary key lookups)的速度极快,并且能够在线添加 NDB 数据存储节点(data node)以便线性地提高数据库性能。由此可见,NDB 存储引擎是高可用、 高性能、高可扩展性的数据库集群系统,其面向的也是 OLTP 的数据库应用类型。
4、Memory 存储引擎
正如其名,Memory 存储引擎中的数据都存放在内存中,数据库重 启或发生崩溃,表中的数据都将消失。它非常适合于存储 OLTP 数据库应用中临时数据的临时表,也可以作为 OLAP 数据库应用中数据仓库的维度表。Memory 存储引擎默认使用哈希 索引,而不是通常熟悉的 B+ 树索引。
5、Infobright 存储引擎
第三方的存储引擎。其特点是存储是按照列而非行的,因此非常 适合 OLAP 的数据库应用。其官方网站是 http://www.infobright.org/,上面有不少成功的数据 仓库案例可供分析。
6、NTSE 存储引擎
网易公司开发的面向其内部使用的存储引擎。目前的版本不支持事务, 但提供压缩、行级缓存等特性,不久的将来会实现面向内存的事务支持。
7、BLACKHOLE
黑洞存储引擎,可以应用于主备复制中的分发主库。
MySQL 数据库还有很多其他存储引擎,上述只是列举了最为常用的一些引擎。如果 你喜欢,完全可以编写专属于自己的引擎,这就是开源赋予我们的能力,也是开源的魅 力所在。
指定表类型/存储引擎
create table t1(id int)engine=innodb;# 默认不写就是innodb
小练习:
创建四张表,分别使用innodb,myisam,memory,blackhole存储引擎,进行插入数据测试
create table t1(id int)engine=innodb; create table t2(id int)engine=myisam; create table t3(id int)engine=memory; create table t4(id int)engine=blackhole;
查看data文件下db1数据库中的文件:
#.frm是存储数据表的框架结构 # .ibd是mysql数据文件 #.MYD是MyISAM表的数据文件的扩展名 #.MYI是MyISAM表的索引的扩展名 #发现后两种存储引擎只有表结构,无数据 #memory,在重启mysql或者重启机器后,表内数据清空 #blackhole,往表内插入任何数据,都相当于丢入黑洞,表内永远不存记录
三、表介绍
表相当于文件,表中的一条记录就相当于文件的一行内容,不同的是,表中的一条记录有对应的标题,称为表的字段
id,name,sex,age,birth称为字段,其余的,一行内容称为一条记录
四、创建表
语法:
create table 表名( 字段名1 类型[(宽度) 约束条件], 字段名2 类型[(宽度) 约束条件], 字段名3 类型[(宽度) 约束条件] ); #注意: 1. 在同一张表中,字段名是不能相同 2. 宽度和约束条件可选 3. 字段名和类型是必须的
1.创建数据库
create database db2 charset utf8;
2.使用数据库
use db2;
3.创建a1表
create table a1( id int, name varchar(50), age int(3) );
4.插入表的记录
insert into a1 values (1,'mjj',18), (2,'wusir',28);
ps:以;作为mysql的结束语
5.查询表的数据和结构
(1)查询a1表中的存储数据
mysql> select * from a1;
+------+-------+------+
| id | name | age |
+------+-------+------+
| 1 | mjj | 18 |
| 2 | wusir | 28 |
+------+-------+------+
2 rows in set (0.02 sec)
mysql>
(2)查看a1表的结构
mysql> desc a1; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | name | varchar(50) | YES | | NULL | | | age | int(3) | YES | | NULL | | +-------+-------------+------+-----+---------+-------+ 3 rows in set (0.16 sec)
(3)查看表的详细结构
mysql> show create table a1G; *************************** 1. row *************************** Table: a1 Create Table: CREATE TABLE `a1` ( `id` int(11) DEFAULT NULL, `name` varchar(50) DEFAULT NULL, `age` int(3) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 1 row in set (0.00 sec)
6.复制表
(1)新创建一个数据库db3
mysql> create database db3 charset utf8; Query OK, 1 row affected (0.00 sec)
(2)使用db3
mysql> use db3; Database changed
#这是上个创建的db2数据库中的a1表 mysql> select * from db2.a1; +------+-------+------+ | id | name | age | +------+-------+------+ | 1 | mjj | 18 | | 2 | wusir | 28 | +------+-------+------+
(3)复制db2.a1的表结构和记录
# 这就是复制表的操作(既复制了表结构,又复制了记录) mysql> create table b1 select * from db2.a1; Query OK, 2 rows affected (0.03 sec)
(4)查看db3.b1中的数据和表结构
#再去查看db3文件夹下的b1表发现 跟db2文件下的a1表数据一样 mysql> select * from db3.b1; +------+-------+------+ | id | name | age | +------+-------+------+ | 1 | mjj | 18 | | 2 | wusir | 28 | +------+-------+------+ 2 rows in set (0.00 sec)
ps1:如果只要表结构,不要记录
#在db2数据库下新创建一个b2表,给一个where条件,条件要求不成立,条件为false,只拷贝表结构 mysql> create table b2 select * from db2.a1 where 1>5; Query OK, 0 rows affected (0.05 sec) Records: 0 Duplicates: 0 Warnings: 0
查看表结构:
# 查看表结构 mysql> desc b2; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | name | varchar(50) | YES | | NULL | | | age | int(3) | YES | | NULL | | +-------+-------------+------+-----+---------+-------+ 3 rows in set (0.02 sec) #查看表结构中的数据,发现是空数据 mysql> select * from b2; Empty set (0.00 sec)
ps2:还有一种做法,使用like(只拷贝表结构,不拷贝记录)
mysql> create table b3 like db2.a1; Query OK, 0 rows affected (0.01 sec) mysql> desc b3; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | name | varchar(50) | YES | | NULL | | | age | int(3) | YES | | NULL | | +-------+-------------+------+-----+---------+-------+ 3 rows in set (0.02 sec) mysql> select * from db3.b3; Empty set (0.00 sec)
7.删除表:
drop table 表名;
07-数据类型
介绍
存储引擎决定了表的类型,而表内存放的数据也要有不同的类型,每种数据类型都有自己的宽度,但宽度是可选的
详细参考链接:http://www.runoob.com/mysql/mysql-data-types.html
mysql常用数据类型概括:
#1. 数字: 整型:tinyinit int bigint 小数: float :在位数比较短的情况下不精准 double :在位数比较长的情况下不精准 0.000001230123123123 存成:0.000001230000 decimal:(如果用小数,则用推荐使用decimal) 精准 内部原理是以字符串形式去存 #2. 字符串: char(10):简单粗暴,浪费空间,存取速度快 root存成root000000 varchar:精准,节省空间,存取速度慢 sql优化:创建表时,定长的类型往前放,变长的往后放 比如性别 比如地址或描述信息 >255个字符,超了就把文件路径存放到数据库中。 比如图片,视频等找一个文件服务器,数据库中只存路径或url。 #3. 时间类型: 最常用:datetime #4. 枚举类型与集合类型
一、数值类型
整数类型:TINYINT SMALLINT MEDIUMINT INT BIGINT
作用:存储年龄,等级,id,各种号码等
======================================== tinyint[(m)] [unsigned] [zerofill] 小整数,数据类型用于保存一些范围的整数数值范围: 有符号: -128 ~ 127 无符号: ~ 255 PS: MySQL中无布尔值,使用tinyint(1)构造。 ======================================== int[(m)][unsigned][zerofill] 整数,数据类型用于保存一些范围的整数数值范围: 有符号: -2147483648 ~ 2147483647 无符号: ~ 4294967295 ======================================== bigint[(m)][unsigned][zerofill] 大整数,数据类型用于保存一些范围的整数数值范围: 有符号: -9223372036854775808 ~ 9223372036854775807 无符号: ~ 18446744073709551615
验证1:有符号和无符号tinyint
============有符号tinyint============== # 创建数据库db4 create database db4 charset utf8; # 切换到当前db4数据库 mysql> use db4; # 创建t1 规定x字段为tinyint数据类型(默认是有符号的) mysql> create table t1(x tinyint); # 验证,插入-1这个数 mysql> insert into t1 values(-1); # 查询 表记录,查询成功(证明默认是有符号类型) mysql> select * from t1; +------+ | x | +------+ | -1 | +------+ #执行如下操作,会发现报错。因为有符号范围在(-128,127) mysql> insert into t1 values(-129),(128); ERROR 1264 (22003): Out of range value for column 'x' at row 1 ============无符号tinyint============== # 创建表时定义记录的字符为无符号类型(0,255) ,使用unsigned mysql> create table t2(x tinyint unsigned); # 报错,超出范围 mysql> insert into t2 values(-129); ERROR 1264 (22003): Out of range value for column 'x' at row 1 # 插入成功 mysql> insert into t2 values(255); Query OK, 1 row affected (0.00 sec)
验证2:int类型后面的存储是显示宽度,而不是存储宽度
mysql> create table t3(id int(1) unsigned); #插入255555记录也是可以的 mysql> insert into t3 values(255555); mysql> select * from t3; +--------+ | id | +--------+ | 255555 | +--------+ ps:以上操作还不能够验证,再来一张表验证用zerofill 用0填充 # zerofill 用0填充 mysql> create table t4(id int(5) unsigned zerofill); mysql> insert into t4 value(1); Query OK, 1 row affected (0.00 sec) #插入的记录是1,但是显示的宽度是00001 mysql> select * from t4; +-------+ | id | +-------+ | 00001 | +-------+ 1 row in set (0.00 sec)
注意:为该类型指定宽度时,仅仅只是指定查询结果的显示宽度,与存储范围无关,存储范围如下
其实我们完全没必要为整数类型指定显示宽度,使用默认的就可以了
默认的显示宽度,都是在最大值的基础上加1
int的存储宽度是4个Bytes,即32个bit,即2**32
无符号最大值为:4294967296-1
有符号最大值:2147483648-1
有符号和无符号的最大数字需要的显示宽度均为10,而针对有符号的最小值则需要11位才能显示完全,所以int类型默认的显示宽度为11是非常合理的
最后:整形类型,其实没有必要指定显示宽度,使用默认的就ok
二、浮点型
定点数类型: DEC等同于DECIMAL
浮点类型:FLOAT DOUBLE
作用:存储薪资、身高、体重、体质参数等
语法:
-------------------------FLOAT------------------- FLOAT[(M,D)] [UNSIGNED] [ZEROFILL] #参数解释:单精度浮点数(非准确小数值),M是全长,D是小数点后个数。M最大值为255,D最大值为30 #有符号: -3.402823466E+38 to -1.175494351E-38, 1.175494351E-38 to 3.402823466E+38 #无符号: 1.175494351E-38 to 3.402823466E+38 #精确度: **** 随着小数的增多,精度变得不准确 **** -------------------------DOUBLE----------------------- DOUBLE[(M,D)] [UNSIGNED] [ZEROFILL] #参数解释: 双精度浮点数(非准确小数值),M是全长,D是小数点后个数。M最大值为255,D最大值为30 #有符号: -1.7976931348623157E+308 to -2.2250738585072014E-308 2.2250738585072014E-308 to 1.7976931348623157E+308 #无符号: 2.2250738585072014E-308 to 1.7976931348623157E+308 #精确度: ****随着小数的增多,精度比float要高,但也会变得不准确 **** ====================================== --------------------DECIMAL------------------------ decimal[(m[,d])] [unsigned] [zerofill] #参数解释:准确的小数值,M是整数部分总个数(负号不算),D是小数点后个数。 M最大值为65,D最大值为30。 #精确度: **** 随着小数的增多,精度始终准确 **** 对于精确数值计算时需要用此类型 decaimal能够存储精确值的原因在于其内部按照字符串存储。
验证三种类型建表:
#1验证FLOAT类型建表: mysql> create table t5(x float(256,31)); ERROR 1425 (42000): Too big scale 31 specified for column 'x'. Maximum is 30. mysql> create table t5(x float(256,30)); ERROR 1439 (42000): Display width out of range for column 'x' (max = 255) mysql> create table t5(x float(255,30)); #建表成功 Query OK, 0 rows affected (0.03 sec) #2验证DOUBLE类型建表: mysql> create table t6(x double(255,30)); #建表成功 Query OK, 0 rows affected (0.03 sec) #3验证deimal类型建表: mysql> create table t7(x decimal(66,31)); ERROR 1425 (42000): Too big scale 31 specified for column 'x'. Maximum is 30. mysql> create table t7(x decimal(66,30)); ERROR 1426 (42000): Too big precision 66 specified for column 'x'. Maximum is 65. mysql> create table t7(x decimal(65,30)); #建表成功 Query OK, 0 rows affected (0.00 sec)
验证三种类型的精度:
# 分别对三张表插入相应的记录 mysql> insert into t5 values(1.1111111111111111111111111111111);#小数点后31个1 Query OK, 1 row affected (0.01 sec) mysql> insert into t6 values(1.1111111111111111111111111111111); Query OK, 1 row affected (0.01 sec) mysql> insert into t7 values(1.1111111111111111111111111111111); Query OK, 1 row affected, 1 warning (0.00 sec) # 查询结果 mysql> select * from t5; #随着小数的增多,精度开始不准确 +----------------------------------+ | x | +----------------------------------+ | 1.111111164093017600000000000000 | +----------------------------------+ 1 row in set (0.00 sec) mysql> select * from t6; #精度比float要准确点,但随着小数的增多,同样变得不准确 +----------------------------------+ | x | +----------------------------------+ | 1.111111111111111200000000000000 | +----------------------------------+ 1 row in set (0.00 sec) mysql> select * from t7; #精度始终准确,d为30,于是只留了30位小数 +----------------------------------+ | x | +----------------------------------+ | 1.111111111111111111111111111111 | +----------------------------------+ 1 row in set (0.00 sec)
三、日期类型
DATE TIME DATETIME TIMESTAMP YEAR
作用:存储用户注册时间,文章发布时间,员工入职时间,出生时间,过期时间等
语法: YEAR YYYY(1901/2155) DATE YYYY-MM-DD(1000-01-01/9999-12-31) TIME HH:MM:SS('-838:59:59'/'838:59:59') DATETIME YYYY-MM-DD HH:MM:SS(1000-01-01 00:00:00/9999-12-31 23:59:59 Y) TIMESTAMP YYYYMMDD HHMMSS(1970-01-01 00:00:00/2037 年某时)
验证:
1、year
mysql> create table t8(born_year year);#无论year指定何种宽度,最后都默认是year(4) Query OK, 0 rows affected (0.03 sec)
#插入失败,超出范围(1901/2155) mysql> insert into t8 values -> (1900), -> (1901), -> (2155), -> (2156); ERROR 1264 (22003): Out of range value for column 'born_year' at row 1
mysql> select * from t8; Empty set (0.01 sec) mysql> insert into t8 values -> (1905), -> (2018); Query OK, 2 rows affected (0.00 sec) #插入记录成功 Records: 2 Duplicates: 0 Warnings: 0
mysql> select * from t8; +-----------+ | born_year | +-----------+ | 1905 | | 2018 | +-----------+ 2 rows in set (0.00 sec)
2、date、year、datetime
#创建t9表 mysql> create table t9(d date,t time,dt datetime); Query OK, 0 rows affected (0.06 sec) #查看表的结构 mysql> desc t9; +-------+----------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+----------+------+-----+---------+-------+ | d | date | YES | | NULL | | | t | time | YES | | NULL | | | dt | datetime | YES | | NULL | | +-------+----------+------+-----+---------+-------+ 3 rows in set (0.14 sec)
# 调用mysql自带的now()函数,获取当前类型指定的时间 如下结构 mysql> insert into t9 values(now(),now(),now()); Query OK, 1 row affected, 1 warning (0.01 sec) mysql> select * from t9; +------------+----------+---------------------+ | d | t | dt | +------------+----------+---------------------+ | 2018-06-09 | 09:35:20 | 2018-06-09 09:35:20 | +------------+----------+---------------------+ 1 row in set (0.00 sec)
3、timestamp(了解即可)
mysql> create table t10(time timestamp); Query OK, 0 rows affected (0.06 sec) mysql> insert into t10 values(); Query OK, 1 row affected (0.00 sec) mysql> insert into t10 values(null); Query OK, 1 row affected (0.00 sec) mysql> select * from t10; +------+ | time | +------+ | NULL | | NULL | +------+ mysql> insert into t10 values(now()); Query OK, 1 row affected (0.01 sec) mysql> select * from t10; +---------------------+ | time | +---------------------+ | 2018-06-09 09:44:48 | +---------------------+ 1 row in set (0.01 sec)
datetime与timestamp的区别
在实际应用的很多场景中,MySQL的这两种日期类型都能够满足我们的需要,存储精度都为秒,但在某些情况下,会展现出他们各自的优劣。 下面就来总结一下两种日期类型的区别。 1.DATETIME的日期范围是1001——9999年,TIMESTAMP的时间范围是1970——2038年。 2.DATETIME存储时间与时区无关,TIMESTAMP存储时间与时区有关,显示的值也依赖于时区。在mysql服务器, 操作系统以及客户端连接都有时区的设置。 3.DATETIME使用8字节的存储空间,TIMESTAMP的存储空间为4字节。因此,TIMESTAMP比DATETIME的空间利用率更高。 4.DATETIME的默认值为null;TIMESTAMP的字段默认不为空(not null),默认值为当前时间(CURRENT_TIMESTAMP), 如果不做特殊处理,并且update语句中没有指定该列的更新值,则默认更新为当前时间。
注意事项
============注意啦,注意啦,注意啦=========== #1. 单独插入时间时,需要以字符串的形式,按照对应的格式插入 #2. 插入年份时,尽量使用4位值 #3. 插入两位年份时,<=69,以20开头,比如50, 结果2050 >=70,以19开头,比如71,结果1971 create table t12(y year); insert into t12 values (50),(71); select * from t12; +------+ | y | +------+ | 2050 | | 1971 | +------+
综合练习:
创建一张学生表(student),要求有id,姓名,出生年份,出生的年月日,进班的时间,以及来老男孩学习的现在具体时间。
mysql> create table student( -> id int, -> name varchar(20), -> born_year year, -> birth date, -> class_time time, -> reg_time datetime -> ); Query OK, 0 rows affected (0.02 sec) mysql> insert into student values -> (1,'alex',"1995","1995-11-11","11:11:11","2017-11-11 11:11:11"), -> (2,'egon',"1997","1997-12-12","12:12:12","2017-12-12 12:12:12"), -> (3,'wsb',"1998","1998-01-01","13:13:13","2017-01-01 13:13:13"); Query OK, 3 rows affected (0.00 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> select * from student; +------+------+-----------+------------+------------+---------------------+ | id | name | born_year | birth | class_time | reg_time | +------+------+-----------+------------+------------+---------------------+ | 1 | alex | 1995 | 1995-11-11 | 11:11:11 | 2017-11-11 11:11:11 | | 2 | egon | 1997 | 1997-12-12 | 12:12:12 | 2017-12-12 12:12:12 | | 3 | wsb | 1998 | 1998-01-01 | 13:13:13 | 2017-01-01 13:13:13 | +------+------+-----------+------------+------------+---------------------+ rows in set (0.00 sec)
08-数据类型(2)
本节重点
- 掌握char类型和varchar类型
- 掌握枚举类型和集合类型
字符类型
#官网:https://dev.mysql.com/doc/refman/5.7/en/char.html #注意:char和varchar括号内的参数指的都是字符的长度 #char类型:定长,简单粗暴,浪费空间,存取速度快 字符长度范围:0-255(一个中文是一个字符,是utf8编码的3个字节) 存储: 存储char类型的值时,会往右填充空格来满足长度 例如:指定长度为10,存>10个字符则报错,存<10个字符则用空格填充直到凑够10个字符存储 检索: 在检索或者说查询时,查出的结果会自动删除尾部的空格,除非我们打开pad_char_to_full_length SQL模式(设置SQL模式:SET sql_mode = 'PAD_CHAR_TO_FULL_LENGTH';
查询sql的默认模式:select @@sql_mode;) #varchar类型:变长,精准,节省空间,存取速度慢 字符长度范围:0-65535(如果大于21845会提示用其他类型 。mysql行最大限制为65535字节,字符编码为utf-8:https://dev.mysql.com/doc/refman/5.7/en/column-count-limit.html) 存储: varchar类型存储数据的真实内容,不会用空格填充,如果'ab ',尾部的空格也会被存起来 强调:varchar类型会在真实数据前加1-2Bytes的前缀,该前缀用来表示真实数据的bytes字节数(1-2Bytes最大表示65535个数字,正好符合mysql对row的最大字节限制,即已经足够使用) 如果真实的数据<255bytes则需要1Bytes的前缀(1Bytes=8bit 2**8最大表示的数字为255) 如果真实的数据>255bytes则需要2Bytes的前缀(2Bytes=16bit 2**16最大表示的数字为65535) 检索: 尾部有空格会保存下来,在检索或者说查询时,也会正常显示包含空格在内的内容
官网解释如下:
验证:
验证之前了解两个函数:
length():查看字节数 char_length():查看字符数
1.char填充空格来满足固定长度,但是在查询时却会很不要脸地删除尾部的空格(装作自己好像没有浪费过空间一样),然后修改sql_mode让其现出原形。
# 创建t1表,分别指明字段x为char类型,字段y为varchar类型 mysql> create table t1(x char(5),y varchar(4)); Query OK, 0 rows affected (0.16 sec) # char存放的是5个字符,而varchar存4个字符 mysql> insert into t1 values('你瞅啥 ','你瞅啥 '); Query OK, 1 row affected (0.01 sec) # 在检索时char很不要脸地将自己浪费的2个字符给删掉了,装的好像自己没浪费过空间一样,而varchar很老实,存了多少,就显示多少 mysql> select x,char_length(x),y,char_length(y) from t1; +-----------+----------------+------------+----------------+ | x | char_length(x) | y | char_length(y) | +-----------+----------------+------------+----------------+ | 你瞅啥 | 3 | 你瞅啥 | 4 | +-----------+----------------+------------+----------------+ 1 row in set (0.02 sec) #略施小计,让char现原形 mysql> SET sql_mode = 'PAD_CHAR_TO_FULL_LENGTH'; Query OK, 0 rows affected (0.00 sec) #查看当前mysql的mode模式 mysql> select @@sql_mode; +-------------------------+ | @@sql_mode | +-------------------------+ | PAD_CHAR_TO_FULL_LENGTH | +-------------------------+ 1 row in set (0.00 sec) #原形毕露了吧。。。。 mysql> select x,char_length(x) y,char_length(y) from t1; +-------------+------+----------------+ | x | y | char_length(y) | +-------------+------+----------------+ | 你瞅啥 | 5 | 4 | +-------------+------+----------------+ 1 row in set (0.00 sec) # 查看字节数 #char类型:3个中文字符+2个空格=11Bytes #varchar类型:3个中文字符+1个空格=10Bytes mysql> select x,length(x),y,length(y) from t1; +-------------+-----------+------------+-----------+ | x | length(x) | y | length(y) | +-------------+-----------+------------+-----------+ | 你瞅啥 | 11 | 你瞅啥 | 10 | +-------------+-----------+------------+-----------+ 1 row in set (0.02 sec)
总结:
#常用字符串系列:char与varchar 注:虽然varchar使用起来较为灵活,但是从整个系统的性能角度来说,char数据类型的处理速度更快,有时甚至可以超出varchar处理速度的50%。因此,用户在设计数据库时应当综合考虑各方面的因素,以求达到最佳的平衡 #其他字符串系列(效率:char>varchar>text) TEXT系列 TINYTEXT TEXT MEDIUMTEXT LONGTEXT BLOB 系列 TINYBLOB BLOB MEDIUMBLOB LONGBLOB BINARY系列 BINARY VARBINARY text:text数据类型用于保存变长的大字符串,可以组多到65535 (2**16 − 1)个字符。 mediumtext:A TEXT column with a maximum length of 16,777,215 (2**24 − 1) characters. longtext:A TEXT column with a maximum length of 4,294,967,295 or 4GB (2**32 − 1) characters.
枚举类型和集合类型
字段的值只能在给定范围中选择,如单选框,多选框
enum 单选 只能在给定的范围内选一个值,如性别 sex 男male/女female
set 多选 在给定的范围内可以选择一个或一个以上的值(爱好1,爱好2,爱好3...)
mysql> create table consumer( -> id int, -> name varchar(50), -> sex enum('male','female','other'), -> level enum('vip1','vip2','vip3','vip4'),#在指定范围内,多选一 -> fav set('play','music','read','study') #在指定范围内,多选多 -> ); Query OK, 0 rows affected (0.03 sec) mysql> insert into consumer values -> (1,'赵云','male','vip2','read,study'), -> (2,'赵云2','other','vip4','play'); Query OK, 2 rows affected (0.00 sec) Records: 2 Duplicates: 0 Warnings: 0 mysql> select * from consumer; +------+---------+-------+-------+------------+ | id | name | sex | level | fav | +------+---------+-------+-------+------------+ | 1 | 赵云 | male | vip2 | read,study | | 2 | 赵云2 | other | vip4 | play | +------+---------+-------+-------+------------+ 2 rows in set (0.00 sec)
09-完整性约束
本节重点:
- not null 与 default
- unique
- primary
- auto_increment
- foreign key
一、介绍
约束条件与数据类型的宽度一样,都是可选参数
作用:用于保证数据的完整性和一致性
主要分为:
PRIMARY KEY (PK) #标识该字段为该表的主键,可以唯一的标识记录 FOREIGN KEY (FK) #标识该字段为该表的外键 NOT NULL #标识该字段不能为空 UNIQUE KEY (UK) #标识该字段的值是唯一的 AUTO_INCREMENT #标识该字段的值自动增长(整数类型,而且为主键) DEFAULT #为该字段设置默认值 UNSIGNED #无符号 ZEROFILL #使用0填充
说明:
#1. 是否允许为空,默认NULL,可设置NOT NULL,字段不允许为空,必须赋值 #2. 字段是否有默认值,缺省的默认值是NULL,如果插入记录时不给字段赋值,此字段使用默认值 sex enum('male','female') not null default 'male' #必须为正值(无符号) 不允许为空 默认是20 age int unsigned NOT NULL default 20 # 3. 是否是key 主键 primary key 外键 foreign key 索引 (index,unique...)
二、not null 与default
是否可空,null表示空,非字符串
not null - 不可空
null - 可空
默认值,创建列时可以指定默认值,当插入数据时如果未主动设置,则自动添加默认值
create table tb1( nid int not null defalut 2, num int not null );
验证1:
mysql> create table t11(id int);# id字段默认可以为空 Query OK, 0 rows affected (0.05 sec) mysql> desc t11; +-------+---------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+---------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | +-------+---------+------+-----+---------+-------+ row in set (0.03 sec) mysql> insert into t11 values(); #给t11表插一个空的值 Query OK, 1 row affected (0.00 sec) #查询结果如下 mysql> select * from t11; +------+ | id | +------+ | NULL | +------+ row in set (0.00 sec) 默认值可以为空
验证2:
mysql> create table t12(id int not null);#设置字段id不为空 Query OK, 0 rows affected (0.03 sec) mysql> desc t12; +-------+---------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+---------+------+-----+---------+-------+ | id | int(11) | NO | | NULL | | +-------+---------+------+-----+---------+-------+ row in set (0.01 sec) mysql> insert into t12 values();#不能插入空 ERROR 1364 (HY000): Field 'id' doesn't have a default value 设置not null,插入值时不能为空
验证3:
# 第一种情况 mysql> create table t13(id int default 1); Query OK, 0 rows affected (0.03 sec) mysql> desc t13; +-------+---------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+---------+------+-----+---------+-------+ | id | int(11) | YES | | 1 | | +-------+---------+------+-----+---------+-------+ row in set (0.01 sec) mysql> insert into t13 values(); Query OK, 1 row affected (0.00 sec) mysql> select * from t13; +------+ | id | +------+ | 1 | +------+ row in set (0.00 sec) # 第二种情况 mysql> create table t14(id int not null default 2); Query OK, 0 rows affected (0.02 sec) mysql> desc t14; +-------+---------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+---------+------+-----+---------+-------+ | id | int(11) | NO | | 2 | | +-------+---------+------+-----+---------+-------+ row in set (0.01 sec) mysql> select * from t14; +----+ | id | +----+ | 2 | +----+ row in set (0.00 sec) 设置id字段有默认值后,则无论id字段是null还是not null,都可以插入空,插入空默认填入default指定的默认值
小练习:
创建学生表student2,设置每个字段的约束条件。
mysql> create table student2( -> id int not null, -> name varchar(50) not null, -> age int(3) unsigned not null default 18, -> sex enum('male','female') default 'male', -> fav set('smoke','drink','tangtou') default 'drink,tangtou' -> ); Query OK, 0 rows affected (0.01 sec) # 只插入了not null约束条件的字段对应的值 mysql> insert into student2(id,name) values(1,'mjj'); Query OK, 1 row affected (0.00 sec) # 查询结果如下 mysql> select * from student2; +----+------+-----+------+---------------+ | id | name | age | sex | fav | +----+------+-----+------+---------------+ | 1 | mjj | 18 | male | drink,tangtou | +----+------+-----+------+---------------+ 1 row in set (0.00 sec)
3、unique
中文翻译:不同的。在mysql中称为单列唯一
举例说明:创建公司部门表(每个公司都有唯一的一个部门)。
mysql> create table department( -> id int, -> name char(10) -> ); Query OK, 0 rows affected (0.01 sec) mysql> insert into department values(1,'IT'),(2,'IT'); Query OK, 2 rows affected (0.00 sec) Records: 2 Duplicates: 0 Warnings: 0 mysql> select * from department; +------+------+ | id | name | +------+------+ | 1 | IT | | 2 | IT | +------+------+ rows in set (0.00 sec) # 发现: 同时插入两个IT部门也是可以的,但这是不合理的,所以我们要设置name字段为unique 解决这种不合理的现象。 验证之前重复插入记录的操作是可行的,但是不符合场景
接下来,使用约束条件unique,来对公司部门的字段进行设置。
#第一种创建unique的方式 #例子1: create table department( id int, name char(10) unique ); mysql> insert into department values(1,'it'),(2,'it'); ERROR 1062 (23000): Duplicate entry 'it' for key 'name' #例子2: create table department( id int unique, name char(10) unique ); insert into department values(1,'it'),(2,'sale'); #第二种创建unique的方式 create table department( id int, name char(10) , unique(id), unique(name) ); insert into department values(1,'it'),(2,'sale');
联合唯一:
# 创建services表 mysql> create table services( -> id int, -> ip char(15), -> port int, -> unique(id), -> unique(ip,port) -> ); Query OK, 0 rows affected (0.05 sec) mysql> desc services; +-------+----------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+----------+------+-----+---------+-------+ | id | int(11) | YES | UNI | NULL | | | ip | char(15) | YES | MUL | NULL | | | port | int(11) | YES | | NULL | | +-------+----------+------+-----+---------+-------+ 3 rows in set (0.01 sec) #联合唯一,只要两列记录,有一列不同,既符合联合唯一的约束 mysql> insert into services values -> (1,'192,168,11,23',80), -> (2,'192,168,11,23',81), -> (3,'192,168,11,25',80); Query OK, 3 rows affected (0.01 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> select * from services; +------+---------------+------+ | id | ip | port | +------+---------------+------+ | 1 | 192,168,11,23 | 80 | | 2 | 192,168,11,23 | 81 | | 3 | 192,168,11,25 | 80 | +------+---------------+------+ 3 rows in set (0.00 sec) mysql> insert into services values (4,'192,168,11,23',80); ERROR 1062 (23000): Duplicate entry '192,168,11,23-80' for key 'ip'
4.primary key
一个表中可以:
单列做主键
多列做主键(复合主键)
约束:等价于 not null unique,字段的值不为空且唯一
存储引擎默认是(innodb):对于innodb存储引擎来说,一张表必须有一个主键。
单列主键
# 创建t14表,为id字段设置主键,唯一的不同的记录 create table t14( id int primary key, name char(16) ); insert into t14 values (1,'xiaoma'), (2,'xiaohong'); mysql> insert into t14 values(2,'wxxx'); ERROR 1062 (23000): Duplicate entry '6' for key 'PRIMARY' # not null + unique的化学反应,相当于给id设置primary key create table t15( id int not null unique, name char(16) ); mysql> create table t15( -> id int not null unique, -> name char(16) -> ); Query OK, 0 rows affected (0.01 sec) mysql> desc t15; +-------+----------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+----------+------+-----+---------+-------+ | id | int(11) | NO | PRI | NULL | | | name | char(16) | YES | | NULL | | +-------+----------+------+-----+---------+-------+ 2 rows in set (0.02 sec)
复合主键
create table t16( ip char(15), port int, primary key(ip,port) ); insert into t16 values ('1.1.1.2',80), ('1.1.1.2',81); 验证复合主键的使用
5.auto_increment
约束:约束的字段为自动增长,约束的字段必须同时被key约束
(重点)验证:
# 创建student create table student( id int primary key auto_increment, name varchar(20), sex enum('male','female') default 'male' ); mysql> desc student; +-------+-----------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-----------------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(20) | YES | | NULL | | | sex | enum('male','female') | YES | | male | | +-------+-----------------------+------+-----+---------+----------------+ rows in set (0.17 sec) #插入记录 mysql> insert into student(name) values ('老白'),('小白'); Query OK, 2 rows affected (0.01 sec) Records: 2 Duplicates: 0 Warnings: 0 mysql> select * from student; +----+--------+------+ | id | name | sex | +----+--------+------+ | 1 | 老白 | male | | 2 | 小白 | male | +----+--------+------+ rows in set (0.00 sec) 不指定id,则自动增长
mysql> insert into student values(4,'asb','female'); Query OK, 1 row affected (0.00 sec) mysql> insert into student values(7,'wsb','female'); Query OK, 1 row affected (0.01 sec) mysql> select * from student; +----+--------+--------+ | id | name | sex | +----+--------+--------+ | 1 | 老白 | male | | 2 | 小白 | male | | 4 | asb | female | | 7 | wsb | female | +----+--------+--------+ rows in set (0.00 sec) # 再次插入一条不指定id的记录,会在之前的最后一条记录继续增长 mysql> insert into student(name) values ('大白'); Query OK, 1 row affected (0.00 sec) mysql> select * from student; +----+--------+--------+ | id | name | sex | +----+--------+--------+ | 1 | 老白 | male | | 2 | 小白 | male | | 4 | asb | female | | 7 | wsb | female | | 8 | 大白 | male | +----+--------+--------+ rows in set (0.00 sec) 也可以指定id
mysql> delete from student; Query OK, 5 rows affected (0.00 sec) mysql> select * from student; Empty set (0.00 sec) mysql> select * from student; Empty set (0.00 sec) mysql> insert into student(name) values('ysb'); Query OK, 1 row affected (0.01 sec) mysql> select * from student; +----+------+------+ | id | name | sex | +----+------+------+ | 9 | ysb | male | +----+------+------+ row in set (0.00 sec) #应该用truncate清空表,比起delete一条一条地删除记录,truncate是直接清空表,在删除大表时用它 mysql> truncate student; Query OK, 0 rows affected (0.03 sec) mysql> insert into student(name) values('xiaobai'); Query OK, 1 row affected (0.00 sec) mysql> select * from student; +----+---------+------+ | id | name | sex | +----+---------+------+ | 1 | xiaobai | male | +----+---------+------+ row in set (0.00 sec) mysql> 对于自增的字段,在用delete删除后,再插入值,该字段仍按照删除前的位置继续增长
了解:
查看可用的 开头auto_inc的词 mysql> show variables like 'auto_inc%'; +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | auto_increment_increment | 1 | | auto_increment_offset | 1 | +--------------------------+-------+ rows in set (0.02 sec) # 步长auto_increment_increment,默认为1 # 起始的偏移量auto_increment_offset, 默认是1 # 设置步长 为会话设置,只在本次连接中有效 set session auto_increment_increment=5; #全局设置步长 都有效。 set global auto_increment_increment=5; # 设置起始偏移量 set global auto_increment_offset=3; #强调:If the value of auto_increment_offset is greater than that of auto_increment_increment, the value of auto_increment_offset is ignored. 翻译:如果auto_increment_offset的值大于auto_increment_increment的值,则auto_increment_offset的值会被忽略 # 设置完起始偏移量和步长之后,再次执行show variables like'auto_inc%'; 发现跟之前一样,必须先exit,再登录才有效。 mysql> show variables like'auto_inc%'; +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | auto_increment_increment | 5 | | auto_increment_offset | 3 | +--------------------------+-------+ rows in set (0.00 sec) #因为之前有一条记录id=1 mysql> select * from student; +----+---------+------+ | id | name | sex | +----+---------+------+ | 1 | xiaobai | male | +----+---------+------+ row in set (0.00 sec) # 下次插入的时候,从起始位置3开始,每次插入记录id+5 mysql> insert into student(name) values('ma1'),('ma2'),('ma3'); Query OK, 3 rows affected (0.00 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> select * from student; +----+---------+------+ | id | name | sex | +----+---------+------+ | 1 | xiaobai | male | | 3 | ma1 | male | | 8 | ma2 | male | | 13 | ma3 | male | +----+---------+------+ auto_increment_increment和 auto_increment_offset
清空表区分delete和truncate的区别:
delete from t1; #如果有自增id,新增的数据,仍然是以删除前的最后一样作为起始。
truncate table t1;数据量大,删除速度比上一条快,且直接从零开始。
6.foreign key
一 快速理解foreign key
之前创建表的时候都是在一张表中添加记录,比如如下表:
公司有3个部门,但是有1个亿的员工,那意味着部门这个字段需要重复存储,部门名字越长,越浪费。
这个时候,
解决方法:
我们完全可以定义一个部门表
然后让员工信息表关联该表,如何关联,即foreign key
我们可以将上表改为如下结构:
此时有两张表,一张是employee表,简称emp表(关联表,也就从表)。一张是department表,简称dep表(被关联表,也叫主表)。
创建两张表操作:
10-外键的变种 三种关系
本节重点:
- 如何找出两张表之间的关系
- 表的三种关系
一、介绍
因为有foreign key的约束,使得两张表形成了三种了关系:
- 多对一
- 多对多
- 一对一
二、重点理解如果找出两张表之间的关系
分析步骤: #1、先站在左表的角度去找 是否左表的多条记录可以对应右表的一条记录,如果是,则证明左表的一个字段foreign key 右表一个字段(通常是id) #2、再站在右表的角度去找 是否右表的多条记录可以对应左表的一条记录,如果是,则证明右表的一个字段foreign key 左表一个字段(通常是id) #3、总结: #多对一: 如果只有步骤1成立,则是左表多对一右表 如果只有步骤2成立,则是右表多对一左表 #多对多 如果步骤1和2同时成立,则证明这两张表时一个双向的多对一,即多对多,需要定义一个这两张表的关系表来专门存放二者的关系 #一对一: 如果1和2都不成立,而是左表的一条记录唯一对应右表的一条记录,反之亦然。这种情况很简单,就是在左表foreign key右表的基础上,将左表的外键字段设置成unique即可
三、表的三种关系
(1)书和出版社
一对多(或多对一):一个出版社可以出版多本书。看图说话。
关联方式:foreign key
create table press( id int primary key auto_increment, name varchar(20) ); create table book( id int primary key auto_increment, name varchar(20), press_id int not null, constraint fk_book_press foreign key(press_id) references press(id) on delete cascade on update cascade ); # 先往被关联表中插入记录 insert into press(name) values ('北京工业地雷出版社'), ('人民音乐不好听出版社'), ('知识产权没有用出版社') ; # 再往关联表中插入记录 insert into book(name,press_id) values ('九阳神功',1), ('九阴真经',2), ('九阴白骨爪',2), ('独孤九剑',3), ('降龙十巴掌',2), ('葵花宝典',3) ; 查询结果: mysql> select * from book; +----+-----------------+----------+ | id | name | press_id | +----+-----------------+----------+ | 1 | 九阳神功 | 1 | | 2 | 九阴真经 | 2 | | 3 | 九阴白骨爪 | 2 | | 4 | 独孤九剑 | 3 | | 5 | 降龙十巴掌 | 2 | | 6 | 葵花宝典 | 3 | +----+-----------------+----------+ rows in set (0.00 sec) mysql> select * from press; +----+--------------------------------+ | id | name | +----+--------------------------------+ | 1 | 北京工业地雷出版社 | | 2 | 人民音乐不好听出版社 | | 3 | 知识产权没有用出版社 | +----+--------------------------------+ rows in set (0.00 sec) 书和出版社(多对一)
(2)作者和书籍的关系
多对多:一个作者可以写多本书,一本书也可以有多个作者,双向的一对多,即多对多。
看图说话。
关联方式:foreign key+一张新的表
# 创建被关联表author表,之前的book表在讲多对一的关系已创建 create table author( id int primary key auto_increment, name varchar(20) ); #这张表就存放了author表和book表的关系,即查询二者的关系查这表就可以了 create table author2book( id int not null unique auto_increment, author_id int not null, book_id int not null, constraint fk_author foreign key(author_id) references author(id) on delete cascade on update cascade, constraint fk_book foreign key(book_id) references book(id) on delete cascade on update cascade, primary key(author_id,book_id) ); #插入四个作者,id依次排开 insert into author(name) values('egon'),('alex'),('wusir'),('yuanhao'); # 每个作者的代表作 egon: 九阳神功、九阴真经、九阴白骨爪、独孤九剑、降龙十巴掌、葵花宝典 alex: 九阳神功、葵花宝典 wusir:独孤九剑、降龙十巴掌、葵花宝典 yuanhao:九阳神功 # 在author2book表中插入相应的数据 insert into author2book(author_id,book_id) values (1,1), (1,2), (1,3), (1,4), (1,5), (1,6), (2,1), (2,6), (3,4), (3,5), (3,6), (4,1) ; # 现在就可以查author2book对应的作者和书的关系了 mysql> select * from author2book; +----+-----------+---------+ | id | author_id | book_id | +----+-----------+---------+ | 1 | 1 | 1 | | 2 | 1 | 2 | | 3 | 1 | 3 | | 4 | 1 | 4 | | 5 | 1 | 5 | | 6 | 1 | 6 | | 7 | 2 | 1 | | 8 | 2 | 6 | | 9 | 3 | 4 | | 10 | 3 | 5 | | 11 | 3 | 6 | | 12 | 4 | 1 | +----+-----------+---------+ rows in set (0.00 sec) 作者与书籍关系(多对多)
(3)用户和博客
一对一:一个用户只能注册一个博客,即一对一的关系。看图说话
关联方式:foreign key+unique
#例如: 一个用户只能注册一个博客 #两张表: 用户表 (user)和 博客表(blog) # 创建用户表 create table user( id int primary key auto_increment, name varchar(20) ); # 创建博客表 create table blog( id int primary key auto_increment, url varchar(100), user_id int unique, constraint fk_user foreign key(user_id) references user(id) on delete cascade on update cascade ); #插入用户表中的记录 insert into user(name) values ('alex'), ('wusir'), ('egon'), ('xiaoma') ; # 插入博客表的记录 insert into blog(url,user_id) values ('http://www.cnblog/alex',1), ('http://www.cnblog/wusir',2), ('http://www.cnblog/egon',3), ('http://www.cnblog/xiaoma',4) ; # 查询wusir的博客地址 select url from blog where user_id=2; 用户和博客(一对一)
11-数据的增删改
本节重点:
- 插入数据 INSERT
- 更新数据 UPDATE
- 删除数据 DELETE
再来回顾一下之前我们练过的一些操作,相信大家都对插入数据、更新数据、删除数据有了全面的认识。那么在mysql中其实最重要的不是这三大操作,而是查数据最重要,下节课知晓
一、 在MySQL管理软件中,可以通过SQL语句中的DML语言来实现数据的操作,包括 1.使用INSERT实现数据的插入 2.UPDATE实现数据的更新 3.使用DELETE实现数据的删除 4.使用SELECT查询数据以及。 二、插入数据 INSERT 1. 插入完整数据(顺序插入) 语法一: INSERT INTO 表名(字段1,字段2,字段3…字段n) VALUES(值1,值2,值3…值n); 语法二: INSERT INTO 表名 VALUES (值1,值2,值3…值n); 2. 指定字段插入数据 语法: INSERT INTO 表名(字段1,字段2,字段3…) VALUES (值1,值2,值3…); 3. 插入多条记录 语法: INSERT INTO 表名 VALUES (值1,值2,值3…值n), (值1,值2,值3…值n), (值1,值2,值3…值n); 4. 插入查询结果 语法: INSERT INTO 表名(字段1,字段2,字段3…字段n) SELECT (字段1,字段2,字段3…字段n) FROM 表2 WHERE …; 三、更新数据UPDATE 语法: UPDATE 表名 SET 字段1=值1, 字段2=值2, WHERE CONDITION; 示例: UPDATE mysql.user SET password=password(‘123’) where user=’root’ and host=’localhost’; 四、删除数据DELETE 语法: DELETE FROM 表名 WHERE CONITION; 示例: DELETE FROM mysql.user WHERE password=’’;
12-单表查询
本节重点:
单表查询
语法:
一、单表查询的语法 SELECT 字段1,字段2... FROM 表名 WHERE 条件 GROUP BY field HAVING 筛选 ORDER BY field LIMIT 限制条数 二、关键字的执行优先级(重点) 重点中的重点:关键字的执行优先级 from where group by having select distinct order by limit
1.找到表:from
2.拿着where指定的约束条件,去文件/表中取出一条条记录
3.将取出的一条条记录进行分组group by,如果没有group by,则整体作为一组
4.将分组的结果进行having过滤
5.执行select
6.去重
7.将结果按条件排序:order by
8.限制结果的显示条数
创建公司员工表,表的字段和数据类型
company.employee 员工id id int 姓名 name varchar 性别 sex enum 年龄 age int 入职日期 hire_date date 岗位 post varchar 职位描述 post_comment varchar 薪水 salary double 办公室 office int 部门编号 depart_id int
#创建表,设置字段的约束条件 create table employee( id int primary key auto_increment, name varchar(20) not null, sex enum('male','female') not null default 'male', #大部分是男的 age int(3) unsigned not null default 28, hire_date date not null, post varchar(50), post_comment varchar(100), salary double(15,2), office int,#一个部门一个屋 depart_id int ); # 查看表结构 mysql> desc employee; +--------------+-----------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------------+-----------------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | emp_name | varchar(20) | NO | | NULL | | | sex | enum('male','female') | NO | | male | | | age | int(3) unsigned | NO | | 28 | | | hire_date | date | NO | | NULL | | | post | varchar(50) | YES | | NULL | | | post_comment | varchar(100) | YES | | NULL | | | salart | double(15,2) | YES | | NULL | | | office | int(11) | YES | | NULL | | | depart_id | int(11) | YES | | NULL | | +--------------+-----------------------+------+-----+---------+----------------+ rows in set (0.08 sec) #插入记录 #三个部门:教学,销售,运营 insert into employee(name ,sex,age,hire_date,post,salary,office,depart_id) values ('egon','male',18,'20170301','老男孩驻沙河办事处外交大使',7300.33,401,1), #以下是教学部 ('alex','male',78,'20150302','teacher',1000000.31,401,1), ('wupeiqi','male',81,'20130305','teacher',8300,401,1), ('yuanhao','male',73,'20140701','teacher',3500,401,1), ('liwenzhou','male',28,'20121101','teacher',2100,401,1), ('jingliyang','female',18,'20110211','teacher',9000,401,1), ('jinxin','male',18,'19000301','teacher',30000,401,1), ('xiaomage','male',48,'20101111','teacher',10000,401,1), ('歪歪','female',48,'20150311','sale',3000.13,402,2),#以下是销售部门 ('丫丫','female',38,'20101101','sale',2000.35,402,2), ('丁丁','female',18,'20110312','sale',1000.37,402,2), ('星星','female',18,'20160513','sale',3000.29,402,2), ('格格','female',28,'20170127','sale',4000.33,402,2), ('张野','male',28,'20160311','operation',10000.13,403,3), #以下是运营部门 ('程咬金','male',18,'19970312','operation',20000,403,3), ('程咬银','female',18,'20130311','operation',19000,403,3), ('程咬铜','male',18,'20150411','operation',18000,403,3), ('程咬铁','female',18,'20140512','operation',17000,403,3) ; 创建员工表,并插入记录
(1)where 约束
where子句中可以使用 1.比较运算符:>、<、>=、<=、<>、!= 2.between 80 and 100 :值在80到100之间 3.in(80,90,100)值是10或20或30 4.like 'xiaomagepattern': pattern可以是%或者_。%小时任意多字符,_表示一个字符 5.逻辑运算符:在多个条件直接可以使用逻辑运算符 and or not
验证结果:
#1 :单条件查询 mysql> select id,emp_name from employee where id > 5; +----+------------+ | id | emp_name | +----+------------+ | 6 | jingliyang | | 7 | jinxin | | 8 | xiaomage | | 9 | 歪歪 | | 10 | 丫丫 | | 11 | 丁丁 | | 12 | 星星 | | 13 | 格格 | | 14 | 张野 | | 15 | 程咬金 | | 16 | 程咬银 | | 17 | 程咬铜 | | 18 | 程咬铁 | #2 多条件查询 mysql> select emp_name from employee where post='teacher' and salary>10000; +----------+ | emp_name | +----------+ | alex | | jinxin | +----------+ #3.关键字BETWEEN AND SELECT name,salary FROM employee WHERE salary BETWEEN 10000 AND 20000; SELECT name,salary FROM employee WHERE salary NOT BETWEEN 10000 AND 20000; #注意''是空字符串,不是null SELECT name,post_comment FROM employee WHERE post_comment=''; ps: 执行 update employee set post_comment='' where id=2; 再用上条查看,就会有结果了 #5:关键字IN集合查询 mysql> SELECT name,salary FROM employee WHERE salary=3000 OR salary=3500 OR salary=4000 OR salary=9000 ; +------------+---------+ | name | salary | +------------+---------+ | yuanhao | 3500.00 | | jingliyang | 9000.00 | +------------+---------+ rows in set (0.00 sec) mysql> SELECT name,salary FROM employee WHERE salary IN (3000,3500,4000,9000) ; +------------+---------+ | name | salary | +------------+---------+ | yuanhao | 3500.00 | | jingliyang | 9000.00 | +------------+---------+ mysql> SELECT name,salary FROM employee WHERE salary NOT IN (3000,3500,4000,9000) ; +-----------+------------+ | name | salary | +-----------+------------+ | egon | 7300.33 | | alex | 1000000.31 | | wupeiqi | 8300.00 | | liwenzhou | 2100.00 | | jinxin | 30000.00 | | xiaomage | 10000.00 | | 歪歪 | 3000.13 | | 丫丫 | 2000.35 | | 丁丁 | 1000.37 | | 星星 | 3000.29 | | 格格 | 4000.33 | | 张野 | 10000.13 | | 程咬金 | 20000.00 | | 程咬银 | 19000.00 | | 程咬铜 | 18000.00 | | 程咬铁 | 17000.00 | +-----------+------------+ rows in set (0.00 sec) #6:关键字LIKE模糊查询 通配符’%’ mysql> SELECT * FROM employee WHERE name LIKE 'jin%'; +----+------------+--------+-----+------------+---------+--------------+----------+--------+-----------+ | id | name | sex | age | hire_date | post | post_comment | salary | office | depart_id | +----+------------+--------+-----+------------+---------+--------------+----------+--------+-----------+ | 6 | jingliyang | female | 18 | 2011-02-11 | teacher | NULL | 9000.00 | 401 | 1 | | 7 | jinxin | male | 18 | 1900-03-01 | teacher | NULL | 30000.00 | 401 | 1 | +----+------------+--------+-----+------------+---------+--------------+----------+--------+-----------+ rows in set (0.00 sec) 通配符'_' mysql> SELECT age FROM employee WHERE name LIKE 'ale_'; +-----+ | age | +-----+ | 78 | +-----+ row in set (0.00 sec) 练习: 1. 查看岗位是teacher的员工姓名、年龄 2. 查看岗位是teacher且年龄大于30岁的员工姓名、年龄 3. 查看岗位是teacher且薪资在9000-1000范围内的员工姓名、年龄、薪资 4. 查看岗位描述不为NULL的员工信息 5. 查看岗位是teacher且薪资是10000或9000或30000的员工姓名、年龄、薪资 6. 查看岗位是teacher且薪资不是10000或9000或30000的员工姓名、年龄、薪资 7. 查看岗位是teacher且名字是jin开头的员工姓名、年薪 #对应的sql语句 select name,age from employee where post = 'teacher'; select name,age from employee where post='teacher' and age > 30; select name,age,salary from employee where post='teacher' and salary between 9000 and 10000; select * from employee where post_comment is not null; select name,age,salary from employee where post='teacher' and salary in (10000,9000,30000); select name,age,salary from employee where post='teacher' and salary not in (10000,9000,30000); select name,salary*12 from employee where post='teacher' and name like 'jin%'; where约束
(2)group by 分组查询
#1、首先明确一点:分组发生在where之后,即分组是基于where之后得到的记录而进行的 #2、分组指的是:将所有记录按照某个相同字段进行归类,比如针对员工信息表的职位分组,或者按照性别进行分组等 #3、为何要分组呢? 取每个部门的最高工资 取每个部门的员工数 取男人数和女人数 小窍门:‘每’这个字后面的字段,就是我们分组的依据 #4、大前提: 可以按照任意字段分组,但是分组完毕后,比如group by post,只能查看post字段,如果想查看组内信息,需要借助于聚合函数
当执行以下sql语句的时候,是以post字段查询了组中的第一条数据,没有任何意义,因为我们现在想查出当前组的多条记录。
mysql> select * from employee group by post; +----+--------+--------+-----+------------+-----------------------------------------+--------------+------------+--------+-----------+ | id | name | sex | age | hire_date | post | post_comment | salary | office | depart_id | +----+--------+--------+-----+------------+-----------------------------------------+--------------+------------+--------+-----------+ | 14 | 张野 | male | 28 | 2016-03-11 | operation | NULL | 10000.13 | 403 | 3 | | 9 | 歪歪 | female | 48 | 2015-03-11 | sale | NULL | 3000.13 | 402 | 2 | | 2 | alex | male | 78 | 2015-03-02 | teacher | | 1000000.31 | 401 | 1 | | 1 | egon | male | 18 | 2017-03-01 | 老男孩驻沙河办事处外交大使 | NULL | 7300.33 | 401 | 1 | +----+--------+--------+-----+------------+-----------------------------------------+--------------+------------+--------+-----------+ 4 rows in set (0.00 sec) #由于没有设置ONLY_FULL_GROUP_BY,于是也可以有结果,默认都是组内的第一条记录,但其实这是没有意义的 如果想分组,则必须要设置全局的sql的模式为ONLY_FULL_GROUP_BY mysql> set global sql_mode='ONLY_FULL_GROUP_BY'; Query OK, 0 rows affected (0.00 sec) #查看MySQL 5.7默认的sql_mode如下: mysql> select @@global.sql_mode; +--------------------+ | @@global.sql_mode | +--------------------+ | ONLY_FULL_GROUP_BY | +--------------------+ 1 row in set (0.00 sec) mysql> exit;#设置成功后,一定要退出,然后重新登录方可生效 Bye
继续验证通过group by分组之后,只能查看当前字段,如果想查看组内信息,需要借助于聚合函数
mysql> select * from emp group by post;# 报错 ERROR 1054 (42S22): Unknown column 'post' in 'group statement' mysql> select post from employee group by post; +-----------------------------------------+ | post | +-----------------------------------------+ | operation | | sale | | teacher | | 老男孩驻沙河办事处外交大使 | +-----------------------------------------+ 4 rows in set (0.00 sec)
(3)聚合函数
max()求最大值 min()求最小值 avg()求平均值 sum() 求和 count() 求总个数 #强调:聚合函数聚合的是组的内容,若是没有分组,则默认一组 # 每个部门有多少个员工 select post,count(id) from employee group by post; # 每个部门的最高薪水 select post,max(salary) from employee group by post; # 每个部门的最低薪水 select post,min(salary) from employee group by post; # 每个部门的平均薪水 select post,avg(salary) from employee group by post; # 每个部门的所有薪水 select post,sum(age) from employee group by post;
1. 查询岗位名以及岗位包含的所有员工名字 2. 查询岗位名以及各岗位内包含的员工个数 3. 查询公司内男员工和女员工的个数 4. 查询岗位名以及各岗位的平均薪资 5. 查询岗位名以及各岗位的最高薪资 6. 查询岗位名以及各岗位的最低薪资 7. 查询男员工与男员工的平均薪资,女员工与女员工的平均薪资 小练习
(4)HAVING过滤
HAVING与WHERE不一样的地方在于 #!!!执行优先级从高到低:where > group by > having #1. Where 发生在分组group by之前,因而Where中可以有任意字段,但是绝对不能使用聚合函数。 #2. Having发生在分组group by之后,因而Having中可以使用分组的字段,无法直接取到其他字段,可以使用聚合函数
验证:
验证: mysql> select * from employee where salary>1000000; +----+------+------+-----+------------+---------+--------------+------------+--------+-----------+ | id | name | sex | age | hire_date | post | post_comment | salary | office | depart_id | +----+------+------+-----+------------+---------+--------------+------------+--------+-----------+ | 2 | alex | male | 78 | 2015-03-02 | teacher | | 1000000.31 | 401 | 1 | +----+------+------+-----+------------+---------+--------------+------------+--------+-----------+ row in set (0.00 sec) mysql> select * from employee having salary>1000000; ERROR 1463 (42000): Non-grouping field 'salary' is used in HAVING clause # 必须使用group by才能使用group_concat()函数,将所有的name值连接 mysql> select post,group_concat(name) from emp group by post having salary > 10000; ##错误,分组后无法直接取到salary字段 ERROR 1054 (42S22): Unknown column 'post' in 'field list'
小练习:
1. 查询各岗位内包含的员工个数小于2的岗位名、岗位内包含员工名字、个数 2. 查询各岗位平均薪资大于10000的岗位名、平均工资 3. 查询各岗位平均薪资大于10000且小于20000的岗位名、平均工资
小练习答案:
# 题1: mysql> select post,group_concat(name),count(id) from employee group by post; +-----------------------------------------+-----------------------------------------------------------+-----------+ | post | group_concat(name) | count(id) | +-----------------------------------------+-----------------------------------------------------------+-----------+ | operation | 程咬铁,程咬铜,程咬银,程咬金,张野 | 5 | | sale | 格格,星星,丁丁,丫丫,歪歪 | 5 | | teacher | xiaomage,jinxin,jingliyang,liwenzhou,yuanhao,wupeiqi,alex | 7 | | 老男孩驻沙河办事处外交大使 | egon | 1 | +-----------------------------------------+-----------------------------------------------------------+-----------+ rows in set (0.00 sec) mysql> select post,group_concat(name),count(id) from employee group by post having count(id)<2; +-----------------------------------------+--------------------+-----------+ | post | group_concat(name) | count(id) | +-----------------------------------------+--------------------+-----------+ | 老男孩驻沙河办事处外交大使 | egon | 1 | +-----------------------------------------+--------------------+-----------+ row in set (0.00 sec) #题2: mysql> select post,avg(salary) from employee group by post having avg(salary) > 10000; +-----------+---------------+ | post | avg(salary) | +-----------+---------------+ | operation | 16800.026000 | | teacher | 151842.901429 | +-----------+---------------+ rows in set (0.00 sec) #题3: mysql> select post,avg(salary) from employee group by post having avg(salary) > 10000 and avg(salary) <20000; +-----------+--------------+ | post | avg(salary) | +-----------+--------------+ | operation | 16800.026000 | +-----------+--------------+ row in set (0.00 sec)
(5)order by 查询排序
按单列排序 SELECT * FROM employee ORDER BY age; SELECT * FROM employee ORDER BY age ASC; SELECT * FROM employee ORDER BY age DESC; 按多列排序:先按照age升序排序,如果年纪相同,则按照id降序 SELECT * from employee ORDER BY age ASC, id DESC;
验证多列排序: SELECT * from employee ORDER BY age ASC,id DESC; mysql> SELECT * from employee ORDER BY age ASC,id DESC; +----+------------+--------+-----+------------+-----------------------------------------+--------------+------------+--------+-----------+ | id | name | sex | age | hire_date | post | post_comment | salary | office | depart_id | +----+------------+--------+-----+------------+-----------------------------------------+--------------+------------+--------+-----------+ | 18 | 程咬铁 | female | 18 | 2014-05-12 | operation | NULL | 17000.00 | 403 | 3 | | 17 | 程咬铜 | male | 18 | 2015-04-11 | operation | NULL | 18000.00 | 403 | 3 | | 16 | 程咬银 | female | 18 | 2013-03-11 | operation | NULL | 19000.00 | 403 | 3 | | 15 | 程咬金 | male | 18 | 1997-03-12 | operation | NULL | 20000.00 | 403 | 3 | | 12 | 星星 | female | 18 | 2016-05-13 | sale | NULL | 3000.29 | 402 | 2 | | 11 | 丁丁 | female | 18 | 2011-03-12 | sale | NULL | 1000.37 | 402 | 2 | | 7 | jinxin | male | 18 | 1900-03-01 | teacher | NULL | 30000.00 | 401 | 1 | | 6 | jingliyang | female | 18 | 2011-02-11 | teacher | NULL | 9000.00 | 401 | 1 | | 1 | egon | male | 18 | 2017-03-01 | 老男孩驻沙河办事处外交大使 | NULL | 7300.33 | 401 | 1 | | 14 | 张野 | male | 28 | 2016-03-11 | operation | NULL | 10000.13 | 403 | 3 | | 13 | 格格 | female | 28 | 2017-01-27 | sale | NULL | 4000.33 | 402 | 2 | | 5 | liwenzhou | male | 28 | 2012-11-01 | teacher | NULL | 2100.00 | 401 | 1 | | 10 | 丫丫 | female | 38 | 2010-11-01 | sale | NULL | 2000.35 | 402 | 2 | | 9 | 歪歪 | female | 48 | 2015-03-11 | sale | NULL | 3000.13 | 402 | 2 | | 8 | xiaomage | male | 48 | 2010-11-11 | teacher | NULL | 10000.00 | 401 | 1 | | 4 | yuanhao | male | 73 | 2014-07-01 | teacher | NULL | 3500.00 | 401 | 1 | | 2 | alex | male | 78 | 2015-03-02 | teacher | | 1000000.31 | 401 | 1 | | 3 | wupeiqi | male | 81 | 2013-03-05 | teacher | NULL | 8300.00 | 401 | 1 | +----+------------+--------+-----+------------+-----------------------------------------+--------------+------------+--------+-----------+ rows in set (0.01 sec) mysql> 验证多列排序
小练习:
1. 查询所有员工信息,先按照age升序排序,如果age相同则按照hire_date降序排序 2. 查询各岗位平均薪资大于10000的岗位名、平均工资,结果按平均薪资升序排列 3. 查询各岗位平均薪资大于10000的岗位名、平均工资,结果按平均薪资降序排列
# 题目1 select * from employee ORDER BY age asc,hire_date desc; # 题目2 mysql> select post,avg(salary) from employee group by post having avg(salary) > 10000 order by avg(salary) asc; +-----------+---------------+ | post | avg(salary) | +-----------+---------------+ | operation | 16800.026000 | | teacher | 151842.901429 | +-----------+---------------+ rows in set (0.00 sec) # 题目3 mysql> select post,avg(salary) from employee group by post having avg(salary) > 10000 order by avg(salary) desc; +-----------+---------------+ | post | avg(salary) | +-----------+---------------+ | teacher | 151842.901429 | | operation | 16800.026000 | +-----------+---------------+ rows in set (0.00 sec) mysql> 小练习答案
(5)limit 限制查询的记录数:
示例: SELECT * FROM employee ORDER BY salary DESC LIMIT 3; #默认初始位置为0 SELECT * FROM employee ORDER BY salary DESC LIMIT 0,5; #从第0开始,即先查询出第一条,然后包含这一条在内往后查5条 SELECT * FROM employee ORDER BY salary DESC LIMIT 5,5; #从第5开始,即先查询出第6条,然后包含这一条在内往后查5条
小练习:
分页显示,每页5条
# 第1页数据 mysql> select * from employee limit 0,5; +----+-----------+------+-----+------------+-----------------------------------------+--------------+------------+--------+-----------+ | id | name | sex | age | hire_date | post | post_comment | salary | office | depart_id | +----+-----------+------+-----+------------+-----------------------------------------+--------------+------------+--------+-----------+ | 1 | egon | male | 18 | 2017-03-01 | 老男孩驻沙河办事处外交大使 | NULL | 7300.33 | 401 | 1 | | 2 | alex | male | 78 | 2015-03-02 | teacher | | 1000000.31 | 401 | 1 | | 3 | wupeiqi | male | 81 | 2013-03-05 | teacher | NULL | 8300.00 | 401 | 1 | | 4 | yuanhao | male | 73 | 2014-07-01 | teacher | NULL | 3500.00 | 401 | 1 | | 5 | liwenzhou | male | 28 | 2012-11-01 | teacher | NULL | 2100.00 | 401 | 1 | +----+-----------+------+-----+------------+-----------------------------------------+--------------+------------+--------+-----------+ rows in set (0.00 sec) # 第2页数据 mysql> select * from employee limit 5,5; +----+------------+--------+-----+------------+---------+--------------+----------+--------+-----------+ | id | name | sex | age | hire_date | post | post_comment | salary | office | depart_id | +----+------------+--------+-----+------------+---------+--------------+----------+--------+-----------+ | 6 | jingliyang | female | 18 | 2011-02-11 | teacher | NULL | 9000.00 | 401 | 1 | | 7 | jinxin | male | 18 | 1900-03-01 | teacher | NULL | 30000.00 | 401 | 1 | | 8 | xiaomage | male | 48 | 2010-11-11 | teacher | NULL | 10000.00 | 401 | 1 | | 9 | 歪歪 | female | 48 | 2015-03-11 | sale | NULL | 3000.13 | 402 | 2 | | 10 | 丫丫 | female | 38 | 2010-11-01 | sale | NULL | 2000.35 | 402 | 2 | +----+------------+--------+-----+------------+---------+--------------+----------+--------+-----------+ rows in set (0.00 sec) # 第3页数据 mysql> select * from employee limit 10,5; +----+-----------+--------+-----+------------+-----------+--------------+----------+--------+-----------+ | id | name | sex | age | hire_date | post | post_comment | salary | office | depart_id | +----+-----------+--------+-----+------------+-----------+--------------+----------+--------+-----------+ | 11 | 丁丁 | female | 18 | 2011-03-12 | sale | NULL | 1000.37 | 402 | 2 | | 12 | 星星 | female | 18 | 2016-05-13 | sale | NULL | 3000.29 | 402 | 2 | | 13 | 格格 | female | 28 | 2017-01-27 | sale | NULL | 4000.33 | 402 | 2 | | 14 | 张野 | male | 28 | 2016-03-11 | operation | NULL | 10000.13 | 403 | 3 | | 15 | 程咬金 | male | 18 | 1997-03-12 | operation | NULL | 20000.00 | 403 | 3 | +----+-----------+--------+-----+------------+-----------+--------------+----------+--------+-----------+ rows in set (0.00 sec) 小练习答案
13-多表查询
本节重点:
- 多表连接查询
- 符合条件连接查询
- 子查询
准备工作:准备两张表,部门表(department)、员工表(employee)
create table department( id int, name varchar(20) ); create table employee( id int primary key auto_increment, name varchar(20), sex enum('male','female') not null default 'male', age int, dep_id int ); #插入数据 insert into department values (200,'技术'), (201,'人力资源'), (202,'销售'), (203,'运营'); insert into employee(name,sex,age,dep_id) values ('egon','male',18,200), ('alex','female',48,201), ('wupeiqi','male',38,201), ('yuanhao','female',28,202), ('nvshen','male',18,200), ('xiaomage','female',18,204) ; # 查看表结构和数据 mysql> desc department; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | name | varchar(20) | YES | | NULL | | +-------+-------------+------+-----+---------+-------+ 2 rows in set (0.19 sec) mysql> desc employee; +--------+-----------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------+-----------------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(20) | YES | | NULL | | | sex | enum('male','female') | NO | | male | | | age | int(11) | YES | | NULL | | | dep_id | int(11) | YES | | NULL | | +--------+-----------------------+------+-----+---------+----------------+ 5 rows in set (0.01 sec) mysql> select * from department; +------+--------------+ | id | name | +------+--------------+ | 200 | 技术 | | 201 | 人力资源 | | 202 | 销售 | | 203 | 运营 | +------+--------------+ 4 rows in set (0.02 sec) mysql> select * from employee; +----+----------+--------+------+--------+ | id | name | sex | age | dep_id | +----+----------+--------+------+--------+ | 1 | egon | male | 18 | 200 | | 2 | alex | female | 48 | 201 | | 3 | wupeiqi | male | 38 | 201 | | 4 | yuanhao | female | 28 | 202 | | 5 | nvshen | male | 18 | 200 | | 6 | xiaomage | female | 18 | 204 | +----+----------+--------+------+--------+ 6 rows in set (0.00 sec)
ps:观察两张表,发现department表中id=203部门在employee中没有对应的员工,发现employee中id=6的员工在department表中没有对应关系。
一、多表连接查询
两张表的准备工作已完成,比如现在我要查询的员工信息以及该员工所在的部门。从该题中,我们看出既要查员工又要查该员工的部门,肯定要将两张表进行连接查询,多表连接查询。
重点:外链接语法
语法:
SELECT 字段列表 FROM 表1 INNER|LEFT|RIGHT JOIN 表2 ON 表1.字段 = 表2.字段;
(1)先看第一种情况交叉连接:不适用任何匹配条件。生成笛卡尔积(关于笛卡尔积的含义,大家百度自行补脑)。
mysql> select * from employee,department; +----+----------+--------+------+--------+------+--------------+ | id | name | sex | age | dep_id | id | name | +----+----------+--------+------+--------+------+--------------+ | 1 | egon | male | 18 | 200 | 200 | 技术 | | 1 | egon | male | 18 | 200 | 201 | 人力资源 | | 1 | egon | male | 18 | 200 | 202 | 销售 | | 1 | egon | male | 18 | 200 | 203 | 运营 | | 2 | alex | female | 48 | 201 | 200 | 技术 | | 2 | alex | female | 48 | 201 | 201 | 人力资源 | | 2 | alex | female | 48 | 201 | 202 | 销售 | | 2 | alex | female | 48 | 201 | 203 | 运营 | | 3 | wupeiqi | male | 38 | 201 | 200 | 技术 | | 3 | wupeiqi | male | 38 | 201 | 201 | 人力资源 | | 3 | wupeiqi | male | 38 | 201 | 202 | 销售 | | 3 | wupeiqi | male | 38 | 201 | 203 | 运营 | | 4 | yuanhao | female | 28 | 202 | 200 | 技术 | | 4 | yuanhao | female | 28 | 202 | 201 | 人力资源 | | 4 | yuanhao | female | 28 | 202 | 202 | 销售 | | 4 | yuanhao | female | 28 | 202 | 203 | 运营 | | 5 | nvshen | male | 18 | 200 | 200 | 技术 | | 5 | nvshen | male | 18 | 200 | 201 | 人力资源 | | 5 | nvshen | male | 18 | 200 | 202 | 销售 | | 5 | nvshen | male | 18 | 200 | 203 | 运营 | | 6 | xiaomage | female | 18 | 204 | 200 | 技术 | | 6 | xiaomage | female | 18 | 204 | 201 | 人力资源 | | 6 | xiaomage | female | 18 | 204 | 202 | 销售 | | 6 | xiaomage | female | 18 | 204 | 203 | 运营 |
(2)内连接:只连接匹配的行
#找两张表共有的部分,相当于利用条件从笛卡尔积结果中筛选出了匹配的结果 #department没有204这个部门,因而employee表中关于204这条员工信息没有匹配出来 mysql> select employee.id,employee.name,employee.age,employee.sex,department.name from employee inner join department on employee.dep_id=department.id; +----+---------+------+--------+--------------+ | id | name | age | sex | name | +----+---------+------+--------+--------------+ | 1 | egon | 18 | male | 技术 | | 2 | alex | 48 | female | 人力资源 | | 3 | wupeiqi | 38 | male | 人力资源 | | 4 | yuanhao | 28 | female | 销售 | | 5 | nvshen | 18 | male | 技术 | +----+---------+------+--------+--------------+ 5 rows in set (0.00 sec) #上述sql等同于 mysql> select employee.id,employee.name,employee.age,employee.sex,department.name from employee,department where employee.dep_id=department.id;
(3)外链接之左连接:优先显示左表全部记录
#以左表为准,即找出所有员工信息,当然包括没有部门的员工 #本质就是:在内连接的基础上增加左边有,右边没有的结果 mysql> select employee.id,employee.name,department.name as depart_name from employee left join department on employee.dep_id=department.id; +----+----------+--------------+ | id | name | depart_name | +----+----------+--------------+ | 1 | egon | 技术 | | 5 | nvshen | 技术 | | 2 | alex | 人力资源 | | 3 | wupeiqi | 人力资源 | | 4 | yuanhao | 销售 | | 6 | xiaomage | NULL | +----+----------+--------------+ 6 rows in set (0.00 sec)
(4) 外链接之右连接:优先显示右表全部记录
#以右表为准,即找出所有部门信息,包括没有员工的部门 #本质就是:在内连接的基础上增加右边有,左边没有的结果 mysql> select employee.id,employee.name,department.name as depart_name from employee right join department on employee.dep_id=department.id; +------+---------+--------------+ | id | name | depart_name | +------+---------+--------------+ | 1 | egon | 技术 | | 2 | alex | 人力资源 | | 3 | wupeiqi | 人力资源 | | 4 | yuanhao | 销售 | | 5 | nvshen | 技术 | | NULL | NULL | 运营 | +------+---------+--------------+ 6 rows in set (0.00 sec)
(5) 全外连接:显示左右两个表全部记录(了解)
#外连接:在内连接的基础上增加左边有右边没有的和右边有左边没有的结果 #注意:mysql不支持全外连接 full JOIN #强调:mysql可以使用此种方式间接实现全外连接 语法:select * from employee left join department on employee.dep_id = department.id union all select * from employee right join department on employee.dep_id = department.id; mysql> select * from employee left join department on employee.dep_id = department.id union select * from employee right join department on employee.dep_id = department.id ; +------+----------+--------+------+--------+------+--------------+ | id | name | sex | age | dep_id | id | name | +------+----------+--------+------+--------+------+--------------+ | 1 | egon | male | 18 | 200 | 200 | 技术 | | 5 | nvshen | male | 18 | 200 | 200 | 技术 | | 2 | alex | female | 48 | 201 | 201 | 人力资源 | | 3 | wupeiqi | male | 38 | 201 | 201 | 人力资源 | | 4 | yuanhao | female | 28 | 202 | 202 | 销售 | | 6 | xiaomage | female | 18 | 204 | NULL | NULL | | NULL | NULL | NULL | NULL | NULL | 203 | 运营 | +------+----------+--------+------+--------+------+--------------+ 7 rows in set (0.01 sec) #注意 union与union all的区别:union会去掉相同的纪录
二、符合条件连接查询
示例1:以内连接的方式查询employee和department表,并且employee表中的age字段值必须大于25,即找出年龄大于25岁的员工以及员工所在的部门
select employee.name,department.name from employee inner join department on employee.dep_id = department.id where age > 25;
示例2:以内连接的方式查询employee和department表,并且以age字段的升序方式显示。
select employee.id,employee.name,employee.age,department.name from employee,department where employee.dep_id = department.id and age > 25 order by age asc;
三、子查询
#1:子查询是将一个查询语句嵌套在另一个查询语句中。 #2:内层查询语句的查询结果,可以为外层查询语句提供查询条件。 #3:子查询中可以包含:IN、NOT IN、ANY、ALL、EXISTS 和 NOT EXISTS等关键字 #4:还可以包含比较运算符:= 、 !=、> 、<等
例子:
(1)带in关键字的子查询
#查询平均年龄在25岁以上的部门名 select id,name from department where id in (select dep_id from employee group by dep_id having avg(age) > 25); # 查看技术部员工姓名 select name from employee where dep_id in (select id from department where name='技术'); #查看不足1人的部门名 select name from department where id not in (select dep_id from employee group by dep_id);
(2)带比较运算符的子查询
#比较运算符:=、!=、>、>=、<、<=、<> #查询大于所有人平均年龄的员工名与年龄 mysql> select name,age from employee where age > (select avg(age) from employee); +---------+------+ | name | age | +---------+------+ | alex | 48 | | wupeiqi | 38 | +---------+------+ #查询大于部门内平均年龄的员工名、年龄 思路: (1)先对员工表(employee)中的人员分组(group by),查询出dep_id以及平均年龄。
(2)将查出的结果作为临时表,再对根据临时表的dep_id和employee的dep_id作为筛选条件将employee表和临时表进行内连接。 (3)最后再将employee员工的年龄是大于平均年龄的员工名字和年龄筛选。 mysql> select t1.name,t1.age from employee as t1 inner join (select dep_id,avg(age) as avg_age from employee group by dep_id) as t2 on t1.dep_id = t2.dep_id where t1.age > t2.avg_age; +------+------+ | name | age | +------+------+ | alex | 48 |
(3)带EXISTS关键字的子查询
#EXISTS关字键字表示存在。在使用EXISTS关键字时,内层查询语句不返回查询的记录。而是返回一个真假值。True或False #当返回True时,外层查询语句将进行查询;当返回值为False时,外层查询语句不进行查询 #department表中存在dept_id=203,Ture mysql> select * from employee where exists (select id from department where id=200); +----+----------+--------+------+--------+ | id | name | sex | age | dep_id | +----+----------+--------+------+--------+ | 1 | egon | male | 18 | 200 | | 2 | alex | female | 48 | 201 | | 3 | wupeiqi | male | 38 | 201 | | 4 | yuanhao | female | 28 | 202 | | 5 | nvshen | male | 18 | 200 | | 6 | xiaomage | female | 18 | 204 | +----+----------+--------+------+--------+ #department表中存在dept_id=205,False mysql> select * from employee where exists (select id from department where id=204); Empty set (0.00 sec)
小练习:
查询每个部门最新入职的那位员工
#创建表 create table employee( id int not null unique auto_increment, name varchar(20) not null, sex enum('male','female') not null default 'male', #大部分是男的 age int(3) unsigned not null default 28, hire_date date not null, post varchar(50), post_comment varchar(100), salary double(15,2), office int, #一个部门一个屋子 depart_id int ); #查看表结构 mysql> desc employee; +--------------+-----------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------------+-----------------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(20) | NO | | NULL | | | sex | enum('male','female') | NO | | male | | | age | int(3) unsigned | NO | | 28 | | | hire_date | date | NO | | NULL | | | post | varchar(50) | YES | | NULL | | | post_comment | varchar(100) | YES | | NULL | | | salary | double(15,2) | YES | | NULL | | | office | int(11) | YES | | NULL | | | depart_id | int(11) | YES | | NULL | | +--------------+-----------------------+------+-----+---------+----------------+ #插入记录 #三个部门:教学,销售,运营 insert into employee(name,sex,age,hire_date,post,salary,office,depart_id) values ('egon','male',18,'20170301','老男孩驻沙河办事处外交大使',7300.33,401,1), #以下是教学部 ('alex','male',78,'20150302','teacher',1000000.31,401,1), ('wupeiqi','male',81,'20130305','teacher',8300,401,1), ('yuanhao','male',73,'20140701','teacher',3500,401,1), ('liwenzhou','male',28,'20121101','teacher',2100,401,1), ('jingliyang','female',18,'20110211','teacher',9000,401,1), ('jinxin','male',18,'19000301','teacher',30000,401,1), ('成龙','male',48,'20101111','teacher',10000,401,1), ('歪歪','female',48,'20150311','sale',3000.13,402,2),#以下是销售部门 ('丫丫','female',38,'20101101','sale',2000.35,402,2), ('丁丁','female',18,'20110312','sale',1000.37,402,2), ('星星','female',18,'20160513','sale',3000.29,402,2), ('格格','female',28,'20170127','sale',4000.33,402,2), ('张野','male',28,'20160311','operation',10000.13,403,3), #以下是运营部门 ('程咬金','male',18,'19970312','operation',20000,403,3), ('程咬银','female',18,'20130311','operation',19000,403,3), ('程咬铜','male',18,'20150411','operation',18000,403,3), ('程咬铁','female',18,'20140512','operation',17000,403,3) ;
select * from employee as t1 inner join (select post,max(hire_date) as new_date from employee group by post) as t2 on t1.post=t2.post where t1.hire_date=t2.new_date;
14-补充内容:MySQl创建用户和授权
权限管理
我们知道我们的最高权限管理者是root用户,它拥有着最高的权限操作。包括select、update、delete、update、grant等操作。那么一般情况在公司之后DBA工程师会创建一个用户和密码,让你去连接数据库的操作,并给当前的用户设置某个操作的权限(或者所有权限)。那么这时就需要我们来简单了解一下:
- 如何创建用户和密码
- 给当前的用户授权
- 移除当前用户的权限
如果你想创建一个新的用户,则需要以下操作:
1.进入到mysql数据库下
mysql> use mysql Database changed
2.对新用户增删改
1.创建用户: # 指定ip:192.118.1.1的mjj用户登录 create user 'mjj'@'192.118.1.1' identified by '123'; # 指定ip:192.118.1.开头的mjj用户登录 create user 'mjj'@'192.118.1.%' identified by '123'; # 指定任何ip的mjj用户登录 create user 'mjj'@'%' identified by '123'; 2.删除用户 drop user '用户名'@'IP地址'; 3.修改用户 rename user '用户名'@'IP地址' to '新用户名'@'IP地址'; 4.修改密码 set password for '用户名'@'IP地址'=Password('新密码');
3.对当前的用户授权管理
#查看权限 show grants for '用户'@'IP地址' #授权 mjj用户仅对db1.t1文件有查询、插入和更新的操作 grant select ,insert,update on db1.t1 to "mjj"@'%'; # 表示有所有的权限,除了grant这个命令,这个命令是root才有的。mjj用户对db1下的t1文件有任意操作 grant all privileges on db1.t1 to "mjj"@'%';
#mjj用户对db1数据库中的文件执行任何操作
grant all privileges on db1.* to "mjj"@'%';
#mjj用户对所有数据库中文件有任何操作
grant all privileges on *.* to "mjj"@'%';
#取消权限
# 取消mjj用户对db1的t1文件的任意操作
revoke all on db1.t1 from 'mjj'@"%";
# 取消来自远程服务器的mjj用户对数据库db1的所有表的所有权限
revoke all on db1.* from 'mjj'@"%";
取消来自远程服务器的mjj用户所有数据库的所有的表的权限 revoke all privileges on *.* from 'mjj'@'%';
ps:在公司中,一般情况下是DBA工程师来做这些授权工作。给你一个用户名和密码,你来连接就可以了。
4.MySql备份命令行操作
# 备份:数据表结构+数据 mysqdump -u root db1 > db1.sql -p # 备份:数据表结构 mysqdump -u root -d db1 > db1.sql -p #导入现有的数据到某个数据库 #1.先创建一个新的数据库 create database db10; # 2.将已有的数据库文件导入到db10数据库中 mysqdump -u root -d db10 < db1.sql -p
15-可视化工具Navicat的使用
本节重点:
- 掌握Navicat的基本使用
# PS:在生产环境中操作MySQL数据库还是推荐使用命令行工具mysql,但在我们自己开发测试时,可以使用可视化工具Navicat,以图形界面的形式操作MySQL数据库
官网下载:https://www.navicat.com/en/products/navicat-for-mysql
网盘下载:https://pan.baidu.com/s/1bpo5mqj
需要掌握基本的操作
掌握: #1. 测试+链接数据库 #2. 新建库 #3. 新建表,新增字段+类型+约束 #4. 设计表:外键 #5. 新建查询 #6. 备份库/表 #注意: 批量加注释:ctrl+?键 批量去注释:ctrl+shift+?键
16-pymysql模块的使用
本节重点:
- pymysql的下载和使用
- execute()之sql注入
- 增、删、改:conn.commit()
- 查:fetchone、fetchmany、fetchall
一、pymysql的下载和使用
之前我们都是通过MySQL自带的命令行客户端工具mysql来操作数据库,那如何在python程序中操作数据库呢?这就用到了pymysql模块,该模块本质就是一个套接字客户端软件,使用前需要事先安装。
(1)pymysql模块的下载
pip3 install pymysql
(2)pymysql的使用
数据库和数据都已存在
# 实现:使用Python实现用户登录,如果用户存在则登录成功(假设该用户已在数据库中) import pymysql user = input('请输入用户名:') pwd = input('请输入密码:') # 1.连接 conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='', db='db8', charset='utf8') # 2.创建游标 cursor = conn.cursor() #注意%s需要加引号 sql = "select * from userinfo where username='%s' and pwd='%s'" %(user, pwd) print(sql) # 3.执行sql语句 cursor.execute(sql) result=cursor.execute(sql) #执行sql语句,返回sql查询成功的记录数目 print(result) # 关闭连接,游标和连接都要关闭 cursor.close() conn.close() if result: print('登陆成功') else: print('登录失败')
二、execute()之sql注入
最后那一个空格,在一条sql语句中如果遇到select * from userinfo where username='mjj' -- asadasdas' and pwd='' 则--之后的条件被注释掉了(注意--后面还有一个空格) #1、sql注入之:用户存在,绕过密码 mjj' -- 任意字符 #2、sql注入之:用户不存在,绕过用户与密码 xxx' or 1=1 -- 任意字符
解决方法:
# 原来是我们对sql进行字符串拼接 # sql="select * from userinfo where name='%s' and password='%s'" %(username,pwd) # print(sql) # result=cursor.execute(sql) #改写为(execute帮我们做字符串拼接,我们无需且一定不能再为%s加引号了) sql="select * from userinfo where name=%s and password=%s" #!!!注意%s需要去掉引号,因为pymysql会自动为我们加上 result=cursor.execute(sql,[user,pwd]) #pymysql模块自动帮我们解决sql注入的问题,只要我们按照pymysql的规矩来。
三、增、删、改:conn.commit()
commit()方法:在数据库里增、删、改的时候,必须要进行提交,否则插入的数据不生效。
import pymysql username = input('请输入用户名:') pwd = input('请输入密码:') # 1.连接 conn = pymysql.connect(host='localhost', port=3306, user='root', password='', db='db8', charset='utf8') # 2.创建游标 cursor = conn.cursor() # 操作 # 增 # sql = "insert into userinfo(username,pwd) values (%s,%s)"
# effect_row = cursor.execute(sql,(username,pwd))
#同时插入多条数据
#cursor.executemany(sql,[('李四','110'),('王五','119')])
# print(effect_row)# # 改 # sql = "update userinfo set username = %s where id = 2" # effect_row = cursor.execute(sql,username) # print(effect_row) # 删 sql = "delete from userinfo where id = 2" effect_row = cursor.execute(sql) print(effect_row) #一定记得commit conn.commit() # 4.关闭游标 cursor.close() # 5.关闭连接 conn.close()
四、查:fetchone、fetchmany、fetchall
fetchone():获取下一行数据,第一次为首行; fetchall():获取所有行数据源 fetchmany(4):获取4行数据
查看一下表内容:
mysql> select * from userinfo; +----+----------+-----+ | id | username | pwd | +----+----------+-----+ | 1 | mjj | 123 | | 3 | 张三 | 110 | | 4 | 李四 | 119 | +----+----------+-----+ 3 rows in set (0.00 sec)
使用fetchone():
import pymysql # 1.连接 conn = pymysql.connect(host='localhost', port=3306, user='root', password='', db='db8', charset='utf8') # 2.创建游标 cursor = conn.cursor() sql = 'select * from userinfo' cursor.execute(sql) # 查询第一行的数据 row = cursor.fetchone() print(row) # (1, 'mjj', '123') # 查询第二行数据 row = cursor.fetchone() print(row) # (3, '张三', '110') # 4.关闭游标 cursor.close() # 5.关闭连接 conn.close()
使用fetchall():
import pymysql # 1.连接 conn = pymysql.connect(host='localhost', port=3306, user='root', password='', db='db8', charset='utf8') # 2.创建游标 cursor = conn.cursor() sql = 'select * from userinfo' cursor.execute(sql) # 获取所有的数据 rows = cursor.fetchall() print(rows) # 4.关闭游标 cursor.close() # 5.关闭连接 conn.close() #运行结果 ((1, 'mjj', '123'), (3, '张三', '110'), (4, '李四', '119'))
默认情况下,我们获取到的返回值是元组,只能看到每行的数据,却不知道每一列代表的是什么,这个时候可以使用以下方式来返回字典,每一行的数据都会生成一个字典:
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) #在实例化的时候,将属性cursor设置为pymysql.cursors.DictCursor
在fetchone示例中,在获取行数据的时候,可以理解开始的时候,有一个行指针指着第一行的上方,获取一行,它就向下移动一行,所以当行指针到最后一行的时候,就不能再获取到行的内容,所以我们可以使用如下方法来移动行指针:
cursor.scroll(1,mode='relative') # 相对当前位置移动 cursor.scroll(2,mode='absolute') # 相对绝对位置移动 第一个值为移动的行数,整数为向下移动,负数为向上移动,mode指定了是相对当前位置移动,还是相对于首行移动
# 1.Python实现用户登录 # 2.Mysql保存数据 import pymysql # 1.连接 conn = pymysql.connect(host='localhost', port=3306, user='root', password='', db='db8', charset='utf8') # 2.创建游标 cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) sql = 'select * from userinfo' cursor.execute(sql) # 查询第一行的数据 row = cursor.fetchone() print(row) # (1, 'mjj', '123') # 查询第二行数据 row = cursor.fetchone() # (3, '张三', '110') print(row) cursor.scroll(-1,mode='relative') #设置之后,光标相对于当前位置往前移动了一行,所以打印的结果为第二行的数据 row = cursor.fetchone() print(row) cursor.scroll(0,mode='absolute') #设置之后,光标相对于首行没有任何变化,所以打印的结果为第一行数据 row = cursor.fetchone() print(row) # 4.关闭游标 cursor.close() # 5.关闭连接 conn.close() #结果如下 {'id': 1, 'username': 'mjj', 'pwd': '123'} {'id': 3, 'username': '张三', 'pwd': '110'} {'id': 3, 'username': '张三', 'pwd': '110'} {'id': 1, 'username': 'mjj', 'pwd': '123'}
fetchmany():
import pymysql # 1.连接 conn = pymysql.connect(host='localhost', port=3306, user='root', password='', db='db8', charset='utf8') # 2.创建游标 cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) sql = 'select * from userinfo' cursor.execute(sql) # 获取2条数据 rows = cursor.fetchmany(2) print(rows) # 4.关闭游标 # rows = cursor.fetchall() # print(rows) cursor.close() # 5.关闭连接 conn.close() #结果如下: [{'id': 1, 'username': 'mjj', 'pwd': '123'}, {'id': 3, 'username': '张三', 'pwd': '110'}]
17-视图
一、视图的定义
视图是虚拟表或逻辑表,它被定义为具有连接的SQL SELECT查询语句。因为数据库视图与数据库表类似,它由行和列组成,因此可以根据数据库表查询数据。其内容由查询定义。
但是,视图并不在数据库中以存储的数据值集形式存在,行和列数据来自由定义视图的查询所引用的表,并且在引用视图时动态生成。简单的来说视图是由其定义结果组成的表;
二、视图的优点
1、数据库视图允许简化复杂查询,通过数据库视图,您只需使用简单的SQL语句,而不是使用具有多个连接的复杂的SQL语句。
2、安全性。一般是这样做的:创建一个视图,定义好该视图所操作的数据。之后将用户权限与视图绑定。这样的方式是使用到了一个特性:grant语句可以针对视图进行授予权限。
三、视图的缺点
1、性能:从数据库视图查询数据可能会很慢,特别是如果视图是基于其他视图创建的。
2、表依赖关系:将根据数据库的基础表创建一个视图。每当更改与其相关联的表的结构时,都必须更改视图。
四、创建视图
语法:
CREATE VIEW 视图名称 AS SQL语句
临时表应用举例:
#两张有关系的表 mysql> select * from course; +-----+--------+------------+ | cid | cname | teacher_id | +-----+--------+------------+ | 1 | 生物 | 1 | | 2 | 物理 | 2 | | 3 | 体育 | 3 | | 4 | 美术 | 2 | +-----+--------+------------+ rows in set (0.00 sec) mysql> select * from teacher; +-----+-----------------+ | tid | tname | +-----+-----------------+ | 1 | 张磊老师 | | 2 | 李平老师 | | 3 | 刘海燕老师 | | 4 | 朱云海老师 | | 5 | 李杰老师 | +-----+-----------------+ rows in set (0.00 sec) #查询李平老师教授的课程名 mysql> select cname from course where teacher_id = (select tid from teacher where tname='李平老师'); +--------+ | cname | +--------+ | 物理 | | 美术 | +--------+ rows in set (0.00 sec) #子查询出临时表,作为teacher_id等判断依据 select tid from teacher where tname='李平老师' 临时表应用举例
视图的创建:
create view teacher_view as select tid from teacher where tname='李平老师'; #于是查询李平老师教授的课程名的sql可以改写为 mysql> select cname from course where teacher_id = (select tid from teacher_view); +--------+ | cname | +--------+ | 物理 | | 美术 | +--------+ rows in set (0.00 sec)
五、使用视图
# 往真实表中插入一条数据,查看一下视图,发现视图表也会跟着更新 insert into course(cname,teacher_id) values('张三丰',2); # 更新一下数据,发现视图的数据也会跟着更新 update course set cname='王五';
不能修改视图的数据,验证为例:
mysql> create view tt as select * from course left join teacher on teacher.tid = course.teacher_id; Query OK, 0 rows affected (0.02 sec) mysql> select * from tt; +-----+--------+------------+------+-----------------+ | cid | cname | teacher_id | tid | tname | +-----+--------+------------+------+-----------------+ | 1 | 王五 | 1 | 1 | 张磊老师 | | 2 | 王五 | 2 | 2 | 丽萍老师 | | 4 | 王五 | 2 | 2 | 丽萍老师 | | 5 | 王五 | 2 | 2 | 丽萍老师 | | 6 | 王五 | 2 | 2 | 丽萍老师 | | 3 | 王五 | 3 | 3 | 王海燕老师 | +-----+--------+------------+------+-----------------+ 6 rows in set (0.01 sec) mysql> insert into tt values(7,'哈哈',2,4,'张三丰老师'); ERROR 1471 (HY000): The target table tt of the INSERT is not insertable-into
六、修改视图
# 语法:ALTER VIEW 视图名称 AS SQL语句 mysql> alter view teacher_view as select * from course where cid>3; Query OK, 0 rows affected (0.04 sec) mysql> select * from teacher_view; +-----+-------+------------+ | cid | cname | teacher_id | +-----+-------+------------+ | 4 | xxx | 2 | | 5 | yyy | 2 | +-----+-------+------------+ rows in set (0.00 sec)
七、删除视图
# 语法:DROP VIEW 视图名称 DROP VIEW teacher_view
18-触发器
使用触发器可以定制用户对表进行【增、删、改】操作时前后的行为,注意:没有查询
一、创建触发器
# 插入前 CREATE TRIGGER tri_before_insert_tb1 BEFORE INSERT ON tb1 FOR EACH ROW BEGIN ... END # 插入后 CREATE TRIGGER tri_after_insert_tb1 AFTER INSERT ON tb1 FOR EACH ROW BEGIN ... END # 删除前 CREATE TRIGGER tri_before_delete_tb1 BEFORE DELETE ON tb1 FOR EACH ROW BEGIN ... END # 删除后 CREATE TRIGGER tri_after_delete_tb1 AFTER DELETE ON tb1 FOR EACH ROW BEGIN ... END # 更新前 CREATE TRIGGER tri_before_update_tb1 BEFORE UPDATE ON tb1 FOR EACH ROW BEGIN ... END # 更新后 CREATE TRIGGER tri_after_update_tb1 AFTER UPDATE ON tb1 FOR EACH ROW BEGIN ... END
例子:用户和日志表。每次创建一个用户之后,就在日志布表中生成这条记录
准备表:
# 创建用户表
create table user(
id int primary key auto_increment,
name varchar(20) not null,
reg_time datetime, # 注册用户的时间
affirm enum('yes','no') # no表示该用户执行失败
);
#创建日志表
create table userLog(
id int primary key auto_increment,
u_name varchar(20) not null,
u_reg_time datetime # 注册用户的时间
);
# 创建触发器 delimiter 默认情况下,delimiter是分号 触发器名称应遵循命名约定[trigger time]_[table name]_[trigger event]
delimiter //
create trigger after_user_insert after insert on user for each row
begin
if new.affirm = 'yes' then
insert into userLog(u_name,u_reg_time) values(new.name,new.reg_time);
end if;
end //
delimiter ;
#往用户表中插入记录,触发触发器,根据if的条件决定是否插入数据
insert into user(name,reg_time,affirm) values ('张三',now(),'yes'),('李四',now(),'yes'),('王五',now(),'no');
# 查看日志表,发现多了两条记录 ,大家应该看到for each row就明白了
mysql> select * from userlog;
+----+--------+---------------------+
| id | u_name | u_reg_time |
+----+--------+---------------------+
| 1 | 张三 | 2018-06-14 17:52:49 |
| 2 | 李四 | 2018-06-14 17:52:49 |
+----+--------+---------------------+
2 rows in set (0.00 sec)
注意:请注意,在为INSERT定义的触发器中,可以仅使用NEW
关键字。不能使用OLD
关键字。但是,在为DELETE
定义的触发器中,没有新行,因此您只能使用OLD
关键字。在UPDATE触发器中,OLD
是指更新前的行,而NEW
是更新后的行
二 使用触发器
触发器无法由用户直接调用,而知由于对表的【增/删/改】操作被动引发的。
三 删除触发器
drop trigger trigger_userLog;
19-函数
MySQL中提供了许多内置函数:
一、数学函数 ROUND(x,y) 返回参数x的四舍五入的有y位小数的值 RAND() 返回0到1内的随机值,可以通过提供一个参数(种子)使RAND()随机数生成器生成一个指定的值。 二、聚合函数(常用于GROUP BY从句的SELECT查询中) AVG(col)返回指定列的平均值 COUNT(col)返回指定列中非NULL值的个数 MIN(col)返回指定列的最小值 MAX(col)返回指定列的最大值 SUM(col)返回指定列的所有值之和 GROUP_CONCAT(col) 返回由属于一组的列值连接组合而成的结果 三、字符串函数 CHAR_LENGTH(str) 返回值为字符串str 的长度,长度的单位为字符。一个多字节字符算作一个单字符。 CONCAT(str1,str2,...) 字符串拼接 如有任何一个参数为NULL ,则返回值为 NULL。 CONCAT_WS(separator,str1,str2,...) 字符串拼接(自定义连接符) CONCAT_WS()不会忽略任何空字符串。 (然而会忽略所有的 NULL)。 CONV(N,from_base,to_base) 进制转换 例如: SELECT CONV('a',16,2); 表示将 a 由16进制转换为2进制字符串表示 FORMAT(X,D) 将数字X 的格式写为'#,###,###.##',以四舍五入的方式保留小数点后 D 位, 并将结果以字符串的形式返回。若 D 为 0, 则返回结果不带有小数点,或不含小数部分。 例如: SELECT FORMAT(12332.1,4); 结果为: '12,332.1000' INSERT(str,pos,len,newstr) 在str的指定位置插入字符串 pos:要替换位置其实位置 len:替换的长度 newstr:新字符串 特别的: 如果pos超过原字符串长度,则返回原字符串 如果len超过原字符串长度,则由新字符串完全替换 INSTR(str,substr) 返回字符串 str 中子字符串的第一个出现位置。 LEFT(str,len) 返回字符串str 从开始的len位置的子序列字符。 LOWER(str) 变小写 UPPER(str) 变大写 REVERSE(str) 返回字符串 str ,顺序和字符顺序相反。 SUBSTRING(str,pos) , SUBSTRING(str FROM pos) SUBSTRING(str,pos,len) , SUBSTRING(str FROM pos FOR len) 不带有len 参数的格式从字符串str返回一个子字符串,起始于位置 pos。带有len参数的格式从字符串str返回一个长度同len字符相同的子字符串,起始于位置 pos。 使用 FROM的格式为标准 SQL 语法。也可能对pos使用一个负值。假若这样,则子字符串的位置起始于字符串结尾的pos 字符,而不是字符串的开头位置。在以下格式的函数中可以对pos 使用一个负值。 mysql> SELECT SUBSTRING('Quadratically',5); -> 'ratically' mysql> SELECT SUBSTRING('foobarbar' FROM 4); -> 'barbar' mysql> SELECT SUBSTRING('Quadratically',5,6); -> 'ratica' mysql> SELECT SUBSTRING('Sakila', -3); -> 'ila' mysql> SELECT SUBSTRING('Sakila', -5, 3); -> 'aki' mysql> SELECT SUBSTRING('Sakila' FROM -4 FOR 2); -> 'ki' 四、日期和时间函数 CURDATE()或CURRENT_DATE() 返回当前的日期 CURTIME()或CURRENT_TIME() 返回当前的时间 DAYOFWEEK(date) 返回date所代表的一星期中的第几天(1~7) DAYOFMONTH(date) 返回date是一个月的第几天(1~31) DAYOFYEAR(date) 返回date是一年的第几天(1~366) DAYNAME(date) 返回date的星期名,如:SELECT DAYNAME(CURRENT_DATE); FROM_UNIXTIME(ts,fmt) 根据指定的fmt格式,格式化UNIX时间戳ts HOUR(time) 返回time的小时值(0~23) MINUTE(time) 返回time的分钟值(0~59) MONTH(date) 返回date的月份值(1~12) MONTHNAME(date) 返回date的月份名,如:SELECT MONTHNAME(CURRENT_DATE); NOW() 返回当前的日期和时间 QUARTER(date) 返回date在一年中的季度(1~4),如SELECT QUARTER(CURRENT_DATE); WEEK(date) 返回日期date为一年中第几周(0~53) YEAR(date) 返回日期date的年份(1000~9999) 重点: DATE_FORMAT(date,format) 根据format字符串格式化date值 mysql> SELECT DATE_FORMAT('2009-10-04 22:23:00', '%W %M %Y'); -> 'Sunday October 2009' mysql> SELECT DATE_FORMAT('2007-10-04 22:23:00', '%H:%i:%s'); -> '22:23:00' mysql> SELECT DATE_FORMAT('1900-10-04 22:23:00', -> '%D %y %a %d %m %b %j'); -> '4th 00 Thu 04 10 Oct 277' mysql> SELECT DATE_FORMAT('1997-10-04 22:23:00', -> '%H %k %I %r %T %S %w'); -> '22 22 10 10:23:00 PM 22:23:00 00 6' mysql> SELECT DATE_FORMAT('1999-01-01', '%X %V'); -> '1998 52' mysql> SELECT DATE_FORMAT('2006-06-00', '%d'); -> '00' 五、加密函数 MD5() 计算字符串str的MD5校验和 PASSWORD(str) 返回字符串str的加密版本,这个加密过程是不可逆转的,和UNIX密码加密过程使用不同的算法。 六、控制流函数 CASE WHEN[test1] THEN [result1]...ELSE [default] END 如果testN是真,则返回resultN,否则返回default CASE [test] WHEN[val1] THEN [result]...ELSE [default]END 如果test和valN相等,则返回resultN,否则返回default IF(test,t,f) 如果test是真,返回t;否则返回f IFNULL(arg1,arg2) 如果arg1不是空,返回arg1,否则返回arg2 NULLIF(arg1,arg2) 如果arg1=arg2返回NULL;否则返回arg1 内置函数
掌握内置函数中的时间格式化DATE_FORMAT()的用法
官网示例:
mysql> SELECT DATE_FORMAT('2009-10-04 22:23:00', '%W %M %Y'); -> 'Sunday October 2009' mysql> SELECT DATE_FORMAT('2007-10-04 22:23:00', '%H:%i:%s'); -> '22:23:00' mysql> SELECT DATE_FORMAT('1900-10-04 22:23:00', -> '%D %y %a %d %m %b %j'); -> '4th 00 Thu 04 10 Oct 277' mysql> SELECT DATE_FORMAT('1997-10-04 22:23:00', -> '%H %k %I %r %T %S %w'); -> '22 22 10 10:23:00 PM 22:23:00 00 6' mysql> SELECT DATE_FORMAT('1999-01-01', '%X %V'); -> '1998 52' mysql> SELECT DATE_FORMAT('2006-06-00', '%d'); -> '00'
举例:
# 1、博客园 时间处理 CREATE TABLE blog ( id INT PRIMARY KEY auto_increment, NAME CHAR (32), sub_time datetime ); #2、插入数据 INSERT INTO blog (NAME, sub_time) VALUES ('第1篇','2015-03-01 11:31:21'), ('第2篇','2015-03-11 16:31:21'), ('第3篇','2016-07-01 10:21:31'), ('第4篇','2016-07-22 09:23:21'), ('第5篇','2016-07-23 10:11:11'), ('第6篇','2016-07-25 11:21:31'), ('第7篇','2017-03-01 15:33:21'), ('第8篇','2017-03-01 17:32:21'), ('第9篇','2017-03-01 18:31:21'); #3. 提取sub_time字段的值,按照格式后的结果即"年月"来分组 SELECT DATE_FORMAT(sub_time,'%Y-%m') as y_m,COUNT(1) as num FROM blog GROUP BY DATE_FORMAT(sub_time,'%Y-%m'); # 结果: +---------+-----+ | y_m | num | +---------+-----+ | 2015-03 | 2 | | 2016-07 | 4 | | 2017-03 | 3 | +---------+-----+ 3 rows in set (0.00 sec)
20-存储过程
一、存储过程的定义
存储过程是存储在数据库目录中的一坨的声明性SQL语句。 Java,Python,PHP等应用程序可以调用存储过程。
MySQL是最受欢迎的开源RDBMS,被社区和企业广泛使用。 然而,在它发布的第一个十年期间,它不支持存储过程,存储函数,触发器和事件。自从MySQL 5.0版本以来,这些功能被添加到MySQL数据库引擎,使其更加灵活和强大。
二、存储过程的优点
#1、通常存储过程有助于提高应用程序的性能。当创建,存储过程被编译之后,就存储在数据库中。 但是,MySQL实现的存储过程略有不同。 MySQL存储过程按需编译。 在编译存储过程之后,MySQL将其放入缓存中。 MySQL为每个连接维护自己的存储过程高速缓存。 如果应用程序在单个连接中多次使用存储过程,则使用编译版本,否则存储过程的工作方式类似于查询。 # 2、存储过程有助于减少应用程序和数据库服务器之间的流量,因为应用程序不必发送多个冗长的SQL语句,而只能发送存储过程的名称和参数。 #3、存储的程序对任何应用程序都是可重用的和透明的。 存储过程将数据库接口暴露给所有应用程序,以便开发人员不必开发存储过程中已支持的功能。 #4、存储的程序是安全的。 数据库管理员可以向访问数据库中存储过程的应用程序授予适当的权限,而不向基础数据库表提供任何权限。
除了这些优点之外,存储过程有其自身的缺点,在数据库中使用它们之前,您应该注意这些缺点。
三、存储过程的缺点
#1、如果使用大量存储过程,那么使用这些存储过程的每个连接的内存使用量将会大大增加。 此外,如果您在存储过程中过度使用大量逻辑操作,则CPU使用率也会增加,因为数据库服务器的设计不当于逻辑运算。 #2、存储过程的构造使得开发具有复杂业务逻辑的存储过程变得更加困难。 #3、很难调试存储过程。只有少数数据库管理系统允许您调试存储过程。不幸的是,MySQL不提供调试存储过程的功能。 #4、开发和维护存储过程并不容易。开发和维护存储过程通常需要一个不是所有应用程序开发人员拥有的专业技能。这可能会导致应用程序开发和维护阶段的问题。
MySQL存储过程有自己的优点和缺点。开发应用程序时,您应该决定是否应该或不应该根据业务需求使用存储过程。
四、一个简单的Mysql存储过程示例
delimiter // create procedure b1() begin select * from blog; end // delimiter ;
解释:
1.第一个命令是delimiter //,它与存储过程语法无关。 delimter语句将标准分隔符 - 分号(;)更改为://。 在这种情况下,分隔符从分号(;)更改为双斜杠//。为什么我们必须更改分隔符? 因为我们想将存储过程作为整体传递给服务器,而不是让mysql工具一次解释每个语句。 在END关键字之后,使用分隔符//来指示存储过程的结束。 最后一个命令(DELIMITER;)将分隔符更改回分号(;)。
2.使用create procedure语句创建一个新的存储过程。在create procedure语句之后指定存储过程的名称。在这个示例中,存储过程的名称为:b1,并把括号放在存储过程的名字之后。
3.begin和end之间的部分称为存储过程的主体。将声明性SQL语句放在主体中以处理业务逻辑。 在这个存储过程中,我们使用一个简单的select语句来查询blog表中的数据。
# mysql中调用存储过程 call b1() #在python中基于pymysql调用 cursor.callproc('b1') print(cursor.fetchall())
五、声明变量
要在存储过程中声明变量,可以使用delclare语句,如下
DECLARE variable_name datatype(size) DEFAULT default_value;
下面来更详细地解释上面的语句:
首先,在DECLARE关键字后面要指定变量名。变量名必须遵循MySQL表列名称的命名规则。
其次,指定变量的数据类型及其大小。变量可以有任何MySQL数据类型,如INT,VARCHAR,DATETIME等。
第三,当声明一个变量时,它的初始值为NULL。但是可以使用DEFAULT关键字为变量分配默认值。
实现:
delimiter // create procedure b2() begin DECLARE n int DEFAULT 1; set n = 5; select * from blog where id = n; end // delimiter ; # mysql中调用存储过程 call b2();
六、存储过程传参
在现实应用中,开发的存储过程几乎都需要参数。这些参数使存储过程更加灵活和有用。 在MySQL中,参数有三种模式:IN,OUT或INOUT。
IN - 是默认模式。在存储过程中定义IN参数时,调用程序必须将参数传递给存储过程。 另外,IN参数的值被保护。这意味着即使在存储过程中更改了IN参数的值,在存储过程结束后仍保留其原始值。换句话说,存储过程只使用IN参数的副本。
OUT - 可以在存储过程中更改OUT参数的值,并将其更改后新值传递回调用程序。请注意,存储过程在启动时无法访问OUT参数的初始值。
INOUT - INOUT参数是IN和OUT参数的组合。这意味着调用程序可以传递参数,并且存储过程可以修改INOUT参数并将新值传递回调用程序。
在存储过程中定义参数的语法如下:
MODE param_name param_type(param_size)
根据存储过程中参数的目的,MODE可以是IN,OUT或INOUT。
param_name是参数的名称。参数的名称必须遵循MySQL中列名的命名规则。
在参数名之后是它的数据类型和大小。和变量一样,参数的数据类型可以是任何有效的MySQL数据类型
ps:如果存储过程有多个参数,则每个参数由逗号(,)分隔。
# 1.in delimiter // create procedure b3( in blogName varchar(30) ) begin select * from blog where NAME = blogName; end // delimiter ; #mysql中调用存储过程 call b3('第5篇'); #python中调用存储过程 cursor.callproc('b3',args = ('第5篇')); # 2.out delimiter // create procedure b4( in year int, out count int ) begin SELECT COUNT(1) into count FROM blog GROUP BY DATE_FORMAT(sub_time,'%Y') having max(DATE_FORMAT(sub_time,'%Y')) = year ; set count = 6; end // delimiter ; call b4(2016,@count); select @count; #out只能当返回值 # 3.inout delimiter // create procedure b5( inout n1 int ) begin select * from blog where id > n1; end // delimiter ; #mysql中调用
set @n = 3;
call b5(@n);
select @n;
#在python中基于pymysql调用 cursor.callproc('b5',(4)) print(cursor.fetchall()) #查询select的查询结果 cursor.execute('select @n1') print(cursor.fetchall()) # inout:既可以传入又可以返回
21-事务
事务用于将某些操作的多个SQL作为原子性操作,一旦有某一个出现错误,即可回滚到原来的状态,从而保证数据库数据完整性。
举例说明:
create table user2( id int primary key auto_increment, name char(32), balance int ); insert into user2(name,balance) values ('wsb',1000), ('egon',1000), ('ysb',1000); #原子操作 start transaction; update user2 set balance=900 where name='wsb'; #买支付100元 update user2 set balance=1010 where name='egon'; #中介拿走10元 update user2 set balance=1090 where name='ysb'; #卖家拿到90元 commit; #出现异常,回滚到初始状态 start transaction; update user2 set balance=900 where name='wsb'; #买支付100元 update user2 set balance=1010 where name='egon'; #中介拿走10元 uppdate user2 set balance=1090 where name='ysb'; #卖家拿到90元,出现异常没有拿到 rollback; mysql> select * from user; +----+------+---------+ | id | name | balance | +----+------+---------+ | 1 | wsb | 1000 | | 2 | egon | 1000 | | 3 | ysb | 1000 | +----+------+---------+ rows in set (0.00 sec)
下面是操作:当p_return_code为1时,表示异常,立马回滚。当为2时,出现警告,立马回滚原始状态。0表示成功
delimiter // create PROCEDURE b6( OUT p_return_code tinyint ) BEGIN DECLARE exit handler for sqlexception BEGIN -- ERROR set p_return_code = 1; rollback; END; DECLARE exit handler for sqlwarning BEGIN -- WARNING set p_return_code = 2; rollback; END; START TRANSACTION; insert into blog(name,sub_time) values('yyy',now()); COMMIT; -- SUCCESS set p_return_code = 0; #0代表执行成功 END // delimiter ; set @res=123; call b6(@res); select @res;
22-索引
一、索引的介绍
数据库中专门用于帮助用户快速查找数据的一种数据结构。类似于字典中的目录,查找字典内容时可以根据目录查找到数据的存放位置吗,然后直接获取。
二 、索引的作用
约束和加速查找
三、常见的几种索引:
- 普通索引
- 唯一索引
- 主键索引
- 联合索引(多列)
- 联合主键索引
- 联合唯一索引
- 联合普通索引
无索引: 从前往后一条一条查询
有索引:创建索引的本质,就是创建额外的文件(某种格式存储,查询的时候,先去格外的文件找,定好位置,然后再去原始表中直接查询。但是创建索引越多,会对硬盘也是有损耗。
建立索引的目的:
a.额外的文件保存特殊的数据结构
b.查询快,但是插入更新删除依然慢
c.创建索引之后,必须命中索引才能有效
无索引和有索引的区别以及建立索引的目的
hash索引和BTree索引 (1)hash类型的索引:查询单条快,范围查询慢 (2)btree类型的索引:b+树,层数越多,数据量指数级增长(我们就用它,因为innodb默认支持它)
3.1 普通索引
作用:仅有一个加速查找
create table userinfo( nid int not null auto_increment primary key, name varchar(32) not null, email varchar(64) not null, index ix_name(name) ); 创建表+普通索引
create index 索引的名字 on 表名(列名)
drop index 索引的名字 on 表名
show index from 表名
3.2 唯一索引
唯一索引有两个功能:加速查找和唯一约束(可含null)
create table userinfo( id int not null auto_increment primary key, name varchar(32) not null, email varchar(64) not null, unique index ix_name(name) ); 创建表+唯一索引
create unique index 索引名 on 表名(列名)
drop index 索引名 on 表名;
3.3 主键索引
主键索引有两个功能: 加速查找和唯一约束(不含null)
create table userinfo( id int not null auto_increment primary key, name varchar(32) not null, email varchar(64) not null, unique index ix_name(name) ) or create table userinfo( id int not null auto_increment, name varchar(32) not null, email varchar(64) not null, primary key(nid), unique index ix_name(name) ) 创建表+主键索引
alter table 表名 add primary key(列名);
alter table 表名 drop primary key; alter table 表名 modify 列名 int, drop primary key;
3.4 组合索引
组合索引是将n个列组合成一个索引
其应用场景为:频繁的同时使用n列来进行查询,如:where name = 'alex' and email = 'alex@qq.com'。
create index 索引名 on 表名(列名1,列名2);
四、索引名词
#覆盖索引:在索引文件中直接获取数据 例如: select name from userinfo where name = 'alex50000'; #索引合并:把多个单列索引合并成使用 例如: select * from userinfo where name = 'alex13131' and id = 13131;
六、正确使用索引的情况
数据库表中添加索引后确实会让查询速度起飞,但前提必须是正确的使用索引来查询,如果以错误的方式使用,则即使建立索引也会不奏效。
使用索引,我们必须知道:
(1)创建索引
(2)命中索引
(3)正确使用索引
准备:
#1. 准备表 create table userinfo( id int, name varchar(20), gender char(6), email varchar(50) ); #2. 创建存储过程,实现批量插入记录 delimiter $$ #声明存储过程的结束符号为$$ create procedure auto_insert1() BEGIN declare i int default 1; while(i<3000000)do insert into userinfo values(i,concat('alex',i),'male',concat('egon',i,'@oldboy')); set i=i+1; end while; END$$ #$$结束 delimiter ; #重新声明分号为结束符号 #3. 查看存储过程 show create procedure auto_insert1G #4. 调用存储过程 call auto_insert1(); 准备300w条数据
测试:
- like '%xx' select * from userinfo where name like '%al'; - 使用函数 select * from userinfo where reverse(name) = 'alex333'; - or select * from userinfo where id = 1 or email = 'alex122@oldbody'; 特别的:当or条件中有未建立索引的列才失效,以下会走索引 select * from userinfo where id = 1 or name = 'alex1222'; select * from userinfo where id = 1 or email = 'alex122@oldbody' and name = 'alex112' - 类型不一致 如果列是字符串类型,传入条件是必须用引号引起来,不然... select * from userinfo where name = 999; - != select count(*) from userinfo where name != 'alex' 特别的:如果是主键,则还是会走索引 select count(*) from userinfo where id != 123 - > select * from userinfo where name > 'alex' 特别的:如果是主键或索引是整数类型,则还是会走索引 select * from userinfo where id > 123 select * from userinfo where num > 123 - order by select email from userinfo order by name desc; 当根据索引排序时候,选择的映射如果不是索引,则不走索引 特别的:如果对主键排序,则还是走索引: select * from userinfo order by nid desc; - 组合索引最左前缀 如果组合索引为:(name,email) name and email -- 使用索引 name -- 使用索引 email -- 不使用索引
什么是最左前缀呢?
最左前缀匹配: create index ix_name_email on userinfo(name,email); select * from userinfo where name = 'alex'; select * from userinfo where name = 'alex' and email='alex@oldBody'; select * from userinfo where email='alex@oldBody'; 如果使用组合索引如上,name和email组合索引之后,查询 (1)name和email ---使用索引 (2)name ---使用索引 (3)email ---不适用索引 对于同时搜索n个条件时,组合索引的性能好于多个单列索引
******组合索引的性能>索引合并的性能*********
七、索引的注意事项
(1)避免使用select * (2)count(1)或count(列) 代替count(*) (3)创建表时尽量使用char代替varchar (4)表的字段顺序固定长度的字段优先 (5)组合索引代替多个单列索引(经常使用多个条件查询时) (6)尽量使用短索引 (create index ix_title on tb(title(16));特殊的数据类型 text类型) (7)使用连接(join)来代替子查询 (8)连表时注意条件类型需一致 (9)索引散列(重复少)不适用于建索引,例如:性别不合适
八、执行计划
explain + 查询SQL - 用于显示SQL执行信息参数,根据参考信息可以进行SQL优化
mysql> explain select * from userinfo; +----+-------------+----------+------+---------------+------+---------+------+---------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------+------+---------------+------+---------+------+---------+-------+ | 1 | SIMPLE | userinfo | ALL | NULL | NULL | NULL | NULL | 2973016 | NULL | +----+-------------+----------+------+---------------+------+---------+------+---------+-------+ mysql> explain select * from (select id,name from userinfo where id <20) as A; +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+ | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 19 | NULL | | 2 | DERIVED | userinfo | range | PRIMARY | PRIMARY | 4 | NULL | 19 | Using where | +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+ 2 rows in set (0.05 sec)
参数说明:
select_type: 查询类型 SIMPLE 简单查询 PRIMARY 最外层查询 SUBQUERY 映射为子查询 DERIVED 子查询 UNION 联合 UNION RESULT 使用联合的结果 table: 正在访问的表名 type: 查询时的访问方式,性能:all < index < range < index_merge < ref_or_null < ref < eq_ref < system/const ALL 全表扫描,对于数据表从头到尾找一遍 select * from userinfo; 特别的:如果有limit限制,则找到之后就不在继续向下扫描 select * from userinfo where email = 'alex112@oldboy' select * from userinfo where email = 'alex112@oldboy' limit 1; 虽然上述两个语句都会进行全表扫描,第二句使用了limit,则找到一个后就不再继续扫描。 INDEX : 全索引扫描,对索引从头到尾找一遍 select nid from userinfo; RANGE: 对索引列进行范围查找 select * from userinfo where name < 'alex'; PS: between and in > >= < <= 操作 注意:!= 和 > 符号 INDEX_MERGE: 合并索引,使用多个单列索引搜索 select * from userinfo where name = 'alex' or nid in (11,22,33); REF: 根据索引查找一个或多个值 select * from userinfo where name = 'alex112'; EQ_REF: 连接时使用primary key 或 unique类型 select userinfo2.id,userinfo.name from userinfo2 left join tuserinfo on userinfo2.id = userinfo.id; CONST:常量 表最多有一个匹配行,因为仅有一行,在这行的列值可被优化器剩余部分认为是常数,const表很快,因为它们只读取一次。 select id from userinfo where id = 2 ; SYSTEM:系统 表仅有一行(=系统表)。这是const联接类型的一个特例。 select * from (select id from userinfo where id = 1) as A; possible_keys:可能使用的索引
key:真实使用的 key_len: MySQL中使用索引字节长度 rows: mysql估计为了找到所需的行而要读取的行数 ------ 只是预估值 extra: 该列包含MySQL解决查询的详细信息 “Using index” 此值表示mysql将使用覆盖索引,以避免访问表。不要把覆盖索引和index访问类型弄混了。 “Using where” 这意味着mysql服务器将在存储引擎检索行后再进行过滤,许多where条件里涉及索引中的列,当(并且如果)它读取索引时,就能被存储引擎检验,因此不是所有带where子句的查询都会显示“Using where”。有时“Using where”的出现就是一个暗示:查询可受益于不同的索引。 “Using temporary” 这意味着mysql在对查询结果排序时会使用一个临时表。 “Using filesort” 这意味着mysql会对结果使用一个外部索引排序,而不是按索引次序从表里读取行。mysql有两种文件排序算法,这两种排序方式都可以在内存或者磁盘上完成,explain不会告诉你mysql将使用哪一种文件排序,也不会告诉你排序会在内存里还是磁盘上完成。 “Range checked for each record(index map: N)” 这个意味着没有好用的索引,新的索引将在联接的每一行上重新估算,N是显示在possible_keys列中索引的位图,并且是冗余的
九、慢日志记录
开启慢查询日志,可以让MySQL记录下查询超过指定时间的语句,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能。
(1) 进入MySql 查询是否开了慢查询 show variables like 'slow_query%'; 参数解释: slow_query_log 慢查询开启状态 OFF 未开启 ON 为开启 slow_query_log_file 慢查询日志存放的位置(这个目录需要MySQL的运行帐号的可写权限,一般设置为MySQL的数据存放目录)
(2)查看慢查询超时时间 show variables like 'long%'; ong_query_time 查询超过多少秒才记录 默认10秒 (3)开启慢日志(1)(是否开启慢查询日志,1表示开启,0表示关闭。) set global slow_query_log=1; (4)再次查看 show variables like '%slow_query_log%'; (5)开启慢日志(2):(推荐) 在my.cnf 文件中 找到[mysqld]下面添加: slow_query_log =1 slow_query_log_file=C:mysql-5.6.40-winx64datalocalhost-slow.log long_query_time = 1 参数说明: slow_query_log 慢查询开启状态 1 为开启 slow_query_log_file 慢查询日志存放的位置 long_query_time 查询超过多少秒才记录 默认10秒 修改为1秒
十、分页性能相关方案
先回顾一下,如何取当前表中的前10条记录,每十条取一次.......
第1页: select * from userinfo limit 0,10; 第2页: select * from userinfo limit 10,10; 第3页: select * from userinfo limit 20,10; 第4页: select * from userinfo limit 30,10; ...... 第2000010页 select * from userinfo limit 2000000,10; PS:会发现,越往后查询,需要的时间约长,是因为越往后查,全文扫描查询,会去数据表中扫描查询。
最优的解决方案
(1)只有上一页和下一页 做一个记录:记录当前页的最大id或最小id 下一页: select * from userinfo where id>max_id limit 10; 上一页: select * from userinfo where id<min_id order by id desc limit 10; (2) 中间有页码的情况 select * from userinfo where id in( select id from (select * from userinfo where id > pre_max_id limit (cur_max_id-pre_max_id)*10) as A order by A.id desc limit 10 );