前言

这是关于 MySQL 系列文章的第二篇,本篇大部分内容都贴近开发中的实际操作,,还有一句话不得不再次赘述,数据库博大精深,本系列文章内容较浅,适合于前端的同学们对 MySQL 的入门,这也是我的学习笔记,希望可以帮助大家。

系列文章:

准备数据

还记得在上一篇的最后我们使用 DDL 创建了完整的表关系,studentscorecourse 三张表如下图。


完整表关系图
完整表关系图


其中 score 是外键表,创建外键约束关联了两张主键表 scudentcource,接下来的 SQL 语句都是基于这几张表来进行的。

SQL 的组成

DDL

DDLdata definition language)是数据定义语言,主要命令有 CREATEALTERDROP 等,DDL 主要用在定义或改变数据表(TABLE)的结构、数据类型、表之间的链接和约束等初始化工作上,大多数在建立表时使用,在上一篇中 DDL 操作表的部分着重做了介绍并创建数据表,也可以将存储 DDL 语句的文件存储在 .sql 后缀名结尾的文件当中,在 DBMS 数据管理系统中导入,直接创建数据库。

DML

DMLdata manipulation language)是数据操做语言,通过 DML 语句对数据库数据表中的实体进行增、删、改、查,数据量非常大时,我们不可能使用 DBMS 一条一条的操作数据,所以都是通过代码完成操作,DML 是在学习 MySQL 数据库中最最重要的内容,也是本篇的重点,后面会非常详细的说明。

DCL

DCLdata control language)是数据库控制语言,用来设置或更改数据库用户或角色权限的语句,包括 grantrevoke 等,grant 是授权命令,revoke 是收回权限命令。

SQL 运算符

SQL 元算符分为算术运算符、逻辑运算符和比较运算符,是用来完成列之间或者变量之间的比较和运算的。

算术运算符

运算符说明
+加运算,求两个数或表达式相加的和,如 1 + 1
-减运算,求两个数或表达式相减的差,如 4 - 1
\*乘运算,求两个数或表达式相乘的积,如 2 * 2
\除运算,求两个数或表达式相除的商,如 6 / 4
%取模运算,求两个数或表达式相除的余数,如 6 % 4


逻辑运算符

运算符说明
AND当且仅当两个布尔表达式都为 true 时,返回 true
OR当且仅当两个布尔表达式都为 false 时,返回 false
NOT布尔表达式的值取反


比较运算符

运算符说明
=等于
>大于
<小于
>=大于等于
<=小于等于
<>不等于
!=不等于
<=>安全不等于


SQL 语句之数据增、删、改

插入数据行

语法:

INSERT [INTO] 表名 [(列名)] VALUES (值列表)

给上面的 student 表插入一条数据,姓名为张三,卡号为 100,年龄为 20,城市默认为北京。

INSERT INTO `student` (`name`, `idcard`, `age`, `city`)
VALUES ("张三", "100", 20, NULL);

注意事项:

  • 每次插入一行数据,不能只插入一部分数据,即不能只插入了一个实体的某几列,插入的数据是否有效将严格按照实体的完整性要求来检验;
  • 每个数据值的数据类型、精度、位数必须要与对应的列名精确匹配,严格按照域的完整性要求来检验;
  • 不能为标识符指定值(规定),例如作为主键自增的标识列;
  • 如果某字段设置非空约束,则该列必须插入不为 null 的数据;
  • 插入数据时还要符合检查性约束的要求,如非空约束、唯一约束等;
  • 设置了默认值的列,有缺省值时,可以使用 DEFAULT 关键字来代替插入的实际值。

更新数据行

语法:

UPDATE 表名 SET 列名 = 更新值 [WHERE <更新条件>]

student 表中 id1 的实体的 idcard 列值改为 200age 列值改为 24

UPDATE `student`
SET `idcard` = "200", `age` = 24
WHERE `id` = 1 AND `name` = "张三";

student 表中 city 列为空的实体 city 列的值设置为 “上海”。

UPDATE `student`
SET `city` = "上海"
WHERE `city` IS NULL;

注意事项:

  • 更新多列时,列赋值语句之间使用逗号分隔,一定要加更新条件,否则会更新所有的行;
  • 多个联合条件根据逻辑关系使用 ANDOR 连接。

删除数据行

语法:

DELETE [FROM] 表名 [WHERE <删除条件>]

删除 student 表中 age 列值为 24 或者 idcard 列值为 200 的实体。

DELETE FROM `student`
WHERE `age` = 24 OR `idcard` = 200;

