MySQL 学习笔记

数据库

认识数据库系统

  1. 数据库的发展历程:人工管理、文件管理、数据库管理

  2. 数据库的基本概念:数据库系统、数据库、数据库应用系统、数据库管理系统、数据库管理员

  3. 数据库的应用模式:C/S、B/S

  4. 数据模型:层次数据模型、网状数据模型、关系数据模型、面向对象数据模型

  5. 数据模型三要素:数据结构、数据操作、数据完整性约束

  6. 结构化查询语言(SQL)的组成:数据定义语言、数据操作语言、数据控制语言、事务控制语言

  7. 主流数据库:Oracle数据库、DB2数据库、SQL Server数据库、MySQ数据库

免安装版(5.7.19)的安装及简单配置

  1. 将MySQL解压到指定目录(下载链接)

  2. bin目录添加到环境变量中

  3. 在MySQL根目录中创建配置文件: my.ini

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [client]
    port=3306
    default-character-set=utf8

    [mysqld]
    basedir=D:\\mysql\mysql-5.7.19-winx64 # 安装目录
    datadir=D:\\mysql\mysql-5.7.19-winx64\data # 数据目录
    bind-address=127.0.0.1
    port=3306
    character-set-server=utf8
    # skip-grant-tables #跳过安全检查
  4. 在安装目录中使用管理员终端安装MySQL服务:mysqld --install

    若提示找不到msvcr120.dll,则需要安装运行库(https://www.ghxi.com/visualcppredist.html)
    删除已安装的mysql服务:sc delete mysql

  5. 使用管理员终端初始化MySQL并复制临时密码:mysqld --initialize --console

    如果报错安装时报错,则删除data文件夹(D:\\mysql\mysql-5.7.19-winx64\data)重新初始化

  6. 修改注册表中的MySQL服务路径:

    注册表路径:\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MySQL
    将 ImagePath 的数值数据修改为: "D:\mysql-5.7.9-winx64\bin\mysql.exe" MySQL

  7. 启动MySQL服务并设置开机自启:

    管理员终端执行:net start mysql && sc config mysql start= auto
    或者打开服务管理器services.msc后再手动启动MySQL服务
    查看服务状态:net query mysql; 手动启动模式: sc config mysql start= demand

  8. 登录MySQL服务:mysql -u root -p

    或者使用默认的本地主机和端口号连接:mysql -h localhost -P 3306 -u root -p
    注意:-p密码不要有空格、-p后面没有写密码,回车会要求输入密码(隐藏)、如果没有写-h主机,默认就是本机、如果没有写-p端口,默认就是3306、在实际工作中,3306一般会修改

  9. 修改root用户密码:SET PASSWORD FOR root@localhost='666';

  10. 重置密码的过程:

  • my.ini 文件中添加以下2行内容:

    1
    2
    [mysqld] 
    skip-grant-tables
  • 执行命令 net stop mysql && net start mysql (重启 MySQL 服务)

  • 执行命令 mysql -u root (以 root 用户身份登录到 MySQL)

  • 执行命令 use mysql; (选择 mysql 数据库)

  • 执行命令 UPDATE user SET authentication_string = PASSWORD('新密码') WHERE User = 'root'; (将 root 用户的密码设置为 “新密码”)

  • 执行命令 FLUSH PRIVILEGES; (刷新权限)

  • 执行命令 exit (退出 MySQL)

  • 根据需要,注释掉 skip-grant-tables 并重启 MySQL

  1. 退出服务器:quitexit

使用图形化管理工具

  1. Navicat

  2. SQLyog

MySQL三层结构

  1. 数据库管理系统(DBMS)

  2. 数据库(Database)

  3. 表(Table)

普通表的本质仍然是文件

数据在数据库中的存储方式

  1. 按行(row)和列(col)来存储

  2. 表的一行称为一条记录,在Java程序中,一行记录往往使用对象表示

SQL语句分类

  1. DDL(Data Definition Language)数据定义语句[creat 表,库]
  2. DML(Data Manipulation Language)数据操作语句[增加 insert, 修改 updata, 删除 delete]
  3. DQL(Data Query Language)数据查询语句[select]
  4. DCL(Data Control Language)数据控制语句[管理数据库]

创建数据库

1
CREAT DATABASE [IF NOT EXISTS] <数据库名>[CHARACTER SET <字符集名>] [COLLATE <排序规则名>] 
  1. CHARACTER SET 指定数据库采用的字符集,如果不指定,则采用默认的字符集(UTF8)

  2. COLLATE 指定数据库字符集的排序规则,如果不指定,则采用默认不区分大小写的排序规则(utf8_general_ci)

  3. 数据库名称必须以字母下划线美元符号开头。

练习:创建、查询数据库.sql

  1. 创建一个名称为mkbk_db01的数据库

  2. 创建一个使用utf8字符集的mkbk_db02数据库

  3. 创建一个使用utf8字符集,并指定排序规则为utf8_binmkbk_db03数据库

  4. 使用图形化工具在mkbk_db03数据库中创建一个名称为mkbk的表, 并使用SELECT语句查询该表中的内容,以验证排序规则是否正确。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#1. 创建一个名称为mkbk_db01的数据库
CREATE DATABASE mkbk_db01;

#删除删除使用图形化工具创建的数据库
DROP DATABASE mkbk_db01;

#2.创建一个使用utf8字符集的mkbk_db02数据库
CREATE DATABASE mkbk_db02 CHARACTER SET utf8;

#3.创建一个使用utf8字符集,并指定排序规则为utf8_bin的mkbk_db03数据库
CREATE DATABASE mkbk_db03 CHARACTER SET utf8 COLLATE utf8_bin;

#校对规则 utf8_bin 区分大小 默认的 utf8_general_ci 不区分大小

/*4. 使用图形化工具在mkbk_db03数据库中创建一个名称为mkbk的表, 并使用SELECT语句查询该表中的内容,以验证排序规则是否正确。*/
SELECT * FROM mkbk WHERE NAME = 'tom'
#select 查询, * 表示所有字段, FROM 从哪个表, WHERE 从哪个字段, NAME = 'tom' 查询名字是 tom

查看、删除数据库语句

  1. 显示数据库语句:

    1
    SHOW DATABASES;
  2. 显示数据库创建语句:

    1
    SHOW CREATE DATABASE <数据库名>;
  3. 删除数据库语句:

    1
    DROP DATABASE <数据库名>;

练习:查看和删除数据库.sql

1
2
3
4
5
6
7
8
9
10
11
#查看当前数据库服务器中的所有数据库
SHOW DATABASES;

#查看前面创建的 mkbk_db01 数据库的定义信息
SHOW CREATE DATABASE mkbk_db01;

#在创建数据库,表的时候,为了规避关键字,可以使用反引号解决
CREATE DATABASE `create`;

#删除前面创建的 create 数据库
DROP DATABASE `create`;

备份/恢复数据库

  1. 备份数据库(注意:在DOS执行)
    mysqldump -u <用户名> -p -B 数据库1 数据库2 数据库n > 文件名.sql

  2. 恢复数据库(注意:进入Mysq命令行再执行)
    Source 文件名.sql

练习 : 备份和恢复数据库.sql

  1. 备份 mkbk_db02 和 mkbk_db03 库中的数据,并恢复
1
2
3
4
5
6
7
8
9
10
11
#备份, 要在 Dos 下执行, mysqldump 指令其实在 mysql 安装目录\bin\mysqldump.exe 
mysqldump -u root -p -B mkbk_db02 mkbk_db03 > d:\\bak.sql;

#删除以备份的数据库
DROP DATABASE mkbk_db03;
DROP DATABASE mkbk_db02;

#恢复数据库(注意:进入 Mysql 命令行再执行)
source d:\\bak.sql

#第二个恢复方法, 直接将 bak.sql 的内容放到查询编辑器中,执行

备份恢复数据库的表

  1. 备份命令:

    1
    mysqldump -u root -p密码 数据库 表1 表2 表n > d:\\文件名.sql

    注意:在DOS下执行, 没进入mysql环境不算执行sql语句,所以不用在其后加分号

  2. 恢复命令:

    1
    mysql -u root -p密码 数据库 < d:\\文件名.sql

    注意:在DOS下执行, 没进入mysql环境不算执行sql语句,所以不用在其后加分号

或者

1
2
use 数据库;
source d:\\文件名.sql;

数据类型

MySQL 常用数据类型(列类型)

数值类型

  • BIT: 位类型,存储位值,取值范围为1到64位。

  • TINYINT:小整数类型,取值范围为-128到127。

  • SMALLINT:短整数类型,取值范围为-32768到32767。

  • MEDIUMINT:中等整数类型,取值范围是-8388608到8388607。

  • INT:整数类型,取值范围为-2147483648到2147483647。

  • BIGINT:大整数类型,取值范围为-9223372036854775808到9223372036854775807。

  • FLOAT:单精度浮点数类型,存储4字节的浮点数。

  • DOUBLE:双精度浮点数类型,存储8字节的浮点数。

  • DECIMAL:定点数类型,用于存储精确的小数,可以指定精度和小数位数。

字符串类型

  • CHAR:固定长度字符串类型,最多可以存储255个字符。
  • VARCHAR:可变长度字符串类型,最多可以存储65535个字符。
  • TEXT:文本类型,用于存储较长的文本数据,最多可以存储65535个字符。
  • ENUM:枚举类型,用于存储预定义的字符串值。
  • SET:集合类型,用于存储多个预定义的字符串值。

日期和时间类型

  • 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’。
  • TIMESTAMP:时间戳类型,存储从’1970-01-01 00:00:01’到’2038-01-19 03:14:07’的秒数。

其他类型

  • BLOB:二进制大对象类型,用于存储二进制数据,最多可以存储65535个字节。
  • JSON:JSON数据类型,用于存储JSON格式的数据。
  • BOOLEAN:布尔类型,存储true或false。

演示整型数值类型的使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#演示整型数值类型的使用方式
#使用 tinyint 来演示范围 有符号 -128 ~ 127 如果没有符号(UNSIGNED) 0-255
#说明:表的字符集,校验规则, 存储引擎,使用默认

CREATE TABLE t1
(
id TINYINT -- 有符号类型:-128 ~ 127
);
INSERT INTO t1 VALUES(127); -- 添加语句
INSERT INTO t1 VALUES(-129); -- 报错,该值超出了该列所允许的范围
SELECT * FROM t1; # 查询语句

CREATE TABLE t2
(
id TINYINT UNSIGNED /*无符号类型UNSIGNED:0 ~ 255 */
);
INSERT INTO t2 VALUES(255);
INSERT INTO t2 VALUES(-1); /* 报错,该值超出了该列所允许的范围 */
SELECT * FROM t2;

演示位(BIT)类型的使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#演示 BIT 类型使用
#1. bit(m) m 范围是 1 ~ 64
#2. 添加数据 范围按照给定的位数来确定,比如 m = 8 表示一个字节 0~255
#3. 显示数据按照二进制显示
#4. 查询时,仍然可以按照十进制数数来查询

CREATE TABLE t3
(
num BIT(8) # 表示这个列将存储一个8位的二进制数,也就是一个字节(8比特),整数范围是 1 ~ 255
);
INSERT INTO t3 VALUES(255);
INSERT INTO t3 VALUES(256); # 报错,超出二进制所能表示的最大值
SELECT * FROM t3; # 返回表格 t3 中所有行的所有列(查询到的数据会按二进制显示)

SELECT * FROM t3 WHERE num = 255; # 添加了一个过滤条件 WHERE num = 255 (可以使用十进制整数来查询对应的二进制数值)

演示位小数类型的使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-- FLOAT/DOUBLE [UNSIGNED] : 单精度小数和双精度小数

/*
DECIMAL(M,D) [UNSIGNED] : 可以支特更加精确的小数位
M是小数位数(精度)的总数,D是小数点(标度)后面的位数
如果D是0,则值没有小数点或分数部分
M最大65, D最大是30, 如果D被省略,默认是0, 如果M被省略, 默认是10
建议:如果希望小数的精度高,推荐使用decimal
*/

# 创建表
CREATE TABLE t4
(
num1 FLOAT,
num2 DOUBLE,
num3 DECIMAL(30 ,20)
);

# 添加数据
INSERT INTO t4 VALUES(88.012345678912345, 88.012345678912345, 88.012345678912345);
SELECT * FROM t4;

# DECIMAL 可以存很大的数值
CREATE TABLE t5( num DECIMAL(65));
INSERT INTO t5 VALUES(012345678901234564890123456789012345648901234567890123456489);
SELECT * FROM t5;

# BIGINT UNSIGNED (0 ~ 2^64-1)
CREATE TABLE t6( num BIGINT UNSIGNED);
INSERT INTO t6 VALUES(8999999933338388388383838838383009338388383838383838383); # 插入失败,超出范围(8Byte)
SELECT * FROM t6;

演示字符串(文本)类型的使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#演示字符串类型使用 char varchar
#注释的快捷键 shift+ctrl+c , 取消注释 shift+ctrl+r

-- CHAR(size)
-- 固定长度字符串 最大 255 个字符
-- VARCHAR(size) 0~65535 字节
-- 可变长度字符串 最大 65532 字节 (utf8 编码最大 21844 字符 1-3 个字节用于记录大小)
-- 如果表的编码是 utf8, 那么最大 size = (65535-3) / 3 = 21844
-- 如果表的编码是 gbk, 那么最大 size = (65535-3) / 2 = 32766

CREATE TABLE t7 (`name` CHAR(255));
CREATE TABLE t8 (`name` VARCHAR(21844)); -- 使用数据库默认的字符编码(utf8)
CREATE TABLE t9 (`name` VARCHAR(32766) CHARSET gbk); -- 指定使用gbk编码

CREATE TABLE t10 (`name` CHAR(256)); -- 超出范围,CHAR 类型最大长度是255个字符
CREATE TABLE t11 (`name` VARCHAR(21845)); -- 超出范围,utf8编码的 VARCHAR 最大长度是21844个字节
CREATE TABLE t12 (`name` VARCHAR(32767) CHARSET gbk); -- 超出范围,gbk编码的 VARCHAR 最大长度是32766个字节

SELECT * FROM t9;

字符串(文本)类型的使用细节

  1. 细节1

    • char(4) //这个4表示字符数(最大255),不是字节数,不管是中文还是字母都
      是放四个, 按字符计算
    • varchar(4) //这个4表示字符数,不管是字母还是中文都以定义好的表的编码
      来存放数据
    • 不管是中文还是英文字母,都是最多存放4个,是按照字符来存放的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #演示字符串(文本)类型的使用细节 
    #char(4) 和 varchar(4): 这个4表示的是字符,而不是字节, 不区分字符是汉字还是字母

    CREATE TABLE t13(`name` CHAR(4));
    INSERT INTO t13 VALUES('你好世界');
    INSERT INTO t13 VALUES('你好世界!'); -- 报错,超出最大4个字符的范围
    SELECT * FROM t13;

    CREATE TABLE t14(`name` VARCHAR(4));
    INSERT INTO t14 VALUES('你好世a') -- 中英混合
    SELECT * FROM t14;

    #如果 varchar 不够用,可以使用 mediumtext 或者 longtext,如果想简单点,可以直接使用 text
    CREATE TABLE t15 (content TEXT, conten2 MEDIUMTEXT, content3 LONGTEXT);
    INSERT INTO t15 VALUES('你好世界', '玛卡巴卡','唔西迪西唔西迪西');
    SELECT * FROM t15
  2. 细节2

    • char(4)是定长(固定的大小),就是说,即使你插入’aa’,也会占用分配的
      4个字符的空间
    • varchar(4)是变长(变化的大小),就是说,如果你插入了’aa’,实际占用空
      间大小并不是4个字符,而是按照实际占用空间来分配
    • varchar本身还需要占用1-3个字节来记录存放内容长度:L = 实际数据大小 + (1~3)Byte
  3. 细节3

    • 什么时候使用char,什么时候使用varchar
    • 1.如果数据是定长,推荐使用char,比如md5的密码,邮编,手
      机号,身份证号码等.char(32)
    • 2.如果一个字段的长度是不确定,我们使用varchar,比如留
      言,文章
    • 查询速度:CHAR > VARCHAR
    • 存储空间:CHAR < VARCHAR
  4. 细节4

    • 在存放文本时,也可以使用Text数据类型.可以将TEXT列视为
      VARCHAR列,注意Text不能有默认值.大小0 ~ 2^16 Byte
    • 如果希望存放更多字符,可以选择MEDIUMTEXT (0 ~ 2^24)或者LONGTEXT(0 ~ 2^32)

演示字日期类型的使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#演示时间相关的类型 
#创建一张表, date , datetime , timestamp

CREATE TABLE t16
(
birthday DATE,
job_time DATETIME,
login_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
SELECT * FROM t16;
INSERT INTO t16(birthday, job_time) VALUES('2008-11-11','2008-11-11 10:11:11');

/*login_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP:
这一行定义了一个名为 login_time 的列,其数据类型为 TIMESTAMP。
同时指定了一些额外的属性:
NOT NULL 表示这一列不允许为空。
DEFAULT CURRENT_TIMESTAMP 表示如果没有提供值,则默认使用当前的日期和时间。
ON UPDATE CURRENT_TIMESTAMP 表示当记录被更新时,将自动更新为当前的日期和时间。
*/

表操作

创建表

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE table_name
(
field1 datatype,
field2 datatype,
field3 datatype
)
CHARACTER SET 字符集 COLLATE 排序规则 ENGINE 存储引擎;

-- field: 指定列名(字段)
-- datatype: 指定列类型(字段类型)
-- CHARACTER SET: 如不指定则使用所在数据库字符集
-- COLLATE: 如不指定则使用所在数据库排序规则
-- ENGINE: 如不指定则使用默认存储引擎

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#创建表时,要根据需保存的数据创建相应的列,并根据数据的类型定义相应的列类型。

#例:create_tab01.sql(分别使用图形化和指令创建)
#id 整形
#name 字符串
#password 字符串
#birthday #日期

CREATE TABLE `user`
(
id INT,
`name` VARCHAR(255),
`password` VARCHAR(255),
birthday DATE
)
CHARACTER SET utf8 COLLATE utf8_bin ENGINE INNODB;

创建表练习

创建一个员工表emp,选用适当的数据类型

字段 属性
id(标识符) 整形
name(名称) 字符型
sex(性别) 字符型
brithday(生日) 日期型
entry_date(入职日期) 日期型
job(工作) 字符型
Salary(工资) 小数型
resume(简历) 文本型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 创建表练习

CREATE TABLE emp(
id INT, -- 标识符
`name` VARCHAR(20), -- 名称
sex CHAR(1), -- 性别
brithday DATE, -- 生日
entry_date DATE, -- 入职日期
job VARCHAR(20), -- 职位
salary DOUBLE, -- 工资
resume TEXT -- 简历
)CHARACTER SET = utf8 COLLATE = utf8_general_ci;

INSERT INTO emp VALUES(100, '张三' ,'男', '1990-01-01', '2017-01-01', 'Java开发', 9000, '**大学');

SELECT * FROM emp;

DML 数据操作语句

操作列

  1. 添加列

    1
    2
    3
    ALTER TABLE table_name 
    ADD (column_name datatype [DEFAULT expr],
    column_name datatype [DEFAULT expr]);
  2. 修改列

    1
    2
    3
    ALTER TABLE table_name
    MODIFY (column_name datatype [DEFAULT expr],
    column_name datatype [DEFAULT expr]);
  3. 删除列

    1
    2
    ALTER TABLE table_name
    DROP (column_name);
  4. 修改列名

    1
    2
    ALTER TABLE table_name 
    CHANGE old_column_name new_column_name datatype;
  5. 查看表的结构

    1
    DESC table_name;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 修改表的操作练习

-- 员工表 emp 的上增加一个 image 列,varchar 类型(要求在 resume 后面)。
ALTER TABLE emp ADD image VARCHAR(100) NOT NULL DEFAULT '' AFTER `resume`;

DESC emp; -- 查看表结构
SELCT * FROM emp; -- 查询emp的所有列

-- 修改 job 列,使其长度为 60。
ALTER TABLE emp MODIFY job VARCHAR(60);

-- 删除 sex 列。
ALTER TABLE emp DROP sex;

-- 表名改为 employee。
RENAME emp TO employee;
DESC employee;

-- 修改表的字符集为 utf8
ALTER TABLE employee CHARACTER SET utf8;
SHOW CREATE TABLE employee;

-- 列名 name 修改为 user_name
ALTER TABLE employee CHANGE `name` user_name VARCHAR(32);

数据操作: C[create]R[read]U[update]D[delete]语句

INSERTUPDATEDELETESELECT

  1. INSERT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# insert 语句的细节 

-- 1.插入的数据应与字段的数据类型相同。 比如 把 'abc' 添加到 int 类型会错误
INSERT INTO goods (id, goods_name, price) VALUES('张三', '小米手机', 2000); -- 报错: 类型不匹配

-- 2. 数据的长度应在列的规定范围内,例如:不能将一个长度为 11 的字符串加入到长度为 10 的列中。
INSERT INTO goods (id, goods_name, price) VALUES(40, '牛牛牛123456789', 3000); -- 报错:数据过长

-- 3. 在 values 中列出的数据位置必须与被加入的列的排列位置相对应。
INSERT INTO goods (id, goods_name, price) VALUES('牛牛手机', 40, 2000); -- 报错: 类型不匹配

-- 4. 字符和日期型数据应包含在单引号中。
INSERT INTO goods (id, goods_name, price) VALUES(50, 花为手机, 3000); -- 报错:找不到该列

-- 5. 列可以插入空值[前提是该字段允许为空]
INSERT INTO goods (id, goods_name, price) VALUES(60, 'xiaomi', NULL);
SELECT * FROM goods;

-- 6. insert into tab_name (列名..) values (),(),() 形式添加多条记录
INSERT INTO goods (id, goods_name, price) VALUES(80, '三星手机', 2300),(60, '海尔手机', 1800);

-- 7. 如果是给表中的所有字段添加数据,可以不写前面的字段名称
INSERT INTO goods VALUES(70, 'IBM 手机', 5000);

-- 8. 默认值的使用,当不给某个字段值时,如果有默认值就会添加默认值,否则报错
CREATE TABLE goods2 (
id INT,
goods_name VARCHAR(60),
price DOUBLE NOT NULL DEFAULT 100 -- 不允许存储空值,未提供price的值时,将被设置为100
);

INSERT INTO goods2 (id, goods_name) VALUES(50, '三星手机');

SELECT * FROM goods2;
  1. UPDATE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-- 演示 update 语句 

-- 要求: 在上面创建的 employee 表中修改表中的纪录

-- 1. 将所有员工薪水修改为 5000 元。[如果没有带 where 条件,会修改所有的记录,因此要小心]
INSERT INTO employee VALUES(100, '张三' , '1990-01-01', '2017-01-01', 'Java开发', 9000, '南京大学', 'd:\6.png');
SELECT * FROM employee;

UPDATE employee SET salary = 5000;

-- 2. 将姓名为张三的员工薪水修改为 3000 元。
UPDATE employee SET salary = 3000 WHERE user_name = '张三';
SELECT * FROM employee;

-- 3. 将张三的薪水在原有基础上增加 1000 元
UPDATE employee SET salary = salary + 1000 WHERE user_name = '张三';
SELECT * FROM employee;

-- 可以修改多个列的值
UPDATE employee SET salary = salary + 1000, job = 'C++开发' WHERE user_name = '张三';
SELECT * FROM employee;

# 细节

-- 1.UPDATE语法可以用新值更新原有表行中的各列。

-- 2.SET子句指示要修改哪些列和要给予哪些值。

-- 3.WHERE子句指定应更新哪些行。如没有WHERE子句,则更新所有的行(记录),因此一定小心。

-- 4. 如果需要修改多个字段,可以通过 set 字段1 = 值1, 字段2 = 值2
  1. DELETE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
-- delete 语句演示

-- 删除表中名称为张三的记录。
CREATE TABLE employee(
id INT, -- 标识符
`name` VARCHAR(20), -- 名称
sex CHAR(1), -- 性别
brithday DATE, -- 生日
entry_date DATE, -- 入职日期
job VARCHAR(20), -- 职位
salary DOUBLE, -- 工资
RESUME TEXT -- 简历
)CHARACTER SET = utf8 COLLATE = utf8_general_ci;

INSERT INTO emp VALUES(100, '张三' ,'男', '1990-01-01', '2017-01-01', 'Java开发', 9000, '**大学');

DELETE FROM employee WHERE user_name = '张三';

-- 删除表中所有记录, 一定要小心
DELETE FROM employee;

-- Delete 语句不能删除某一列的值(可使用 update 设为 null 或者 '')
UPDATE employee SET job = NULL;

-- 删除这个表
DROP TABLE employee;

# 细节

-- 如果不使用where子句,将删除表中所有数据

-- Delete语句不能删除某一列的值 (可使用update 设为 null 或者)使用delete语句仅删除记录,不删除表本身。

-- 如要删除表,使用drop table语句。drop table 表名
  1. SELECT

SELECT语句是用于从数据库中检索数据的基本语句。其基本语法如下:

1
SELECT 列名1, 列名2, ... FROM 表名 WHERE 条件;
  • SELECT 关键字指示查询开始。
  • 列名1, 列名2, ... 是要返回的列名。可以使用 * 来返回所有列。
  • FROM关键字指定要查询的表名。
  • WHERE关键字用于指定可选的过滤条件。

可以用as(alias)给查询结果集的列或表达式指定别名,以便在结果集中更清晰地标识或引用它们

1
2
SELECT column_name AS alias_name
FROM table_name;

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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
);

SHOW CREATE DATABASE mkbk; -- sqlyog的默认类型不是utf8
ALTER DATABASE mkbk CHARACTER SET utf8;

SHOW CREATE TABLE student;
-- alter table student CHARACTER SET utf8;
ALTER TABLE student CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

INSERT INTO student VALUES(1,'韩顺平',89,87,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,'韩信',45,65,99);


-- 1.查询表中所有学生的信息。
SELECT * FROM student;

-- 2.查询表中所有学生的姓名和对应的英语成绩。
SELECT `name` AS '姓名', english AS '英语' FROM student;

-- 3.过滤表中英语成绩的重复数据distinct
SELECT DISTINCT english AS '英语' FROM student;

-- 4.要查询的记录,每个字段都相同才会去重
SELECT DISTINCT `name` AS '姓名', english AS '英语' FROM student; -- 可以指定多列

SELECT 使用表达式对查询的列进行运算

1
2
3
4
5
6
7
8
9
10
11
-- SELECT 使用表达式对查询的列进行运算
SELECT * FROM student;

-- 统计每个学生的总分
SELECT `name` AS '姓名', (chinese + english + math) AS '总分' FROM student;

-- 所有学生总分加 10 分
SELECT `name` AS '姓名', (chinese + english + math + 10) AS '总分' FROM student;

-- 使用别名表示学生分数。
SELECT `name` AS '姓名', (chinese + english + math) AS '总分' FROM student;

SELECT 使用 where 过滤条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
-- SELECT 使用 where 过滤条件

-- select 语句
SELECT * FROM student;

-- 查询姓名为赵云的学生成绩
SELECT * FROM student WHERE `name` = '赵云';
SELECT `name`, chinese, math, english FROM student WHERE `name` = '赵云'; -- 等价

-- 查询英语成绩大于 90 分的同学
SELECT `name`, english FROM student WHERE english > 90;

-- 查询总分大于 200 分的所有同学
SELECT * FROM student WHERE (math + english + chinese) > 200;
SELECT * , (math + english + chinese) AS '总分' FROM student WHERE (math + english + chinese) > 200; -- 显示总分

-- 查询 math 大于 60 并且(and) id 大于 4 的学生成绩
SELECT `name`, id, math FROM student WHERE math > 60 AND id > 4;
SELECT `name`, id, math FROM student WHERE (math > 60) AND (id > 4); -- 括号

-- 查询英语成绩大于语文成绩的同学
SELECT `name`, english, chinese FROM student WHERE english > chinese;

-- 查询总分大于 200 分 并且 数学成绩小于语文成绩,的姓赵的学生. -- 赵%
SELECT * FROM student WHERE (english + math + chinese) > 200 AND math < chinese AND `name` LIKE '赵%'; -- LIKE操作符是用于模糊匹配的条件操作符

-- 查询英语分数在 80-90 之间的同学。
SELECT * FROM student WHERE english BETWEEN 80 AND 90; -- 闭区间 BETWEEN ... AND ...
SELECT * FROM student WHERE english > 80 AND english < 90;

-- 查询数学分数为 89,90,91 的同学。
SELECT `name`, math FROM student WHERE math IN(89, 90, 91); -- IN()函数是用来判断某个字段的值是否在一个给定的值列表中
SELECT `name`, math FROM student WHERE math = 89 OR math = 90 OR math = 91;

-- 查询所有姓韩的学生成绩。
SELECT * FROM student WHERE `name` LIKE '韩%';

-- 查询语文分数在70-80之间的同学。
SELECT `name`, chinese FROM student WHERE chinses BETWEEN 70 AND 80;

-- 查询总分为189,190,191的同学。
SELECT * FROM student WHERE (math + chinese + english) IN (266, 170, 242);

-- 查询所有姓韩或者姓张的学生成绩。
SELECT * FROM student WHERE `name` LIKE '韩%' OR 'name' LIKE '张%';

-- 查询数学比语文多30分的同学。
SELECT * FROM student WHERE math > chinese + 30;

函数

统计函数

  1. COUNT(column): 计算某列的行数。

  2. SUM(column): 计算某列的总和。

  3. AVG(column): 计算某列的平均值。

  4. MAX(column): 找出某列的最大值。

  5. MIN(column): 找出某列的最小值。

COUNT()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
-- 演示 mysql 的统计函数的使用 

-- 统计一个班级共有多少学生?
SELECT COUNT(*) FROM student;

-- 统计数学成绩大于 80 的学生有多少个?
SELECT COUNT(math) FROM student WHERE math > 80;

-- 统计总分大于 250 的人数有多少?
SELECT COUNT(*) FROM student WHERE (chinese +english + math) > 250;
SELECT COUNT(chinese +english + math) FROM student WHERE (chinese +english + math) > 250; -- 等价


CREATE TABLE t17(
`name` VARCHAR (20)
)CHARACTER SET utf8;

INSERT INTO t17 VALUES('tom');
INSERT INTO t17 VALUES('jack');
INSERT INTO t17 VALUES('mary');
INSERT INTO t17 VALUES(NULL);

SELECT * FROM t17;

-- count(*) 和 count(列) 的区别
-- count(*) 返回满足条件的记录的行数
-- count(列): 统计满足条件的某列有多少个,但是会排除 为 null 的情况
SELECT COUNT(`name`) FROM t17; -- 3
SELECT COUNT(*) FROM t17; -- 4

SUM()

1
2
3
4
5
6
7
8
9
10
-- 演示 sum 函数的使用

-- 统计一个班级数学总成绩?
SELECT SUM(math) AS '数学总分' FROM student;

-- 统计一个班级语文、英语、数学各科的总成绩
SELECT SUM(chinese), SUM(english), SUM(math) FROM student;

-- 统计一个班级语文、英语、数学的成绩总和
SELECT SUM(chinese + english + math) FROM student;

AVG()

1
2
3
4
5
6
7
8
9
10
11
12
-- 演示 avg 的使用 

-- 统计一个班级语文成绩平均分
SELECT AVG(chinese) FROM student;
SELECT SUM(chinese) / COUNT(*) FROM student; -- 等价

-- 求一个班级数学平均分
SELECT AVG(math) FROM student;

-- 求一个班级总分平均分
SELECT AVG(math + chinese + english) FROM student;
SELECT SUM(math + chinese + english) / COUNT(*) FROM student; -- 等价

MAX() , MIN()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 演示 max 和 min 的使用

-- 求班级最高分和最低分(数值范围在统计中特别有用)
SELECT MAX(math + chinese + english), MIN(math + chinese + english) FROM student;

-- 求出班级数学最高分和最低分
SELECT MAX(math), MIN(math) FROM student;

-- 同时查询具有相应分数的学生的姓名
SELECT
(SELECT NAME FROM student WHERE math = (SELECT MAX(math) FROM student)) AS max_math_student,
MAX(math) AS max_math,
(SELECT NAME FROM student WHERE math = (SELECT MIN(math) FROM student)) AS min_math_student,
MIN(math) AS min_math
FROM student;

ORDER BY

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 演示 order by 使用 

-- 对数学成绩排序后输出[升序] ASC
SELECT `name`, math FROM student ORDER BY math ASC;

-- 对总分按从高到低的顺序输出 [降序] DESC
SELECT `name` , (english + math + chinese) AS '总分'
FROM student
ORDER BY (english + math + chinese) DESC;

-- 对姓韩的学生成绩[总分]排序输出(升序) where + order by
SELECT `name`, (english + math + chinese) AS '总分' FROM student
WHERE `name` LIKE '韩%'
ORDER BY 总分 ASC; -- 别名不能加单引号

GROUP BY

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/*部门表*/
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');

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);

