MySQL 增删改查(CRUD)操作完全指南
覆盖 Create(增)、Retrieve(查)、Update(改)、Delete(删) 四大操作,以及 聚合函数、分组查询、内置函数 。
所有示例均可在 MySQL 8.0 环境下直接运行,配套的建表与数据可在正文中一键复现。
目录
- [1. 本节目标](#1. 本节目标)
- [2. CRUD 简介](#2. CRUD 简介)
- [3. Create 新增](#3. Create 新增)
- [4. Retrieve 检索](#4. Retrieve 检索)
- [4.3 Select 基础查询](#4.3 Select 基础查询)
- [4.4 Where 条件查询](#4.4 Where 条件查询)
- [4.5 Order by 排序](#4.5 Order by 排序)
- [4.6 分页查询](#4.6 分页查询)
- [5. Update 修改](#5. Update 修改)
- [6. Delete 删除](#6. Delete 删除)
- [7. 截断表 TRUNCATE](#7. 截断表 TRUNCATE)
- [8. 插入查询结果](#8. 插入查询结果)
- [9. 聚合函数](#9. 聚合函数)
- [10. Group by 分组查询](#10. Group by 分组查询)
- [11. 内置函数](#11. 内置函数)
- [11.1 日期函数](#11.1 日期函数)
- [11.2 字符串处理函数](#11.2 字符串处理函数)
- [11.3 数学函数](#11.3 数学函数)
- [11.4 其他常用函数](#11.4 其他常用函数)
- [12. 全文总结](#12. 全文总结)
1. 本节目标
学习完本节内容后,你应当能够:
- 熟练使用 SQL 语句对数据库进行 CRUD 操作:即对表中记录完成增、删、改、查四类最基本的动作。
- 掌握聚合函数的使用 :
COUNT、SUM、AVG、MAX、MIN五个最常用的聚合函数。 - 掌握分组查询以及对分组结果进行过滤 :理解
GROUP BY的作用,并能用HAVING对分组后的结果二次过滤。 - 了解内置函数:包括日期函数、字符串处理函数、数学函数及其他常用工具函数。
💡 扩展提示 :CRUD 是所有关系型数据库操作的基石。无论上层用 Java、Python 还是任何框架,
最终落到数据库层面几乎都是这四类语句,因此本节是整个 SQL 学习的重中之重。
2. CRUD 简介
CRUD 是对数据库中记录进行基本操作的四个英文单词首字母缩写:
| 字母 | 英文 | 中文 | 对应 SQL 语句 |
|---|---|---|---|
| C | Create | 创建(增) | INSERT |
| R | Retrieve | 读取(查) | SELECT |
| U | Update | 更新(改) | UPDATE |
| D | Delete | 删除(删) | DELETE |
📌 注:讲义原文标题处偶有写作 "CURD",属于笔误,业界标准写法是 CRUD (顺序为增、查、改、删)。
它描述的是一类「对记录(行)的操作」,与之相对的是 DDL(建库建表等结构操作)。
3. Create 新增
3.1 语法
sql
INSERT [INTO] table_name
[(column [, column] ...)]
VALUES
(value_list) [, (value_list)] ...
-- value_list: value, [, value] ...
INTO关键字可省略;- 可在表名后用括号显式指定要插入的列,省略时表示按表结构的全部列、按定义顺序插入;
VALUES后可跟多个(value_list),每个括号代表一行数据,从而实现多行插入。
3.2 示例:准备演示表
sql
# 创建一个用于演示的表
CREATE TABLE users (
id BIGINT,
name VARCHAR(20) COMMENT '用户名'
);
3.2.1 单行数据全列插入
要求:
value_list中值的数量、顺序必须与建表时定义的列完全一致。
sql
# 插入第一条记录
mysql> INSERT INTO users VALUES (1, '张三');
Query OK, 1 row affected (0.02 sec)
# 插入第二条记录
mysql> INSERT INTO users VALUES (2, '李四');
Query OK, 1 row affected (0.01 sec)
# 查询结果
mysql> SELECT * FROM users;
+------+------+
| id | name |
+------+------+
| 1 | 张三 |
| 2 | 李四 |
+------+------+
2 rows in set (0.00 sec)
3.2.2 单行数据指定列插入
要求:
value_list中值的数量与顺序必须与指定列一致;未被指定的列由数据库填默认值或 NULL。
sql
# 指定了具体要插入的列
mysql> INSERT INTO users(id, name) VALUES (3, '王五');
Query OK, 1 row affected (0.01 sec)
mysql> SELECT * FROM users;
+------+------+
| id | name |
+------+------+
| 1 | 张三 |
| 2 | 李四 |
| 3 | 王五 |
+------+------+
3 rows in set (0.00 sec)
3.2.3 多行数据指定列插入
在一条
INSERT语句中指定多个value_list,即可一次插入多行,效率远高于逐条插入。
sql
# 每个 value_list 表示一行数据
mysql> INSERT INTO users(id, name) VALUES (4, '赵六'), (5, '钱七');
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> SELECT * FROM users;
+------+------+
| id | name |
+------+------+
| 1 | 张三 |
| 2 | 李四 |
| 3 | 王五 |
| 4 | 赵六 |
| 5 | 钱七 |
+------+------+
5 rows in set (0.00 sec)
💡 工程实践 :批量
INSERT把网络往返和事务开销合并,是数据导入最常用的写法;数据量极大时还可以配合
LOAD DATA INFILE进一步提速。
4. Retrieve 检索
SELECT 是 SQL 中最丰富、最常用的语句,本节几乎所有进阶能力都建立在它之上。
4.1 语法
sql
SELECT
[DISTINCT]
select_expr [, select_expr] ...
[FROM table_references]
[WHERE where_condition]
[GROUP BY {col_name | expr}, ...]
[HAVING where_condition]
[ORDER BY {col_name | expr } [ASC | DESC], ... ]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
各子句的执行逻辑顺序为:FROM → WHERE → GROUP BY → 聚合 → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT。
理解这一顺序,是写出正确语句(尤其是 WHERE 与 HAVING 的区别)的关键。
4.2 示例:构造数据
后续检索示例统一使用下面的 exam(考试成绩)表:
sql
-- 创建表结构
CREATE TABLE exam (
id BIGINT,
name VARCHAR(20) COMMENT '同学姓名',
chinese FLOAT COMMENT '语文成绩',
math FLOAT COMMENT '数学成绩',
english FLOAT COMMENT '英语成绩'
);
-- 插入测试数据
INSERT INTO exam (id, name, chinese, math, english) VALUES
(1, '唐三藏', 67, 98, 56),
(2, '孙悟空', 87, 78, 77),
(3, '猪悟能', 88, 98, 90),
(4, '曹孟德', 82, 84, 67),
(5, '刘玄德', 55, 85, 45),
(6, '孙权', 70, 73, 78),
(7, '宋公明', 75, 65, 30);
Query OK, 7 rows affected (0.00 sec)
说明:讲义原文此处写为
INSERT INTO exam (name, chinese, math, english) VALUES (1, '唐三藏', ...),列表只有 4 列而每条数据有 5 个值,属于笔误。正确写法是像上面一样把
id也列入列名,已修正。
4.3 Select 基础查询
4.3.1 全列查询
sql
# 使用 * 可以查询表中所有列的值
mysql> SELECT * FROM exam;
+----+--------+---------+------+---------+
| id | name | chinese | math | english |
+----+--------+---------+------+---------+
| 1 | 唐三藏 | 67 | 98 | 56 |
| 2 | 孙悟空 | 87 | 78 | 77 |
| 3 | 猪悟能 | 88 | 98 | 90 |
| 4 | 曹孟德 | 82 | 84 | 67 |
| 5 | 刘玄德 | 55 | 85 | 45 |
| 6 | 孙权 | 70 | 73 | 78 |
| 7 | 宋公明 | 75 | 65 | 30 |
+----+--------+---------+------+---------+
7 rows in set (0.00 sec)
4.3.2 指定列查询
sql
# 查询所有人的编号、姓名和语文成绩
mysql> SELECT id, name, chinese FROM exam;
+----+--------+---------+
| id | name | chinese |
+----+--------+---------+
| 1 | 唐三藏 | 67 |
| 2 | 孙悟空 | 87 |
| 3 | 猪悟能 | 88 |
| 4 | 曹孟德 | 82 |
| 5 | 刘玄德 | 55 |
| 6 | 孙权 | 70 |
| 7 | 宋公明 | 75 |
+----+--------+---------+
7 rows in set (0.00 sec)
- 在
SELECT后的查询列表中指定希望查询的列,可以是一个也可以是多个,中间用逗号隔开; - 指定列的顺序与表结构中的列顺序无关,返回结果按你书写的顺序展示。
4.3.3 查询字段为表达式
sql
# 表达式本身就是一个常量
mysql> SELECT id, name, 10 FROM exam;
+----+--------+----+
| id | name | 10 |
+----+--------+----+
| 1 | 唐三藏 | 10 |
| 2 | 孙悟空 | 10 |
| ... | ... | .. |
+----+--------+----+
7 rows in set (0.00 sec)
# 也可以是常量的运算
mysql> SELECT id, name, 10 + 1 FROM exam;
+----+--------+--------+
| id | name | 10 + 1 |
+----+--------+--------+
| 1 | 唐三藏 | 11 |
| ... | ... | 11 |
+----+--------+--------+
7 rows in set (0.01 sec)
# 把所有学生的语文成绩加 10 分(表达式中包含一个字段)
mysql> SELECT id, name, chinese + 10 FROM exam;
+----+--------+--------------+
| id | name | chinese + 10 |
+----+--------+--------------+
| 1 | 唐三藏 | 77 |
| 2 | 孙悟空 | 97 |
| 3 | 猪悟能 | 98 |
| 4 | 曹孟德 | 92 |
| 5 | 刘玄德 | 65 |
| 6 | 孙权 | 80 |
| 7 | 宋公明 | 85 |
+----+--------+--------------+
7 rows in set (0.00 sec)
# 计算所有学生语文、数学和英语成绩的总分(表达式包含多个字段)
mysql> SELECT id, name, chinese + math + english FROM exam;
+----+--------+--------------------------+
| id | name | chinese + math + english |
+----+--------+--------------------------+
| 1 | 唐三藏 | 221 |
| 2 | 孙悟空 | 242 |
| 3 | 猪悟能 | 276 |
| 4 | 曹孟德 | 233 |
| 5 | 刘玄德 | 185 |
| 6 | 孙权 | 221 |
| 7 | 宋公明 | 170 |
+----+--------+--------------------------+
7 rows in set (0.00 sec)
4.3.4 为查询结果指定别名
sql
SELECT column [AS] alias_name [, ...] FROM table_name;
AS可以省略;- 别名如果包含空格,必须用单引号包裹。
sql
# 为总分这一列指定别名
mysql> SELECT id, name, chinese + math + english AS 总分 FROM exam;
+----+--------+------+
| id | name | 总分 | # 表头以别名显示
+----+--------+------+
| 1 | 唐三藏 | 221 |
| 2 | 孙悟空 | 242 |
| 3 | 猪悟能 | 276 |
| 4 | 曹孟德 | 233 |
| 5 | 刘玄德 | 185 |
| 6 | 孙权 | 221 |
| 7 | 宋公明 | 170 |
+----+--------+------+
7 rows in set (0.00 sec)
4.3.5 结果去重查询
sql
# 1. 查询所有数学成绩,观察到有两条 98
mysql> SELECT math FROM exam;
+------+
| math |
+------+
| 98 | # 第一条
| 78 |
| 98 | # 第二条
| 84 |
| 85 |
| 73 |
| 65 |
+------+
7 rows in set (0.00 sec)
# 2. 在结果集中去除重复记录,可以使用 DISTINCT
mysql> SELECT DISTINCT math FROM exam;
+------+
| math |
+------+
| 98 |
| 78 |
| 84 |
| 85 |
| 73 |
| 65 |
+------+
6 rows in set (0.00 sec)
使用
DISTINCT去重时,只有查询列表中所有列的值都相同 才会判定为重复。例如
SELECT DISTINCT name, math是把「姓名+数学」整体作为去重键,而非只按math。
⚠️ 重要注意:
- 查询时不加限制条件会返回表中所有结果,如果数据量过大,会把服务器资源消耗殆尽;
- 生产环境不要执行不带限制条件(尤其是全表
SELECT *)的查询。
4.4 Where 条件查询
4.4.1 语法
sql
SELECT
select_expr [, select_expr] ... [FROM table_references]
WHERE where_condition
4.4.2 比较运算符
| 运算符 | 说明 |
|---|---|
= |
等于。但对 NULL 的比较不可靠 ,NULL = NULL 结果为 NULL,需用下面的 <=> |
<=> |
安全等于。对 NULL 的比较是安全的 ,NULL <=> NULL 结果为 TRUE(1) |
>、>=、<、<= |
大于、大于等于、小于、小于等于 |
!=、<> |
不等于 |
value BETWEEN a0 AND a1 |
范围匹配,闭区间 [a0, a1];若 a0 <= value <= a1 返回 TRUE(1),NOT BETWEEN 取反 |
value IN (option, ...) |
value 在 option 列表中则返回 TRUE(1),NOT IN 取反 |
IS NULL |
是 NULL |
IS NOT NULL |
不是 NULL |
LIKE |
模糊匹配:% 表示任意多个(含 0 个)字符,_ 表示任意一个字符;NOT LIKE 取反 |
📌 讲义原文表格中「等于 / 安全等于」两行被识别错乱,已按 MySQL 8.0 语义恢复:
普通
=遇到 NULL 结果也是 NULL,因此判断 NULL 必须用IS NULL,或做安全比较用<=>。
4.4.3 逻辑运算符
| 运算符 | 说明 |
|---|---|
AND |
多个条件必须都为 TRUE(1),结果才是 TRUE(1) |
OR |
任意一个条件为 TRUE(1),结果即为 TRUE(1) |
NOT |
对条件取反:条件为 TRUE(1),结果为 FALSE(0) |
4.4.4 示例
4.4.4.1 关系运算查询
sql
# 查询英语成绩为 78 分的同学
mysql> SELECT name, english FROM exam WHERE english = 78;
# 查询语文成绩大于英语成绩的同学
mysql> SELECT name, chinese, english FROM exam WHERE chinese > english;
+--------+---------+---------+
| name | chinese | english |
+--------+---------+---------+
| 唐三藏 | 67 | 56 |
| 孙悟空 | 87 | 77 |
| 曹孟德 | 82 | 67 |
| 刘玄德 | 55 | 45 |
| 宋公明 | 75 | 30 |
+--------+---------+---------+
5 rows in set (0.00 sec)
# 总分在 200 分以下的同学
mysql> SELECT name, chinese + math + english AS 总分
FROM exam WHERE chinese + math + english < 200;
注意:
WHERE条件中可以使用表达式,但不能使用SELECT中定义的别名 (如上面的总分),因为
WHERE在SELECT之前执行,此时别名还不存在。
4.4.4.2 逻辑运算查询
sql
# 语文成绩大于 80 且英语成绩大于 80 的同学
mysql> SELECT * FROM exam WHERE chinese > 80 AND english > 80;
+----+--------+---------+------+---------+
| id | name | chinese | math | english |
+----+--------+---------+------+---------+
| 3 | 猪悟能 | 88 | 98 | 90 |
+----+--------+---------+------+---------+
1 row in set (0.00 sec)
# 语文成绩大于 80 或英语成绩大于 80 的同学
mysql> SELECT * FROM exam WHERE chinese > 80 OR english > 80;
+----+--------+---------+------+---------+
| id | name | chinese | math | english |
+----+--------+---------+------+---------+
| 2 | 孙悟空 | 87 | 78 | 77 |
| 3 | 猪悟能 | 88 | 98 | 90 |
| 4 | 曹孟德 | 82 | 84 | 67 |
+----+--------+---------+------+---------+
3 rows in set (0.00 sec)
# 观察 AND 和 OR 的优先级(下面两条结果不同)
mysql> SELECT * FROM exam WHERE chinese > 80 OR math > 70 AND english > 70;
mysql> SELECT * FROM exam WHERE (chinese > 80 OR math > 70) AND english > 70;
⚠️ AND 的优先级高于 OR ,同时使用时务必用圆括号
()包裹希望优先执行的部分,避免逻辑错误。
4.4.4.3 范围查询
sql
# 语文成绩在 [80, 90] 分的同学及语文成绩
# 方式一:使用 BETWEEN AND
mysql> SELECT name, chinese FROM exam WHERE chinese BETWEEN 80 AND 90;
# 方式二:使用 AND
mysql> SELECT name, chinese FROM exam WHERE chinese >= 80 AND chinese <= 90;
# 数学成绩为 78、79、98、99 之一的同学
# 方式一:使用 IN
mysql> SELECT name, math FROM exam WHERE math IN (78, 79, 98, 99);
# 方式二:使用 OR
mysql> SELECT name, math FROM exam WHERE math = 78 OR math = 79 OR math = 98 OR math = 99;
+--------+------+
| name | math |
+--------+------+
| 唐三藏 | 98 |
| 孙悟空 | 78 |
| 猪悟能 | 98 |
+--------+------+
3 rows in set (0.00 sec)
4.4.4.4 模糊查询
sql
# 查询所有姓孙的同学
mysql> SELECT * FROM exam WHERE name LIKE '孙%';
+----+--------+---------+------+---------+
| id | name | chinese | math | english |
+----+--------+---------+------+---------+
| 2 | 孙悟空 | 87 | 78 | 77 |
| 6 | 孙权 | 70 | 73 | 78 |
+----+--------+---------+------+---------+
2 rows in set (0.00 sec)
# 查询姓孙且姓名共有两个字的同学
mysql> SELECT * FROM exam WHERE name LIKE '孙_';
+----+------+---------+------+---------+
| id | name | chinese | math | english |
+----+------+---------+------+---------+
| 6 | 孙权 | 70 | 73 | 78 |
+----+------+---------+------+---------+
1 row in set (0.00 sec)
4.4.4.5 NULL 的查询
sql
# 构造数据:写入一条英语成绩为 NULL 的记录
INSERT INTO exam VALUES (8, '张飞', 27, 0, NULL);
# 查询英语成绩为 NULL 的记录(使用 IS NULL)
mysql> SELECT * FROM exam WHERE english IS NULL;
+----+------+---------+------+---------+
| id | name | chinese | math | english |
+----+------+---------+------+---------+
| 8 | 张飞 | 27 | 0 | NULL |
+----+------+---------+------+---------+
1 row in set (0.00 sec)
# 查询英语成绩不为 NULL 的记录(使用 IS NOT NULL)
mysql> SELECT * FROM exam WHERE english IS NOT NULL; -- 返回 7 条(不含张飞)
sql
# NULL 与其他值进行运算结果仍为 NULL
mysql> SELECT name, chinese + math + english AS 总分 FROM exam;
+--------+------+
| name | 总分 |
+--------+------+
| 唐三藏 | 221 |
| 孙悟空 | 242 |
| 猪悟能 | 276 |
| 曹孟德 | 233 |
| 刘玄德 | 185 |
| 孙权 | 221 |
| 宋公明 | 170 |
| 张飞 | NULL | # 含 NULL 的加法结果为 NULL
+--------+------+
8 rows in set (0.00 sec)
⚠️ NULL 相关注意事项:
WHERE中可使用表达式,但不能使用SELECT中定义的别名;AND优先级高于OR,建议用()明确优先级;- 过滤 NULL 时不要使用
=与!=(<>) ,必须用IS NULL/IS NOT NULL;- NULL 与任何值运算结果都为 NULL。
4.5 Order by 排序
4.5.1 语法
sql
-- ASC 为升序(从小到大)
-- DESC 为降序(从大到小)
-- 默认为 ASC
SELECT ... FROM table_name [WHERE ...]
ORDER BY {col_name | expr} [ASC | DESC], ... ;
4.5.2 示例
sql
# 按数学成绩从低到高排序(升序)
mysql> SELECT name, math FROM exam ORDER BY math ASC;
+--------+------+
| name | math |
+--------+------+
| 张飞 | 0 |
| 宋公明 | 65 |
| 孙权 | 73 |
| 孙悟空 | 78 |
| 曹孟德 | 84 |
| 刘玄德 | 85 |
| 唐三藏 | 98 |
| 猪悟能 | 98 |
+--------+------+
8 rows in set (0.00 sec)
# 按语文成绩从高到低排序(降序)
mysql> SELECT name, chinese FROM exam ORDER BY chinese DESC; -- 88 87 82 75 70 67 55 27
# 按英语成绩从高到低排序
mysql> SELECT name, english FROM exam ORDER BY english DESC;
+--------+---------+
| name | english |
+--------+---------+
| 猪悟能 | 90 |
| 孙权 | 78 |
| 孙悟空 | 77 |
| 曹孟德 | 67 |
| 唐三藏 | 56 |
| 刘玄德 | 45 |
| 宋公明 | 30 |
| 张飞 | NULL | # NULL 被看做比任何值都小
+--------+---------+
8 rows in set (0.00 sec)
# 依次按数学降序、英语升序、语文升序的多列排序
mysql> SELECT name, math, english, chinese FROM exam
ORDER BY math DESC, english ASC, chinese ASC;
# 查询同学及总分,由高到低排序
mysql> SELECT name, chinese + math + english FROM exam
ORDER BY chinese + math + english DESC;
# 可以使用列的别名进行排序
mysql> SELECT name, chinese + math + english AS 总分 FROM exam ORDER BY 总分 DESC;
# 英语成绩不为 NULL 的同学,按语文成绩从高到低排序
mysql> SELECT * FROM exam WHERE english IS NOT NULL ORDER BY chinese DESC;
⚠️ 排序注意事项:
- 查询中没有
ORDER BY时,返回顺序是未定义的,永远不要依赖它;ORDER BY子句中可以使用列的别名 (这一点与WHERE相反);- NULL 排序时视为比任何值都小:升序时出现在最上面,降序时出现在最下面。
4.6 分页查询
4.6.1 语法
sql
-- 起始下标为 0
-- 从 0 开始,筛选 num 条结果
SELECT ... FROM table_name [WHERE ...] [ORDER BY ...] LIMIT num;
-- 从 start 开始,筛选 num 条结果
SELECT ... FROM table_name [WHERE ...] [ORDER BY ...] LIMIT start, num;
-- 从 start 开始,筛选 num 条结果(比上一种写法语义更明确,推荐使用)
SELECT ... FROM table_name [WHERE ...] [ORDER BY ...] LIMIT num OFFSET start;
💡 分页公式 :若每页
pageSize条、当前第pageNo页(从 1 开始),则
LIMIT (pageNo-1)*pageSize, pageSize。LIMIT中第一个参数是「偏移量 offset」,第二个才是「条数」。
4.6.2 示例
sql
# 查询第一页数据
mysql> SELECT * FROM exam ORDER BY id ASC LIMIT 0, 3;
+----+--------+---------+------+---------+
| id | name | chinese | math | english |
+----+--------+---------+------+---------+
| 1 | 唐三藏 | 67 | 98 | 56 |
| 2 | 孙悟空 | 87 | 78 | 77 |
| 3 | 猪悟能 | 88 | 98 | 90 |
+----+--------+---------+------+---------+
3 rows in set (0.00 sec)
# 查询第二页数据
mysql> SELECT * FROM exam ORDER BY id ASC LIMIT 3, 3;
+----+--------+---------+------+---------+
| id | name | chinese | math | english |
+----+--------+---------+------+---------+
| 4 | 曹孟德 | 82 | 84 | 67 |
| 5 | 刘玄德 | 55 | 85 | 45 |
| 6 | 孙权 | 70 | 73 | 78 |
+----+--------+---------+------+---------+
3 rows in set (0.00 sec)
# 查询第三页数据:未达到 limit 条数也无影响,有多少显示多少
mysql> SELECT * FROM exam ORDER BY id ASC LIMIT 6, 3;
+----+--------+---------+------+---------+
| id | name | chinese | math | english |
+----+--------+---------+------+---------+
| 7 | 宋公明 | 75 | 65 | 30 |
| 8 | 张飞 | 27 | 0 | NULL |
+----+--------+---------+------+---------+
2 rows in set (0.00 sec)
5. Update 修改
5.1 语法
sql
UPDATE [LOW_PRIORITY] [IGNORE] table_reference
SET assignment [, assignment] ...
[WHERE where_condition]
[ORDER BY ...]
[LIMIT row_count]
- 对符合条件的结果进行列值更新;
SET后是「列 = 值」的赋值列表,多个赋值用逗号分隔;- 可选
WHERE限定范围,ORDER BY+LIMIT可只更新排序后的前若干行。
5.2 示例
sql
# 将孙悟空同学的数学成绩变更为 80 分
mysql> SELECT * FROM exam WHERE name = '孙悟空';
-- 原始:id=2, name=孙悟空, chinese=87, math=78, english=77
mysql> UPDATE exam SET math = 80 WHERE name = '孙悟空';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT * FROM exam WHERE name = '孙悟空'; -- math 已变为 80
sql
# 将曹孟德同学的数学成绩变更为 60 分、语文成绩变更为 70 分(一次更新多列)
mysql> UPDATE exam SET math = 60, chinese = 70 WHERE name = '曹孟德';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
sql
# 将总成绩倒数前三的 3 位同学的数学成绩加上 30 分
# 查看原始数据(总分升序取前三)
mysql> SELECT name, math, chinese + math + english AS 总分
FROM exam WHERE chinese + math + english IS NOT NULL
ORDER BY 总分 ASC LIMIT 3;
+--------+------+------+
| name | math | 总分 |
+--------+------+------+
| 宋公明 | 65 | 170 |
| 刘玄德 | 85 | 185 |
| 曹孟德 | 60 | 197 |
+--------+------+------+
# 更新操作:以原值为基础 +30
mysql> UPDATE exam SET math = math + 30
WHERE chinese + math + english IS NOT NULL
ORDER BY chinese + math + english ASC LIMIT 3;
Query OK, 3 rows affected (0.01 sec)
# 修改后总成绩倒数前三
mysql> SELECT name, math, chinese + math + english AS 总分
FROM exam WHERE chinese + math + english IS NOT NULL
ORDER BY 总分 ASC LIMIT 3;
+--------+------+------+
| name | math | 总分 |
+--------+------+------+
| 宋公明 | 95 | 200 |
| 刘玄德 | 115 | 215 |
| 唐三藏 | 98 | 221 |
+--------+------+------+
sql
# 将所有同学的语文成绩更新为原来的 2 倍(不带 WHERE,作用于全表)
mysql> UPDATE exam SET chinese = chinese * 2;
Query OK, 8 rows affected (0.01 sec)
Rows matched: 8 Changed: 8 Warnings: 0
5.3 Update 注意事项
- 以原值为基础做变更时,不能使用
math += 30这样的语法 ,MySQL 没有复合赋值运算符,必须写成math = math + 30; - 不加
WHERE条件会导致全表数据被更新 ,务必谨慎操作(生产环境建议先SELECT验证范围,或开启安全更新模式)。
6. Delete 删除
6.1 语法
sql
DELETE FROM tbl_name [WHERE where_condition] [ORDER BY ...] [LIMIT row_count]
6.2 示例
sql
# 删除孙悟空同学的考试成绩
mysql> SELECT * FROM exam WHERE name = '孙悟空';
-- 存在一条记录
mysql> DELETE FROM exam WHERE name = '孙悟空';
Query OK, 1 row affected (0.01 sec)
mysql> SELECT * FROM exam WHERE name = '孙悟空';
Empty set (0.00 sec)
sql
# 删除整张表的数据
# 准备测试表
mysql> CREATE TABLE t_delete (
id INT,
name VARCHAR(20)
);
# 插入测试数据
mysql> INSERT INTO t_delete (id, name) VALUES (1, 'A'), (2, 'B'), (3, 'C');
Query OK, 3 rows affected (0.00 sec)
mysql> SELECT * FROM t_delete;
+------+------+
| id | name |
+------+------+
| 1 | A |
| 2 | B |
| 3 | C |
+------+------+
3 rows in set (0.00 sec)
# 删除整张表中的数据(不带 WHERE)
mysql> DELETE FROM t_delete;
Query OK, 3 rows affected (0.00 sec)
mysql> SELECT * FROM t_delete;
Empty set (0.00 sec)
6.3 Delete 注意事项
- 执行
DELETE时不加条件会删除整张表的数据,谨慎操作; DELETE是逐行删除、可回滚(DML),删除后AUTO_INCREMENT计数器不会被重置。
7. 截断表 TRUNCATE
7.1 语法
sql
TRUNCATE [TABLE] tbl_name
7.2 示例
sql
# 准备测试表
mysql> CREATE TABLE t_truncate (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20)
);
# 插入测试数据
mysql> INSERT INTO t_truncate (name) VALUES ('A'), ('B'), ('C');
mysql> SELECT * FROM t_truncate;
+----+------+
| id | name |
+----+------+
| 1 | A |
| 2 | B |
| 3 | C |
+----+------+
3 rows in set (0.00 sec)
# 查看建表结构,AUTO_INCREMENT=4
mysql> SHOW CREATE TABLE t_truncate\G
-- ... ENGINE=InnoDB AUTO_INCREMENT=4 ...
# 截断表(注意受影响的行数是 0)
mysql> TRUNCATE TABLE t_truncate;
Query OK, 0 rows affected (0.01 sec)
mysql> SELECT * FROM t_truncate;
Empty set (0.00 sec)
# 查看表结构,AUTO_INCREMENT 已被重置
mysql> SHOW CREATE TABLE t_truncate\G
-- ... 不再显示 AUTO_INCREMENT ...
# 继续写入数据,自增主键从 1 开始重新计数
mysql> INSERT INTO t_truncate (name) VALUES ('D');
mysql> SELECT * FROM t_truncate;
+----+------+
| id | name |
+----+------+
| 1 | D | # id 重新从 1 开始
+----+------+
1 row in set (0.00 sec)
7.3 Truncate 注意事项
- 只能对整表操作 ,不能像
DELETE一样针对部分数据; - 不对逐行数据做操作,因此比
DELETE更快 ;TRUNCATE删除数据时不经过真正的(行级)事务,无法回滚; - 会重置
AUTO_INCREMENT项 (这一点是与DELETE的关键区别)。
📊 DELETE vs TRUNCATE 对比:
维度 DELETETRUNCATE操作类型 DML(数据操作) DDL(结构操作) 范围 可带 WHERE删部分只能清空整表 速度 逐行删除,较慢 直接重置,很快 事务回滚 可回滚 不可回滚 自增主键 保留计数 重置为初值
8. 插入查询结果
8.1 语法
sql
INSERT INTO table_name [(column [, column ...])] SELECT ...
把一个 SELECT 查询出来的结果集,直接作为数据插入到另一张表中。
8.2 示例:删除表中的重复记录(重复数据只保留一份)
思路说明 :原始表中的数据一般不会主动删除,但真正查询时又不需要重复数据。若每次都靠
DISTINCT 去重,会严重影响效率。更优做法是:创建一张与原表结构相同的新表,把去重后的记录写入
新表,以后都查新表------真实数据不丢失,同时保证查询效率。
sql
# 创建测试表,并构造数据
mysql> CREATE TABLE t_recored (id INT, name VARCHAR(20));
INSERT INTO t_recored VALUES
(100, 'aaa'),
(100, 'aaa'),
(200, 'bbb'),
(200, 'bbb'),
(200, 'bbb'),
(300, 'ccc');
mysql> SELECT * FROM t_recored;
+------+
| id | name |
+------+------+
| 100 | aaa |
| 100 | aaa |
| 200 | bbb |
| 200 | bbb |
| 200 | bbb |
| 300 | ccc |
+------+
6 rows in set (0.00 sec)
# 创建一张新表,表结构与 t_recored 相同
mysql> CREATE TABLE t_recored_new LIKE t_recored;
mysql> SELECT * FROM t_recored_new; -- 新表为空
Empty set (0.00 sec)
# 原表中的记录去重后写入到新表
mysql> INSERT INTO t_recored_new SELECT DISTINCT * FROM t_recored;
Query OK, 3 rows affected (0.00 sec)
mysql> SELECT * FROM t_recored_new; -- 去重成功,只剩 3 行
+------+
| id | name |
+------+------+
| 100 | aaa |
| 200 | bbb |
| 300 | ccc |
+------+
3 rows in set (0.00 sec)
# 新表与原表重命名(原子完成,业务无感知)
mysql> RENAME TABLE t_recored TO t_recored_old, t_recored_new TO t_recored;
# 重命名后,t_recored 是去重后的数据,原始数据仍保留在 t_recored_old 中
mysql> SELECT * FROM t_recored; -- 3 行(去重)
mysql> SELECT * FROM t_recored_old; -- 6 行(原始,未受影响)
💡 这里把
INSERT ... SELECT与CREATE TABLE ... LIKE、RENAME TABLE组合,是一套非常经典的「大表去重但不丢数据」的工程套路。
9. 聚合函数
9.1 常用函数
| 函数 | 说明 |
|---|---|
COUNT([DISTINCT] expr) |
返回查询到的数据的数量 |
SUM([DISTINCT] expr) |
返回查询到的数据的总和,非数字没有意义 |
AVG([DISTINCT] expr) |
返回查询到的数据的平均值,非数字没有意义 |
MAX([DISTINCT] expr) |
返回查询到的数据的最大值,非数字没有意义 |
MIN([DISTINCT] expr) |
返回查询到的数据的最小值,非数字没有意义 |
🔑 核心规律 :除
COUNT(*)外,聚合函数都会忽略 NULL 值 ;非数值列做SUM/AVG没有意义。
9.2 示例
9.2.1 COUNT
sql
# 统计 exam 表中有多少记录
mysql> SELECT COUNT(*) FROM exam; -- 用 * 统计:行数
+----------+
| count(*) |
+----------+
| 7 |
+----------+
mysql> SELECT COUNT(1) FROM exam; -- 用常量统计,结果同上
+----------+
| count(1) |
+----------+
| 7 |
+----------+
# 统计有多少学生参加数学考试(用指定列统计)
mysql> SELECT COUNT(math) FROM exam; -- 7
# 统计有多少学生参加英语考试
mysql> SELECT COUNT(english) FROM exam;
+----------------+
| count(english) |
+----------------+
| 6 | # 张飞英语为 NULL,不计入
+----------------+
# 结论:COUNT(列) 会忽略该列为 NULL 的行,COUNT(*) 不会。
# 统计语文成绩小于 50 分的学生个数(加入 WHERE 条件)
mysql> SELECT COUNT(chinese) FROM exam WHERE chinese < 50;
📌 讲义原文函数名写作 "COUTN",为笔误,正确为 COUNT,已修正。
9.2.2 SUM
sql
# 统计所有学生数学成绩总分
mysql> SELECT SUM(math) FROM exam; -- 569
# 统计所有学生英语成绩总分(值为 NULL 的列不参与统计)
mysql> SELECT SUM(english) FROM exam;
+--------------+
| sum(english) |
+--------------+
| 366 |
+--------------+
# 不能统计非数值的列
mysql> SELECT SUM(name) FROM exam;
+-----------+
| sum(name) |
+-----------+
| 0 |
+-----------+
1 row in set, 7 warnings (0.01 sec) # 出现警告,可用 SHOW WARNINGS 查看
9.2.3 AVG
sql
# 统计英语成绩的平均分(NULL 值不参与统计,分母是 6 而非 7)
mysql> SELECT AVG(english) FROM exam; -- 61
# 统计平均总分
mysql> SELECT AVG(chinese + math + english) AS 总分 FROM exam; -- 297.5
9.2.4 MAX
sql
# 查询英语最高分
mysql> SELECT MAX(english) FROM exam; -- 90
9.2.5 MIN
sql
# 查询 70 分以上的数学最低分
mysql> SELECT MIN(math) FROM exam WHERE math > 70; -- 73
# 一行内使用多个聚合函数:数学最高分与英语最低分
mysql> SELECT MAX(math), MIN(english) FROM exam;
+-----------+--------------+
| max(math) | min(english) |
+-----------+--------------+
| 115 | 30 |
+-----------+--------------+
10. Group by 分组查询
GROUP BY 子句的作用是通过一定的规则将一个数据集划分成若干个小的分组,然后针对各分组进行数据处理,
比如使用聚合函数对分组进行统计。
10.1 语法
sql
SELECT {col_name | expr} [, ...], aggregate_function(aggregate_expr)
FROM table_references
GROUP BY {col_name | expr}, ...
[HAVING where_condition]
col_name | expr:要查询的列或表达式,可以有多个,必须在GROUP BY子句中作为分组的依据;aggregate_function:聚合函数,比如COUNT()、SUM()、AVG()、MAX()、MIN();aggregate_expr:聚合函数传入的列或表达式,如果它不在GROUP BY子句中,就必须包含在聚合函数中。
📌 讲义原文写作 "GOURP BY",为笔误,正确为 GROUP BY ,已修正。
这条规则也是 MySQL 开启
ONLY_FULL_GROUP_BY(8.0 默认开启)后的硬性要求:
SELECT里出现的非聚合列,必须同时出现在GROUP BY中,否则报错。
10.2 示例
准备测试表:职员表 emp(列:id 编号、name 姓名、role 角色、salary 薪水)
sql
DROP TABLE IF EXISTS emp;
CREATE TABLE emp (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20) NOT NULL,
role VARCHAR(20) NOT NULL,
salary DECIMAL(10, 2) NOT NULL
);
INSERT INTO emp VALUES (1, '马云', '老板', 1500000.00);
INSERT INTO emp VALUES (2, '马化腾', '老板', 1800000.00);
INSERT INTO emp VALUES (3, '鑫哥', '讲师', 10000.00);
INSERT INTO emp VALUES (4, '博哥', '讲师', 12000.00);
INSERT INTO emp VALUES (5, '平姐', '学管', 9000.00);
INSERT INTO emp VALUES (6, '莹姐', '学管', 8000.00);
INSERT INTO emp VALUES (7, '孙悟空', '游戏角色', 956.8);
INSERT INTO emp VALUES (8, '猪悟能', '游戏角色', 700.5);
INSERT INTO emp VALUES (9, '沙和尚', '游戏角色', 333.3);
sql
# 统计每个角色的人数
mysql> SELECT role, COUNT(*) FROM emp GROUP BY role;
+----------+----------+
| role | count(*) |
+----------+----------+
| 老板 | 2 |
| 讲师 | 2 |
| 学管 | 2 |
| 游戏角色 | 3 |
+----------+----------+
4 rows in set (0.00 sec)
# 统计每个角色的平均工资、最高工资、最低工资(配合 ROUND 保留 2 位)
mysql> SELECT role,
ROUND(AVG(salary), 2) AS 平均工资,
ROUND(MAX(salary), 2) AS 最高工资,
ROUND(MIN(salary), 2) AS 最低工资
FROM emp GROUP BY role;
+----------+------------+------------+------------+
| role | 平均工资 | 最高工资 | 最低工资 |
+----------+------------+------------+------------+
| 老板 | 1650000.00 | 1800000.00 | 1500000.00 |
| 讲师 | 11000.00 | 12000.00 | 10000.00 |
| 学管 | 8500.00 | 9000.00 | 8000.00 |
| 游戏角色 | 663.53 | 956.80 | 333.30 |
+----------+------------+------------+------------+
4 rows in set (0.00 sec)
10.3 HAVING 子句
使用 GROUP BY 分组后,对分组的结果进行过滤时不能使用 WHERE,而要使用 HAVING。
sql
# 显示平均工资低于 1500 的角色和它的平均工资
mysql> SELECT role, AVG(salary) FROM emp GROUP BY role HAVING AVG(salary) < 1500;
+----------+-------------+
| role | avg(salary) |
+----------+-------------+
| 游戏角色 | 663.533333 |
+----------+-------------+
1 row in set (0.00 sec)
10.4 Having 与 Where 的区别
| 子句 | 过滤对象 | 执行时机 |
|---|---|---|
WHERE |
对表中真实数据的条件过滤 | 分组(GROUP BY)之前 |
HAVING |
对分组后的结果进行条件过滤 | 分组(GROUP BY)之后 |
💡 经验法则:过滤原始行用
WHERE,过滤聚合结果(含聚合函数)用HAVING。若既要过滤原始行又要过滤聚合结果,则两者可同时使用,
WHERE在前、HAVING在后。
11. 内置函数
MySQL 提供了丰富的内置函数,合理使用可大幅减少业务代码量。以下按类别整理,并附典型示例。
11.1 日期函数
| 函数 | 说明 |
|---|---|
CURDATE() |
返回当前日期,同义词 CURRENT_DATE、CURRENT_DATE() |
CURTIME() |
返回当前时间,同义词 CURRENT_TIME、CURRENT_TIME([fsp]) |
NOW() |
返回当前日期和时间,同义词 CURRENT_TIMESTAMP、CURRENT_TIMESTAMP() |
DATE(date) |
提取 date 或 datetime 表达式的日期部分 |
ADDDATE(date, INTERVAL expr unit) |
向日期值添加时间间隔,同义词 DATE_ADD() |
SUBDATE(date, INTERVAL expr unit) |
向日期值减去时间间隔,同义词 DATE_SUB() |
DATEDIFF(expr1, expr2) |
两个日期的差(以天为单位),结果 = expr1 − expr2 |
sql
mysql> SELECT CURDATE(); -- 2024-08-27(当前日期)
mysql> SELECT CURTIME(); -- 10:26:33(当前时间)
mysql> SELECT NOW(); -- 2024-08-27 10:26:47(当前日期+时间)
# 提取指定 datetime 的日期部分
mysql> SELECT DATE('2024-08-27 10:26:47'); -- 2024-08-27
# 在给定日期基础上加 31 天
mysql> SELECT ADDDATE('2024-01-02', INTERVAL 31 DAY); -- 2024-02-02
# 在给定日期基础上减去 1 个月
mysql> SELECT SUBDATE('2024-08-02', INTERVAL 1 MONTH); -- 2024-07-02
# 计算两个日期相差多少天(只使用日期部分)
mysql> SELECT DATEDIFF('2024-12-31 23:59:59', '2024-12-30'); -- 1
# 表达式1 早于表达式2 时返回负数
mysql> SELECT DATEDIFF('2024-11-30 23:59:59', '2024-12-31'); -- -31
11.2 字符串处理函数
| 函数 | 说明 |
|---|---|
CHAR_LENGTH(str) |
返回字符串的字符长度 ,同义词 CHARACTER_LENGTH() |
LENGTH(str) |
返回字符串的字节数,与当前字符编码集有关 |
CONCAT(str1, str2, ...) |
返回拼接后的字符串 |
CONCAT_WS(sep, str1, str2, ...) |
返回拼接后带分隔符的字符串 |
LCASE(str) |
转小写,同义词 LOWER() |
UCASE(str) |
转大写,同义词 UPPER() |
HEX(str) / HEX(N) |
返回字符串或数字的十六进制表示 |
INSTR(str, substr) |
返回 substr 第一次出现的索引 |
INSERT(str, pos, len, newstr) |
在指定位置插入/替换子字符串 |
SUBSTR(str, pos) / SUBSTR(str FROM pos FOR len) |
返回指定子串,同义词 SUBSTRING() |
REPLACE(str, from, to) |
把所有 from 替换为 to(区分大小写) |
STRCMP(expr1, expr2) |
逐字符比较两个字符串,返回 −1 / 0 / 1 |
LEFT(str,len) / RIGHT(str,len) |
返回最左 / 最右 len 个字符 |
LTRIM(str) / RTRIM(str) / TRIM(str) |
删除前导 / 末尾 / 前后空格 |
| `TRIM([{LEADING | TRAILING |
sql
# 姓名字符数 vs 字节长度(utf8 下中文占多字节)
mysql> SELECT name, CHAR_LENGTH(name), LENGTH(name) FROM exam;
+-----------+-------------------+--------------+
| name | char_length(name) | length(name) |
+-----------+-------------------+--------------+
| 唐三藏 | 3 | 6 | # 3 个汉字,6 个字节(此处每汉字按 2 字节计)
| 孙权 | 2 | 4 |
+-----------+-------------------+--------------+
# CONCAT 拼接成一句完整的话
mysql> SELECT CONCAT(name, '的语文成绩:', chinese, '分,数学成绩:', math,
'分,英语成绩:', english, '分') AS 分数
FROM exam;
-- 张飞英语为 NULL,整句 CONCAT 结果为 NULL(再次体现"含 NULL 即 NULL")
# CONCAT_WS 用逗号作分隔符(注意:NULL 会被自动跳过)
mysql> SELECT CONCAT_WS(',', name, chinese, math, english) AS 分数 FROM exam;
+----------------------+
| 分数 |
+----------------------+
| 唐三藏,134,98,56 |
| 张飞,54,0 | # 张飞的 NULL 被跳过,只剩 3 段
+----------------------+
mysql> SELECT LCASE('ABC'); -- abc
mysql> SELECT UCASE('abc'); -- ABC
# 转十六进制
mysql> SELECT HEX('Hello MySQL'); -- 48656C6C6F204D7953514C
mysql> SELECT HEX(15); -- F
# 子串首次出现的索引
mysql> SELECT INSTR('Hello MySQL', 'sql'); -- 9
# 指定位置插入子字符串
mysql> SELECT INSERT('Hello, Database', 8, 0, 'MySQL '); -- Hello, MySQL Database(插入)
mysql> SELECT INSERT('Hello, Database', 8, 20, 'MySQL'); -- Hello, MySQL(覆盖删除)
# 截取子串
mysql> SELECT SUBSTR('Hello, MySQL', 8); -- MySQL
mysql> SELECT SUBSTR('Hello, MySQL' FROM 8 FOR 1); -- M
mysql> SELECT SUBSTR('Hello, MySQL' FROM 8 FOR 10); -- MySQL(不足则取到结尾)
# 替换(区分大小写)
mysql> SELECT REPLACE('Hello Database', 'Database', 'MySQL'); -- Hello MySQL
# 比较字符串,返回 -1 / 0 / 1
mysql> SELECT STRCMP('text', 'text'); -- 0
mysql> SELECT STRCMP('text', 'text1'); -- -1
mysql> SELECT STRCMP('text1', 'text'); -- 1
# 取最左 / 最右字符
mysql> SELECT LEFT('Hello MySQL', 5); -- Hello
mysql> SELECT RIGHT('Hello MySQL', 5); -- MySQL
# 去空格
mysql> SELECT LTRIM(' ABC'), RTRIM('ABC '), TRIM(' ABC ');
# 去除指定的前导/末尾字符串(BOTH 可省略)
mysql> SELECT TRIM(BOTH 'xxx' FROM 'xxxABCxxx'); -- ABC
mysql> SELECT TRIM(LEADING 'xxx' FROM 'xxxABCxxx'); -- ABCxxx
mysql> SELECT TRIM(TRAILING 'xxx' FROM 'xxxABCxxx'); -- xxxABC
11.3 数学函数
| 函数 | 说明 |
|---|---|
ABS(X) |
返回 X 的绝对值 |
CEIL(X) |
返回不小于 X 的最小整数值(向上取整),同义词 CEILING(X) |
FLOOR(X) |
返回不大于 X 的最大整数值(向下取整) |
CONV(N, from_base, to_base) |
不同进制之间的转换 |
FORMAT(X, D) |
将 X 格式化为 "#,###,###" 并四舍五入到小数点后 D 位,以字符串返回 |
RAND([N]) |
返回 [0.0, 1.0) 范围内的随机浮点值 |
ROUND(X) / ROUND(X, D) |
将 X 四舍五入到小数点后 D 位 |
CRC32(expr) |
计算循环冗余校验值,返回 32 位无符号整数 |
sql
mysql> SELECT ABS(-3.14); -- 3.14
mysql> SELECT CEIL(20.36); -- 21(向上取整)
mysql> SELECT FLOOR(11.32); -- 11(向下取整)
mysql> SELECT CONV(15, 10, 16); -- F(十进制转十六进制)
mysql> SELECT FORMAT(1234567.654321, 5); -- 1,234,567.65432
mysql> SELECT RAND(); -- 0.9555353624332095(随机)
mysql> SELECT ROUND(RAND(), 6); -- 例如 0.612703
mysql> SELECT ROUND(RAND(), 6) * 1000000; -- 生成 6 位随机数,如 982956
mysql> SELECT CRC32('Hello MySQL'); -- 1944582821
11.4 其他常用函数
| 函数 | 说明 |
|---|---|
VERSION() |
显示当前数据库版本 |
DATABASE() |
显示当前正在使用的数据库 |
USER() |
显示当前用户 |
MD5(str) |
对字符串做 MD5 摘要,得到 32 位字符串 |
IFNULL(val1, val2) |
若 val1 为 NULL 返回 val2,否则返回 val1 |
sql
mysql> SELECT VERSION(); -- 8.0.39
# 未选择数据库时
mysql> SELECT DATABASE(); -- NULL
mysql> USE java01; -- Database changed
mysql> SELECT DATABASE(); -- java01
mysql> SELECT USER(); -- root@localhost
# 对字符串做 MD5 摘要
mysql> SELECT MD5('hello world'); -- 5eb63bbbe01eeed093cb22bb8f5acdc3
# IFNULL:第一个参数非 NULL 返回第一个,否则返回第二个
mysql> SELECT IFNULL('database', 'MySQL'); -- database
mysql> SELECT IFNULL(NULL, 'MySQL'); -- MySQL
💡
IFNULL在处理「含 NULL 的聚合/运算」时非常实用。例如统计总分时若某科为 NULL,可写成
chinese + IFNULL(math, 0) + IFNULL(english, 0),避免整个结果被 NULL "传染"。
12. 全文总结
至此,MySQL 的 CRUD 四大操作 + 聚合 + 分组 + 内置函数 已完整覆盖。一张表回顾核心要点:
| 操作 / 能力 | 关键语句 | 必须记住的坑 |
|---|---|---|
| 增 | INSERT ... VALUES |
全列插入需列数顺序一致;多行用多个 () |
| 查 | SELECT ... FROM ... WHERE ... |
判 NULL 用 IS NULL;WHERE 不能用别名 |
| 改 | UPDATE ... SET ... WHERE |
无复合赋值,用 x = x + 1;不带 WHERE 会全表更新 |
| 删 | DELETE ... WHERE |
不带条件删全表;可回滚 |
| 清空 | TRUNCATE TABLE |
DDL、更快、不可回滚、重置自增 |
| 排序 | ORDER BY |
无排序顺序未定义;NULL 视为最小;可用别名 |
| 分页 | LIMIT offset, count |
第一个参数是偏移量;页码换算公式 |
| 聚合 | COUNT/SUM/AVG/MAX/MIN |
除 COUNT(*) 外均忽略 NULL;非数值列 SUM 无意义 |
| 分组 | GROUP BY ... HAVING |
非聚合列必须在 GROUP BY 中;分组过滤用 HAVING |
| 内置函数 | 日期 / 字符串 / 数学 / 其他 | CHAR_LENGTH 数字符、LENGTH 数字节 |
最后提醒(来自讲义的三个高频"易错点"):
- NULL :与任何值运算都为 NULL;过滤只能用
IS NULL / IS NOT NULL,不能用=/!=。- 优先级 :
AND高于OR,混用时用()明确逻辑。- 全表操作 :不带
WHERE的UPDATE / DELETE极其危险,生产环境务必先验证范围。
把本讲义里的每张表、每条数据按顺序跑一遍,CRUD 就能真正上手。