零基础学MySQL
笔记目录:(https://www.cnblogs.com/wenjie2000/p/16378441.html)
一个问题
淘宝网,京东、微信,抖音都有各自的功能,那么当我们退出系统的时候,下次再访问时,为什么信息还存在? =》数据库
解决之道-文件、数据库
为了解决上述问题,使用更加利于管理数据的东东-数据库,它能更有效的管理数据。
举一个生活化的复列说明
如果说图书馆是保存书籍的,那么数据库就是保存数据的。
数据库的简单原理图
MySQL安装和配置
Mysql下载地址:https://downloads.mysql.com/archives/community/
我安装的版本为MySQL5.7。如果是安装其他版本,请自行百度。不同版本的MySql安装过程存在差别。
特别说明
如果安装过Mysql过程中,出错了或者想重新安装
sc delete mysql 【会删除已经安装好的mysql服务 。慎重使用】
安装步骤
请注意,zip 安装文件是压缩文件,和.exe安装文件是不一样的, 要严格的下面的步骤来执行,否则安装很可能不会成功。
下载后会得到zip 安装文件
解压的路径最好不要有中文和空格
这里我解压到 D:\ZhuangYeRuanJian\mysql\mysql-5.7.38-winx64 目录下 【可自行指定目录,目录不能有中文,尽量不安装在系统盘】
添加环境变量 : 电脑-属性-高级系统设置-环境变量,在Path 环境变量增加mysql的安装目录\bin目录。如下图所示
在D:\ZhuangYeRuanJian\mysql\mysql-5.7.38-winx64目录下下创建 my.ini 文件, 需要我们自己创建(其他非5.7版本会有差异)。用记事本打开该文件,写入以下内容并保存(注意要根据自己的安装位置更改文本中的目录)。
[client]port=3306default-character-set=utf8[mysqld]# 设置为自己MYSQL的安装目录basedir=D:\ZhuangYeRuanJian\mysql\mysql-5.7.38-winx64\# 设置为MYSQL的数据目录datadir=D:\ZhuangYeRuanJian\mysql\mysql-5.7.38-winx64\data\port=3306character_set_server=utf8#跳过安全检查(登录不需要密码)skip-grant-tables
使用管理员身份打开 cmd , 并切换到 D:\ZhuangYeRuanJian\mysql\mysql-5.7.38-winx64\bin 目录下, 执行mysqld -install
d:cd D:\ZhuangYeRuanJian\mysql\mysql-5.7.38-winx64mysqld -install
如果入到下面两种情况,是由于你的电脑缺失了相关的系统文件。下载并安装:https://www.microsoft.com/zh-CN/download/details.aspx?id=40784
初始化数据库: mysqld –initialize-insecure –user=mysql
如果执行成功,会生成 data目录:
启动mysql 服务: net start mysql 【停止mysql服务指令 net stop mysql】, 如果成功:
任务管理器中也会出现MySQL服务
进入mysql 管理终端: mysql -u root -p 【当前root 用户密码为 空,下一行要输入密码时直接回车】
修改root 用户密码
use mysql; update user set authentication_string=password('123456') where user='root' and Host='localhost';解读: 上面的语句就是修改 root用户的密码为 123456注意:在后面需要带 分号,回车即可执行该指令执行: flush privileges; 刷新权限退出: quit
修改my.ini , 再次进入就会进行权限验证了
#跳过安全检查(登录不需要密码)#skip-grant-tables
重新启动mysql
net stop mysqlnet start mysql提示: 该指令需要退出mysql , 在Dos下执行.
再次进入Mysql, 输入正确的用户名和密码
mysql -u root -p密码正确,进入mysql密码错误,提示如下信息ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
安装过程中,一定要按照老师的步骤来,不然会错误.
如果真的错误了, 清除mysql服务, 再次安装.
连接到Mysql服务(Mysql数据库)的指令
mysql -h 主机IP -P 端口 -u 用户名 -p密码
提醒:
- -p密码不要有空格
- -p后面没有写密码,回车会要求输入密码
- 如果没有写-h主机,默认就是本机
- 如果没有写-P端口,默认就是3306
- 在实际工作中,3306一般修改
安装Navicat
http://www.navicat.com.cn/download/navicat-for-mysql
安装过程很简单,此处省略
如果想破解,百度,教程很多
安装后,打开,点击左侧的“连接”,进行如下配置。
数据库
注意:navicat只是方便手动操作和查看,光是会使用它没有太大意义。实际开发中程序员操作数据库还是要通过指令。指令是必须要认真学的。
数据库三层结构-破除 MySQL 神秘
所谓安装Mysql数据库,就是在主机安装一个数据库管理系统(DBMS),这个管理程序可以管理多个数据库。DBMS(database manage system)
一个数据库中可以创建多个表,以保存数据(信息)。
数据库管理系统(DBMS)、数据库和表的关系如图所示:示意图
数据在数据库中的存储方式
SQL 语句分类
DDL:数据定义语句[create表,库…]
DML:数据操作语句[增加insert,修update,删除delete]
DQL:数据查询语句[select ]
DCL:数据控制语句[管理数据库:比如用户权限 grant revoke ]
创建
注意:
在mysql数据库中,sql关键字和大小写 是不区分大小写的。
windows下mysql中的数据库名、表名、列名默认是不区分大小写的,但是linux会区分大小写,所以当执行的sql语句有大小写区别时需要注意。
因此,mysql创建数据库、表、列时,尽量使用小写命名,如果名字由多个单词组成可以使用“_”作为间隔符。如果名字和关键字重名可以用反引号“`”括起来。
具体情况可以看这篇博客:https://www.cnblogs.com/chenhaoblog/p/13604727.html
CREATE DATABASE [IF NOT EXISTS] db_name[create_specification [,create_specification]...]create_specification:[DEFAULT]CHARACTER SET charset_namel[DEFAULT]COLLATE collation_name#中括号中的内容为可写可不写的,根据实际情况决定。实际语句的使用中不要写上中括号
CHARACTER SET:指定数据库采用的字符集,如果不指定字符集,默认utf&,
COLLATE:指定数据库字符集的校对规则(常用的utf8 bin[区分大小写]、utf8 general ci[不区分大小写(例:A==a)]注意默认是utf8_general_ci)
# 演示数据库的操作#创建一个名称为 hsp_db01 的数据库。[图形化和指令 演示]#使用指令创建数据库CREATE DATABASE hsp_db01;#删除数据库指令DROP DATABASE hsp_db01#创建一个使用 utf8 字符集的 hsp_db02 数据库CREATE DATABASE hsp_db02 CHARACTER SET utf8#创建一个使用 utf8 字符集,并带校对规则的 hsp_db03 数据库CREATE DATABASE hsp_db03 CHARACTER SET utf8 COLLATE utf8_bin#校对规则 utf8_bin 区分大小 默认 utf8_general_ci 不区分大小写#下面是一条查询的 sql , select 查询 * 表示所有字段 FROM 从哪个表#WHERE 从哪个字段 NAME = 'tom' 查询名字是 tomSELECT * FROM t1 WHERE NAME = 'tom'
查看、删除数据库
显示数据库语句:SHOW DATABASES显示数据库创建语句:SHOW CREATE DATABASE db_name数据库删除语句[一定要慎用]:DROP DATABASE [IF EXISTS] db_name
#演示删除和查询数据库#查看当前数据库服务器中的所有数据库SHOW DATABASES#查看前面创建的 hsp_db01 数据库的定义信息SHOW CREATE DATABASE `hsp_db01` #老师说明 在创建数据库,表的时候,为了规避关键字,可以使用反引号解决CREATE DATABASE `INT`#删除前面创建的 hsp_db01 数据库DROP DATABASE hsp_db01
备份恢复数据库
备份数据库(注意:在DOS执行)
mysqldump -u 用户名 -p[密码] -B 数据库1 数据库2 数据库n > 路径\\文件名.sql-p后面可以加上密码。也可以不写,回车后再输入密码
恢复数据库(注意:cmd进入MySQL命令行再执行)
Source 文件名.sql
#练习 : database03.sql 备份 hsp_db02 和 hsp_db03 库中的数据,并恢复#备份, 要在 Dos 下执行 mysqldump 指令其实在 mysql 安装目录\bin#这个备份的文件,就是对应的 sql 语句mysqldump -u root -p -B hsp_db02 hsp_db03 > d:\\bak.sqlDROP DATABASE ecshop;#恢复数据库(注意:在DOS界面,先进入 Mysql 命令行再执行)source d:\\bak.sql#第二个恢复方法, 直接将 bak.sql 的内容放到查询编辑器中,执行
备份恢复数据库的表
mysqldump -u 用户名 -p[密码] 数据库 表1 表2 表n > 路径\\文件名.sql-p后面可以加上密码。也可以不写,回车后再输入密码恢复方式和上面一样
表创建
CREATE TABLE table_name(field1 datatype, field2 datatype, field3 datatype)character set 字符集 collate 校对规则 engine 引擎field:指定列名 datatype:指定列类型(字段类型)character set:如不指定则为所在数据库字符集collate:如不指定则为所在数据库校对规则engine:引擎(这个涉及内容较多,后面单独讲解)
删除
drop table 表名;
修改
如果看了b站 韩顺平老师的课程,注意他的PPT这部分的指令有问题,下面的才是对的。
#添加列ALTER TABLE 表名ADD 列名 datatype [DEFAULT expr];#修改列类型ALTER TABLE 表名MODIFY 列名 datatype [DEFAULT expr];#修改列类型及名称ALTER TABLE 表名 CHANGE 列名 新列名 datatype;#删除列ALTER TABLE 表名;DROP 列名;查看表的结构: desc 表名; --可以查看表的列#修改表名:rename table 表名 to 新表名;#修改表字符集:alter table 表名 character set 字符集;
应用实例:
- 员工表emp增加一个image列,varchar类型(要求在resume后面).
- 修改job列,使其长度为60。
- 删除sex列。
- 表名改为employee。
- 修改表的字符集为utf8
- 列名name修改为user_name
ALTER TABLE empADD image varchar(32) NOT NULL DEFAULT '' AFTER resume;ALTER TABLE empMODIFY job varchar(60);ALTER TABLE empDROP sex;DESC emp;rename table emp to employee;ALTER TABLE employee character set utf8;ALTER TABLE employee CHANGE `name` `user_name` varchar(20);
Mysql数据类型
数值型(整数)的基本使用
说明:在能够满足需求的情况下,尽量选择占用空间小的类型(节省资源)
类型 | 字节 | 最小值 | 最大值 |
---|---|---|---|
(带符号的/无符号的) | (带符号的/无符号的) | ||
TINYINT | 1 | -128 | 127 |
[unsigned] | 0 | 255 | |
SMALLINT | 2 | -32768 | 32767 |
0 | 65535 | ||
MEDIUMINT | 3 | -8388608 | 8388607 |
0 | 16777215 | ||
INT | 4 | -2147483648 | 2147483647 |
0 | 4294967295 | ||
BIGINT | 8 | -9223372036854775808 | 9223372036854775807 |
0 | 18446744073709551615 |
#演示整型的是一个#使用 tinyint 来演示范围 有符号 -128 ~ 127 如果没有符号 0-255#说明: 表的字符集,校验规则, 存储引擎,老师使用默认#1. 如果没有指定 unsinged , 则 TINYINT 就是有符号#2. 如果指定 unsinged , 则 TINYINT 就是无符号 0-255CREATE TABLE t3 (id TINYINT);CREATE TABLE t4 (id TINYINT UNSIGNED);INSERT INTO t3 VALUES(127); #这是非常简单的添加语句SELECT * FROM t3;INSERT INTO t4 VALUES(255);SELECT * FROM t4;
数值型(bit)的使用
基本使用
create table t02 (num bit(8));insert into t02 (1,3);insert into t02 values(2,65);
细节说明
bit字段显示时,按照位的方式显示.查询的时候仍然可以用使用添加的数值
如果一个值只有0,1可以考虑使用bit(1),可以节约空间位类型。M指定位数,默认值1,范围1-64
使用不多.#演示 bit 类型使用#说明#1. bit(m) m 在 1-64#2. 添加数据 范围 按照你给的位数来确定,比如 m = 8 表示一个字节 0~255#3. 显示按照 bit#4. 查询时,仍然可以按照数来查询CREATE TABLE t05 (num BIT(8));INSERT INTO t05 VALUES(255);SELECT * FROM t05;SELECT * FROM t05 WHERE num = 1;
数值型(小数)的基本使用
- FLOAT/DOUBLE [UNSIGNED]
Float单精度精度,Double 双精度
DECIMAL[M,D] [UNSIGNED]
可以支持更加精确的小数位。M是位数(整数位数+小数位数)的总数,D是小数点(标度)后面的位数。
如果D是0,则值没有小数点或分数部分。M最大65。D最大是30。如果D被省略,默认是0。如果M被省略,默认是10。
建议:如果希望小数的精度高,推荐使用decimal
#演示 decimal 类型、float、double 使用#创建表CREATE TABLE t06 ( num1 FLOAT, num2 DOUBLE, num3 DECIMAL(30,20));#添加数据INSERT INTO t06 VALUES(88.12345678912345, 88.12345678912345,88.12345678912345);SELECT * FROM t06;#decimal 可以存放很大的数CREATE TABLE t07 (num DECIMAL(65));INSERT INTO t07 VALUES(8999999933338388388383838838383009338388383838383838383);
字符串的基本使用
CHAR(size)
固定长度字符串最大255字符
VARCHAR(size)
可变长度字符串最大65532字节【utf8编码最大21844字符(因为(65535-2)/3)=21844.3),1-2个字节用于记录存储数据长度。如果允许为null也要占用一个字节,不允许为空则不占用这一个字节】
字符串使用细节
细节1
char(4)//这个4表示字符数(最大255),不是字节数,不管是中文还是字母都是放四个,按字符计算.
varchar(4)//这个4表示字符数,不管是字母还是中文都以定义好的表的编码来存放数据.
不管是中文还是英文字母,都是最多存放4个,是按照字符来存放的.
细节2
char(4)是定长(固定的大小),就是说,即使你插入’aa’,也会占用分配的4个字符的空间.
varchar(4)是变长,就是说,如果你插入了’aa’,实际占用空间大小并不是4个字符,而是按照实际占用空间来分配(说明:varchar本身还需要多占用1-3个字节)
细节3
什么时候使用char,什么时候使用varchar
- 如果数据是定长,推荐使用char,比如md5的密码,邮编,手机号,身份证号码等. char(32)
- 如果一个字段的长度是不确定,我们使用varchar,比如留言,文章
查询速度:char > varchar
细节4
在存放文本时,也可以使用Text 数据类型。可以将TEXT列视为VARCHAR列,注意Text不能有默认值,大小0~216字节
如果希望存放更多字符,可以选择 MEDIUMTEXT (0~224字节) 或者LONGTEXT (0~232字节)
#演示字符串类型的使用细节#char(4) 和 varchar(4) 这个 4 表示的是字符,而不是字节, 不区分字符是汉字还是字母CREATE TABLE t11(`name` CHAR(4));INSERT INTO t11 VALUES('韩顺平123');SELECT * FROM t11;CREATE TABLE t12(`name` VARCHAR(4));INSERT INTO t12 VALUES('韩顺平212');INSERT INTO t12 VALUES('ab 北京');SELECT * FROM t12;#如果 varchar 不够用,可以考试使用 mediumtext 或者 longtext, #如果想简单点,可以使用直接使用 textCREATE TABLE t13( content TEXT, content2 MEDIUMTEXT , content3 LONGTEXT);INSERT INTO t13 VALUES('韩顺平教育', '韩顺平教育 100', '韩顺平教育 1000~~');SELECT * FROM t13;
日期类型的基本使用
CREATE TABLE birthday( t1 DATE, t2 DATETIME,t3 TIMESTAMP NOT NULL DEFAULTCURRENT TIMESTAMP ON UPDATECURRENT TIMESTAMP ); mysql> INSERT INTO birthday(t1,t2)VALUES('2022-11-11','2022-11-11 10:10:10');#timestamp时间戳
日期类型的细节说明
TimeStamp在Insert和update时,自动更新
#演示时间相关的类型#创建一张表, date , datetime , timestampCREATE TABLE t14 ( birthday DATE , -- 生日 job_time DATETIME, -- 记录年月日 时分秒 login_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP); -- 登录时间, 如果希望 login_time 列自动更新, 需要配置SELECT * FROM t14;INSERT INTO t14(birthday, job_time)VALUES('2022-11-11','2022-11-11 10:10:10');-- 如果我们更新 t14 表的某条记录,login_time 列会自动的以当前时间进行更新
练习题
-- 自己一定要练习一把CREATE TABLE `emp` (id INT, `name` VARCHAR(32), sex CHAR(1), brithday DATE, entry_date DATETIME, job VARCHAR(32), salary DOUBLE, `resume` TEXT) CHARSET utf8 COLLATE utf8_bin ENGINE INNODB; -- 添加一条INSERT INTO `emp`VALUES(100, '小妖怪', '男', '2000-11-11','2010-11-10 11:11:11', '巡山的', 3000, '大王叫我来巡山');SELECT * FROM `emp`;
CRUD
C[creat]R[read]U[update]D[delete]
lnsert(添加数据)
INSERT INTO 表名 [(column1 [, column2. . .])]VALUES (value1 [, value2...]);
快速入门案例:
创建一张商品表goods(id int , goods_name varchar(10),price double);
添加2条记录
create table goods(id int,goods_name varchar(10),price double);INSERT INTO goods VALUES (10,'可乐',4.5);INSERT INTO goods (id,goods_name,price) VALUES (11,'可乐2',4.6);
细节说明
插入的数据应与字段的数据类型相同。
比如把’abc’添加到int类型会错误
数据的长度应在列的规定范围内,例如:不能将一个长度为80的字符串加入到长度为40的列中。
在values中列出的数据位置必须与被加入的列的排列位置相对应。
字符和日期型数据应包含在单引号中。
列可以插入空值[前提是该字段允许为空],insert into table value(null)
insert into 表名 (列名…) values (),(),()形式添加多条记录
INSERT INTO `goods` (id,goods_name,price)VALUES(50,'三星手机',2300),(60,'海尔手机',1800);
如果是给表中的所有字段添加数据,可以不写前面的字段名称
INSERT INTO goods VALUES (10,'可乐',4.5);
默认值的使用,当不给某个字段值时,如果有默认值就会添加,否则报错
Update(更新数据)
UPDATE 表名SET 列名1=expr1 [, 列名2=expr2 ...][WHERE where_definition]
基本使用:
要求:在上面创建的employee表中修改表中的纪录
将所有员工薪水修改为5000元。
将姓名为小妖怪的员工薪水修改为3000元。
将老妖怪的薪水在原有基础上增加1000元。
-- 1. 将所有员工薪水修改为 5000 元。[如果没有带 where 条件,会修改所有的记录,因此要小心]UPDATE employee SET salary=5000;-- 2UPDATE employee SET salary=3000 WHERE `user_name`='小妖怪';-- 3UPDATE employee SET salary=salary+1000 WHERE `user_name`='老妖怪';
使用细节:
UPDATE语法可以用新值更新原有表行中的各列。
SET子句指示要修改哪些列和要给予哪些值。
WHERE子句指定应更新哪些行。如没有WHERE子句,则更新所有的行。
如果需要修改多个字段,可以通过 set 字段1=值1,字段2=值2…….
UPDATE employeeSET salary =salary +1000,job ='出主意的' WHERE user_name ='老妖怪';
Delete(删除数据)
delete from 表名[WHERE where_definition]
快速入门案例(使用employee测试)
删除表中名称为’老妖怪’的记录
删除表中所有记录。
-- 1DELETE FROM `employee` WHERE user_name='老妖怪';-- 2DELETE FROM `employee`;
使用细节
- 如果不使用where子句,将删除表中所有数据。
- Delete语句不能删除某一列的值(可使用update设为null或者”)
- 使用delete语句仅删除记录,不删除表本身。如要删除表,使用droptable语句。drop table 表名;
Select(查找数据-单表)
SELECT [DISTINCT] * | {column1, column2, column3... }FROM 表名;
注意事项
- Select 指定查询哪些列的数据。
- column指定列名。
- *号代表查询所有列。
- From指定查询哪张表。
- DISTINCT可选,指显示结果时,去掉重复数据(此处重复指的是两行内容完全相同)
练习题
先使用以下指令创建表,不只是这题需要用,后面还要用:
create table student(id int not null default 1,name varchar(20) not null default '',chinese float not null default 0.0,english float not null default 0.0,math float not null default 0.0);insert into student(id,name,chinese,english,math) values(1,'韩顺平',89,78,90);insert into student(id,name,chinese,english,math) values(2,'张飞',67,98,56);insert into student(id,name,chinese,english,math) values(3,'宋江',87,78,77);insert into student(id,name,chinese,english,math) values(4,'关羽',88,98,90);insert into student(id,name,chinese,english,math) values(5,'赵云',82,84,67);insert into student(id,name,chinese,english,math) values(6,'欧阳锋',55,85,45);insert into student(id,name,chinese,english,math) values(7,'黄蓉',75,65,30);insert into student(id,name,chinese,english,math) values(8,'李明',80,65,30);
查询表中所有学生的信息。
查询表中所有学生的姓名和对应的英语成绩。
过滤表中重复数据distinct 。
要查询的记录,每个字段都相同,才会去重
-- 1SELECT * FROM `student`;-- 2SELECT `name`,`english` FROM student;-- 3SELECT DISTINCT * FROM `student`;-- 4SELECT DISTINCT * FROM `student`;
使用表达式对查询的列进行运算
SELECT * | { column1 | expression,column2 | expression,...}FROM tablename;
在select语句中可使用as语句
SELECT column_name [as] 别名 from 表名;-- 此处as可加可不加。如果不加就必须中间有空格
练习
统计每个学生的总分
在所有学生总分加10分的情况
使用别名表示学生分数。
-- 1SELECT `name`,(chinese+english+math) FROM `student`;-- 2SELECT `name`,(chinese+english+math+10) FROM `student`;-- 3SELECT `name` AS '名字',(chinese+english+math) AS total_score FROM `student`;
在where子句中经常使用的运算符
练习
使用where子句,进行过滤查询select
查询姓名为赵云的学生成绩
查询英语成绩大于90分的同学
查询总分大于200分的所有同学
-- 1SELECT * FROM `student` WHERE `name`='赵云';-- 2SELECT * FROM `student` WHERE `english`>90;-- 3SELECT * FROM `student` WHERE (chinese+english+math)>200;
练习2
使用where子句,练习[5min]:
查询math大于60并且(and) id大于4的学生成绩
查询英语成绩大于语文成绩的同学
查询总分大于200分并且数学成绩小于语文成绩,的姓赵的学生.
-- 1SELECT * FROM `student` WHERE `math`>60 and `id`>4;-- 2SELECT * FROM `student` WHERE `english`>`chinese`;-- 3#韩% 表示名字以韩开头的就可以,%表示0个到多个字符,可以和Like搭配使用SELECT * FROM `student` WHERE (chinese+english+math)>200 and `math`<`chinese` and `name` LIKE '赵%';
练习3
查询英语分数在80 – 90之间的同学。
查询数学分数为89,90,91的同学。
查询所有姓李的学生成绩。
查询数学分>80,语文分>80的同学。
查询总分为189,190,233的同学。
查询所有姓李或者姓宋的学生成绩。
查询数学比语文多30分的同学。
-- 1SELECT * FROM `student` WHERE `english`>=80 and `english`80 and `chinese`>80;-- 5SELECT * FROM `student` WHERE (chinese+english+math)=189 OR (chinese+english+math)=190 or (chinese+english+math)=233;SELECT * FROM `student` WHERE (chinese+english+math) in (189,190,233);-- 6SELECT * FROM `student` WHERE `name` Like '李%' or `name` Like '宋%';-- 7SELECT * FROM `student` WHERE `math`-`chinese`>30;
使用order by子句排序查询结果
SELECT column1, column2, column3...FROM tableorder by column4 asc|desc,column5 asc|desc,...
- Order by指定排序的列,排序的列既可以是表中的列名,也可以是select语句后指定的列名。
- Asc升序[默认]、Desc降序
- ORDER BY子句应位于SELECT语句的结尾。
练习:
对数学成绩排序后输出【升序】。
对总分按从高到低的顺序输出
对姓李的学生成绩[总分]排序输出(升序)
-- 1SELECT * FROM `student` ORDER BY `math`;-- 2#可以使用别名排序SELECT *,(chinese+english+math) AS total_score FROM `student` ORDER BY total_score DESC;-- 3SELECT `name`,(chinese+english+math) AS total_score FROM `student` WHERE `name` Like '李%' ORDER BY total_score;
函数统计函数合计/统计函数- count
Count返回行的总数
Select count(*) | count(列名) from table_name[WHERE where_definition]
练习:
统计一个班级共有多少学生?
统计数学成绩大于90的学生有多少个?
统计总分大于250的人数有多少?
count(*)和count(列)的区别
-- 1Select count(*) FROM `student`;-- 2Select count(*) FROM `student` WHERE `math`>90;-- 3SELECT count(*) FROM `student` WHERE (chinese+english+math)>250;-- 4#count(*)和count(列)的区别-- 解释: count(*)返回满足条件的记录的行数-- count(列):统计满足条件的某列有多少个,但是会排除为null的情况
合计函数-sum
Sum函数返回满足where条件的行的和 一般使用在数值列
select sum(列名){, sum(列名)...} from tablename[WHERE where_definition]
练习
统计一个班级数学总成绩?
统计一个班级语文、英语、数学各科的总成绩
统计一个班级语文、英语、数学的成绩总和
统计一个班级语文成绩平均分
注意: sum仅对数值起作用,否则没有意义。
注意:对多列求和,“,”号不能少。
-- 1SELECT SUM(math) from `student`;-- 2select sum(chinese),sum(english),sum(math) from `student`;-- 3SELECT SUM(chinese+english+math) from `student`;-- 4SELECT SUM(chinese)/count(*) from `student`;
合计函数- avg
AVG函数返回满足where条件的一列的平均值
Select avg(列名){,avg(列名)...]from tablename[WHERE where_definition]
练习:
求一个班级数学平均分?
求一个班级总分平均分
-- 1select avg(math) from `student`;-- 2select avg(chinese+english+math) from `student`;
合计函数-Max/min
Max/min函数返回满足where条件的一列的最大/最小值
select max(列名) from tablename[WHERE where_definition]
练习:
求班级最高分和最低分(数值范围在统计中特别有用)
select max(chinese+english+math),min(chinese+english+math) from `student`;
分组统计GROUP BY和HAVING
使用group by子句对列进行分组[先创建测试表]
SELECT column1, column2. column3... FROM tablegroup by column1 [,column2...];
使用having子句对分组后的结果进行过滤
SELECT column1, column2, column3 ...FROM tablegroup by column having ...
group by用于对查询的结果分组统计
having子句用于限制分组显示结果.(注意:where是对原始每行的数据过滤,having是对经过分组集成的数据进行进行限制,一般和group by配套使用)
练习
先使用下面的指令创建表
CREATE TABLE dept( /*部门表*/deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, dname VARCHAR(20) NOT NULL DEFAULT "",loc VARCHAR(13) NOT NULL DEFAULT "");INSERT INTO dept VALUES(10, 'ACCOUNTING', 'NEW YORK'), (20, 'RESEARCH', 'DALLAS'), (30, 'SALES', 'CHICAGO'), (40, 'OPERATIONS', 'BOSTON');#创建表EMP雇员CREATE TABLE emp(empno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, /*编号*/ename VARCHAR(20) NOT NULL DEFAULT "", /*名字*/job VARCHAR(9) NOT NULL DEFAULT "",/*工作*/mgr MEDIUMINT UNSIGNED ,/*上级编号*/hiredate DATE NOT NULL,/*入职时间*/sal DECIMAL(7,2) NOT NULL,/*薪水*/comm DECIMAL(7,2) ,/*红利*/deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0 /*部门编号*/);INSERT INTO emp VALUES(7369, 'SMITH', 'CLERK', 7902, '1990-12-17', 800.00,NULL , 20), (7499, 'ALLEN', 'SALESMAN', 7698, '1991-2-20', 1600.00, 300.00, 30), (7521, 'WARD', 'SALESMAN', 7698, '1991-2-22', 1250.00, 500.00, 30), (7566, 'JONES', 'MANAGER', 7839, '1991-4-2', 2975.00,NULL,20), (7654, 'MARTIN', 'SALESMAN', 7698, '1991-9-28',1250.00,1400.00,30), (7698, 'BLAKE','MANAGER', 7839,'1991-5-1', 2850.00,NULL,30), (7782, 'CLARK','MANAGER', 7839, '1991-6-9',2450.00,NULL,10), (7788, 'SCOTT','ANALYST',7566, '1997-4-19',3000.00,NULL,20), (7839, 'KING','PRESIDENT',NULL,'1991-11-17',5000.00,NULL,10), (7844, 'TURNER', 'SALESMAN',7698, '1991-9-8', 1500.00, NULL,30), (7900, 'JAMES','CLERK',7698, '1991-12-3',950.00,NULL,30), (7902, 'FORD', 'ANALYST',7566,'1991-12-3',3000.00, NULL,20), (7934,'MILLER','CLERK',7782,'1992-1-23', 1300.00, NULL,10);#工资级别表CREATE TABLE salgrade(grade MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,losal DECIMAL(17,2) NOT NULL,hisal DECIMAL(17,2) NOT NULL);INSERT INTO salgrade VALUES (1,700,1200);INSERT INTO salgrade VALUES (2,1201,1400);INSERT INTO salgrade VALUES (3,1401,2000);INSERT INTO salgrade VALUES (4,2001,3000);INSERT INTO salgrade VALUES (5,3001,9999);
如何显示每个部门的平均工资和最高工资
显示每个部门的每种岗位的平均工资和最低工资
显示平均工资低于2000的部门号和它的平均工资
-- 1select deptno,avg(sal),max(sal) from emp group by deptno;-- 2select deptno,job,avg(sal),min(sal) from emp group by deptno,job;-- 3-- 分析〔写sql语句的思路是化繁为简,各个击破]-- 3.1 显示各个部门的平均工资和部门号-- 3.2 在1的结果基础上,进行过滤,保留AVG(sal) <2000select deptno,avg(sal) from emp group by deptno;select deptno,avg(sal) from emp group by deptno HAVING AVG(sal) <2000;
字符串函数
-- 演示字符串相关函数的使用 , 使用 emp 表来演示-- CHARSET(str) 返回字串字符集SELECT CHARSET(ename) FROM emp; -- CONCAT (string2 [,... ]) 连接字串, 将多个列拼接成一列SELECT CONCAT(ename, ' 工作是 ', job) FROM emp; -- INSTR (string ,substring ) 返回 substring 在 string 中出现的位置,没有则返回 0-- 注意:和java不一样 ,mysql是从1开始计算顺序的-- dual 亚元表, 系统表 可以作为测试表使用SELECT INSTR('hanshunping', 'ping') FROM DUAL; -- UCASE (string2 ) 转换成大写SELECT UCASE(ename) FROM emp; -- LCASE (string2 ) 转换成小写SELECT LCASE(ename) FROM emp; -- LEFT (string2 ,length )从 string2 中的左边起取 length 个字符-- RIGHT (string2 ,length ) 从 string2 中的右边起取 length 个字符SELECT ename,LEFT(ename, 2),RIGHT(ename,2) FROM emp;-- LENGTH (string )string 长度[按照字节]SELECT LENGTH(ename) FROM emp; -- REPLACE (str ,search_str ,replace_str )-- 在 str 中用 replace_str 替换所有的 search_str-- 如果是 manager 就替换成 经理SELECT ename, REPLACE(job,'MANAGER', '经理') FROM emp;-- STRCMP (string1 ,string2 ) 逐字符比较两字串大小(不区分大小写) 返回数字0(一样),-1(string2大),1(string1大)SELECT STRCMP('hsp', 'jsp') FROM DUAL;#输出 -1-- SUBSTRING (str , position [,length ])-- 从 str 的 position 开始【从 1 开始计算】,取 length 个字符-- 从 ename 列的第一个位置开始取出 2 个字符SELECT SUBSTRING(ename, 1, 2) FROM emp; -- LTRIM (string2) RTRIM (string2 ) TRIM(string)-- 去除前端空格或后端或前后两端空格SELECT LTRIM(' 韩顺平教育') FROM DUAL;SELECT RTRIM('韩顺平教育 ') FROM DUAL;SELECT TRIM(' 韩顺平教育 ') FROM DUAL;
练习:
以首字母小写的方式显示所有员工emp表的姓名
SELECT ename,CONCAT(LCASE(LEFT(`ename`,1)),SUBSTRING(ename,2)) from emp;
数学函数
-- 演示数学相关函数-- ABS(num) 绝对值SELECT ABS(-10) FROM DUAL; -- BIN (decimal_number )十进制转二进制SELECT BIN(10) FROM DUAL; -- CEILING (number2 ) 向上取整, 得到 >=num2 的最小整数SELECT CEILING(-1.1) FROM DUAL; -- CONV(number2,from_base,to_base) 进制转换-- 下面的 8 是十进制的 8, 转成 2 进制输出SELECT CONV(8, 10, 2) FROM DUAL; -- 下面的 16 是 16 进制的 16, 转成 10 进制输出SELECT CONV(16, 16, 10) FROM DUAL; -- FLOOR (number2 ) 向下取整,得到比 num2 小的最大整数SELECT FLOOR(-1.1) FROM DUAL; -- FORMAT (number,decimal_places ) 保留小数位数(四舍五入)SELECT FORMAT(78.125458,2) FROM DUAL; -- HEX (DecimalNumber ) 转十六进制 (括号内的数是十进制)SELECT HEX(46) FROM DUAL;#2E-- LEAST (number , number2 [,..]) 求最小值SELECT LEAST(0,1, -10, 4) FROM DUAL; -- MOD (numerator ,denominator ) 求余SELECT MOD(10, 3) FROM DUAL; -- RAND([seed]) 返回随机数 其范围为 0 ≤ v ≤ 1.0-- 说明-- 1. 如果使用 rand() 每次返回不同的随机数 ,在 0 ≤ v ≤ 1.0-- 2. 如果使用 rand(seed) 返回随机数, 范围 0 ≤ v ≤ 1.0, 如果 seed 不变,该随机数也不变了SELECT RAND() FROM DUAL;-- ROUND(number) 四舍五入select round(5.5);
时间日期
上面函数的细节说明:
- DATE_ADD()中的interval后面可以是year、month、day、hour、minute、second。
- DATE SUB()中的interval后面可以是year、month、day、hour、minute、second。
- DATEDIFF(date1,date2)得到的是天数,而且是date1-date2的天数,因此可以取负数
- DATE、DATE_ADD、DATE_SUB、DATEDIFF这四个函数的日期类型可以是date, datetime或者timestamp
-- 日期时间相关函数【1】-- CURRENT_DATE ( ) 当前日期SELECT CURRENT_DATE() FROM DUAL; #2022-09-15-- CURRENT_TIME ( )当前时间SELECT CURRENT_TIME() FROM DUAL; #20:23:10-- CURRENT_TIMESTAMP ( ) 当前时间戳SELECT CURRENT_TIMESTAMP() FROM DUAL; #2022-09-15 20:23:19-- NOW( ) 当前时间-- CURRENT_TIMESTAMP()和NOW()没区别SELECT NOW() FROM DUAL; #2022-09-15 20:23:20-- LAST_DAY(datetime) 返回datetime当月最后一天的日期LAST_DAY(NOW()); -- 2022-09-15
实例
-- 创建测试表 信息表CREATE TABLE mes( id INT ,content VARCHAR(30), send_time DATETIME);-- 添加记录INSERT INTO mes VALUES(1, '北京新闻', CURRENT_TIMESTAMP());INSERT INTO mes VALUES(2, '上海新闻', NOW());INSERT INTO mes VALUES(3, '广州新闻', NOW());SELECT * FROM mes;
显示所有留言信息,发布日期只显示日期,不用显示时间.
请查询在10分钟内发布的帖子
请在mysql 的sql语句中求出2011-11-11和1990-1-1相差多少天
请用mysql的sql语句求出你活了多少天?[练习]
如果你能活80岁,求出你还能活多少天.[练习]
-- 日期时间相关函数【2】-- 1SELECT id,content,DATE(send_time) FROM mes;-- 2 (两种方式)select * FROM mes WHERE TIMEDIFF(NOW(),send_time)= NOW();SELECT * FROM mes WHERE send_time >= DATE_SUB(NOW(), INTERVAL 10 MINUTE)-- 3SELECT DATEDIFF('2011-11-11','1990-1-1');-- 4SELECT DATEDIFF(NOW(), '2000-08-11'); -- 5SELECT DATEDIFF(DATE_ADD('2000-08-11', INTERVAL 80 YEAR),NOW());
-- 日期时间相关函数【3】-- YEAR|Month|DAY|DATE (datetime )SELECT YEAR(NOW()) FROM DUAL;#2022SELECT MONTH(NOW()) FROM DUAL;#9SELECT DAY(NOW()) FROM DUAL;#15SELECT MONTH('2013-11-10');#11-- unix_timestamp() : 返回的是 1970-1-1 到现在的秒数SELECT UNIX_TIMESTAMP() FROM DUAL;-- FROM_UNIXTIME() : 可以把一个 unix_timestamp 秒数[时间戳],转成指定格式的时间-- %Y-%m-%d 格式是规定好的,表示年月日-- 意义:在开发中,可以存放一个整数,然后表示时间,通过 FROM_UNIXTIME 转换SELECT FROM_UNIXTIME(1618483484, '%Y-%m-%d') FROM DUAL;SELECT FROM_UNIXTIME(1618483100, '%Y-%m-%d %H:%i:%s') FROM DUAL;
加密和系统函数
-- 演示加密函数和系统函数-- USER() 查询用户-- 可以查看登录到 mysql 的有哪些用户,以及登录的 IPSELECT USER(); -- 用户@IP 地址-- DATABASE()查询当前使用数据库名称SELECT DATABASE();-- MD5(str) 为字符串算出一个 MD5 32 的字符串,常用(用户密码)加密-- root 密码是 hsp -> 加密 md5 -> 在数据库中存放的是加密后的密码SELECT MD5('hsp') FROM DUAL;SELECT LENGTH(MD5('hsp')) FROM DUAL; -- 演示用户表,存放密码时,是 md5CREATE TABLE hsp_user(id INT , `name` VARCHAR(32) NOT NULL DEFAULT '', pwd CHAR(32) NOT NULL DEFAULT '');INSERT INTO hsp_user VALUES(100, '韩顺平', MD5('hsp'));SELECT * FROM hsp_user;SELECT * FROM hsp_user WHERE `name`='韩顺平' AND pwd = MD5('hsp');-- PASSWORD(str)-- 加密函数, MySQL 数据库的用户密码就是 PASSWORD 函数加密SELECT PASSWORD('123456') FROM DUAL; -- 数据库中存储的密码是 *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9-- select * from mysql.user \G 从原文密码 str 计算并返回密码字符串-- 通常用于对 mysql 数据库的用户密码加密-- mysql.user 表示 '数据库.表' 这样不不需要切换到mysql表再查userSELECT user,user.authentication_string FROM mysql.user;#可看到用户名和加密后的密码select password('123456'); #*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 和数据库中存储的密码对应
流程控制
先看两个需求:
- 查询emp表,如果comm是null,则显示0.0
- 如果emp表的job是CLERK则显示职员,如果是 MANAGER 则显示经理如果是SALESMAN则显示销售人员,其它正常显示.
# 演示流程控制语句# IF(expr1,expr2,expr3) 如果 expr1 为 True ,则返回 expr2 否则返回 expr3SELECT IF(TRUE, '北京', '上海') FROM DUAL;# IFNULL(expr1,expr2) 如果 expr1 为空 NULL,返回 expr2,否则返回 expr1SELECT IFNULL( NULL, '韩顺平教育') FROM DUAL;# SELECT CASE WHEN expr1 THEN expr2 WHEN expr3 THEN expr4 ELSE expr5 END; [类似多重分支.]# 如果 expr1 为 TRUE,则返回 expr2,如果 expr2 为 t, 返回 expr4, 否则返回 expr5SELECT CASE WHEN TRUE THEN 'jack'WHEN FALSE THEN 'tom' ELSE 'mary' END; -- jack
练习
- 查询 emp 表, 如果 comm 是 null , 则显示 0.0
- 如果 emp 表的 job 是 CLERK 则显示 职员, 如果是 MANAGER 则显示经理– 如果是 SALESMAN 则显示 销售人员,其它正常显示
-- 1-- 说明,判断是否为 null 要使用 is null, 判断不为空 使用 is notSELECT ename, IF(comm IS NULL , 0.0, comm) FROM emp;SELECT ename, IFNULL(comm, 0.0) FROM emp;-- 2SELECT ename, CASEWHEN job = 'CLERK' THEN '职员' WHEN job = 'MANAGER' THEN '经理' WHEN job = 'SALESMAN' THEN '销售人员' ELSE job END AS 'job' FROM emp;SELECT * FROM emp;
MySQL表查询–加强
先执行以下语句创建表,如果看过函数部分已经创建过(emp、dept、salgrade这三张表)则忽略
CREATE TABLE dept( /*部门表*/deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, dname VARCHAR(20) NOT NULL DEFAULT "",loc VARCHAR(13) NOT NULL DEFAULT "");INSERT INTO dept VALUES(10, 'ACCOUNTING', 'NEW YORK'), (20, 'RESEARCH', 'DALLAS'), (30, 'SALES', 'CHICAGO'), (40, 'OPERATIONS', 'BOSTON');#创建表EMP雇员CREATE TABLE emp(empno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, /*编号*/ename VARCHAR(20) NOT NULL DEFAULT "", /*名字*/job VARCHAR(9) NOT NULL DEFAULT "",/*工作*/mgr MEDIUMINT UNSIGNED ,/*上级编号*/hiredate DATE NOT NULL,/*入职时间*/sal DECIMAL(7,2) NOT NULL,/*薪水*/comm DECIMAL(7,2) ,/*红利*/deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0 /*部门编号*/);INSERT INTO emp VALUES(7369, 'SMITH', 'CLERK', 7902, '1990-12-17', 800.00,NULL , 20), (7499, 'ALLEN', 'SALESMAN', 7698, '1991-2-20', 1600.00, 300.00, 30), (7521, 'WARD', 'SALESMAN', 7698, '1991-2-22', 1250.00, 500.00, 30), (7566, 'JONES', 'MANAGER', 7839, '1991-4-2', 2975.00,NULL,20), (7654, 'MARTIN', 'SALESMAN', 7698, '1991-9-28',1250.00,1400.00,30), (7698, 'BLAKE','MANAGER', 7839,'1991-5-1', 2850.00,NULL,30), (7782, 'CLARK','MANAGER', 7839, '1991-6-9',2450.00,NULL,10), (7788, 'SCOTT','ANALYST',7566, '1997-4-19',3000.00,NULL,20), (7839, 'KING','PRESIDENT',NULL,'1991-11-17',5000.00,NULL,10), (7844, 'TURNER', 'SALESMAN',7698, '1991-9-8', 1500.00, NULL,30), (7900, 'JAMES','CLERK',7698, '1991-12-3',950.00,NULL,30), (7902, 'FORD', 'ANALYST',7566,'1991-12-3',3000.00, NULL,20), (7934,'MILLER','CLERK',7782,'1992-1-23', 1300.00, NULL,10);#工资级别表CREATE TABLE salgrade(grade MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,losal DECIMAL(17,2) NOT NULL,hisal DECIMAL(17,2) NOT NULL);INSERT INTO salgrade VALUES (1,700,1200);INSERT INTO salgrade VALUES (2,1201,1400);INSERT INTO salgrade VALUES (3,1401,2000);INSERT INTO salgrade VALUES (4,2001,3000);INSERT INTO salgrade VALUES (5,3001,9999);
使用where子句
- 如何查找1992.1.1后入职的员工
如何使用like操作符
- %:表示O到多个字符:表示单个字符
- 如何显示首字符为S的员工姓名和工资
- 如何显示第三个字符为大写O的所有员工的姓名和工资
如何显示没有上级的雇员的情况
查询表结构
-- 查询加强-- ■ 使用 where 子句-- ?如何查找 1992.1.1 后入职的员工-- 老师说明: 在 mysql 中,日期类型可以直接比较, 需要注意格式SELECT * FROM emp WHERE hiredate > '1992-01-01'-- ■ 如何使用 like 操作符(模糊)-- %: 表示 0 到多个任意字符 _: 表示单个任意字符-- ?如何显示首字符为 S 的员工姓名和工资SELECT ename, sal FROM emp WHERE ename LIKE 'S%' -- ?如何显示第三个字符为大写 O 的所有员工的姓名和工资SELECT ename, sal FROM emp WHERE ename LIKE '__O%' -- ■ 如何显示没有上级的雇员的情况SELECT * FROM emp WHERE mgr IS NULL; -- 注意这里不能用“=”,要用“is”-- ■ 查询表结构DESC emp;-- 使用 order by 子句-- ?如何按照工资的从低到高的顺序[升序],显示雇员的信息SELECT * FROM emp ORDER BY sal;-- ?按照部门号升序而同一部门中的雇员的工资降序排列, 显示雇员信息SELECT * FROM emp ORDER BY deptno ASC , sal DESC;
分页查询
按雇员的id号升序取出,每页显示3条记录,请分别显示第一页,第二页,第三页
基本语法:select … limit start, rows
表示从start+1行开始取,取出rows行, start 从0开始计算
练习题:
按雇员的id号降序取出,每页显示5条记录。请分别显示第3页,第5页对应的sql语句
-- 分页查询-- 第 1 页SELECT * FROM emp ORDER BY empno LIMIT 0, 3; -- 第 2 页SELECT * FROM emp ORDER BY empno LIMIT 3, 3; -- 第 3 页SELECT * FROM emp ORDER BY empno LIMIT 6, 3;-- 导一个公式SELECT * FROM empORDER BY empnoLIMIT 每页显示记录数 * (第几页-1) , 每页显示记录数;
分组函数和分组子句group by
显示每种岗位的雇员总数、平均工资。
显示雇员总数,以及获得补助(comm非空)的雇员数。
显示管理者的总人数。(即mgr有多少种)
显示雇员工资的最大差额。
-- 1select job,count(*),avg(sal) from emp group by job;-- 2-- 思路: 获得补助的雇员数 就是 comm 列为非 null, 就是 count(列).如果此处的值为null,不会统计select count(*),count(comm) from emp;-- 扩展要求:统计没有获得补助的雇员数SELECT COUNT(*), COUNT(IF(comm IS NULL, 1, NULL)) FROM emp;SELECT COUNT(*), COUNT(*) - COUNT(comm) FROM emp-- 3SELECT COUNT(DISTINCT mgr) FROM emp;-- 4SELECT MAX(sal) - MIN(sal) FROM emp;
数据分组的总结
如果select语句同时包含有where,group by ,having , limit,order by那么他们的顺序是where,group by,having , order by,limit
SELECT column1, column2, column3... FROM tablewhere where_definition group by column having condition order by column limit start, rows;
应用案例:
请统计各个部门的平均工资,并且是大于 1000 的,并且按照平均工资从高到低排序,取出前两行记录
select deptno,avg(sal) as avg_sal from emp group by deptno having avg_sal>1000 order by avg_sal desc limit 0,2;
MySQL多表查询
多表查询是指基于两个和两个以上的表查询.在实际应用中,查询单个表可能不能满足你的需求,(如下面的课堂练习),需要使用到(dept表和emp表)
多表查询练习
显示雇员名,雇员工资及所在部门的名字【笛卡尔集】
小技巧:多表查询的条件不能少于表的个数-1,否则会出现笛卡尔集?
如何显示部门号为10的部门名、员工名和工资
显示各个员工的姓名,工资,及其工资的级别
-- 多表查询-- 1. 显示雇员名,雇员工资及所在部门的名字 【笛卡尔集】/*分析 1. 雇员名,雇员工资 来自 emp 表 2. 部门的名字 来自 dept 表 3. 需求对 emp 和 dept 查询 ename,sal,dname,deptno 4. 当我们需要指定显示某个表的列时,需要 表名.列名*/SELECT ename,sal,dname,emp.deptno FROM emp, dept WHERE emp.deptno = dept.deptno; SELECT * FROM emp;SELECT * FROM dept;SELECT * FROM salgrade; -- 小技巧:多表查询的条件不能少于 表的个数-1, 否则会出现笛卡尔集-- 2. 如何显示部门号为 10 的部门名、员工名和工资SELECT ename,sal,dname,emp.deptno FROM emp, dept WHERE emp.deptno = dept.deptno AND emp.deptno = 10 -- 3.显示各个员工的姓名,工资,及其工资的级别-- 思路 姓名,工资 来自 emp 13-- 工资级别 salgrade 5-- 写 sql , 先写一个简单,然后加入过滤条件...select ename, sal, grade from emp , salgrade where sal between losal and hisal; -- 练习:显示雇员名(ename),雇员工资(sal)及所在部门的名字(dname),并按部门排序[降序排].select ename,sal,dname from emp,dept where emp.deptno=dept.deptno order by dname desc;
自连接
自连接是指在同一张表的连接查询[将同一张表看做两张表]。
思考题:
显示公司员工和他的上级的名字(给表取别名,使其能当两张表用)
select worker.ename as '员工名',boss.ename as '上级' from emp worker,emp boss where worker.mgr=boss.empno;
子查询
什么是子查询:
子查询是指嵌入在其它sql语句中的select语句,也叫嵌套查询
单行子查询:
单行子查询是指只返回一行数据的子查询语句
请思考:如何显示与SMITH同一部门的所有员工?
多行子查询:
多行子查询指返回多行数据的子查询 使用关键字in
-- 子查询的演示-- 单行子查询-- 请思考:如何显示与 SMITH 同一部门的所有员工?/* 1. 先查询到 SMITH 的部门号得到 2. 把上面的 select 语句当做一个子查询来使用*/SELECT deptno FROM emp WHERE ename = 'SMITH' ;-- 下面的答案. SELECT *FROM emp WHERE deptno = (SELECT deptno FROM emp WHERE ename = 'SMITH' );-- 多行子查询-- 练习:如何查询和部门 10 的工作相同的雇员的-- 名字、岗位、工资、部门号, 但是不含 10 号部门自己的雇员./* 1. 查询到 10 号部门有哪些工作 2. 把上面查询的结果当做子查询使用*/select distinct job from emp where deptno=10;select ename, job,sal,deptno from emp where job in ( select distinct job from emp where deptno=10) and deptno != 10;
子查询当做临时表使用
练习题:
先使用ecshop.sql文件创建数据库—- 下载
查询ecshop中各个类别(cat_id)中,价格最高的商品.结果 如下:
提示,可以将子查询当做一张临时表使用
select * from ecshop.ecs_goods;select goods_id,cat_id,goods_name,shop_price from ecshop.ecs_goods;-- 1-- 先得到 各个类别中,价格最高的商品 max + group by cat_id, 当做临时表select cat_id,Max(shop_price) from ecshop.ecs_goods group by cat_id;-- 把子查询当做一张临时表可以解决很多很多复杂的查询select goods_id,cat_id,goods_name,shop_price from ecshop.ecs_goods;select temp.cat_id,goods_id,goods_name,shop_price from ( select cat_id,Max(shop_price) as max_price from ecshop.ecs_goods group by cat_id) temp,ecs_goods where temp.cat_id=ecs_goods.cat_id and temp.max_price=ecs_goods.shop_price;
在多行子查询中使用all操作符
请思考:如何显示工资比部门30的所有员工的工资高的员工的姓名工资和部门号
-- 使用max()select ename,sal,deptno from emp where sal >(select max(sal) from emp where deptno=30);-- 使用all()select ename,sal,deptno from emp where sal >all(select sal from emp where deptno=30);
在多行子查询中使用any操作符
请思考:如何显示工资比部门30的其中一个员工的工资高的员工的姓名、工资和部门号
-- 使用min()select ename,sal,deptno from emp where sal >(select min(sal) from emp where deptno=30);-- 使用any()select ename,sal,deptno from emp where sal >any(select sal from emp where deptno=30);
多列子查询
多列子查序则是指查询返回多个列数据的子查询语句
(字段1,字段2…)=(select 字段1,字段2 from …)
练习题:
请查询student表中和宋江数学,英语,语文完全相同的学生
请思考如何查询emp表中与allen的部门和岗位完全相同的所有雇员(并且不含smith本人)
-- 1select math,english,chinese from student where name='宋江';select * from student where (math,english,chinese)=(select math,english,chinese from student where name='宋江') and name!='宋江';-- 2select deptno,job from emp where LCASE(ename)='allen';SELECT * FROM emp WHERE (deptno,job)=(select deptno,job from emp where LCASE(ename)='allen') and LCASE(ename)!='allen';
课后练习(先自己做)
- emp表中,查找每个部门工资高于本部门平均工资的人的资料
- emp表中,查找每个部门工资最高的人的详细资料
- 查询每个部部门的信息(包括:部门名,编号,地址(dept表中))和人员数量,
-- 1select deptno,avg(sal) as avg_sal from emp group by deptno;select ename,sal,avg_sal,temp.deptno from emp,(select deptno,avg(sal) as avg_sal from emp group by deptno) temp where emp.deptno=temp.deptno and emp.sal>avg_sal;-- 2select deptno,max(sal) as max_sal from emp group by deptno;select empno,ename,job,sal,emp.deptno from emp,(select deptno,max(sal) as max_sal from emp group by deptno) temp where emp.deptno=temp.deptno and sal=max_sal;-- 3-- 表名.* 表示将该表所有列都显示出来, 可以简化 sql 语句select deptno,count(*) as count_dept from emp group by deptno;select dept.*,count_dept from dept,(select deptno,count(*) as count_dept from emp group by deptno) temp where dept.deptno=temp.deptno;
合并查询
有时在实际应用中,为了合并多个select语句的结果,可以使用集合操作符号
union all
该操作符用于取得两个结果集的并集。当使用该操作符时,不会取消重复行。(注意:可以理解为把第二个表的内容接在第一个表的下面。不需要数据类型一致,但列数必须相同)
select ename,sal,job from emp where sal>2500 union select ename,job,sal from emp where job='MANAGER';
union
该操作赋与union all相似,但是会自动去掉结果集中重复行
select ename,sal,job from emp where sal>2500 union all select ename,sal,job from emp where job='manager';
mysql表内连接
select ... from 表1 [inner] join 表2 on 条件 -- inner加不加都一样
获取两个表中字段匹配关系的记录。
个人认为内连接用处不大,它能处理的问题用之前的自连接知识也能处理。
mysql表外连接
提出一个问题
前面我们学习的查询,是利用where子句对两张表或者多张表,形成的笛卡尔积进行筛选,根据关联条件,显示所有匹配的记录,匹配不上的,不显示
比如:列出部门名称和这些部门的员工名称和工作,同时要求显示出那些没有员工的部门。
使用我们学习过的多表查询的SQL,看看效果如何?
SELECT dname, ename, jobFROM emp, deptWHERE emp.deptno = dept.deptno;-- 因为dept.deptno为40时,其中没有员工。“emp.deptno = dept.deptno”就会因为emp表中没有deptno为40的员工,导致 不会显示没有员工的部门。
外连接
左外连接
如果左侧的表完全显示我们就说是左外连接
select ... from 表1 left join 表2 on 条件 [表1就是左表 表2就是右表]
右外连接
如果右侧的表完全显示我们就说是右外连接
select ... from 表1 right join 表2 on 条件 [表1:就是左表 表2:就是右表]
先创建表:
-- 创建 stu/*id name1 Jack2 Tom3 Kity4 nono*/CREATE TABLE stu (id INT, `name` VARCHAR(32));INSERT INTO stu VALUES(1, 'jack'),(2,'tom'),(3, 'kity'),(4, 'nono');SELECT * FROM stu; -- 创建 exam/*id grade1 562 7611 8*/CREATE TABLE exam( id INT, grade INT);INSERT INTO exam VALUES(1, 56),(2,76),(11, 8);SELECT * FROM exam;
使用
使用左连接—(显示所有人的成绩,如果没有成绩,也要显示该人的姓名和id号,成绩显示为空)
使用右外连接—(显示所有成绩,如果没有名字匹配,显示空)
-- 1-- 使用左连接-- (显示所有人的成绩,如果没有成绩,也要显示该人的姓名和 id 号,成绩显示为空)SELECT `name`, stu.id, gradeFROM stu, examWHERE stu.id = exam.id; -- 改成左外连接SELECT `name`, stu.id, grade FROM stu LEFT JOIN exam ON stu.id = exam.id;-- 2-- 使用右外连接(显示所有成绩,如果没有名字匹配,显示空)-- 即:右边的表(exam) 和左表没有匹配的记录,也会把右表的记录显示出来SELECT `name`, stu.id, grade FROM stu RIGHT JOIN exam ON stu.id = exam.id;-- 自己练习:-- 列出部门名称和这些部门的员工信息(名字和工作),同时列出那些没有员工的部门名。5min-- 1.使用左外连接实现-- 2.使用右外连接实现select dname,ename,job from dept left join emp on emp.deptno=dept.deptno;select dname,ename,job from emp right join dept on emp.deptno=dept.deptno;
表复制和表去重
表复制
-- 表的复制-- 为了对某个 sql 语句进行效率测试,我们需要海量数据时,可以使用此法为表创建海量数据CREATE TABLE my_tab01( id INT, `name` VARCHAR(32), sal DOUBLE, job VARCHAR(32), deptno INT);DESC my_tab01;SELECT * FROM my_tab01; -- 演示如何自我复制-- 1. 先把 emp 表的记录复制到 my_tab01INSERT INTO my_tab01(id, `name`, sal, job,deptno)SELECT empno, ename, sal, job, deptno FROM emp; -- 2. 自我复制INSERT INTO my_tab01 SELECT * FROM my_tab01;-- 自我复制, 如果多次使用,最后数据量会很大SELECT COUNT(*) FROM my_tab01;
表去重(面试题)
-- 如何删除掉一张表重复记录-- 1. 先创建一张表 my_tab02, -- 2. 让 my_tab02 有重复的记录CREATE TABLE my_tab02 LIKE emp; -- 这个语句 把 emp 表的结构(列),复制到 my_tab02DESC my_tab02;INSERT INTO my_tab02 SELECT * FROM emp;SELECT * FROM my_tab02;-- 3. 考虑去重 my_tab02 的记录/* 思路 (1) 先创建一张临时表 my_tmp , 该表的结构和 my_tab02 一样 (2) 把 my_tmp 的记录 通过 distinct 关键字 处理后 把记录复制到 my_tmp (3) 清除掉 my_tab02 记录 (4) 把 my_tmp 表的记录复制到 my_tab02 (5) drop 掉 临时表 my_tmp*/ -- (1) 先创建一张临时表 my_tmp , 该表的结构和 my_tab02 一样create table my_tmp like my_tab02;-- (2) 把 my_tmp 的记录 通过 distinct 关键字 处理后 把记录复制到 my_tmpinsert into my_tmp select distinct * from my_tab02;-- (3) 清除掉 my_tab02 记录delete from my_tab02; -- (4) 把 my_tmp 表的记录复制到 my_tab02insert into my_tab02 select * from my_tmp; -- (5) drop 掉 临时表 my_tmpdrop table my_tmp;select * from my_tab02;
约束
约束用于确保数据库的数据满足特定的商业规则。在mysql中,约束包括: not null,unique,primary key,foreign key,和check五种.
primary key(主键)
primary key(主键)-基本使用
字段名 字段类型 primary key
用于唯一的标示表行的数据,当定义主键约束后,该列不能重复
-- 主键使用-- id name emailCREATE TABLE t17(id INT PRIMARY KEY, -- 表示 id 列是主键`name` VARCHAR(32),email VARCHAR(32)); -- 主键列的值是不可以重复INSERT INTO t17 VALUES(1, 'jack', 'jack@sohu.com');INSERT INTO t17 VALUES(2, 'tom', 'tom@sohu.com');INSERT INTO t17 VALUES(1, 'hsp', 'hsp@sohu.com');SELECT * FROM t17; -- 主键使用的细节讨论-- primary key 不能重复而且不能为 null。INSERT INTO t17 VALUES(NULL, 'hsp', 'hsp@sohu.com'); -- 一张表最多只能有一个主键, 但可以是复合主键(比如 id+name)CREATE TABLE t18(id INT PRIMARY KEY, -- 表示 id 列是主键`name` VARCHAR(32) PRIMARY KEY, -- 错误的email VARCHAR(32)); -- 演示复合主键 (id 和 name 做成复合主键)CREATE TABLE t18(id INT , `name` VARCHAR(32),email VARCHAR(32), PRIMARY KEY (id, `name`) -- 这里就是复合主键);INSERT INTO t18 VALUES(1, 'tom', 'tom@sohu.com');INSERT INTO t18 VALUES(1, 'jack', 'jack@sohu.com');INSERT INTO t18 VALUES(1, 'tom', 'xx@sohu.com'); -- 这里就违反了复合主键SELECT * FROM t18; -- 主键的指定方式 有两种-- 1. 直接在字段名后指定:字段名 primakry key-- 2. 在表定义最后写 primary key(列名);CREATE TABLE t19(id INT , `name` VARCHAR(32) PRIMARY KEY, email VARCHAR(32));CREATE TABLE t20(id INT , `name` VARCHAR(32) , email VARCHAR(32), PRIMARY KEY(`name`)); -- 在表定义最后写 primary key(列名) -- 使用 desc 表名,可以看到 primary key 的情况DESC t20 -- 查看 t20 表的结果,显示约束的情况DESC t18
primary key(主键)-细节说明
primary key不能重复而且不能为null。
一张表最多只能有一个主键,但可以是复合主键
主键的指定方式有两种
直接在字段名后指定:字段名primary key
在表定义最后写primary key(列名);
使用desc表名,可以看到primary key的情况
提醒:在实际开发中,每个表往往都有主键!
not nulI(非空)
如果在列上定义了not null,那么当插入数据时,必须为列提供数据。
字段名 字段类型 not null
unique(唯一)
当定义了唯一约束后,该列值是不能重复的.。
字段名 字段类型 unique
unique细节(注意):
- 如果没有指定not null,则unique字段可以有多个null
- 一张表可以有多个unique字段
-- unique 的使用CREATE TABLE t21(id INT UNIQUE, -- 表示 id 列是不可以重复的. `name` VARCHAR(32) , email VARCHAR(32));INSERT INTO t21 VALUES(1, 'jack', 'jack@sohu.com');INSERT INTO t21 VALUES(1, 'tom', 'tom@sohu.com'); -- unqiue 使用细节-- 1.如果没有指定 not null , 则 unique 字段可以有多个 null -- 如果一个列(字段), 是 unique not null 使用效果类似 primary keyINSERT INTO t21 VALUES(NULL, 'tom', 'tom@sohu.com');SELECT * FROM t21; -- 2. 一张表可以有多个 unique 字段CREATE TABLE t22(id INT UNIQUE , -- 表示 id 列是不可以重复的. `name` VARCHAR(32) UNIQUE , -- 表示 name 不可以重复 email VARCHAR(32));DESC t22;
foreign key(外键)
foreign key (本表字段名) references 主表名(主键名或unique字段名)
用于定义主表和从表之间的关系:外键约束要定义在从表上,主表则必须具有主键约束或是unique约束.当定义外键约束后,要求外键列数据必须在主表的主键列存在或是为null(学生/班级图示)
-- 外键演示-- 创建 主表 my_classCREATE TABLE my_class (id INT PRIMARY KEY , -- 班级编号`name` VARCHAR(32) NOT NULL DEFAULT ''); -- 创建 从表 my_stuCREATE TABLE my_stu ( id INT PRIMARY KEY , -- 学生编号 `name` VARCHAR(32) NOT NULL DEFAULT '', class_id INT , -- 学生所在班级的编号 -- 下面指定外键关系 FOREIGN KEY (class_id) REFERENCES my_class(id));-- 测试数据INSERT INTO my_class VALUES(100, 'java'), (200, 'web');INSERT INTO my_class VALUES(300, 'php');SELECT * FROM my_class;INSERT INTO my_stu VALUES(1, 'tom', 100);INSERT INTO my_stu VALUES(2, 'jack', 200);INSERT INTO my_stu VALUES(3, 'hsp', 300);INSERT INTO my_stu VALUES(4, 'mary', 400); -- 这里会失败...因为 400 班级不存在INSERT INTO my_stu VALUES(5, 'king', NULL); -- 可以, 外键 没有写 not nullSELECT * FROM my_class; DELETE FROM my_class WHERE id = 100;-- 一旦建立主外键的关系,数据不能随意删除了
foreign key(外键)—细节说明
- 外键指向的表的字段,要求是primary key或者是unique
- 表的类型是innodb,这样的表才支持外键
- 外键字段的类型要和主键字段的类型一致(长度可以不同)
- 外键字段的值,必须在主键字段中出现过,或者为null [前提是外键字段允许为null]
- 一旦建立主外键的关系,数据不能随意删除了.
check【了解就行】
列名 类型 check (check条件)
用于强制行数据必须满足的条件,假定在sal列上定义了check约束,要求sal列值在1000~2000之间如果不在1000 ~2000之间就会提示出错。
oracle和sql server均支持check ,但是mysql5.7目前还不支持check ,只做语法校验,但不会生效。
-- 演示 check 的使用-- mysql5.7 目前还不支持 check ,只做语法校验,但不会生效-- 了解-- 学习 oracle, sql server, 这两个数据库是真的生效. -- 测试CREATE TABLE t23 ( id INT PRIMARY KEY, `name` VARCHAR(32) , sex VARCHAR(6) CHECK (sex IN('man','woman')), sal DOUBLE CHECK ( sal > 1000 AND sal < 2000)); -- 添加数据INSERT INTO t23 VALUES(1, 'jack', 'mid', 1);-- 不会报错SELECT * FROM t23;-- check没有生效
商店售货系统表设计案例【先自己练,再看对答案】
现有一个商店的数据库shop_db,记录客户及其购物情况,由下面三个表组成:
商品goods(商品号goods_id,商品名goods_name,单价unitprice,商品类别category,供应商provider);
客户customer(客户号customer_id,姓名name,住址address,电邮email,性别sex,身份证card_ld);
购买purchase(购买订单号order_id,客户号customer_id,商品号goods_id,购买数量nums);
建表,在定义中要求声明[进行合理设计]:
每个表的主外键;
客户的姓名不能为空值;
电邮不能够重复;
客户的性别[男|女] check 枚举..
单价unitprice在1.0 – 9999.99之间 check
create table goods(goods_id int primary key,goods_name varchar(50),unitprice double check(unitprice>=1.0 and unitprice<=9999.99),category varchar(20),provider varchar(20));create table customer(customer_id int primary key,name varchar(10) not null,address varchar(30),email varchar(30) unique,sex enum('男','女'),card_ld char(18));create table purchase(order_id int primary key,customer_id int,goods_id int,nums int,foreign key (customer_id) references customer(customer_id),foreign key (goods_id) references goods(goods_id));
自增长
自增长基本介绍一个问题
在某张表中,存在一个id列(整数),我们希望在添加记录的时候,该列从1开始,自动的增长,怎么处理?
字段名 整数类型 primary key auto_increment
添加自增长的字段的方式
insert into xxx(字段1,字段2...) values(null,'值'...);insert into xxx(字段2...) values('值1','值2'...);insert into xxx values(null,'值1'...);
自增长使用细节
- 一般来说自增长是和primary key配合使用的
- 自增长也可以单独使用[但是需要配合一个unique]
- 自增长修饰的字段为整数型的(虽然小数也可以但是非常非常少这样使用)
- 自增长默认从1开始,你也可以通过如下命令修改:alter table 表名 auto_increment = 新的开始值;
- 建议:如果你添加数据时,给自增长字段(列)指定的有值,则以指定的值为准,如果指定了自增长,一般来说,就按照自增长的规则来添加数据。
-- 演示自增长的使用-- 创建表CREATE TABLE t24(id INT PRIMARY KEY AUTO_INCREMENT, email VARCHAR(32)NOT NULL DEFAULT '',`name` VARCHAR(32)NOT NULL DEFAULT '');DESC t24;-- 测试自增长的使用INSERT INTO t24 VALUES(NULL, 'tom@qq.com', 'tom');INSERT INTO t24 (email, `name`) VALUES('hsp@sohu.com', 'hsp');INSERT INTO t24 VALUES(8,'hsp@sohu.com', 'hsp');-- 可以在自增长的位置插入该列不存在的int值,但不推荐,实际情况不会这样用,会出现一系列问题SELECT * FROM t24;-- 修改默认的自增长开始值CREATE TABLE t25(id INT PRIMARY KEY AUTO_INCREMENT, email VARCHAR(32)NOT NULL DEFAULT '', `name` VARCHAR(32)NOT NULL DEFAULT '');ALTER TABLE t25 AUTO_INCREMENT = 100;INSERT INTO t25 VALUES(NULL, 'mary@qq.com', 'mary');INSERT INTO t25 VALUES(666, 'hsp@qq.com', 'hsp');SELECT * FROM t25;
索引
说起提高数据库性能,索引是最物美价廉的东西了。不用加内存,不用改程序,不用调sql,查询速度就可能提高百倍干倍。
这里我们举例说明索引的好处【构建海量表8000000】(快速体验案例index.sql)
先执行以下语句创建数据库tmp (注意有八百万条数据,大概需要花费二十分钟时间,占用600M的空间)
-- 创建测试数据库 tmpCREATE DATABASE tmp;
先切换到tmp数据库再执行以下语句
CREATE TABLE dept( /*部门表*/deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,dname VARCHAR(20) NOT NULL DEFAULT "",loc VARCHAR(13) NOT NULL DEFAULT "") ;#创建表EMP雇员CREATE TABLE emp(empno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, /*编号*/ename VARCHAR(20) NOT NULL DEFAULT "", /*名字*/job VARCHAR(9) NOT NULL DEFAULT "",/*工作*/mgr MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,/*上级编号*/hiredate DATE NOT NULL,/*入职时间*/sal DECIMAL(7,2) NOT NULL,/*薪水*/comm DECIMAL(7,2) NOT NULL,/*红利*/deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0 /*部门编号*/) ;#工资级别表CREATE TABLE salgrade(grade MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,losal DECIMAL(17,2) NOT NULL,hisal DECIMAL(17,2) NOT NULL);#测试数据INSERT INTO salgrade VALUES (1,700,1200);INSERT INTO salgrade VALUES (2,1201,1400);INSERT INTO salgrade VALUES (3,1401,2000);INSERT INTO salgrade VALUES (4,2001,3000);INSERT INTO salgrade VALUES (5,3001,9999);delimiter $$#创建一个函数,名字 rand_string,可以随机返回我指定的个数字符串create function rand_string(n INT)returns varchar(255) #该函数会返回一个字符串begin#定义了一个变量 chars_str, 类型 varchar(100)#默认给 chars_str 初始值 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ' declare chars_str varchar(100) default 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ'; declare return_str varchar(255) default ''; declare i int default 0; while i < n do # concat 函数 : 连接函数mysql函数 set return_str =concat(return_str,substring(chars_str,floor(1+rand()*52),1)); set i = i + 1; end while; return return_str; end $$ #这里我们又自定了一个函数,返回一个随机的部门号create function rand_num( )returns int(5)begindeclare i int default 0;set i = floor(10+rand()*500);return i;end $$ #创建一个存储过程, 可以添加雇员create procedure insert_emp(in start int(10),in max_num int(10))begindeclare i int default 0;#set autocommit =0 把autocommit设置成0 #autocommit = 0 含义: 不要自动提交 set autocommit = 0; #默认不提交sql语句 repeat set i = i + 1; #通过前面写的函数随机产生字符串和部门编号,然后加入到emp表 insert into emp values ((start+i) ,rand_string(6),'SALESMAN',0001,curdate(),2000,400,rand_num()); until i = max_num end repeat; #commit整体提交所有sql语句,提高效率 commit; end $$ #添加8000000数据call insert_emp(100001,8000000)$$#命令结束符,再重新设置为;delimiter ;
索引效果
SELECT COUNT(*) FROM emp; -- 在没有创建索引时,我们的查询一条记录SELECT * FROM emp WHERE empno = 1234567 -- 我的电脑的查询时间为6.329s-- 使用索引来优化一下, 体验索引的牛-- 在没有创建索引前 , emp.ibd 文件大小 是 524m-- 创建索引后 emp.ibd 文件大小 是 655m [索引本身也会占用空间.]-- 创建 ename 列索引,emp.ibd 文件大小 是 827m-- empno_index 索引名称-- ON emp (empno) : 表示在 emp 表的 empno 列创建索引CREATE INDEX empno_index ON emp (empno);-- 耗时22.726s-- 创建索引后, 查询的速度如何SELECT * FROM emp WHERE empno = 1234578; -- 0.023s 原来是 6.329s-- 创建索引后,只对创建了索引的列有效SELECT * FROM emp WHERE ename = 'PjDlwy'; -- 没有在 ename 创建索引时,时间 7.948sCREATE INDEX ename_index ON emp (ename); -- 在 ename 上创建索引SELECT * FROM emp WHERE ename = 'PjDlwy';-- 0.029S
索引的原理
没有索引为什么会慢? 因为全表扫描.
使用索引为什么会快? 形成一个索引的数据结构,比如二叉树(例:进行n次比较最多能够覆盖范围2n-1个)
索引的代价
- 磁盘占用
- 对表内容的 增、删、改 语句的效率影响(会对索引进行修改维护,对速度有影响。)
告诉你,在我们实际项目中,select[90%]多还是update,delete,insert[10%]操作多?
索引的类型
主键索引,主键自动的为主索引(类型Primary key)
唯一索引(UNIQUE)
普通索引(INDEX)
全文索引 (FULLTEXT) [适用于MylSAM]
一般开发,不使用mysql自带的全文索引(因为比较拉胯),而是使用:全文搜索Solr和 ElasticSearch (ES)
索引使用
注意:约束中的primary key(主键)和unique(唯一)就是主键索引和唯一索引。创建表时如果设置了主键或unique,也就是创建了此处的主键索引或唯一索引
添加索引(唯一索引/普通索引)
create [UNIQUE] index index_name on tbl_name (col_name [(length)][ASC|DESC],...);alter table table_name ADD [UNIQUE] INDEX [index_name] (index_col_name,...);
添加主键(索引)
ALTER TABLE 表名 ADD PRIMARY KEY(列名,…);
删除索引(唯一索引/普通索引)
DROP INDEX index_name ON tbl_name;alter table table_name drop index index_name;
删除主键索引 比较特别:
删除索引也就是删除了约束中的主键。
alter table t_b drop primary key;
查询索引(三种方式)
show index(es) from table_name;-- 加不加es结果都一样show keys from table_name;desc table_Name;-- 不推荐,不能显示详细信息
演示:
-- 演示 mysql 的索引的使用-- 创建索引CREATE TABLE t25 (id INT,`name` VARCHAR(32)); -- 查询表是否有索引SHOW INDEXES FROM t25; -- 添加索引-- 添加唯一索引CREATE UNIQUE INDEX id_index ON t25 (id); -- 添加普通索引方式1CREATE INDEX id_index ON t25 (id); -- 如何选择-- 1. 如果某列的值,是不会重复的,则优先考虑使用 unique 索引, 否则使用普通索引-- 添加普通索引方式2ALTER TABLE t25 ADD INDEX id_index (id);-- 添加主键索引CREATE TABLE t26 (id INT , `name` VARCHAR(32));ALTER TABLE t26 ADD PRIMARY KEY (id);SHOW INDEX FROM t25;-- 删除索引(非主键索引)DROP INDEX id_index ON t25;-- 删除主键索引(同时也会删除此表的主键约束)ALTER TABLE t26 DROP PRIMARY KEY-- 修改索引: 先删除,再添加新的索引-- 查询索引-- 1. 方式SHOW INDEX FROM t25-- 2. 方式SHOW INDEXES FROM t25-- 3. 方式SHOW KEYS FROM t25-- 4 方式 (不推荐)DESC t25 -- 能在显示结果的Key列看到索引,但不能看到详细信息
练习题:
创建一张订单表order (id号,商品名,订购人,数量).要求id号为主键,请使用2种方式来创建主键.(提示:为练习方便,可以是order1 , order2)
创建一张特价菜谱表menu (id号,菜谱名,厨师,点餐人身份证,价格).要求id号为主键,点餐人身份证是unique请使用两种方式来创建unique.(提示:为练习方便,可以是menu1 , menu2)
创建一张运动员表sportman (id号,名字,特长).要求id号为主键,
名字为普通索引,请使用两种方式来创建索引(提示:为练习方便,可以是不同表名sportman1 , sportman2)-- 1CREATE TABLE `order1`(id int primary key,name varchar(50),cou int);CREATE TABLE `order2`(id int,name varchar(50),cou int);alter table order2 add primary key(id);-- 2create table menu1(id int primary key,menu_name varchar(30),cook varchar(30),user varchar(30) unique,price double);SHOW INDEXES FROM menu1;create table menu2(id int primary key,menu_name varchar(30),cook varchar(30),user varchar(30),price double);alter table menu2 add unique index uni(user);SHOW INDEX FROM menu2;create table menu3(id int primary key,menu_name varchar(30),cook varchar(30),user varchar(30),price double);create unique index aa on menu3(user);SHOW Keys FROM menu2;-- 3create table sportman1(id int primary key, name varchar(20),specialty varchar(20));create index iname on sportman1(name);create table sportman2(id int primary key, name varchar(20),specialty varchar(20));alter table sportman2 add index iname(name);
哪些列上适合使用索引
较频繁的作为查询条件字段应该创建索引
select* from emp where empno =1
唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
select * from emp where sex =’男‘(性别列只有两种内容)
更新非常频繁的字段不适合创建索引
select * from emp where logincount =1
不会出现在WHERE子句中字段不该创建索引
事务
什么是事务
事务用于保证数据的一致性,它由一组相关的dml语句组成,该组的dml语句要么全部成功,要么全部失败。如:转账就要用事务来处理,用以保证数据的一致性。
事务和锁
当执行事务操作时(dml语句),mysql会在表上加锁,防止其它用户改表的数据.这对用户来讲是非常重要的。
mysql数据库控制台事务的几个重要操作
- start transaction — 开始一个事务
- savepoint 保存点名 –设置保存点
- rollback to 保存点名 –回退事务
- rollback –回退全部事务
- commit — 提交事务,所有的操作生效,不能回退
-- 事务的一个重要的概念和具体操作-- 看一个图[看示意图]-- 演示-- 1. 创建一张测试表CREATE TABLE t27( id INT, `name` VARCHAR(32)); -- 2. 开始事务START TRANSACTION-- 3. 设置保存点SAVEPOINT a-- 执行 dml 操作INSERT INTO t27 VALUES(100, 'tom');SELECT * FROM t27;SAVEPOINT b-- 执行 dml 操作INSERT INTO t27 VALUES(200, 'jack');-- 回退到 bROLLBACK TO b-- 继续回退 aROLLBACK TO a-- 如果这样, 表示直接回退到事务开始的状态. ROLLBACKCOMMIT
回退事务
在介绍回退事务前,先介绍一下保存点(savepoint).保存点是事务中的点.用于取消部分事务,当结束事务时,会自动的删除该事务所定义的所有保存点.当执行回退事务时,通过指定保存点可以回退到指定的点。回退后会把中间的所有点删掉。
提交事务
使用commit语句可以提交事务.当执行了commit语句子后,会确认事务的变化、结束事务、删除保存点、释放锁,数据生效。当使用commit语句结束事务子后,其它会话[其他连接]将可以查看到事务变化后的新数据【所有数据正式生效】
事务细节讨论
- 如果不开始事务,默认情况下,dml操作是自动提交的,不能回滚
- 如果开始一个事务,你没有创建保存点.你可以执行rollback,默认就是回退到你事务开始的状态.
- 你也可以在这个事务中(还没有提交时),创建多个保存点.比如: savepoint aaa;执行dml , savepoint bbb;
- 你可以在事务没有提交前,选择回退到哪个保存点.
- mysql的事务机制需要innodb的存储引擎还可以使用,myisam不好使.
- 开始一个事务start transaction, set autocommit=off;
-- 1. 如果不开始事务,默认情况下,dml 操作是自动提交的,不能回滚INSERT INTO t27 VALUES(300, 'milan'); -- 自动提交 commitSELECT * FROM t27-- 2. 如果开始一个事务,你没有创建保存点. 你可以执行 rollback,-- 默认就是回退到你事务开始的状态START TRANSACTIONINSERT INTO t27 VALUES(400, 'king');INSERT INTO t27 VALUES(500, 'scott');ROLLBACK -- 表示直接回退到事务开始的的状态COMMIT; -- 3. 你也可以在这个事务中(还没有提交时), 创建多个保存点.比如: savepoint aaa; -- 执行 dml , savepoint bbb-- 4. 你可以在事务没有提交前,选择回退到哪个保存点-- 5. InnoDB 存储引擎支持事务 , MyISAM 不支持-- 6. 开始一个事务 start transaction, set autocommit=off;
mysql事务隔离级别
事务隔离级别介绍
- 多个连接开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个连接在获取数据时的准确性。(通俗解释)
- 如果不考虑隔离性,可能会引发如下问题:
- 脏读(dirty read):当一个事务读取另一个事务尚未提交的改变(增、删、改)时,产生脏读
- 不可重复读(nonrepeatable read):同一查询在同一事务中多次进行,由于其·他提交事务所做的修改或删除,每次返回不同的结果集,此时发生不可重复读。
- 幻读(phantom read):同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发生幻读。
事务隔离级别
概念:Mysql隔离级别定义了事务与事务之间的隔离程度。
Mysql隔离级别(4种) | 脏读 | 不可重复读 | 幻读 | 加锁读 |
---|---|---|---|---|
读未提交(Read uncommitted) | ✔ | ✔ | ✔ | 不加锁 |
读已提交(Read committed) | ✘ | ✔ | ✔ | 不加锁 |
可重复读(Repeatable read) | ✘ | ✘ | ✘ | 不加锁 |
可串行化(Serializable)[演示重开客户端] | ✘ | ✘ | ✘ | 加锁 |
说明: ✔可能出现 ✘不会出现 |
---|
设置事务隔离级别
查看当前会话隔离级别
select @@tx_isolation;
查看系统当前隔离级别
select @@global.tx_isolation;
设置当前会话事务隔离级别(一般在事务未启动时设置,否则需要在提交后才生效)
set session transaction isolation level repeatable read; -- repeatable read根据情况换成适合的隔离级别
设置系统当前隔离级别
set global transaction isolation level repeatable read;-- repeatable read根据情况换成适合的隔离级别
mysql默认的事务隔离级别是repeatable read,一般情况下,没有特殊要求,没有必要修改(因为该级别可以满足绝大部分项目需求)
隔离级别全局修改,修改mysql.ini配置文件,在最后加上。加上后需要重启mysql服务(不用背,面试官一般不会问)
#可选参数有:READ-UNCOMMITTED,READ-COMMITTED, REPEATABLE-READ,SERIALIZABLE.[mysqld]transaction-isolation = REPEATABLE-READ
事务的 acid 特性
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态
隔离性(lsolation)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有何影响
事务的课堂练习[一定要自己去练习,体会]
登录mysql控制客户端A,创建表dog (id, name),添加两条记录,开始一个事务;
登录mysql控制客户端B,设置为读未提交,开始一个事务.
A客户端修改Dog 一条记录,不要提交。看看B客户端是否看到变化,说明什么问题?
登录mysql客户端C,设置为读已提交,开始一个事务,这时A客户修改一条记录,不要提交,看看C客户端是否看到变化,A提交后,看C客户端是否有变化。说明什么问题?
-- 1-- Ause db01;CREATE TABLE dog(id int,name varchar(20));INSERT INTO dog values (1,'aa'),(2,'bb');start transaction;-- 2-- Buse db01;set session transaction isolation level read uncommitted;start transaction;-- 3-- AUPDATE dog set name='cc' where id=2;-- BSELECT * FROM dog; -- 可以看到A修改后未提交的内容-- 4-- Cuse db01;set session transaction isolation level read committed;start transaction;-- AUPDATE dog set name='ddd' where id=1;-- Cselect * from dog; -- 不能看到A修改后未提交的内容-- Acommit;-- Cselect * from dog; -- 能看到A修改后提交的内容
mysql表类型和存储引擎
基本介绍
MySQL的表类型由存储引擎(Storage Engines)决定,主要包括MyISAM、innoDB、Memory等。
MySQL 数据表主要支持六种类型,分别是:CSV、Memory、ARCHIVE、MRG MYISAM、MYISAM、InnoBDB。
这六种又分为两类,一类是”事务安全型”(transaction-safe), 比如:InnoDB;其余都属于第二类,称为”非事务安全型”(non-transaction-safe)[mysiam和memory].
-- 展示引擎类型show engines;
以下为引擎查询结果
Engine | Support | Comment | Transactions | XA | Savepoints |
---|---|---|---|---|---|
InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES | YES | YES |
MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO |
MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO |
BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO |
MyISAM | YES | MyISAM storage engine | NO | NO | NO |
CSV | YES | CSV storage engine | NO | NO | NO |
ARCHIVE | YES | Archive storage engine | NO | NO | NO |
主要的存储引擎/表类型特点
细节说明
我这里重点给大家介绍三种: MylSAM、InnoDB、MEMORY
- MyISAM不支持事务、也不支持外键,但其访问速度快,对事务完整性没有要求
- InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是比起MyISAM存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。
- MEMORY存储引擎使用存在内存中的内容来创建表。每个MEMORY表只实际对应一个磁盘文件。MEMORY类型的表访问非常得快,因为它的数据是放在内存中的,并且默认使用HASH索引。但是一旦MySQL服务关闭,表中的数据就会丢失掉,表的结构还在。
三种存储引擎表使用案例
对前面我们提到的三种存储引擎,我们举例说明:
-- 表类型和存储引擎-- 查看所有的存储引擎SHOW ENGINES-- innodb 存储引擎,是前面使用过. -- 1. 支持事务 2. 支持外键 3. 支持行级锁-- myisam 存储引擎CREATE TABLE t28 (id INT, `name` VARCHAR(32)) ENGINE MYISAM;-- 1. 添加速度快 2. 不支持外键和事务 3. 支持表级锁START TRANSACTION;SAVEPOINT t1;INSERT INTO t28 VALUES(1, 'jack');SELECT * FROM t28;ROLLBACK TO t1; -- 会回滚失败-- memory 存储引擎-- 1. 数据存储在内存中[关闭了 Mysql 服务,数据丢失, 但是表结构还在]-- 2. 执行速度很快(没有 IO 读写) 3. 默认支持索引(hash 表)CREATE TABLE t29 (id INT, `name` VARCHAR(32)) ENGINE MEMORY;DESC t29INSERT INTO t29 VALUES(1,'tom'), (2,'jack'), (3, 'hsp');SELECT * FROM t29;-- 自己重启mysql服务再看看数据和表结构是否还在-- 指令修改存储引擎ALTER TABLE `t29` ENGINE = INNODB
如何选择表的存储引擎
如果你的应用不需要事务,处理的只是基本的CRUD操作,那么MylSAM是不二选择,速度快
如果需要支持事务,选择lnnoDB。
Memory存储引擎就是将数据存储在内存中,由于没有磁盘I/O的等待,速度极快。但由于是内存存储引擎,所做的任何修改在服务器重启后都将消失。(经典用法用户的在线状态().)
修改存储引擎
ALTER TABLE 表名 ENGINE=储存引擎;
视图(view)
看一个需求
emp表的列信息很多,有些信息是个人重要信息(比如sal, comm,mgr, hiredate),如果我们希望某个用户只能查询emp表的(empno,ename, job和deptno )信息,有什么办法?=》视图
基本概念
视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含列,其数据来自对应的真实表(基表)
视图和基表关系的示意图
视图的基本使用
创建视图
create view 视图名 as select语句
更新成新的视图
alter view 视图名 as select语句
查看创建视图的命令
SHOW CREATE VIEW 视图名
删除视图
drop view 视图名1,视图名2
完成前面提出的需求
创建一个视图emp_view01,只能查询emp表的(empno、ename, job和deptno)信息
-- 创建视图CREATE VIEW emp_wiew01 AS SELECT empno,ename,job,deptno from emp;-- 查看视图desc emp_wiew01;select * from emp_wiew01;SELECT empno, job FROM emp_view01; -- 查看创建视图的指令SHOW CREATE VIEW emp_view01-- 删除视图DROP VIEW emp_view01;
视图细节讨论
- 创建视图后,到数据库去看,对应视图只有一个视图结构文件(形式:视图名.frm)
- 视图的数据变化会影响到基表,基表的数据变化也会影响到视图[insert update delete ]
- 视图中可以再使用视图
-- 视图的细节-- 1. 创建视图后,到数据库去看,对应视图只有一个视图结构文件(形式: 视图名.frm)-- 2. 视图的数据变化会影响到基表,基表的数据变化也会影响到视图[insert update delete ]-- 修改视图 会影响到基表UPDATE emp_view01 SET job = 'MANAGER' WHERE empno = 7369;SELECT * FROM emp; -- 查询基表SELECT * FROM emp_view01-- 修改基本表, 会影响到视图UPDATE emp SET job = 'SALESMAN' WHERE empno = 7369;-- 3. 视图中可以再使用视图 , 比如从 emp_view01 视图中,选出 empno,和 ename 做出新视图DESC emp_view01CREATE VIEW emp_view02 AS SELECT empno, ename FROM emp_view01;SELECT * FROM emp_view02;
视图最佳实践
- 安全。一些数据表有着重要的信息。有些字段是保密的,不能让用户直接看到。这时就可以创建一个视图,在这张视图中只保留一部分字段。这样,用户就可以查询自己需要的字段,不能查看保密的字段。
- 性能。关系数据库的数据常常会分表存储,使用外键建立这些表的之间关系。这时,数据库查询通常会用到连接(JOIN)。这样做不但麻烦,效率相对也比较低。如果建立一个视图,将相关的表和字段组合在一起,就可以避免使用JOIN查询数据。
- 灵活。如果系统中有一张旧的表,这张表由于设计的问题,即将被废弃。然而,很多应用都是基于这张表,不易修改。这时就可以建立一张视图,视图中的数据直接映射到新建的表。这样,就可以少做很多改动,也达到了升级数据表的目的。
MySQL管理
Mysql 用户
mysql中的用户,都存储在系统数据库mysql中user表中
其中user表的重要字段说明:
- host:允许登录的“位置”,localhost表示该用户只允许本机登录,也可以指定ip地址,比如:192.168.1.100
- user:用户名;
- authentication string:密码,是通过mysql的password()函数加密之后的密码。
创建用户
create user '用户名'@'允许登录位置' identified by '密码';
说明:创建用户,同时指定密码
删除用户
drop user '用户名'@'允许登录位置';
用户修改密码
修改自己的密码:
set password = password('密码');
修改他人的密码(需要有修改用户密码权限):
set password for '用户名'@'登录位置' = password('密码');
mysql 中的权限
给用户授权
基本语法:
grant 权限列表 on 库.对象名 to '用户名'@'登录位置' [identified by '密码']
说明:
权限列表,多个权限用逗号分开
grant select on ......grant select,delete,create on ......grant all [privileges] on ...... //表示赋予该用户在该对象上的所有权限
特别说明
*.*:代表本系统中的所有数据库的所有对象(表,视图,存储过程)
库.*:表示某个数据库中的所有数据对象(表,视图,存储过程等)
identified by可以省略,也可以写出.
(1)如果用户存在,就是修改该用户的密码。
(2)如果该用户不存在,就是创建该用户!
回收用户授权
revoke 权限列表 on 库.对象名 from '用户名'@'登录位置';
权限生效指令
该指令针对5.7以下版本,5.7以及之后的版本给了权限马上就会生效。
FLUSH PRIVILEGES;
课堂练习:
用户管理练习题
创建一个用户(你的名字:拼音),密码123,并且只可以从本地登录,不让远程登录mysql
创建库和表testdb下的news表,要求:使用root用户创建
给用户分配查看news表和添加数据的权限
测试看看用户是否只有这几个权限
修改密码为abc ,要求:使用root用户完成
重新登录
回收用户的news表的所有权限
使用root 用户删除你的用户
-- 1create user 'zwj'@'localhost' identified by '123';-- 2create database testdb;use testdb;create table news(id int ,content varchar(32));-- 3grant select,insert on testdb.news to 'zwj'@'localhost';-- 4登录zwj用户SELECT * FROM news;INSERT INTO news VALUES(200,'上海新闻');-- 5set password FOR 'zwj'@'localhost'=password('abc');-- 7REVOKE select,insert on testdb.news from 'zwj'@'localhost';-- 8drop user 'zwj'@'localhost';
●细节说明
在创建用户的时候,如果不指定Host,则为%,%表示表示所有IP都有连接权限
create user xxx;你也可以这样指定
create user ‘xxx’@’192.168.1.%’表示 xxx用户在192.168.1.*的ip可以登录mysql
在删除用户的时候,如果host不是%需要明确指定‘用户’@’host值’