SELECT * FROM dept;
SELECT * FROM emp;
SELECT * FROM salgrade;

# 演示 group by + having

-- GROUP by 用于对查询的结果分组统计
-- having 子句用于限制分组显示结果.

-- 如何显示每个部门的平均工资和最高工资
-- 1. avg(sal) max(sal)
-- 2. 按照部分来分组查询
SELECT * FROM emp;
SELECT AVG(sal), MAX(sal), deptno FROM emp GROUP BY deptno;


-- 显示每个部门的每种岗位的平均工资和最低工资
-- 1. 显示每个部门的平均工资和最低工资
-- 2. 显示每个部门的每种岗位的平均工资和最低工资
SELECT AVG(sal), MIN(sal), deptno FROM emp GROUP BY deptno;


-- 显示平均工资低于 2000 的部门号和它的平均工资 // 别名
-- 写 sql 语句的思路是化繁为简, 逐个击破
-- 1. 显示各个部门的平均工资和部门号
-- 2. 在 1 的结果基础上,进行过滤,保留 AVG(sal) < 2000
SELECT AVG(sal), deptno FROM emp GROUP BY deptno;
SELECT AVG(sal), deptno FROM emp GROUP BY deptno HAVING AVG(sal) < 2000;

-- 3. 使用别名进行过滤(不用重复调用函数,效率更高)
SELECT AVG(sal) AS 平均工资, deptno FROM emp GROUP BY deptno HAVING 平均工资 < 2000;

