这是关于 MySQL 系列文章的第二篇,在上一篇 《MySQL —— 数据库基础》 中,主要介绍了一些数据库的基础概念、创建表的方式以及使用 DDL 数据定义语言对表的操作,本篇的主要内容是在开发中使用 SQL 语句对数据库的增、删、改、查,函数、聚合函数的应用以及表连接,本篇大部分内容都贴近开发中的实际操作,不会像上一篇那么枯燥,也可当作一个小小的查询文档,还有一句话不得不再次赘述,数据库博大精深,本系列文章内容较浅,适合于前端的同学们对 MySQL 的入门,这也是我的学习笔记,希望可以帮助大家。
还记得在上一篇的最后我们使用 DDL 创建了完整的表关系,student
、score
和 course
三张表如下图。
其中 score
是外键表,创建外键约束关联了两张主键表 scudent
和 cource
,接下来的 SQL 语句都是基于这几张表来进行的。
DDL(data definition language)是数据定义语言,主要命令有 CREATE
、ALTER
、DROP
等,DDL 主要用在定义或改变数据表(TABLE)的结构、数据类型、表之间的链接和约束等初始化工作上,大多数在建立表时使用,在上一篇中 DDL 操作表的部分着重做了介绍并创建数据表,也可以将存储 DDL 语句的文件存储在 .sql
后缀名结尾的文件当中,在 DBMS 数据管理系统中导入,直接创建数据库。
DML(data manipulation language)是数据操做语言,通过 DML 语句对数据库数据表中的实体进行增、删、改、查,数据量非常大时,我们不可能使用 DBMS 一条一条的操作数据,所以都是通过代码完成操作,DML 是在学习 MySQL 数据库中最最重要的内容,也是本篇的重点,后面会非常详细的说明。
DCL(data control language)是数据库控制语言,用来设置或更改数据库用户或角色权限的语句,包括 grant
、revoke
等,grant
是授权命令,revoke
是收回权限命令。
SQL 元算符分为算术运算符、逻辑运算符和比较运算符,是用来完成列之间或者变量之间的比较和运算的。
运算符 | 说明 |
---|---|
+ | 加运算,求两个数或表达式相加的和,如 1 + 1 |
- | 减运算,求两个数或表达式相减的差,如 4 - 1 |
* | 乘运算,求两个数或表达式相乘的积,如 2 * 2 |
\ | 除运算,求两个数或表达式相除的商,如 6 / 4 |
% | 取模运算,求两个数或表达式相除的余数,如 6 % 4 |
运算符 | 说明 |
---|---|
AND | 当且仅当两个布尔表达式都为 true 时,返回 true |
OR | 当且仅当两个布尔表达式都为 false 时,返回 false |
NOT | 布尔表达式的值取反 |
运算符 | 说明 |
---|---|
= | 等于 |
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
<> | 不等于 |
!= | 不等于 |
<=> | 安全不等于 |
语法:
INSERT [INTO] 表名 [(列名)] VALUES (值列表)
给上面的 student
表插入一条数据,姓名为张三,卡号为 100
,年龄为 20
,城市默认为北京。
INSERT INTO `student` (`name`, `idcard`, `age`, `city`)
VALUES ("张三", "100", 20, NULL);
注意事项:
null
的数据;DEFAULT
关键字来代替插入的实际值。语法:
UPDATE 表名 SET 列名 = 更新值 [WHERE <更新条件>]
将 student
表中 id
为 1
的实体的 idcard
列值改为 200
,age
列值改为 24
。
UPDATE `student`
SET `idcard` = "200", `age` = 24
WHERE `id` = 1 AND `name` = "张三";
将 student
表中 city
列为空的实体 city
列的值设置为 “上海”。
UPDATE `student`
SET `city` = "上海"
WHERE `city` IS NULL;
注意事项:
AND
或 OR
连接。语法:
DELETE [FROM] 表名 [WHERE <删除条件>]
删除 student
表中 age
列值为 24
或者 idcard
列值为 200
的实体。
DELETE FROM `student`
WHERE `age` = 24 OR `idcard` = 200;
注意事项:
在 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
表,返回第 4
到 6
条数据。
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
,没意义。
首先我们将上面的三张表 student
,cource
和 score
三张表插入数据。
-- 给 student 表插入数据
INSERT INTO `student`
VALUES ("1", "郭靖", "1", "40", "济南", "山东省", "1978-09-03", 1, "[email protected]");
INSERT INTO `student`
VALUES ("2", "黄蓉", "2", "36", "济南", "山东省", "1982-09-03", 0, "[email protected]");
INSERT INTO `student`
VALUES ("3", "杨过", "3", "22", "终南山", "陕西省", "1996-09-03", 1, "[email protected]");
INSERT INTO `student`
VALUES ("4", "小龙女", "4", "28", "终南山", "陕西省", "1990-09-03", 0, "[email protected]");
INSERT INTO `student`
VALUES ("5", "欧阳锋", "5", "70", "白驼山", "新疆", "1948-09-03", 1, "[email protected]");
-- 给 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;
在上一节中我们所使用的查询叫 “完整查询” 或 “精准查询”,而本节的 “模糊查询” 是指查询条件不明确,可以尽量多的匹配满足查询条件的数据。
“通配符” 用于代替一个或多个真正的字符,与 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...
用于查询某一列在指定范围内的记录,包括两个边界。
查询 score
表中成绩在 80
到 90
范围内的所有学生信息。
-- 精准查询
SELECT *
FROM `score`
WHERE `grade` >= 80 AND `grade` <= 90;
-- 模糊查询
SELECT *
FROM `score`
WHERE `grade` BETWEEN 80 AND 90;
查询某一列中的值在列出的内容列表中。
查询 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
函数的作用是返回输入字符串的长度。
SELECT LENGTH("panda"); -- 5
SELECT LENGTH("你好啊"); -- 9
CONCAT
函数用于将多个字符串拼接成一个字符串,CONCAT_WS
函数用于使用分隔符将多个字符串拼接成一个字符串,第一个参数为分隔符。
SELECT CONCAT("Panda", "Shen"); -- PandaShen
SELECT CONCAT_WS("-", "Hello", "Panda", "Shen"); -- Hello-Panda-Shen
UPPER
函数用于将字符串转换为大写,LOWER
函数用于将字符串转换为小写。
SELECT UPPER("panda"); -- PANDA
SELECT LOWER("PANDA"); -- panda
SUBSTR
函数用于截取字符串,第一个参数为源字符串,第二个参数为起始的索引(从 1
开始),第三个参数为截取的长度,不传默认截取后面全部字符串。
SELECT SUBSTR("panda", 3); -- nda
SELECT SUBSTR("panda", 3, 2); -- nd
LEFT
函数和 RIGHT
函数分别代表在字符串取左边或右边的规定位数,第一个参数为源字符串,第二个参数为规定的位数。
SELECT LEFT("panda12345", 5); -- panda
SELECT RIGHT("panda12345", 5); -- 12345
INSTR
函数用于查找某个字符串在一个源字符串中的起始(第一个匹配到的)索引,第一个参数为源字符串,第二个参数为查询字符串,查询不到返回 0
。
SELECT INSTR("panda", "a"); -- 2
SELECT INSTR("panda", "x"); -- 0
LTRIM
函数用于去掉字符串左侧空格,RTRIM
用于去掉字符串右侧空格,TRIM
用于去掉字符串两侧的空格,如果 TRIM
的参数将两个字符串使用 FROM
连接,则代表去掉右侧字符串左右两侧的左侧字符串(说的有点绕,看例子)。
SELECT LTRIM(" panda"); -- panda
SELECT RTRIM("panda "); -- panda
SELECT TRIM(" panda "); -- panda
SELECT TRIM("@" FROM "@@panda@@"); -- panda
LPAD
函数和 RPAD
函数分别用于向源字符串的左、右按照规定字符串的长度填充定义的字符。
SELECT LPAD("panda", 10, "@"); -- @@@@@panda
SELECT RPAD("panda", 10, "@"); -- panda@@@@@
REPLACE
函数用于替换某字符串的部分字符,第一个参数为源字符串,第二个参数为被替换字符,第三个参数为替换字符。
SELECT REPLACE("panda", "a", "e"); -- pende
查询 id
为 3
学生的邮箱域名。
SELECT SUBSTR(`email`, INSTR(`email`, "@") + 1)
FROM `student`
WHERE `id` = 3;
-- qq.com
将 student
表中的邮箱地址全部由小写改成大写。
UPDATE `student`
SET `email` = UPPER(`email`);
给 student
表新增一列 level
,类型为 varchar
,值为 1-1
、1-2
、1-3
、2-1
、2-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
函数用于将数字格式化,第一个参数为要格式化的数字,第二个参数为保留的小数位数,返回值为字符串,整数部分每三位使用 ,
分隔。
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
函数为向下取整。
SELECT CEIL(2.4); -- 3
SELECT FLOOR(2.4); -- 2
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
函数用于幂运算,第一个参数为底数,第二个参数为指数。
SELECT POWER(2, 3); -- 8
MOD
函数用于取余运算,第一个参数为被除数,第二个参数为除数。
SElECT MOD(10, 3); -- 1
NOW
函数用于获取当前的时间,格式为 yyyy-mm-dd hh:mm:ss
,CURDATE
函数用于获取当前日期,格式为 yyyy-mm-dd hh:mm:ss
,CURTIME
函数用于获取当前时间,格式为 hh:mm:ss
,返回类型均为日期格式。
SELECT NOW(); -- 2018-08-09 19:23:18
SELECT CURDATE(); -- 2018-08-09
SELECT CURTIME(); -- 19:25:31
DATE_ADD
函数用于增加时间,即将时间求和,第一个参数为 yyyy-mm-dd hh:mm:ss
格式的时间,第二个参数为时间间隔,INTERVAL
关键字代表时间间隔,后面跟上数字,单位为 DAY
、MONTH
、YEAR
等,返回类型为字符串。
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
函数可以将一个日期格式的字符串转换成日期,第一个参数为日期格式的字符串,第二个参数为日期的格式描述,如 %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
)为 1982
年 9
月 3
的学生信息。
SELECT *
FROM `student`
WHERE `birthday` = STR_TO_DATE('09-03 1982','%m-%d %Y');
-- 结果查到了黄蓉的信息
格式描述参考下表:
序号 | 格式符 | 功能 |
---|---|---|
1 | %Y | 4 位的年份 |
2 | %y | 2 位的年份 |
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
函数与 STR_TO_DATE
作用正好相反,是将一个日期类型按照格式描述转换成日期字符串,第一个参数为日期对象,第二个参数为日期的格式描述。
SELECT DATE_FORMAT(STR_TO_DATE("2018-08-09", "%Y-%m-%d"), "%Y年%m月%d日");
-- 2018年08月09日
DATEDIFF
函数用于计算时间间隔,两个参数均为日期对象,得到的结果为第一个与第二个的差值,单位为 “天”。
SELECT DATEDIFF(NOW(), STR_TO_DATE("2018-08-08", "%Y-%m-%d")); -- 1
通过查询 student
的 birthday
列计算每个人的年龄(周岁)。
SELECT FLOOR(DATEDIFF(NOW(), `birthday`) / 365)
FROM `student`;
下面方法传入参数均为日期对象,返回的时间中对应的部分均为字符串。
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;
连接每个数据库都会分配对应的连接号,我们称为连接 ID,CONNECTION_ID
函数可以帮我们获取连接 ID。
SELECT CONNECTION_ID(); -- 10
DATABASE
函数用于查看当前的连接的数据库,返回数据库名称。
SELECT DATABASE(); -- school
VERSION
函数用来查看当前 MySQL 数据库的版本号。
SELECT VERSION(); -- 5.5.61
LAST_INSERT_ID
可以获取上次向表中(任意表)插入数据时,插入数据的 id
号。
select LAST_INSERT_ID(); -- 0
USER
用于查询当前数据库登录的用户。
SELECT USER(); -- root@
在 MySQL 数据库中存在一张表存放着 MySQL 当前所有的用户,可以通过下面命令查询。
SELECT * FROM mysql.user;
通过查询结果可以看出密码的位置存储的是密文,如果要修改密码需要将设置的密码转换成密文进行存储,PASSWORD
函数就是来做这件事的,修改密码命令如下。
UPDATE FROM mysql.user
SET `password` = PASSWORD("123456");
MD5
可以将传入的字符串进行一个 MD5
散列算法的转换并输出。
SELECT MD5("panda"); -- ce61649168c4550c2f7acab92354dc6e
IF
函数可以根据传入的判断条件在两个结果中进行选择输出。
SELECT IF(1 > 2, "yes", "no"); -- no
CASE
、WHEN
、THEN
、ELSE
和 END
共同组成了一个流程控制函数,可以代替多个 IF
,CASE
为条件判断起始关键字,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`;
SELECT
或 INSERT
语句,如果函数体是复合结构(多条语句)要用 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
函数用于累加所有行的值。
还是以上面的 score
表为例,我们取出 student_id
值为 1
的学生全部科目的总成绩。
SELECT SUM(`grade`)
FROM `score`
WHERE `student_id` = 1; -- 260
其实上面的操作是分为两部分进行,首先先查出 student_id
为 1
的所有学科的成绩,在对这些成绩进行求和计算后返回。
AVG
函数用于计算所有行的平均值。
在 score
表中查询 student_id
值为 3
的学生全部科目成绩的平均值。
SELECT AVG(`grade`)
FROM `score`
WHERE `student_id` = 3; -- 80
MAX
和 MIN
分别用于计算所有行的最大值和最小值。
在 score
表中查询 student_id
值为 2
的学生全部科目成绩的最大值和最小值。
SELECT MAX(`grade`), MIN(`grade`)
FROM `score`
WHERE `student_id` = 2; -- 100 40
COUNT
按照某一列统计所有行的总条数,如果参照的列值为 null
则不统计在内。
在 score
表中查询 student_id
值为 1
的学生成绩总数。
SELECT COUNT(`grade`)
FROM `score`
WHERE `student_id` = 1; -- 3
分组查询就是按某一列或某几列的值进行分组,相同的值分为一组,然后可以对组内使用聚合函数分别进行统计。
以 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
表中各省男女同学人数(多列分组,按照 province
和 gender
进行分组)。
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;
子查询的注意点:
SELECT
语句;SELECT
、INSERT
、UPDATE
、SET
等;在查询通过 WHERE
进行条件筛选时,可以使用比较运算符连接子查询。
查询年龄大于平均年龄的学生。
SELECT *
FROM `student`
WHERE YEAR(NOW()) - YEAR(`birthday`) > (
SELECT AVG(YEAR(NOW()) - YEAR(`birthday`))
FROM `student`
);
当查询一张表的数据需要另一张表中的数据作为筛选条件时,可以在查询时通过 IN
和 NOT 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
关键字用于连接子查询,代表 “存在” 或 “不存在” 的意思,接收的子查询返回为数据为布尔值。
拿上面查询 student
表在 score
表中有成绩学生信息的例子,使用 EXISTS
实现查询。
SELECT *
FROM `student`
WHERE EXISTS (
SELECT `student_id`
FROM `score`
WHERE score.student_id = student.id
);
IN
、NOT IN
与 EXISTS
、NOT EXISTS
比较,后者的性能更高,查询速度更快,因为判断是在子查询内部,EXISTS
、NOT EXISTS
只需要判断布尔值就可以了,IN
、NOT IN
在子查询返回数据的基础上还要再次过滤。
当子查询返回了多条数据的时候,可以使用 ALL
、SOME
和 ANY
关键字作为比较运算符和子查询之间的连接。
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
关键字用来设定表的连接条件,也可以使用 WHERE
对结果进行过滤的方式来代替。
对 student
和 score
两张表使用内连接。
SELECT *
FROM `student`
INNER JOIN `score` ON student.id = score.student_id;
查询结果如下:
对 student
和 score
两张表使用左外连接。
SELECT *
FROM `student`
LEFT JOIN `score` ON student.id = score.student_id;
查询结果如下:
对 student
和 score
两张表使用右外连接。
SELECT *
FROM `student`
RIGHT JOIN `score` ON student.id = score.student_id;
查询结果如下:
连接 student
、score
和 course
三张表,分别查询学生姓名、学科和分数。
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 语句相关的知识点,里面涵盖了基本的增、删、改、查以及函数、聚合函数的应用等,如果记不住的话(当然我也记不住,只是负责替大家和我自己整理)建议收藏,在使用时随时查阅,这一篇的内容对于前端而言,可以说基本够用了,如果你对数据库还有兴趣,想了解关于表的设计的相关思想,期待我的下一篇 《MySQL —— 简单聊一聊数据库设计》 更新吧。