注意事项:

  • 删除的是一整行的数据,所以不需要提供列名;
  • 如果要删除主表数据,则要先删除外键连接对应的子表数据。

SQL 语句之数据查询

SQL 语句的增、删、改、查中,最繁琐和复杂的就是查询语句,在实际开发中七分查询三分增、删、改的说法一点也不为过,查询语句是在业务中使用最多的语句,所以我们将查询语句与增、删、改分离出来,用这单独的一节来叙述。

查询简介

查询就是从客户端发出查询,请求数据库服务器,并从数据库返回查询结果的过程,每次执行查询只是从数据表中提取数据,并按照表的方式呈现出来,查询产生的虚拟表并不会保存。

查询语法

SELECT <列名>
FROM <表名>
[WHERE <查询条件表达式>]
[ORDER BY <排序的列名> [ASC 或 DESC]]
LIMIT <起始索引>, <查询条数>;

我们继续操作之前的三张表,查询 “北京” 的学生并按 id 的正序排序。

SELECT *
FROM `student`
WHERE `city` = "北京"
ORDER BY `id` ASC;

上面 SQL 语句中的 * 代表查询表中所有的列,如果查找某些列,列名之间用 , 分隔。

别名

在查询数据表的某个列的时候可以通过别名改变查询到虚拟表列的名称,在列的后面使用 AS 关键字加上别名,AS 可以省略。

SELECT `name`, `age`, `city` AS `城市`
FROM `student`
WHERE `city` = "北京"
ORDER BY `id` DESC;

查询空行

假设表中的某一列值为 null,我们可以通过 IS 关键字查询这一列值为空的数据。

SELECT `name`, `age`, `city`
FROM `student`
WHERE `city` IS NULL
ORDER BY `id` DESC;

常量列

查询一个数据表中没有的列,我们可以在查询时将这一列增加为常量列,如 city 的值都是中国的,我们可以增加一个常量列值都为 “中国”,列名为 country

SELECT `name`, `age`, `city`, "中国" AS `country`
FROM `student`;

查询列的不重复数据

假设查询 student 表中同学们来自那些城市,正常按照 city 列查询,有多条数据的 city 列都来自同一个城市,我们可以使用 DISTINCT 关键字,只保留一个。

SELECT DISTINCT `city`
FROM `student`;

限制查询条数

LIMIT 关键字用来限制查询返回的条数,后面的值为数字,下面查询 student 表,只返回 3 条数据。

SELECT *
FROM `student`
LIMIT 3;

LIMIT 参数大于一个时,第一个参数代表起始的索引(跳过的条数),第二个参数代表返回的条数,中间用 , 隔开,还是查询 student 表,返回第 46 条数据。

SELECT *
FROM `student`
LIMIT 3, 3;

运算符在查询中的使用

SELECT 1 + 1; -- 2
SELECT 1 + "1"; -- 2
SELECT 1 + "panda"; -- 1
SELECT 1 + NULL; -- NULL

通过上面的例子可以看出在 SQL 语句中,使用算术运算符相加无法起到字符串拼接的作用,会将值转化为数字进行计算,字符串转为数字 0,数字与 null 相加结果为 null,没意义。

查询语句练习案例

首先我们将上面的三张表 studentcourcescore 三张表插入数据。

-- 给 student 表插入数据
INSERT INTO `student`
VALUES ("1", "郭靖", "1", "40", "济南", "山东省", "1978-09-03", 1, "1@qq.com");
INSERT INTO `student`
VALUES ("2", "黄蓉", "2", "36", "济南", "山东省", "1982-09-03", 0, "2@qq.com");
INSERT INTO `student`
VALUES ("3", "杨过", "3", "22", "终南山", "陕西省", "1996-09-03", 1, "3@qq.com");
INSERT INTO `student`
VALUES ("4", "小龙女", "4", "28", "终南山", "陕西省", "1990-09-03", 0, "4@qq.com");
INSERT INTO `student`
VALUES ("5", "欧阳锋", "5", "70", "白驼山", "新疆", "1948-09-03", 1, "5@qq.com");

-- 给 cource 表插入数据
INSERT INTO `course` VALUES ("1", "语文");
INSERT INTO `course` VALUES ("2", "数学");
INSERT INTO `course` VALUES ("3", "英语");

-- 给 score 表插入数据
INSERT INTO `score` VALUES ("1", "1", "100");
INSERT INTO `score` VALUES ("1", "2", "90");
INSERT INTO `score` VALUES ("1", "3", "70");
INSERT INTO `score` VALUES ("2", "1", "100");
INSERT INTO `score` VALUES ("2", "2", "40");
INSERT INTO `score` VALUES ("2", "3", "80");
INSERT INTO `score` VALUES ("3", "1", "100");
INSERT INTO `score` VALUES ("3", "2", "60");
INSERT INTO `score` VALUES ("3", "3", "80");