字符串函数

  1. CHARSET(str): 返回字符串的字符集。

  2. CONCAT (string2 [,... ]): 将多个字符串连接成一个字符串。

  3. INSTR (string ,substring ): 返回子字符串在原字符串中首次出现的位置。

  4. UCASE (string2 ): 将字符串转换成大写。

  5. LCASE (string2 ): 将字符串转换成小写。

  6. LEFT (string2 ,length ): 从字符串的左边起取指定长度的字符。

  7. RIGHT (string2 ,length ): 从字符串的右边起取指定长度的字符。

  8. LENGTH (string ): 返回字符串的长度(按照字节计算)。

  9. REPLACE (str ,search_str ,replace_str ): 在字符串中用指定字符串替换另一个字符串。

  10. STRCMP (string1 ,string2 ): 逐字符比较两个字符串的大小。

  11. SUBSTRING (str , position [,length ]): 从字符串的指定位置开始取指定长度的字符。

  12. LTRIM (string2 ): 去除字符串左边的空格。

  13. RTRIM (string2 ): 去除字符串右边的空格。

  14. TRIM(string): 去除字符串两端的空格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
-- 演示字符串相关函数的使用 , 使用 emp 表来演示
SELECT * FROM emp;

-- CHARSET(str) 返回字串字符集
SELECT CHARSET(ename) FROM emp;

-- CONCAT (string2 [,... ]) 连接字串, 将多个列拼接成一列
-- dual 亚元表, 系统表 可以作为测试表使用
SELECT CONCAT('hello', 'world!') FROM DUAL;

-- INSTR (string ,substring ) 返回 substring 在 string 中出现的位置,没有返回 0
SELECT INSTR('hello world!', 'll') FROM DUAL;

-- UCASE (string2 ) 转换成大写
SELECT UCASE('hello') FROM DUAL;

-- LCASE (string2 ) 转换成小写
SELECT LCASE('hello') FROM DUAL;

-- LEFT (string2 ,length )从 string2 中的左边起取 length 个字符
SELECT LEFT('hello', 2) FROM DUAL;

-- RIGHT (string2 ,length ) 从 string2 中的右边起取 length 个字符
SELECT RIGHT('hello', 2) FROM DUAL;

-- LENGTH (string )string 长度[按照字节]
SELECT LENGTH('hello') FROM DUAL;

-- REPLACE (str ,search_str ,replace_str )
-- 在 str 中用 replace_str 替换 search_str
-- 如果是 manager 就替换成 经理
SELECT REPLACE('hello', 'llo', 'rt') FROM DUAL;
SELECT REPLACE(job, 'MANAGER', '经理') FROM emp;

-- STRCMP (string1 ,string2 ) 逐字符比较两字串大小
SELECT STRCMP('T', 'l') FROM DUAL;
SELECT STRCMP('SALESMAN', 'MANAGER') FROM DUAL;

-- SUBSTRING (str , position [,length ])
-- 从 str 的 position 开始【从 1 开始计算】,取 length 个字符
-- 从 ename 列的第一个位置开始取出 2 个字符
-- 第三个参数为空则表示取完
SELECT SUBSTRING('hello world', 7, 5) FROM DUAL;
SELECT SUBSTRING('hello world', 7, 5) FROM DUAL;

-- LTRIM (string2 ) RTRIM (string2 ) TRIM(string)
-- 去除前端空格或后端空格
SELECT LTRIM(' hello world') FROM DUAL;
SELECT RTRIM('hello world ') FROM DUAL;
SELECT TRIM(' hello world ') FROM DUAL;

-- 练习: 以首字母小写的方式显示所有员工 emp 表的姓名
-- 方法 1 -- 思路先取出 ename 的第一个字符,转成小写的
-- 把他和后面的字符串进行拼接输出即可
SELECT CONCAT(LCASE(LEFT(ename,1)), SUBSTRING(ename, 2)) FROM emp;
SELECT CONCAT(LCASE(SUBSTRING(ename, 1, 1)), SUBSTRING(ename, 2)) FROM emp;

数学相关函数

  1. ABS(num): 返回一个数的绝对值。

  2. BIN(decimal_number): 将一个十进制数转换为二进制。

  3. CEILING(number2): 向上取整,返回不小于指定数的最小整数。

  4. CONV(number2, from_base, to_base): 进制转换,将一个数从指定的进制转换为另一个进制。

  5. FLOOR(number2): 向下取整,返回不大于指定数的最大整数。

  6. FORMAT(number, decimal_places): 格式化数字并保留指定的小数位数(四舍五入)。

  7. HEX(DecimalNumber): 将一个十进制数转换为十六进制。

  8. LEAST(number, number2 [,..]): 返回指定数字中的最小值。

  9. MOD(numerator, denominator): 返回两个数相除的余数。

  10. RAND([seed]): 返回一个随机数,范围在 0 到 1 之间。如果提供了种子(seed),那么每次调用都会返回相同的随机数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
-- 演示数学相关函数 

-- ABS(num) 绝对值
SELECT ABS(-6) FROM DUAL;

-- BIN (decimal_number )十进制转二进制
SELECT BIN(10) FROM DUAL;

-- CEILING (number2 ) 向上取整, 得到比 num2 大的最小整数
SELECT CEILING(1.1) FROM DUAL;
SELECT CEILING(-1.1) FROM DUAL;

-- CONV(number2,from_base,to_base) 进制转换
-- 下面的含义: 8 是十进制的 8, 转成 2 进制输出
SELECT CONV(8, 10, 2) FROM DUAL;
-- 下面的含义: 8 是 16 进制的 8, 转成 2 进制输出
SELECT CONV(8, 16, 2) FROM DUAL;

-- FLOOR (number2 ) 向下取整,得到比 num2 小的最大整数
SELECT FLOOR(1.1) FROM DUAL;
SELECT FLOOR(-1.1) FROM DUAL;

-- FORMAT (number,decimal_places ) 保留小数位数(四舍五入)
SELECT FORMAT(1.123456, 4) FROM DUAL;

-- HEX (DecimalNumber ) 转十六进制
SELECT HEX(10) FROM DUAL;

-- LEAST (number , number2 [,..]) 求最小值
SELECT LEAST(10, 12, 9) FROM DUAL;

-- MOD (numerator ,denominator ) 求余
SELECT MOD(10, 3) FROM DUAL;
SELECT MOD(-10, 3) FROM DUAL;
SELECT MOD(10, -3) FROM DUAL;

-- RAND([seed]) RAND([seed]) 返回随机数 其范围为 0 ≤ v ≤ 1.0
-- 说明
-- 1. 如果使用 rand() 每次返回不同的随机数 ,在 0 ≤ v ≤ 1.0
-- 2. 如果使用 rand(seed) 返回随机数, 范围 0 ≤ v ≤ 1.0, 如果 seed 不变,
-- 该随机数也不变了
SELECT RAND() FROM DUAL;
SELECT RAND(6) FROM DUAL;

时间日期相关函数

  1. CURRENT_DATE(): 返回当前日期。

  2. CURRENT_TIME(): 返回当前时间。

  3. CURRENT_TIMESTAMP(): 返回当前的日期时间戳。

  4. DATE_ADD(datetime, INTERVAL n unit): 在日期时间上加上一段时间间隔。

  5. DATE_SUB(datetime, INTERVAL n unit): 在日期时间上减去一段时间间隔。

  6. DATEDIFF(date1, date2): 返回两个日期之间的天数差。

  7. TIMEDIFF(time1, time2): 返回两个时间之间的时间差。

  8. YEAR(date): 返回日期中的年份。

  9. MONTH(date): 返回日期中的月份。

  10. DAY(date): 返回日期中的天数。

  11. UNIX_TIMESTAMP(): 返回从1970年1月1日以来的秒数(时间戳)。

  12. FROM_UNIXTIME(unix_timestamp, format): 将时间戳转换为指定格式的日期时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
-- 日期时间相关函数

-- CURRENT_DATE ( ) 当前日期
SELECT CURRENT_DATE() FROM DUAL;

-- CURRENT_TIME ( ) 当前时间
SELECT CURRENT_TIME() FROM DUAL;

-- CURRENT_TIMESTAMP ( ) 当前时间戳
SELECT CURRENT_TIMESTAMP() FROM DUAL;

-- 创建测试表 信息表
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;
SELECT NOW() FROM DUAL;

-- 应用实例
-- 显示所有新闻信息,发布日期只显示 日期,不用显示时间.
SELECT id, content, DATE(send_time) FROM mes;

-- 请查询在10分钟内发布的新闻, 思路一定要梳理一下.
SELECT * FROM mes WHERE DATE_ADD(send_time, INTERVAL 10 MINUTE) >= NOW();
SELECT * FROM mes WHERE DATE_SUB(NOW(), INTERVAL 10 MINUTE) <= send_time; -- 等价

-- 请在mysql 的sql语句中求出 2011-11-11 和 1990-1-1 相差多少天
SELECT DATEDIFF('2011-11-11' , '1990-1-1') FROM DUAL;
SELECT DATEDIFF('2011-11-11' , '1990-1-1') / 365 FROM DUAL; -- 换算单位为年

-- 请用mysql 的sql语句求出你活了多少天?
SELECT DATEDIFF(NOW(), '2000-01-01') FROM DUAL;

-- 如果你能活80岁,求出你还能活多少天.[练习] 1986-11-11 出生
-- 先求出活80岁 时, 是什么日期 X
-- 然后在使用 datediff(x, now()); 1986-11-11->datetime
-- INTERVAL 80 YEAR : YEAR 可以是 年月日,时分秒
-- '1986-11-11' 可以date,datetime timestamp
SELECT DATEDIFF(DATE_ADD('2000-01-01', INTERVAL 80 YEAR), NOW()) FROM DUAL;
SELECT DATEDIFF('2023-09-15', '2023-08-15 10:10:36') FROM DUAL; -- DATEDIFF函数只计算日期部分之间的差距,忽略时间部分

-- unix_timestamp() : 返回的是1970-1-1 到现在的秒数
SELECT UNIX_TIMESTAMP() FROM DUAL; -- 1694763630 (sec)
SELECT UNIX_TIMESTAMP() / (3600*24*365) FROM DUAL; -- 换算单位为年

-- FROM_UNIXTIME() : 可以把一个unix_timestamp 秒数[时间戳],转成指定格式的日期
-- 意义:在开发中,可以存放一个整数,然后表示时间,通过FROM_UNIXTIME转换
SELECT FROM_UNIXTIME(1694763630) FROM DUAL;

-- %Y-%m-%d 格式是规定好的,表示年月日
SELECT FROM_UNIXTIME(1694763630, '%Y-%m-%d %H:%m:%s') FROM DUAL;
SELECT FROM_UNIXTIME(1618483100, '%Y-%m-%d') FROM DUAL;
SELECT FROM_UNIXTIME(1618483100, '%H:%m:%s') FROM DUAL;

系统函数和加密函数

  1. USER(): 返回当前用户和主机名。

  2. DATABASE(): 返回当前正在使用的数据库名称。

  3. MD5(str): 计算字符串的 MD5 散列值,通常用于对用户密码进行加密。

  4. PASSWORD(str): MySQL特定的加密函数,用于加密用户的密码。在MySQL数据库中,用户密码就是使用PASSWORD()函数加密后的密文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-- 演示加密函数和系统函数

-- USER() 查询用户
-- 可以查看登录到mysql的有哪些用户,以及登录的IP
SELECT USER() FROM DUAL;

-- DATABASE() 查询当前正在使用数据库名称
SELECT DATABASE() FROM DUAL;

-- MD5(str) 为字符串算出一个 MD5 32的字符串,常用(用户密码)加密
-- root 密码是 666 -> 加密md5 -> 在数据库中存放的是加密后的密码
-- 演示用户表,存放密码时,是md5
CREATE TABLE users(
id INT,
`name` VARCHAR(32) NOT NULL DEFAULT '',
pwd CHAR(32) NOT NULL DEFAULT ''
);

INSERT INTO users VALUES(100, '玛卡巴卡', MD5('mkbk'));
INSERT INTO users VALUES(200, '唔西迪西', MD5('wxdi'));

SELECT * FROM users;

SELECT * FROM users WHERE `name` = '玛卡巴卡' AND pwd = 'mkbk'; -- 查不到
SELECT * FROM users WHERE `name` = '玛卡巴卡' AND pwd = MD5('mkbk') -- SQL注入问题

-- PASSWORD(str) -- 加密函数, MySQL数据库的用户密码就是 PASSWORD()函数加密
SELECT * FROM mysql.user; -- 数据库用户表中的登录密码加密后的密文
# *007D50CA06F69776D307B1BEC71CD73D0EA0999C
SELECT PASSWORD('666') FROM DUAL; -- 当前使用的密码加密后的密文
# *007D50CA06F69776D307B1BEC71CD73D0EA0999C

流程控制函数

  1. IF(expr1,expr2,expr3): 如果 expr1 为真,则返回 expr2,否则返回 expr3

    1
    SELECT IF(TRUE, '北京', '上海') FROM DUAL; -- 返回 '北京'
  2. IFNULL(expr1,expr2): 如果 expr1NULL 则返回指定的替代值 expr2,如果不为 NULL 则返回原始值 expr1

    1
    SELECT ename, IFNULL(comm, 0.00) AS 奖金 FROM emp;
  3. CASE WHEN expr1 THEN expr2 WHEN expr3 THEN expr4 ELSE expr5 END: 类似多重分支。如果 expr1 为真,则返回 expr2,如果 expr2 为真,则返回 expr4,否则返回 expr5

    1
    2
    3
    4
    5
    6
    7
    8
    SELECT ename, 
    CASE
    WHEN job = 'CLERK' THEN '职员'
    WHEN job = 'MANAGER' THEN '经理'
    WHEN job = 'SALESMAN' THEN '销售人员'
    ELSE job
    END AS 职位
    FROM emp;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 演示流程控制语句 