添加数据后的三张表如下图:


表的具体数据
表的具体数据


查询山东省学生的全部信息:

SELECT *
FROM `student`
WHERE `province` = "山东";

查询山东省男学生的全部信息:

SELECT *
FROM `student`
WHERE `province` = "山东" AND `gender` = 1;

查询没填写电子邮箱学生的姓名和身份证号:

SELECT `name`, `idcard`
FROM `student`
WHERE `email` IS NULL;

把学生的成绩按升序排列:

SELECT *
FROM `score`
ORDER BY `grade` ASC;

课程编号升序排列,成绩降序排列:

SELECT *
FROM `score`
ORDER BY `course_id` ASC, `grade` DESC;

SQL 语句之模糊查询

在上一节中我们所使用的查询叫 “完整查询” 或 “精准查询”,而本节的 “模糊查询” 是指查询条件不明确,可以尽量多的匹配满足查询条件的数据。

通配符

“通配符” 用于代替一个或多个真正的字符,与 LIKE 关键字一起使用,通配符的解释、示例如下表。

通配符解释示例符合条件的值
_一个任意字符LIKE "a_"as、ad 等
%任意长度的字符串LIKE "c%"code、cat 等
[ ]括号中所指定范围内的一个字符LIKE "1[35]5"135 或 155
[^]不再括号中所指定范围内的一个字符LIKE "1[^1-2]5"135 或 155 等

查询 student 表中 email 列是 qq 邮箱的学生信息。

SELECT *
FROM `student`
WHERE `emai` LIKE `%qq%`;

查询 student 表中姓杨的名字为两个字的所有学生信息。

SELECT * FROM `student`
WHERE `name` LIKE "杨_";

BETWEEN…AND…

BETWEEN...AND... 用于查询某一列在指定范围内的记录,包括两个边界。

查询 score 表中成绩在 8090 范围内的所有学生信息。

-- 精准查询
SELECT *
FROM `score`
WHERE `grade` >= 80 AND `grade` <= 90;

-- 模糊查询
SELECT *
FROM `score`
WHERE `grade` BETWEEN 80 AND 90;

IN

查询某一列中的值在列出的内容列表中。

查询 student 表中 city 列为 “北京” 或 “上海” 或 “杭州” 所有学生的信息。

-- 精准查询
SELECT *
FROM `student`
WHERE `city` = "北京" OR `city` = "上海" OR `city` = "杭州";

-- 模糊查询
SELECT *
FROM `student`
WHERE `city` IN ("北京", "上海", "杭州");

截断表

作为主键的标识列,值是会随着数据的增加,自动递增的,且具有 “断号保留” 的特性,随着表中数据的增删,标识列的值会非常混乱,我们可以通过截断表来解决该问题。

截断表有两个作用如下:

  • 使表中数据的标识列的值重新从 1 开始计数;
  • 清空表中数据。

使用截断表时,表的列和约束不会改动,使用的前提是表与表之间不能存在外键约束,表之间如果通过外键进行关联,无法清空主表中数据,即无法截断表。

语法:

TRUNCATE TABLE 表名

截断 student 表:

TRUNCATE TABLE `student`;

截断表操作轻易不要使用,因为普通的 SQL 操作会记录日志,并且可以还原,而 TRUNCATE 操作不会记录日志,并且不可还原,万一删错,彻底完蛋。

函数

MySQL 中有很多内置的函数帮我们实现一些功能,函数大类可分为普通函数和聚合函数,我们本节主要介绍普通函数,常用的普通函数大概可分为字符函数、数字函数、日期函数和流程控制函数等,还有一些与数据库相关的辅助函数以及自定义函数,都会在本节一一介绍。

字符函数

LENGTH

LENGTH 函数的作用是返回输入字符串的长度。

SELECT LENGTH("panda"); -- 5
SELECT LENGTH("你好啊"); -- 9

CONCAT 和 CONCAT_WS

CONCAT 函数用于将多个字符串拼接成一个字符串,CONCAT_WS 函数用于使用分隔符将多个字符串拼接成一个字符串,第一个参数为分隔符。

SELECT CONCAT("Panda", "Shen"); -- PandaShen
SELECT CONCAT_WS("-", "Hello", "Panda", "Shen"); -- Hello-Panda-Shen

UPPER 和 LOWER

UPPER 函数用于将字符串转换为大写,LOWER 函数用于将字符串转换为小写。