SELECT * FROM emp;

# IF(expr1,expr2,expr3) 如果 expr1 为 True ,则返回 expr2 否则返回 expr3
SELECT IF(comm, comm, '0.00') FROM emp;
SELECT IF(TRUE, '北京', '上海') FROM DUAL;

# IFNULL(expr1,expr2) 如果expr1为NULL则返回指定的替代值(expr2),如果不为NULL则返回原始值(expr1)
SELECT ename, comm, IFNULL(comm, 0.00) FROM emp;
SELECT IFNULL(FALSE, 'mkbk') FROM DUAL; -- 0(false)

# SELECT CASE WHEN expr1 THEN expr2 WHEN expr3 THEN expr4 ELSE expr5 END; [类似多重分支.]
# 如果 expr1 为 TRUE,则返回 expr2,如果 expr2 为 true, 返回 expr4, 否则返回 expr5
SELECT CASE
WHEN TRUE THEN 'jack' -- 返回真值
WHEN FALSE THEN 'tom'
ELSE 'mary'
END AS `name` FROM DUAL;

SELECT ename, sal, CASE
WHEN sal >= 3000 THEN '优秀'
WHEN sal < 3000 AND sal >= 2000 THEN '良好'
WHEN sal < 2000 AND sal >= 1500 THEN '及格'
ELSE '不及格'
END AS result
FROM emp ORDER BY sal DESC; -- 薪水按降序排序

-- 1. 查询 emp 表, 如果 comm 是 null , 则显示 0.0
-- 判断是否为 NULL 要使用 IS NULL, 判断不为空 使用 IS NOT NULL
SELECT ename, IFNULL(comm, 0.00) AS 奖金 FROM emp;
SELECT ename, IF(comm, comm, 0.00) AS 奖金 FROM emp;

SELECT ename, IF(comm IS NULL, 0.00, comm) 奖金 FROM emp;
SELECT ename, IF(comm IS NOT NULL, comm, 0.00) 奖金 FROM emp;

-- 2. 如果 emp 表的 job 是 CLERK 则显示 职员, 如果是 MANAGER 则显示经理
-- 如果是 SALESMAN 则显示 销售人员,其它正常显示
SELECT ename,
REPLACE(job, 'CLERK', '职员'),
REPLACE(job, 'MANAGER', '经理'),
REPLACE(job, 'SALESMAN', '销售人员')
FROM emp; -- 不可行

SELECT ename, CASE
WHEN job = 'CLERK' THEN '职员'
WHEN job = 'MANAGER' THEN '经理'
WHEN job = 'SALESMAN' THEN '销售人员'
ELSE job -- 没有匹配则返回job列的原始值
END AS 职位
FROM emp;

SELECT ename, CASE job -- 更简洁的写法
WHEN 'CLERK' THEN '职员'
WHEN 'MANAGER' THEN '经理'
WHEN 'SALESMAN' THEN '销售人员'
ELSE job
END AS 职位
FROM emp;

DQL 数据查询语句

单表查询巩固练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
-- 单表查询巩固练习
SELECT * FROM emp;

-- 使用 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 NULL" ,不是 "= NULL"

-- 查询表结构 DESC emp
DESC emp; -- DESCRIBE, 返回包含表中每个列的信息的结果集
SHOW CREATE TABLE emp; -- 显示创建指定表的完整创建语句

-- 使用 order by 子句
-- 如何按照工资的从低到高的顺序[升序],显示雇员的信息
SELECT * FROM emp ORDER BY sal ASC; -- Ascending

-- 按照部门号升序而雇员的工资降序排列 , 显示雇员信息
SELECT * FROM emp ORDER BY deptno ASC, sal DESC; -- Descending

分页查询

语法:

1
2
3
4
5
SELECT column1, column2, ...
FROM table
WHERE condition
ORDER BY column
LIMIT offset, count;
  1. SELECT:指定要查询的列或表达式。
  2. FROM:指定要查询的表名或表的联接。
  3. WHERE:可选项,用于指定筛选条件。
  4. ORDER BY:可选项,用于指定排序的列和排序顺序。
  5. LIMIT:offset表示偏移量,即从查询结果的第几条记录开始返回,count表示返回的记录数量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-- 分页查询 
SELECT * FROM emp;

-- 按雇员的 id 号升序取出, 每页显示 3 条记录,请分别显示 第 1 页,第 2 页,第 3 页
-- 第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 emp
ORDER BY empno
LIMIT 每页显示记录数 * (第几页 - 1) , 每页显示记录数

-- 练习:按雇员的empno降序取出,每页显示5条记录, 显示第3页
SELECT * FROM emp ORDER BY empno LIMIT 10, 5;

-- 测试
-- 显示雇员总数,以及获得奖金的雇员数
SELECT COUNT(*), COUNT(comm) FROM emp;
SELECT COUNT(*), SUM(CASE WHEN comm IS NOT NULL THEN 1 ELSE 0 END) FROM emp; -- CASE语句检查每个雇员的comm列是否为非空值,然后,SUM函数对这些返回的值进行求和

使用分组统计函数和分组子句 group by

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
-- 增强 group by 的使用
SELECT * FROM emp;

-- (1) 显示每种岗位的雇员总数、平均工资。
SELECT job, COUNT(ename), AVG(sal) FROM emp GROUP BY job;

-- (2) 显示雇员总数,以及获得补助的雇员数。
-- 思路: 获得补助的雇员数 就是 comm 列为非 null, 就是 count(列),如果该列的值为 null, 是
-- SQL 非常灵活,需要我们多动脑筋
SELECT COUNT(ename), COUNT(comm) FROM emp;
SELECT COUNT(ename), COUNT(IFNULL(comm, NULL)) FROM emp;
SELECT COUNT(ename), COUNT(IF(comm, 1, NULL)) FROM emp;
SELECT COUNT(ename), COUNT(comm) FROM emp WHERE comm IS NOT NULL OR ename IS NOT NULL;

-- 统计没有获得补助的雇员数
SELECT COUNT(ename), COUNT(*) - COUNT(comm) FROM emp;
SELECT COUNT(ename), (COUNT(*) - COUNT(IF(comm, 1, NULL))) FROM emp;
SELECT COUNT(ename), COUNT(comm) FROM emp WHERE comm IS NULL;

-- (3) 显示管理者的总人数。小技巧:尝试写->修改->尝试[正确的]
SELECT COUNT(DISTINCT mgr) FROM emp;

-- (4) 显示雇员工资的最大差额。
-- 思路: max(sal) - min(sal)
SELECT MAX(sal) - MIN(sal) FROM emp;

-- 应用案例:请统计各个部门 group by 的平均工资 avg,
-- 并且是大于 1000 的 having,并且按照平均工资从高到低排序, order by
-- 取出前两行记录 limit 0, 2
SELECT deptno, AVG(sal) AS 平均工资
FROM emp
GROUP BY deptno
HAVING 平均工资 > 1000
ORDER BY 平均工资 DESC
LIMIT 0, 2;

多表查询

在默认情况下:当两个表查询时, 规则

  1. 从第一张表中,取出一行 和第二张表的每一行进行组合 , 返回结果[含有两张表的所有列]
  2. 一共返回的记录数为第一张表行数*第二张表的行数
  3. 这样多表查询默认处理返回的结果,称为笛卡尔积
  4. 解决这个多表的关键就是要写出正确的过滤条件 where
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
-- 多表查询 
SELECT * FROM dept;
SELECT * FROM emp;
SELECT * FROM salgrade;

-- 显示雇员名,雇员工资及所在部门的编号 [笛卡尔积]
/* 分析
1. 雇员名(ename),雇员工资(sal) 来自 emp 表
2. 部门的名字(dname) 来自 dept 表
3. 需求对 emp 和 dept 查询 ename,sal和deptno
4. 当我们需要指定显示某个表的列是,需要 表.列表 */
SELECT ename, sal, emp.deptno
FROM dept, emp
WHERE dept.deptno = emp.deptno;

-- 小技巧:多表查询的条件不能少于 表的个数 -1, 否则会出现笛卡尔集
SELECT * FROM emp, dept; -- 13 * 4 = 52 行

-- 如何显示部门号为 10 的部门名、员工名和工资
SELECT emp.deptno, dname, ename, sal
FROM emp, dept
WHERE dept.deptno = emp.deptno AND emp.deptno = 10;

-- 显示各个员工的姓名,工资,及其工资的级别
-- 思路 姓名,工资 来自 emp 13
-- 工资级别 salgrade 5
-- 写 sql , 先写一个简单,然后加入过滤条件...
SELECT ename, sal, grade
FROM emp, salgrade
WHERE sal >= losal AND sal <= hisal;

SELECT ename, sal, grade
FROM emp, salgrade
WHERE sal BETWEEN losal AND hisal; -- 通常情况下,BETWEEN 和 AND 默认是闭区间(inclusive),也就是指定的范围包括了边界值

-- 练习
-- 显示雇员名,雇员工资及所在部门名字,并按照部门排序[降序]
SELECT ename, sal, dname
FROM emp, dept
WHERE emp.deptno = dept.deptno
ORDER BY dname DESC; -- ORDER BY 可以对字符进行排序

多表查询的自连接

自连接:把一张表连接查询两次,查询的结果是两张表的记录的组合(将同一张表看作两张表)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- 多表查询的自连接 
SELECT * FROM emp;

-- 思考题: 显示公司员工名字和他的上级的名字
-- 老韩分析: 员工名字 在 emp, 上级的名字的名字 emp
-- 员工和上级是通过 emp 表的 mgr 列关联
SELECT 员工.ename, 上级.ename
FROM emp AS 员工, emp AS 上级
WHERE 员工.mgr = 上级.empno

-- 自连接的特点
-- 1. 把同一张表当做两张表使用
-- 2. 需要给表取别名: 表名 表别名
-- 3. 列名不明确,可以指定列的别名 列名 as 列的别名
SELECT *
FROM emp AS worker, emp AS boss; -- 笛卡尔积(13*13=169条记录)

SELECT worker.ename AS 职员名, boss.ename AS 上级名 -- 需要查询员工姓名和上级姓名
FROM emp AS worker, emp AS boss;

SELECT worker.ename AS 职员名, boss.ename AS 上级名
FROM emp AS worker, emp AS boss
WHERE worker.mgr = boss.empno; -- 员工(worker)的上级(mgr)等于员工(boss)的编号(empno)

多表子查询

  1. 子查询是指嵌入在其它sql语句中的select语句,也叫嵌套查询
  2. 单行子查询:单行子查询是指只返回一行数据的子查询语句请
  3. 多行子查询:多行子查询指返回多行数据的子查询 使用关键字 IN
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- 子查询的演示 

-- 思考:如何显示与 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;

子查询的临时表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
CREATE TABLE Orders(
OrderID INT,
CustomerID INT,
OrderDate DATE,
Amount DECIMAL(10, 2)
);

INSERT INTO Orders (OrderID, CustomerID, OrderDate, Amount)
VALUES
(1, 1001, '2023-01-01', 150.50),
(2, 1002, '2023-01-02', 200.75),
(3, 1001, '2023-01-03', 75.25),
(4, 1003, '2023-01-04', 300.00),
(5, 1002, '2023-01-05', 125.60),
(6, 1001, '2023-01-06', 180.90),
(7, 1003, '2023-01-07', 250.30);

SELECT * FROM Orders;

-- 使用子查询临时表来找到每个客户的订单总金额,并筛选出订单总金额大于平均订单金额的客户
SELECT CustomerID, SUM(Amount) AS 总价 FROM Orders GROUP BY CustomerID;
SELECT AVG(Amount) FROM Orders;

SELECT CustomerID, TotalAmount
FROM (
SELECT CustomerID, SUM(Amount) AS TotalAmount
FROM Orders
GROUP BY CustomerID
) AS temp_table -- 不加别名将报错
WHERE TotalAmount > (SELECT AVG(Amount) FROM Orders);

/*下面这条语句是无效的(报错提示无法识别"Amount"列),
因为内层子查询的表并不存在Amount列,只有AVG(Amount),而嵌套使用SUM函数SUM(Amount)是不允许的,
因此只能使用别名*/
SELECT CustomerID, SUM(Amount)
FROM (
SELECT CustomerID, SUM(Amount)
FROM Orders
GROUP BY CustomerID
) AS temp_table
WHERE SUM(Amount) > (SELECT AVG(Amount) FROM Orders);

allany 的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- all 和 any 的使用 

-- 请思考:显示工资比部门 30 的所有员工的工资高的员工的姓名、工资和部门号
SELECT ename, sal, deptno
FROM emp
WHERE sal > ALL(SELECT sal FROM emp WHERE deptno = 30);

-- 或者:
SELECT ename, sal, deptno
FROM emp
WHERE sal > (SELECT MAX(sal) FROM emp WHERE deptno = 30);

-- 请思考:如何显示工资比部门 30 的其中一个员工的工资高的员工的姓名、工资和部门号
SELECT ename, sal, deptno
FROM emp
WHERE sal > ANY(SELECT sal FROM emp WHERE deptno = 30);

-- 或者
SELECT ename, sal, deptno
FROM emp
WHERE sal > (SELECT MIN(sal) FROM emp WHERE deptno = 30);

多列子查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-- 多列子查询 
SELECT * FROM emp;

-- 请思考如何查询与 ALLEN 的部门和岗位完全相同的所有雇员(并且不含 ALLEN 本人)
-- (字段 1, 字段 2 ...) = (select 字段 1,字段 2 from 。。。。)
-- 分析: 1. 得到 ALLEN 的部门和岗位
-- 分析: 2 把上面的查询当做子查询来使用,并且使用多列子查询的语法进行匹配
SELECT deptno, job FROM emp WHERE ename = 'ALLEN';

SELECT *
FROM emp
WHERE (deptno, job) = (
SELECT deptno, job
FROM emp
WHERE ename = 'ALLEN')
AND ename <> 'ALLEN';


-- 请查询 和宋江数学,英语,语文 成绩 完全相同的学生
SELECT * FROM student WHERE `name` = '宋江';

SELECT *
FROM student
WHERE (math, english, chinese) = (
SELECT math, english, chinese
FROM student
WHERE `name` = '宋江')

子查询练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
-- 子查询练习 

-- 查找每个部门工资高于本部门平均工资的人的资料
-- 这里要用到数据查询的小技巧,把一个子查询当作一个临时表使用
-- 1. 先得到每个部门的 部门号和 对应的平均工资
-- 2. 把上面的结果当做子查询, 和 emp 进行多表查询
SELECT deptno, AVG(sal)
FROM emp
GROUP BY deptno;

SELECT ename, job, sal, emp.deptno, avg_sal
FROM emp, (SELECT deptno, AVG(sal) AS avg_sal
FROM emp
GROUP BY deptno
) AS temp
WHERE emp.deptno = temp.deptno
AND sal > avg_sal;

-- 查找每个部门工资最高的人的详细资料
-- 查询每个部门的信息(包括:部门名,编号,地址)和人员数量
-- 1. 部门名,编号,地址 来自 dept 表
-- 2. 各个部门的人员数量 -》 构建一个临时表
SELECT deptno, MAX(sal)
FROM emp
GROUP BY deptno;

SELECT ename, job, sal, emp.deptno, max_sal
FROM emp, (SELECT deptno, MAX(sal) AS max_sal
FROM emp
GROUP BY deptno
) AS temp
WHERE emp.deptno = temp.deptno
AND sal = max_sal;

-- 还有一种写法 表.* 表示将该表所有列都显示出来, 可以简化 sql 语句
-- 在多表查询中,当多个表的列不重复时,才可以直接写列名(deptno)
SELECT ename, job, sal,temp.*
FROM emp, (SELECT deptno, MAX(sal) AS max_sal
FROM emp
GROUP BY deptno
) AS temp
WHERE emp.deptno = temp.deptno
AND sal = max_sal;

自我复制数据(蠕虫复制)

  • 为了对某个 sql 语句进行效率测试,我们需要海量数据时,可以使用此法为表创建海量数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
-- 表的复制 

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_tab01
INSERT INTO my_tab01
SELECT empno, `ename`, sal, job, deptno
FROM emp;

-- 2. 自我复制
INSERT INTO my_tab01
SELECT * FROM my_tab01;

SELECT COUNT(*) FROM my_tab01;
SELECT * FROM my_tab01;


-- 如何删除掉一张表的重复记录
-- 1. 先创建一张表 my_tab02
CREATE TABLE my_tab02 LIKE emp; -- 复制表结构

-- 2. 让 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; -- 复制表结构

DESC my_tmp;

-- (2) 把 my_tmp 的记录 通过 distinct 关键字 处理后 把记录复制到 my_tmp
INSERT INTO my_tmp -- 将过滤后的结果写入到my_tmp表中
SELECT DISTINCT * FROM my_tab02; -- 使用DISTINCT关键字过滤重复值

SELECT * FROM my_tmp;

-- (3) 清除掉 my_tab02 记录
DELETE FROM my_tab02;

-- 或者直接修改表名
DROP TABLE my_tab02; -- 先删除已存在的表

RENAME TABLE my_tmp TO my_tab02;
ALTER TABLE my_tmp RENAME TO my_tab02; -- 和前一条命令等价

SHOW TABLES;

-- (4) 把 my_tmp 表的记录复制到 my_tab02
INSERT INTO my_tab02
SELECT * FROM my_tmp;

SELECT * FROM my_tab02;

-- (5) drop 掉 临时表
DROP TABLE my_tmp;

合并查询 UNION UNION ALL

  1. UNION 用于合并两个查询的结果集,并去除重复的行。它的语法如下:

    1
    2
    3
    4
    5
    SELECT column1, column2, ...
    FROM table1
    UNION
    SELECT column1, column2, ...
    FROM table2;
    • 查询的列数和数据类型必须匹配,否则会出现错误。
    • UNION 自动去重,确保合并后的结果集中不会包含重复行。
  2. UNION ALL 也用于合并两个查询的结果集,但不会去除重复的行。它的语法如下:

    1
    2
    3
    4
    5
    SELECT column1, column2, ...
    FROM table1
    UNION ALL
    SELECT column1, column2, ...
    FROM table2;
    • UNION ALL 不去重,合并后的结果集可能包含重复的行。
    • UNION ALL 的执行速度通常比 UNION 快,因为它不需要进行去重的额外工作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 合并查询
SELECT ename, job, sal FROM emp WHERE job = 'MANAGER';

SELECT ename, job, sal FROM emp WHERE sal > 1500;

-- union all 就是将两个查询结果合并,不会去重
SELECT ename, job, sal FROM emp WHERE job = 'MANAGER'
UNION ALL
SELECT ename, job, sal FROM emp WHERE sal > 1500;

-- union 就是将两个查询结果合并,会去重
SELECT ename, job, sal FROM emp WHERE job = 'MANAGER'
UNION
SELECT ename, job, sal FROM emp WHERE sal > 1500;

外连接 LEFT/RIGHT JOIN ON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
-- 外连接 
-- 比如:列出部门名称和这些部门的员工名称和工作,
-- 同时要求 显示出那些没有员工的部门。
-- 使用我们学习过的多表查询的 SQL, 看看效果如何?
SELECT * FROM emp;
SELECT * FROM dept;

SELECT dname, ename, emp.deptno, job
FROM emp, dept
WHERE emp.deptno= dept.deptno
ORDER BY emp.deptno; -- 40号部门未关联,因此未显示

-- 创建 stu
/*id
name 1
Jack 2
Tom
3 Kity
4 nono */
CREATE TABLE stu (
id INT,
`name` VARCHAR(32)
);

INSERT INTO stu VALUES
(1, 'jack'),
(2,'tom'),
(3, 'kity'),
(4, 'nono');

-- 创建 exam
/*id grade
1 56
2 76
11 8
*/
CREATE TABLE exam(
id INT,
grade INT
);

INSERT INTO exam VALUES
(1, 56),
(2,76),
(11, 8);

SELECT * FROM exam; -- id(1, 2, 11)
SELECT * FROM stu; -- id(1, 2, 3, 4)

-- 使用左连接
-- 显示所有人的成绩,如果没有成绩,也要显示该人的姓名和 id 号,成绩显示为空
SELECT stu.id, `name`, grade
FROM stu, exam
WHERE stu.id = exam.id -- id 3和4为未关联,因此未显示

-- 改成左外连接
SELECT * FROM stu; -- id(1, 2, 3, 4)

SELECT stu.id, `name`, grade
FROM stu LEFT JOIN exam
ON stu.id = exam.id


-- 使用右外连接(显示所有成绩,如果没有名字匹配,显示空)
-- 即:右边的表(exam) 和左表没有匹配的记录,也会把右表的记录显示出来
SELECT * FROM exam; -- id(1, 2, 11)

SELECT stu.id, `name`, grade
FROM stu RIGHT JOIN exam
ON stu.id = exam.id;

-- 列出部门名称和这些部门的员工信息(名字和工作),
SELECT dept.deptno, dname, ename, job
FROM emp, dept
WHERE emp.deptno = dept.deptno
ORDER BY dept.deptno;

-- 同时列出那些没有员工的部门名。
-- 使用左外连接实现
SELECT dept.deptno, dname, ename, job
FROM dept LEFT JOIN emp
ON emp.deptno = dept.deptno
ORDER BY dept.deptno;

-- 使用右外连接实现
SELECT dept.deptno, dname, ename, job
FROM emp RIGHT JOIN dept
ON emp.deptno = dept.deptno
ORDER BY dept.deptno;

约束

主键约束 PRIMARY KEY

用于唯一的标示表行的数据,当定义主键约束后,该列不能重复

primary key(主键)-细节说明

  1. primary key:不能重复而且不能为null。
  2. 一张表最多只能有一个主键,但可以是复合主键
  3. 主键的指定方式有两种, 一种是直接在字段名后指定,一种是在表定义最后写primary key(列名):
  4. 使用desc表名,可以看到primary key的情况.
  5. 在实际开发中,每个表往往都会设定一个主键,而且主键一般都会是整数,并且主键一般都会是自增的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
-- 主键使用

CREATE TABLE t18(
id INT PRIMARY KEY, -- 表示id列是主键
`name` VARCHAR(32),
email VARCHAR(32)
);

-- 主键列的值是不可以重复
INSERT INTO t18 VALUES(1, 'mkbk', 'mkbk@qq.com');
INSERT INTO t18 VALUES(2, 'wxdx', 'wxdx@qq.com');
INSERT INTO t18 VALUES(2, 'ygyg', 'ygyg@qq.com'); -- 违反了主键的唯一性约束

SELECT * FROM t18;

-- 主键使用的细节讨论
-- primary key不能重复而且不能为 null。
CREATE TABLE t19(
id INT PRIMARY KEY, -- 表示id列是主键
`name` VARCHAR(32),
email VARCHAR(32)
);
INSERT INTO t19 VALUES(1, 'mkbk', 'mkbk@qq.com');
INSERT INTO t19 VALUES(2, 'wxdx', 'wxdx@qq.com');
SELECT * FROM t19;
INSERT INTO t19 VALUES(NULL, 'ygyg', 'ygyg@qq.com'); -- 主键列被定义为不允许为空

-- 一张表最多只能有一个主键, 但可以是复合主键(比如 id+name)
CREATE TABLE t21(
id INT PRIMARY KEY,
`name` VARCHAR(32) PRIMARY KEY, -- 在一个表中只能定义一个主键。
email VARCHAR(32)
);

-- 演示复合主键 (id 和 name 做成复合主键)
CREATE TABLE t20(
id INT,
`name` VARCHAR(32),
email VARCHAR(32),
PRIMARY KEY(id, `name`) -- 复合主键 (id 和 name 做成复合主键)
);

SELECT * FROM t20;
INSERT INTO t20 VALUES(1, 'mkbk', 'mkbk@qq.com');
INSERT INTO t20 VALUES(2, 'wxdx', 'wxdx@qq.com');
INSERT INTO t20 VALUES(1, 'ygyg', 'ygyg@qq.com'); -- 不违反主键的唯一性约束
INSERT INTO t20 VALUES(1, 'mkbk', 'ygyg@qq.com'); -- 违反了主键的唯一性约束