SELECT UPPER("panda"); -- PANDA
SELECT LOWER("PANDA"); -- panda

SUBSTR

SUBSTR 函数用于截取字符串,第一个参数为源字符串,第二个参数为起始的索引(从 1 开始),第三个参数为截取的长度,不传默认截取后面全部字符串。

SELECT SUBSTR("panda", 3); -- nda
SELECT SUBSTR("panda", 3, 2); -- nd

LEFT 和 RIGHT

LEFT 函数和 RIGHT 函数分别代表在字符串取左边或右边的规定位数,第一个参数为源字符串,第二个参数为规定的位数。

SELECT LEFT("panda12345", 5); -- panda
SELECT RIGHT("panda12345", 5); -- 12345

INSTR

INSTR 函数用于查找某个字符串在一个源字符串中的起始(第一个匹配到的)索引,第一个参数为源字符串,第二个参数为查询字符串,查询不到返回 0

SELECT INSTR("panda", "a"); -- 2
SELECT INSTR("panda", "x"); -- 0

LTRIM、RTRIM 和 TRIM

LTRIM 函数用于去掉字符串左侧空格,RTRIM 用于去掉字符串右侧空格,TRIM 用于去掉字符串两侧的空格,如果 TRIM 的参数将两个字符串使用 FROM 连接,则代表去掉右侧字符串左右两侧的左侧字符串(说的有点绕,看例子)。

SELECT LTRIM("  panda"); -- panda
SELECT RTRIM("panda  "); -- panda
SELECT TRIM("  panda  "); -- panda
SELECT TRIM("@" FROM "@@panda@@"); -- panda

LPAD 和 RPAD

LPAD 函数和 RPAD 函数分别用于向源字符串的左、右按照规定字符串的长度填充定义的字符。

SELECT LPAD("panda", 10, "@"); -- @@@@@panda
SELECT RPAD("panda", 10, "@"); -- panda@@@@@

REPLACE

REPLACE 函数用于替换某字符串的部分字符,第一个参数为源字符串,第二个参数为被替换字符,第三个参数为替换字符。

SELECT REPLACE("panda", "a", "e"); -- pende

综合练习

查询 id3 学生的邮箱域名。

SELECT SUBSTR(`email`, INSTR(`email`, "@") + 1)
FROM `student`
WHERE `id` = 3;

-- qq.com

student 表中的邮箱地址全部由小写改成大写。

UPDATE `student`
SET `email` = UPPER(`email`);

student 表新增一列 level,类型为 varchar,值为 1-11-21-32-12-2 等,按照 level 列值中 - 的前半段正序排列,后半段逆序排列。

SELECT `level`,
SUBSTR(`level`, 1, INSTR(`level`, "-") - 1),
SUBSTR(`level`, INSTR(`level`, "-") + 1)
FROM `student`
ORDER BY
SUBSTR(`level`, 1, INSTR(`level`, "-") - 1) ASC,
SUBSTR(`level`, INSTR(`level`, "-") + 1) DESC;

pandashen 转换成 Panda_Shen,将两个单词拆开,并实现首字母大写,其他字符小写,然后用 _ 拼接。

SELECT
CONCAT(
  CONCAT(
    UPPER(SUBSTR(SUBSTR("pandashen", 1, 5), 1, 1)),
    SUBSTR(SUBSTR("pandashen", 1, 5), 2)
  ),
  "_",
  CONCAT(
    UPPER(SUBSTR(SUBSTR("pandashen", 6), 1, 1)),
    SUBSTR(SUBSTR("pandashen", 6), 2)
  )
);

-- Panda_Shen

数学函数

FORMAT

FORMAT 函数用于将数字格式化,第一个参数为要格式化的数字,第二个参数为保留的小数位数,返回值为字符串,整数部分每三位使用 , 分隔。

SELECT FORMAT(100000, 2); -- 100,000.00
SELECT FORMAT(100.31111, 3); -- 100.311
SELECT FORMAT(100.31111, 0); -- 100
SELECT FORMAT(423423234.65534453, 2); -- 423,423,234.66

CEIL 和 FLOOR

CEIL 函数为向上取整,FLOOR 函数为向下取整。

SELECT CEIL(2.4); -- 3
SELECT FLOOR(2.4); -- 2

ROUND 和 TRUNCATE

ROUND 函数为四舍五入,TRUNCATE 函数为小数截断,第一个参数为截断的数字,第二个参数为保留的小数位数。