-- 主键的指定方式 有两种
-- 1. 直接在字段名后指定:字段名 primakry key
CREATE TABLE t22(
id INT PRIMARY KEY, -- 单个主键
`name` VARCHAR(32),
email VARCHAR(32)
);

-- 2. 在表定义最后写 primary key(列名);
CREATE TABLE t23(
id INT,
`name` VARCHAR(32),
email VARCHAR(32),
PRIMARY KEY(id, `name`) -- 复合主键
);

-- 使用desc 表名,可以看到primary key的情况
DESC t18;
DESC t20; -- 查看t20表的结果,显示约束的情况

唯一约束 UNIQUE

当定义了唯一约束后,该列值是不能重复的。

unique细节(注意):

  1. 如果没有指定not null,则unique字段可以有多个null
  2. 一张表可以有多个unique字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
-- unique的使用

CREATE TABLE t24(
id INT UNIQUE, -- 表示 id 列是不可重复的
`name` VARCHAR(32),
email VARCHAR(32)
);
DESC t24;

SELECT * FROM t24;
INSERT INTO t24 VALUES(1, 'mkbk', 'mkbk@qq.com');
INSERT INTO t24 VALUES(2, 'wxdx', 'wxdx@qq.com');
INSERT INTO t24 VALUES(1, 'ygyg', 'ygyg@qq.com'); -- 数据重复,拒绝写入

-- unqiue使用细节
-- 1. 如果没有指定 not null , 则 unique 字段可以有多个null
INSERT INTO t24 VALUES(NULL, 'mkbk', 'mkbk@qq.com');
INSERT INTO t24 VALUES(NULL, 'wxdi', 'wxdx@qq.com');
SELECT * FROM t24;

-- 如果一个列(字段), 是 unique not null 使用效果类似 primary key
CREATE TABLE t25(
id INT UNIQUE NOT NULL, -- 表示 id 列是不可重复的
`name` VARCHAR(32),
email VARCHAR(32)
);
DESC t25;

INSERT INTO t25 VALUES(1, 'mkbk', 'mkbk@qq.com');
INSERT INTO t25 VALUES(2, 'wxdx', 'wxdx@qq.com');
INSERT INTO t25 VALUES(NULL, 'wxdi', 'wxdx@qq.com'); -- 违反NOT NULL约束
SELECT * FROM t25;

-- 2. 一张表可以有多个unique字段
CREATE TABLE t26(
id INT UNIQUE NOT NULL,
`name` VARCHAR(32) UNIQUE NOT NULL,
email VARCHAR(32)
);
DESC t26;

INSERT INTO t26 VALUES(1, 'mkbk', 'mkbk@qq.com');
INSERT INTO t26 VALUES(2, 'wxdx', 'wxdx@qq.com');
INSERT INTO t26 VALUES(1, 'ygyg', 'ygyg@qq.com'); -- 违反一个字段的唯一性约束
INSERT INTO t26 VALUES(1, 'mkbk', 'mkbk@qq.com'); -- 违反两个字段的唯一性约束
SELECT * FROM t26;

外键约束 FOREIGN KEY

用于定义主表和从表之间的关系: 外键约束要定义在从表上,主表则必须具有主键约束或是unique约束,当定义外键约束后,要求外键列数据必须在主表的主键列存在或是为null

细节:

  1. 外键指向的表的字段,要求是primary key 或者是unique
  2. 表的类型是innodb,这样的表才支持外键
  3. 外键字段的类型要和主键字段的类型一致(长度可以不同)
  4. 外键字段的值,必须在主键字段中出现过,或者为null(前提是外键字段允许为null)
  5. 一旦建立主外键的关系,数据不能随意删除了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
-- 外键演示 

-- 创建 主表 my_class
CREATE TABLE my_class(
id INT PRIMARY KEY,
`name` VARCHAR(32) NOT NULL DEFAULT ''
);

-- 创建 从表 my_stu
CREATE 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');
SELECT * FROM my_class;

INSERT INTO my_stu VALUES(1, 'tom', 100), (2, 'jack', 200);
SELECT * FROM my_stu;
INSERT INTO my_stu VALUES(3, 'hsp', 100); -- 正确,未超出外键约束
INSERT INTO my_stu VALUES(3, 'hsp', 300); -- 主键所在表中不存在300班级
INSERT INTO my_stu VALUES(4, 'king', NULL); -- 可以, 外键没有写 not null

-- 一旦建立主外键的关系,主键数据就不能随意删除了
DELETE FROM my_class WHERE id = 100; -- 删除该记录时会导致父表(my_class)与子表(my_stu)之间的关联被破坏,所以无法完成删除操作

检查约束 CHECK

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 演示 check 的使用
-- mysql5.7 目前还不支持 check ,只做语法校验,但不会生效
-- mysq8.0及以上版本已经支持
-- 学习 oracle, sql server, 这两个数据库是真的生效
-- 测试
CREATE TABLE t27(
id INT PRIMARY KEY,
`name` VARCHAR(32),
sex VARCHAR(6) CHECK (sex IN ('man', 'woman')) -- 检查约束
);

-- 添加数据
INSERT INTO t27 VALUES(1, 'mkbk', 'man');
INSERT INTO t27 VALUES(2, 'wxdx', 'mid'); -- 5.7版本生效
SELECT * FROM t27;

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
-- 使用约束的练习 

-- 现有一个商店的数据库 shop_db,记录客户及其购物情况,由下面三个表组成:
-- 商品表 goods(商品号 goods_id,商品名 goods_name,单价 unitprice,商品类别 category,供应商 provider);

-- 客户表 customer(客户号 customer_id,姓名 name,住址 address,电邮 email 性别 sex,身份证 card_Id);

-- 购买表 purchase(购买订单号 order_id,客户号 customer_id,商品号 goods_id,购买数量 nums);

-- 1 建表,在定义中要求声明 [进行合理设计]:
-- (1)每个表的主外键;
-- (2)客户的姓名不能为空值;
-- (3)电邮不能够重复;
-- (4)客户的性别[男|女] check、枚举..
-- (5)单价 unitprice 在 1.0 - 9999.99 之间 check


CREATE TABLE goods( -- 商品表
goods_id INT PRIMARY KEY,
goods_name VARCHAR(60) NOT NULL DEFAULT '',
unitprice DECIMAL(10, 3) NOT NULL DEFAULT 0.000 CHECK(unitprice >= 1.0 AND unitprice <= 9999.99), -- 使用检查约束
category VARCHAR(20) NOT NULL DEFAULT '',
provider VARCHAR(20) NOT NULL DEFAULT ''
);


CREATE TABLE customer( -- 客户表
customer_id INT PRIMARY KEY,
`name` VARCHAR(20) NOT NULL DEFAULT '',
address VARCHAR(50) NOT NULL DEFAULT '',
email VARCHAR(60) UNIQUE NOT NULL,
sex ENUM('男', '女') NOT NULL, -- 枚举类型, 生效
card_Id CHAR(18) UNIQUE NOT NULL DEFAULT ''
);

CREATE TABLE purchase( -- 购买表
order_id INT UNSIGNED PRIMARY KEY,
customer_id INT NOT NULL,
FOREIGN KEY(customer_id) REFERENCES customer(customer_id),
goods_id INT NOT NULL,
FOREIGN KEY(goods_id) REFERENCES goods(goods_id),
nums INT NOT NULL DEFAULT 0
);

自增长 AUTO_INCREMENT

自增长使用细节

  1. 一般来说自增长是和primary key配合使用的
  2. 自增长也可以单独使用[但是需要配合一个unique]
  3. 自增长修饰的字段为整数型的(虽然小数也可以但是非常非常少这样使用)
  4. 自增长默认从1开始,你也可以通过此命令修改ater table 表名 auto increment = 新的开始值;
  5. 如果你添加数据时,给自增长字段(列)指定的有值,则以指定的值为准,如果指定了自增长,一般来说,就按照自增长的规则来添加数据.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-- 演示自增长的使用 

-- 创建表
CREATE TABLE t28(
id INT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(32) NOT NULL DEFAULT '',
`email` VARCHAR(32) NOT NULL DEFAULT ''
);
DESC t28;

-- 测试自增长的使用
INSERT INTO t28 (email, `name`) VALUES ('mkbk', 'mkbk@qq.com');
INSERT INTO t28 (id, email, `name`) VALUES (NULL, 'mkbk', 'mkbk@qq.com');
INSERT INTO t28 VALUES (NULL, 'mkbk', 'mkbk@qq.com');

SELECT * FROM t28;

-- 修改默认的自增长开始值
CREATE TABLE t29(
id INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(32) NOT NULL DEFAULT '',
email VARCHAR(32) NOT NULL DEFAULT ''
);
SELECT * FROM t29;

INSERT INTO t29 VALUES (NULL, 'mkbk', 'mkbk@qq.com');

ALTER TABLE t29 AUTO_INCREMENT = 666; -- 修改自动增长的起始值
INSERT INTO t29 VALUES (NULL, 'mkbk', 'mkbk@qq.com');

INSERT INTO t29 VALUES (777, 'mkbk', 'mkbk@qq.com');

索引

原理

索引是帮助数据库高效获取数据的数据结构。
当我们没有索引时,默认进行全表扫描(索引),查询速度慢
思考如果我们比较了30次,覆盖的表的则是范围2^30
如果对表进行dml(修改,删除,添加),将会对索引进行维护,对速度有影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- 在没有创建索引时,我们的查询一条记录
SELECT * FROM emp WHERE empno = 1234567; -- 3.017 sec

-- 使用索引来优化一下, 体验索引的作用
-- 在没有创建索引前 , emp.ibd 文件大小 是 524m
-- 创建索引后 emp.ibd 文件大小 是 655m [索引本身也会占用空间.]
-- 创建ename列索引,emp.ibd 文件大小 是 827m
-- empno_index 索引名称
-- ON emp (empno) : 表示在 emp表的 empno列创建索引
CREATE INDEX empno_index ON emp (empno); -- 14.819 sec

-- 创建索引后, 查询的速度如何
SELECT * FROM emp WHERE empno = 1234578; -- 0.001 sec 原来是3.017 sec

-- 创建索引后,只对创建了索引的列有效
SELECT * FROM emp WHERE ename = 'PjDlwy'; -- 没有在ename创建索引时,时间3.162 sec

CREATE INDEX ename_index ON emp (ename); -- 在ename上创建索引 18.729 sec

SELECT * FROM emp WHERE ename = 'PjDlwy'; -- 0.008 sec

分类

  • 主键索引(PRIMARY KEY):它是一种特殊的唯一索引,不允许有空值。
  • 普通索引(INDEX):最基本的索引,没有任何限制。
  • 唯一索引(UNIQUE):与普通索引类似,不同的就是索引列的值必须唯一,但允许有空值。
  • 全文索3引(FULLTEXT)[适用于MyISAM]一般开发,不使用mysq自带的全文索引,而是使用:全文搜索框架Solr ElasticSearch (ES)
1
2
3
4
5
6
7
8
9
10
CREATE TABLE t28(
id INT PRIMARY KEY, -- 主键,同时也是索引,称为主键索引

`name` VARCHAR(32)
);

CREATE TABLE t29(
id INT UNIQUE, -- id是唯一的,但可以为空值,同时也是索引,称为唯一索引
`name` VARCHAR(32)
);

使用索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
-- 演示 mysql 的索引的使用 

-- 创建表
CREATE TABLE t30(
id INT ,
`name` VARCHAR(32)
);

-- 查询表是否有索引
SHOW INDEX FROM t30;

-- 添加索引
-- 添加唯一索引
ALTER TABLE t30 ADD UNIQUE (id); -- 方式 1
CREATE UNIQUE INDEX id_index ON t30(id); -- 方式 2

-- 添加普通索引
ALTER TABLE t30 ADD INDEX (id); -- 方式 1
CREATE INDEX id_index ON t30(id); -- 方式 2

-- 添加主键索引
ALTER TABLE t30 ADD PRIMARY KEY (id) -- 方式 1
CREATE TABLE t31(
id INT PRIMARY KEY, -- 方式 2
`name` VARCHAR(32)
);