SELECT ROUND(2.1); -- 2
SELECT ROUND(2.5); -- 3
SELECT TRUNCATE(1.66, 0); -- 1
SELECT TRUNCATE(1.66, 1); -- 1.6
SELECT TRUNCATE(1.66, 2); -- 1.66
SELECT TRUNCATE(1.66, 3); -- 1.66

POWER

POWER 函数用于幂运算,第一个参数为底数,第二个参数为指数。

SELECT POWER(2, 3); -- 8

MOD

MOD 函数用于取余运算,第一个参数为被除数,第二个参数为除数。

SElECT MOD(10, 3); -- 1

日期函数

NOW、CURDATE 和 CURTIME

NOW 函数用于获取当前的时间,格式为 yyyy-mm-dd hh:mm:ssCURDATE 函数用于获取当前日期,格式为 yyyy-mm-dd hh:mm:ssCURTIME 函数用于获取当前时间,格式为 hh:mm:ss,返回类型均为日期格式。

SELECT NOW(); -- 2018-08-09 19:23:18
SELECT CURDATE(); -- 2018-08-09
SELECT CURTIME(); -- 19:25:31

DATE_ADD

DATE_ADD 函数用于增加时间,即将时间求和,第一个参数为 yyyy-mm-dd hh:mm:ss 格式的时间,第二个参数为时间间隔,INTERVAL 关键字代表时间间隔,后面跟上数字,单位为 DAYMONTHYEAR 等,返回类型为字符串。

SELECT DATE_ADD(NOW(), INTERVAL 365 DAY); -- 2019-08-09 19:30:15
SELECT DATE_ADD(NOW(), INTERVAL 1 MONTH); -- 2018-09-09 19:30:15
SELECT DATE_ADD(NOW(), INTERVAL 1 YEAR); -- 2019-08-09 19:30:15

STR_TO_DATE

STR_TO_DATE 函数可以将一个日期格式的字符串转换成日期,第一个参数为日期格式的字符串,第二个参数为日期的格式描述,如 %Y-%m-%d,传入的日期字符串的格式与日期格式描述必须保持一致。

SELECT STR_TO_DATE("2018-08-09", "%Y-%m-%d"); -- 2018-08-09
SELECT STR_TO_DATE("2018/08/09", "%Y/%m/%d"); -- 2018-08-09
SELECT STR_TO_DATE("2018.08.09", "%Y.%m.%d"); -- 2018-08-09

student 表中查询一个生日(列名为 birthday)为 198293 的学生信息。

SELECT *
FROM `student`
WHERE `birthday` = STR_TO_DATE('09-03 1982','%m-%d %Y');

-- 结果查到了黄蓉的信息

格式描述参考下表:

序号格式符功能
1%Y4 位的年份
2%y2 位的年份
3%m月份(01,02)
4%c月份(1,2)
5%d日(01,02)
6%H小时(24 小时制)
7%h小时(12 小时制)
8%i分钟(00,01)
9%s秒(00,01)


DATE_FORMAT

DATE_FORMAT 函数与 STR_TO_DATE 作用正好相反,是将一个日期类型按照格式描述转换成日期字符串,第一个参数为日期对象,第二个参数为日期的格式描述。

SELECT DATE_FORMAT(STR_TO_DATE("2018-08-09", "%Y-%m-%d"), "%Y年%m月%d日");

-- 2018年08月09日

DATEDIFF

DATEDIFF 函数用于计算时间间隔,两个参数均为日期对象,得到的结果为第一个与第二个的差值,单位为 “天”。

SELECT DATEDIFF(NOW(), STR_TO_DATE("2018-08-08", "%Y-%m-%d")); -- 1

通过查询 studentbirthday 列计算每个人的年龄(周岁)。

SELECT FLOOR(DATEDIFF(NOW(), `birthday`) / 365)
FROM `student`;

返回日期中指定的部分

下面方法传入参数均为日期对象,返回的时间中对应的部分均为字符串:

  • YEAR:返回年;
  • MONTH:返回月;
  • DAY:返回日;
  • HOUR:返回小时;
  • MINUTE:返回分钟;
  • SECOND:返回秒;
  • MONTHNAME:返回月份名称(英文)。
SELECT YEAR(NOW()); -- 2018
SELECT MONTH(NOW()); -- 8
SELECT DAY(NOW()); -- 9
SELECT HOUR(NOW()); -- 19
SELECT MINUTE(NOW()); -- 48
SELECT SECOND(NOW()); -- 53
SELECT MONTHNAME(NOW()); -- August

综合练习

查询 student 表中出生月份是 9 月的学生。

SELECT *
FROM `student`
WHERE MONTH(`birthday`) = 9;

查询 student 表中生日是今天的学生。

SELECT *
FROM `student`
WHERE MONTH(NOW()) = MONTH(`birthday`) AND DAY(NOW()) = DAY(`birthday`);

student 表中查询年龄超过 18 岁的男同学。

-- 写法 1:求生日与现在时间的差值转换成年与年龄比较
SELECT *
FROM `student`
WHERE DATEDIFF(NOW(), `birthday`) / 365 > 18;
-- 写法 2:给生日加上 18 年看看是否大于今天的时间
SELECT *
FROM `student`
WHERE DATE_ADD(`birthday`, INTERVAL 18 YEAR) < NOW();
-- 写法 3:获取今天时间的年份和生日的年份做差,看是否大于 18
SELECT *
FROM `student`
WHERE YEAR(NOW()) - YEAR(`birthday`) > 18;

数据库辅助函数

CONNECTION_ID

连接每个数据库都会分配对应的连接号,我们称为连接 IDCONNECTION_ID 函数可以帮我们获取连接 ID

SELECT CONNECTION_ID(); -- 10

DATABASE

DATABASE 函数用于查看当前的连接的数据库,返回数据库名称。

SELECT DATABASE(); -- school

VERSION

VERSION 函数用来查看当前 MySQL 数据库的版本号。

SELECT VERSION(); -- 5.5.61

LAST_INSERT_ID

LAST_INSERT_ID 可以获取上次向表中(任意表)插入数据时,插入数据的 id 号。

select LAST_INSERT_ID(); -- 0

USER 和 PASSWORD

USER 用于查询当前数据库登录的用户。

SELECT USER(); -- root@

MySQL 数据库中存在一张表存放着 MySQL 当前所有的用户,可以通过下面命令查询。

SELECT * FROM mysql.user;

通过查询结果可以看出密码的位置存储的是密文,如果要修改密码需要将设置的密码转换成密文进行存储,PASSWORD 函数就是来做这件事的,修改密码命令如下。

UPDATE FROM mysql.user
SET `password` = PASSWORD("123456");

MD5

MD5 可以将传入的字符串进行一个 MD5 散列算法的转换并输出。

SELECT MD5("panda"); -- ce61649168c4550c2f7acab92354dc6e

流程控制函数

IF

IF 函数可以根据传入的判断条件在两个结果中进行选择输出。

SELECT IF(1 > 2, "yes", "no"); -- no

CASE、WHEN、THEN、ELSE 和 END

CASEWHENTHENELSEEND 共同组成了一个流程控制函数,可以代替多个 IFCASE 为条件判断起始关键字,WHEN 为判断条件关键字(可以有多个),THEN 为输出结果关键字,ELSE 为默认输出关键字,END 为流程结束关键字。

SELECT `grade`,
CASE
WHEN `grade` > 90 THEN "优"
WHEN `grade` > 80 THEN "良"
WHEN `grade` > 70 THEN "中"
WHEN `grade` > 60 THEN "及格"
ELSE "不及格"
END
FROM `score`;

自定义函数

规则

  • 自定义函数是对 MySQL 的扩展,使用方式和内置函数相同;
  • 函数必须要有参数和返回值,函数可以接收任意类型的值和参数,返回值只能有一个;
  • 函数体由合法的 SQL 语句组成,可以包含声明、循环和流程控制,也可以是 SELECTINSERT 语句,如果函数体是复合结构(多条语句)要用 BEGIN...END

语法

CREATE FUNCTION FUNC_NAME RETURNS {String|Integer}
BODY
RETURN VALUE

CREATE FUNCTION 为创建函数关键字,FUNC_NAME 为函数的名字,RETURNS 为返回类型关键字,{ } 中的内容为返回值类型,如 varchar 等,BODY 代表函数体,RETURN VALUE 代表返回值。

实例

写一个自定义函数用于获取中文格式的当前时间。

-- 函数 ZHNOW
CREATE FUNCTION ZHNOW() RETURNS VARCHAR(64)
RETURN DATE_FORMAT(NOW(), "%Y年%m月%d日");

-- 使用 ZHNOW
SELECT ZHNOW(); -- 2018年08月10日

实现一个自定义函数对两个数字进行相乘运算并将结果返回。

-- 函数 MYPRODUCT,参数为两个数字
CREATE FUNCTION MYPRODUCT(n INT, m INT) RETURNS INT
RETURN n * m;

-- 使用 MYPRODUCT
SELECT MYPRODUCT(2, 5); -- 10

针对上面的 course 学科表,写一个自定义函数用来增加学科并返回增加数据的 id 值。