-- 如何选择索引
-- 1. 如果某列的值,是不会重复的,则优先考虑使用 unique 索引, 否则使用普通索引

-- 删除索引
DROP INDEX id ON t30;
DROP INDEX id_index ON t30;
ALTER TABLE t31 DROP PRIMARY KEY; -- 要删除主键索引,需要先删除整个主键

SHOW INDEX FROM t30;

-- 修改索引,先删除,再添加新的索引
DROP INDEX index_name ON table_name;
CREATE INDEX new_index_name ON table_name (column_name);

-- 查询索引
-- 1. 方式
SHOW INDEX FROM t30;
-- 2. 方式
SHOW INDEXES FROM t30;
-- 3. 方式
SHOW KEYS FROM t30;
-- 4 方式
DESC t30;

使用规则

  1. 较频繁作为查询条件字段应该创建索引:这是索引的主要作用之一,可以加快查询速度。

  2. 唯一性太差的字段不适合单独创建索引:如果一个字段的唯一性非常差,比如大部分行都具有相同的值,那么在该字段上创建索引可能不会带来明显的性能提升。

  3. 更新非常频繁的字段不适合创建索引:当一个字段的值经常被更新时,维护索引会导致额外的开销,因此在这种情况下创建索引可能会降低性能。

  4. 不会出现在WHERE子句中的字段不应该创建索引:索引的目的是加速查询,如果一个字段不会被用于过滤或搜索条件,那么在其上创建索引通常没有实质性的帮助。

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*1.创建一张订单表order(id号,商品名,订购人,数量).
要求id号为主键,请使用4种方式来创建主键.(提示:为练习方便,可以是order1,order2)*/

-- 方式1(主键索引)
CREATE TABLE `order` (
id INT PRIMARY KEY AUTO_INCREMENT, -- 创建主键索引
goods_name VARCHAR(60) NOT NULL DEFAULT '',
orderer VARCHAR(20) NOT NULL DEFAULT '',
nums INT NOT NULL DEFAULT 0
);

SHOW INDEX FROM `order`; -- 索引名为PRIMARY

-- 方式2(唯一索引)
CREATE TABLE order1 (
id INT AUTO_INCREMENT UNIQUE, -- 唯一索引
goods_name VARCHAR(60) NOT NULL DEFAULT '',
orderer VARCHAR(20) NOT NULL DEFAULT '',
nums INT NOT NULL DEFAULT 0
);

SHOW INDEX FROM order1; -- 索引名为id

-- 方式3(普通索引)
CREATE TABLE order2 (
id INT,
goods_name VARCHAR(60) NOT NULL DEFAULT '',
orderer VARCHAR(20) NOT NULL DEFAULT '',
nums INT NOT NULL DEFAULT 0
);

CREATE INDEX id_index ON order2 (id); -- 创建普通索引
SHOW INDEX FROM order2; -- 索引名为id_index

-- 方式4(普通索引)
CREATE TABLE order3 (
id INT,
goods_name VARCHAR(60) NOT NULL DEFAULT '',
orderer VARCHAR(20) NOT NULL DEFAULT '',
nums INT NOT NULL DEFAULT 0
);
ALTER TABLE order3 ADD INDEX (id); -- 创建普通索引
SHOW INDEX FROM order3; -- 索引名为id

事务

概念

事务用于保证数据的一致性,它由一组相关的dml(Data Manipulation Language,即数据操作语言)语句组成,该组的dml语句要么全部成功,要么全部失败。如:转账就要用事务来处理,用以保证数据的一致性。

使用

  1. 在介绍回退事务前,先介绍一下保存点(savepoint)。保存点是事务中的点,用于取消部分事务,当结束事务时(commit),会自动的删除该事务所定义的所有保存点,当执行回退事务时,通过指定保存点可以回退到指定的点。

  2. 使用commiti语句可以提交事务,当执行了commit语句子后,会确认事务的变化、结束事务、删除保存点、释放锁,数据生效。当使用commiti语句结束事务子后,其它会话[其他连接]将可以查着到事务变化后的新数据(所有数据就正式生效)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
-- 演示 

-- 创建一张测试表
CREATE TABLE t32(
id INT,
`name` VARCHAR(32)
);

-- 开始事务
START TRANSACTION;

-- 设置保存点
SAVEPOINT a;

-- 执行 dml 操作
INSERT INTO t32 VALUES(100, 'mkbk');

-- 设置保存点
SAVEPOINT b;

-- 执行 dml 操作
INSERT INTO t32 VALUES(200, 'wxdx');
INSERT INTO t32 VALUES(300, 'ygyg');

-- 回退到 b
ROLLBACK TO b;
SELECT * FROM t32;

-- 继续回退到 a
ROLLBACK TO a;
SELECT * FROM t32;

-- 如果这样, 表示直接回退到事务开始的状态.
ROLLBACK;

-- 提交事务
COMMIT;

细节

  1. 如果不开始事务,默认情况下,dml操作是自动提交的,不能回滚
  2. 如果开始一个事务,没有创建保存点.可以执行rollback,默认就是回退到事务开始的状态.
  3. 也可以在这个事务中(还没有提交时),创建多个保存点.
  4. 你可以在事务没有提交前,选择回退到哪个保存点
  5. mysqlf的事务机制需要innodb的存储引擎才可以使用,myisam不好使.
  6. 开始一个事务start transaction;set autocommit = off;

隔离级别

事务隔离级别是数据库管理系统(DBMS)用来控制不同事务之间相互影响程度的一种机制。在 MySQL 中,有四种事务隔离级别,分别是:

  1. READ UNCOMMITTED(读未提交)

    • 最低的隔离级别。
    • 允许一个事务可以读取另一个事务未提交的数据。
    • 可能会导致脏读、不可重复读、幻读等问题。
  2. READ COMMITTED(读已提交)

    • 允许一个事务只能读取另一个事务已经提交的数据。
    • 避免了脏读问题,但仍然可能存在不可重复读和幻读的问题。
  3. REPEATABLE READ(可重复读)

    • 确保一个事务在执行期间看到的数据是一致的。
    • 在同一个事务内的多次读取相同数据会得到相同的结果,不受其他事务的影响。
    • 避免了脏读和不可重复读问题,但仍然可能存在幻读问题。
  4. SERIALIZABLE(串行化)

    • 提供最高级别的隔离。
    • 确保事务之间完全隔离,不会发生任何并发问题,但可能会导致性能下降。

在 MySQL 中,默认的事务隔离级别是 REPEATABLE READ

你可以在启动事务时通过以下语句来设置不同的隔离级别:

1
2
3
4
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

每种隔离级别都有其适用的场景,选择正确的隔离级别取决于具体的业务需求和数据一致性要求。需要根据实际情况来权衡隔离级别的选择。

隔离问题

脏读、不可重复读和幻读是在并发事务环境下可能出现的三种数据一致性问题。

  1. 脏读(Dirty Read)

    • 脏读发生在一个事务读取了另一个事务未提交的数据。
    • 例如,事务A修改了一行数据,但还未提交,此时事务B读取了这行数据。如果事务A回滚,那么事务B读取的数据就是无效的。
    • READ UNCOMMITTED 隔离级别允许脏读。
  2. 不可重复读(Non-repeatable Read)

    • 不可重复读发生在一个事务内两次读取同一行数据,但得到的结果不一致。
    • 例如,事务A首先读取了一行数据,然后事务B修改了这行数据并提交。随后,事务A再次读取同一行数据,但得到了不同的结果。
    • READ COMMITTED 隔离级别避免了脏读,但仍然可能发生不可重复读。
  3. 幻读(Phantom Read)

    • 幻读发生在一个事务内两次查询同一范围的数据,但得到的结果集不一致。
    • 例如,事务A首先查询了一个范围内的数据,然后事务B插入了一些新数据,符合范围条件。接着,事务A再次查询相同范围的数据,但结果集发生了变化,出现了新增的”幻影”行。
    • REPEATABLE READ 和 SERIALIZABLE 隔离级别避免了幻读。

不同的事务隔离级别会导致不同程度的这些问题:

  • READ UNCOMMITTED 允许脏读、不可重复读和幻读。
  • READ COMMITTED 避免脏读,但仍可能发生不可重复读和幻读。
  • REPEATABLE READ 避免了脏读和不可重复读,但仍可能发生幻读。
  • SERIALIZABLE 提供了最高级别的隔离,可以避免脏读、不可重复读和幻读,但可能会影响性能。

选择合适的隔离级别取决于具体的业务需求和数据一致性要求。需要根据实际情况来权衡隔离级别的选择。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
-- 查看当前会话隔离级别 
SELECT @@tx_isolation;
-- 查看系统当前隔离级别
SELECT @@global.tx_isolation;

-- 设置当前会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- 设置系统当前隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL [你设置的级别];

-- 演示 mysql 的事务隔离级别

-- 开了两个 mysql 的控制台
-- 查看当前 mysql 的隔离级别
SELECT @@tx_isolation; -- 在 MySQL 中,默认的事务隔离级别是 REPEATABLE READ(可重复读)

-- 把事务2的隔离级别设置为 READ UNCOMMITTED(读未提交)
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- 各自启动事件
START TRANSACTION;

-- 事务1 创建表
CREATE TABLE `account`(
id INT,
`name` VARCHAR(32)
);

-- 各自查询表(事务2需使用和事务1一样的数据库)
SELECT * FROM `account`;

-- 事务1插入数据
INSERT INTO `account` VALUES (1, 'mkbk');
INSERT INTO `account` VALUES (2, 'wxdx');

-- 各自查询表
SELECT * FROM `account`; -- 发现事务2查询到了事务一未提交的数据

-- 结论
-- 隔离级别为读未提交时会发生脏读

-- -------------------------------

-- 事务1清空表
DELETE FROM `account`;
-- 或者
ROLLBACK;

-- 事务1提交
COMMIT;

-- -------------------------------

-- 两连接各开启事务
START TRANSACTION;

-- 事务2修改隔离级别:READ COMMITTED(读已提交)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT @@tx_isolation;

-- 事务1添加数据
INSERT INTO `account` VALUES (1, 'mkbk');
INSERT INTO `account` VALUES (2, 'wxdx');

-- 各自查询表
SELECT * FROM `account`; -- 事务2没有读取到事务1未提交的数据(未出现脏读)

-- 事务1提交更改, 事务2查询数据
COMMIT;
SELECT * FROM `account`; -- 事务2出现了事务1提交的更改(幻读)

-- 终端1开启事务
START TRANSACTION;

-- 事务1修改值
UPDATE `account` SET `name` = 'ygyg' WHERE `name` = 'wxdx';

-- 事务2查询数据
SELECT * FROM `account`; -- 事务2并未查询到事务1修改的值(未出现不可重复读)

-- 结论
-- READ COMMITTED 不会发生脏读,但是会发生不可重复读和幻读

-- ---------------------------------------------------


存储引擎

  1. 介绍

  1. 如何选择

  2. 修改存储引擎

1
ALTER TABLE table_name ENGINE = new_engine_name;
  1. 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
-- 存储引擎

-- 查看支持的所有存储引擎
SHOW ENGINES;

-- innodb 存储引擎
-- 1. 支持事务 2. 支持外键 3. 支持行级锁

-- myisam 存储引擎
CREATE TABLE t33(
id INT,
`name` VARCHAR(32)
) ENGINE MYISAM;

-- 1. 添加速度快 2. 不支持外键和事务 3. 支持表级锁
START TRANSACTION;
SAVEPOINT a;
INSERT INTO t33 VALUES (1, 'mkbk');
SELECT * FROM t33;
ROLLBACK TO a; -- 出现警告
SELECT * FROM t33; -- 数据还在


-- memory 存储引擎
-- 1. 数据存储在内存中 2. 执行速度很快 3. 默认支持索引(hash表)
CREATE TABLE t34(
id INT,
`name` VARCHAR(32)
) ENGINE MEMORY;

INSERT INTO t34 VALUES (1, 'ygyg');
SELECT * FROM t34;
net STOP mysql
net START mysql
SELECT * FROM t34; -- 数据在重启服务后丢失

参考课程:

  1. https://www.bilibili.com/video/BV1H64y1U7GJ/