-- 函数 ADD_COURSE,参数为添加课程的名称
CREATE FUNCTION ADD_COURSE(name VARCHAR(32)) RETURNS INT
BEGIN
INSERT INTO `course` (`name`)
VALUES (name);
RETURN LAST_INSERT_ID();
END

-- 使用 ADD_COURSE
SELECT ADD_COURSE("体育"); -- 4

聚合函数

上一节中的函数都是普通函数,多用于操作表达式后返回结果,而聚合函数更多是对一组值进行计算,并返回计算后的值,一般用来统计数据,使用难度要大于普通函数。

SUM

SUM 函数用于累加所有行的值。

还是以上面的 score 表为例,我们取出 student_id 值为 1 的学生全部科目的总成绩。

SELECT SUM(`grade`)
FROM `score`
WHERE `student_id` = 1; -- 260

其实上面的操作是分为两部分进行,首先先查出 student_id1 的所有学科的成绩,在对这些成绩进行求和计算后返回。

AVG

AVG 函数用于计算所有行的平均值。

score 表中查询 student_id 值为 3 的学生全部科目成绩的平均值。

SELECT AVG(`grade`)
FROM `score`
WHERE `student_id` = 3; -- 80

MAX 和 MIN

MAXMIN 分别用于计算所有行的最大值和最小值。

score 表中查询 student_id 值为 2 的学生全部科目成绩的最大值和最小值。

SELECT MAX(`grade`), MIN(`grade`)
FROM `score`
WHERE `student_id` = 2; -- 100 40

COUNT

COUNT 按照某一列统计所有行的总条数,如果参照的列值为 null 则不统计在内。

score 表中查询 student_id 值为 1 的学生成绩总数。

SELECT COUNT(`grade`)
FROM `score`
WHERE `student_id` = 1; -- 3

SQL 语句之分组聚合

分组

分组查询就是按某一列或某几列的值进行分组,相同的值分为一组,然后可以对组内使用聚合函数分别进行统计。

score 表为例,分组表示图如下:


成绩表分组数据
成绩表分组数据


语法:

SELECT 列名, 查询表达式
FROM  <表名>
GROUP BY <分组字段>

GROUP BY 关键字用于对筛选后的结果进行分组。

例子:

统计 score 表中每位同学的平均成绩(单列分组,对 student_id 进行分组)。

SELECT `student_id`, AVG(`grade`)
FROM `score`
GROUP BY `student_id`;

统计 score 表中每门课程的最高分(单列分组,对 course_id 进行分组),并按照分数从低到高排序。

SELECT `course_id`, MAX(`grade`)
FROM `score`
GROUP BY `course_id`
ORDER BY `grade` ASC;

统计 student 表中各省男女同学人数(多列分组,按照 provincegender 进行分组)。

SELECT `province`, `gender`, COUNT(*)
FROM `student`
GROUP BY `province`, `gender`;

COUNT 中的 * 代表任意一列。

分组筛选

语法:

SELECT 列名, 查询表达式
FROM  <表名>
WHERE  <条件>
GROUP BY <分组字段>
HAVING 分组后的过滤条件
ORDER BY 列名 [ASC, DESC]
LIMIT 偏移量, 条数

WHERE 用于过滤掉不符合条件的记录,HAVING 用于过滤分组后的记录。

注意:上面查询的关键字不一定每条查询语句都要有,但是如果有其中的某些关键字,一定要按照上面的顺序进行。

例子:

统计 student 表中学生人数多于一个的省份。

SELECT `province`, COUNT(*)
FROM `student`
GROUP BY `province`
HAVING COUNT(*) > 1;

统计 score 表中不及格次数大于 1 的学生按逆序排列并取前 3 条。

SELECT `student_id`, COUNT(*)
FROM `score`
WHERE `grade` < 60
GROUP BY `student_id`
HAVING COUNT(`grade`) > 1
ORDER BY COUNT(`grade`) DESC
LIMIT 0, 3;

SQL 语句之子查询

子查询的注意点:

  • 子查询是出现在其他 SQL 语句中的 SELECT 语句;
  • 子查询嵌套在查询内部,且必须写在圆括号中,外层可以是 SELECTINSERTUPDATESET 等;
  • 子查询可以包含多个关键字或条件;
  • 子查询可以返回常量、一行数据、一列数据或其它子查询(返回多行数据需使用其他关键字处理)。

比较运算符连接子查询

在查询通过 WHERE 进行条件筛选时,可以使用比较运算符连接子查询。

查询年龄大于平均年龄的学生。

SELECT *
FROM `student`
WHERE YEAR(NOW()) - YEAR(`birthday`) > (
  SELECT AVG(YEAR(NOW()) - YEAR(`birthday`))
  FROM `student`
);

IN 和 NOT IN

当查询一张表的数据需要另一张表中的数据作为筛选条件时,可以在查询时通过 INNOT IN 关键字连接子查询。

查询 student 有不及格成绩的学生信息。

SELECT *
FROM `student`
WHERE `id` IN (
  SELECT DISTINCT `student_id`
  FROM `score`
  WHERE `grade` < 60
);

-- 结果查到了黄蓉

上面例子查到了 “黄蓉” 同学的信息,如果将 IN 修改为 NOT IN 则会查询到除 “黄蓉” 以外其他同学的信息。

查询 student 表中在 score 表中有成绩的学生信息。

SELECT *
FROM `student`
WHERE student.id IN (
  SELECT DISTINCT `student_id`
  FROM `score`
);

EXISTS 和 NOT EXISTS

EXISTSNOT EXISTS 关键字用于连接子查询,代表 “存在” 或 “不存在” 的意思,接收的子查询返回为数据为布尔值。

拿上面查询 student 表在 score 表中有成绩学生信息的例子,使用 EXISTS 实现查询。

SELECT *
FROM `student`
WHERE EXISTS (
  SELECT `student_id`
  FROM `score`
  WHERE score.student_id = student.id
);

INNOT INEXISTSNOT EXISTS 比较,后者的性能更高,查询速度更快,因为判断是在子查询内部,EXISTSNOT EXISTS 只需要判断布尔值就可以了,INNOT IN 在子查询返回数据的基础上还要再次过滤。

ALL、SOME 和 ANY

当子查询返回了多条数据的时候,可以使用 ALLSOMEANY 关键字作为比较运算符和子查询之间的连接。

  • ALL 关键字代表全部,即查询的数据筛选条件要去和所有子查询返回的比较并都满足;
  • SOME 关键字代表某些,查询数据筛选条件只要满足子查询返回数据的某些条即可;
  • ANY 关键字代表任何一个,查询数据的筛选条件只要满足子查询返回数据的任意一条即可。

查询 student 表中年龄大于等于所有人的学生信息。

SELECT *
FROM `student`
WHERE `age` >= ALL (
  SELECT `age`
  FROM `student`
);

-- 结果查到了欧阳锋

查询 student 表中年龄大于某些人的学生信息。

SELECT *
FROM `student`
WHERE `age` > SOME (
  SELECT `age`
  FROM `student`
);

-- 结果查到除杨过以外的所有人(因为杨过年龄最小)

查询 student 表中年龄大于任意一个人的学生信息。

SELECT *
FROM `student`
WHERE `age` > ANY (
  SELECT `age`
  FROM `student`
);

-- 返回结果同 SOME 没区别

表连接

连接类型

在 MySQL 中的表连接类型:

  • INNER JOIN:内连接,代表取两张关联表的交集,可以用 JOIN 关键字代替;
  • LEFT:左外连接,代表取两张表的交集后再和左侧表求并集,即包含左侧表全部数据;
  • RIGHT:右外连接,代表取两张表的交际后再和右侧表求并集,即包含右侧表全部数据。

三种连接类型表示如下图:


表连接类型韦恩图
表连接类型韦恩图


连接条件 ON

ON 关键字用来设定表的连接条件,也可以使用 WHERE 对结果进行过滤的方式来代替。

内连接

studentscore 两张表使用内连接。

SELECT *
FROM `student`
INNER JOIN `score` ON student.id = score.student_id;

查询结果如下:


内连接查询结果
内连接查询结果


左外连接

studentscore 两张表使用左外连接。

SELECT *
FROM `student`
LEFT JOIN `score` ON student.id = score.student_id;

查询结果如下:


左外连接查询结果
左外连接查询结果


右外连接

studentscore 两张表使用右外连接。

SELECT *
FROM `student`
RIGHT JOIN `score` ON student.id = score.student_id;

查询结果如下:


右外连接查询结果
右外连接查询结果


多表连接案例

连接 studentscorecourse 三张表,分别查询学生姓名、学科和分数。

SELECT student.name, course.name, score.grade
FROM `score`
INNER JOIN `student` ON student.id = score.student_id
INNER JOIN `course` ON course.id = score.course_id;

总结

到此 MySQL 系列文章的第二篇就结束了,本篇的大部分都是和操作数据库的 SQl 语句相关的知识点,里面涵盖了基本的增、删、改、查以及函数、聚合函数的应用等,如果记不住的话(当然我也记不住,只是负责替大家和我自己整理)建议收藏,在使用时随时查阅,这一篇的内容对于前端而言,可以说基本够用了。