MySQL 从零入门(四):表的增删改查与基础查询详解

MySQL 从零入门(四):表的增删改查与基础查询详解------INSERT、SELECT、UPDATE、DELETE、GROUP BY(附 SQL 实战)


🔥 星恒随风: 个人主页 ❄️ 个人专栏: 《Linux网络编程从0到1》 | 《C语言基础》 | 《数据结构和算法》 | 《MySQL入门心法》 | 《前端基础》 | 《python基础》 | 《C++从入门到入土》 | 《Linux系统编程》 ✨ 数据即知识,压缩即智能


文章目录

  • [MySQL 从零入门(四):表的增删改查与基础查询详解------INSERT、SELECT、UPDATE、DELETE、GROUP BY(附 SQL 实战)](#MySQL 从零入门(四):表的增删改查与基础查询详解——INSERT、SELECT、UPDATE、DELETE、GROUP BY(附 SQL 实战))
  • [一、CRUD 的基本概念](#一、CRUD 的基本概念)
    • [1.1 什么是 CRUD?](#1.1 什么是 CRUD?)
    • [1.2 准备实验数据库](#1.2 准备实验数据库)
  • 二、INSERT:向表中插入数据
    • [2.1 INSERT 基本语法](#2.1 INSERT 基本语法)
    • [2.2 创建实验表](#2.2 创建实验表)
    • [2.3 单行数据:全列插入](#2.3 单行数据:全列插入)
    • [2.4 指定字段插入](#2.4 指定字段插入)
    • [2.5 一次插入多条数据](#2.5 一次插入多条数据)
    • [2.6 主键或唯一键冲突](#2.6 主键或唯一键冲突)
  • [三、INSERT ON DUPLICATE KEY UPDATE:插入否则更新](#三、INSERT ON DUPLICATE KEY UPDATE:插入否则更新)
    • [3.1 为什么需要插入否则更新?](#3.1 为什么需要插入否则更新?)
    • [3.2 基本语法](#3.2 基本语法)
    • [3.3 影响行数的含义](#3.3 影响行数的含义)
  • 四、REPLACE:替换记录
    • [4.1 REPLACE 的作用](#4.1 REPLACE 的作用)
    • [4.2 REPLACE 与 ON DUPLICATE KEY UPDATE 的区别](#4.2 REPLACE 与 ON DUPLICATE KEY UPDATE 的区别)
  • 五、SELECT:查询表中的数据
    • [5.1 SELECT 基本语法](#5.1 SELECT 基本语法)
    • [5.2 准备考试成绩表](#5.2 准备考试成绩表)
  • [六、SELECT 查询指定列](#六、SELECT 查询指定列)
    • [6.1 全列查询](#6.1 全列查询)
    • [6.2 指定列查询](#6.2 指定列查询)
    • [6.3 查询表达式](#6.3 查询表达式)
    • [6.4 使用多个字段组成表达式](#6.4 使用多个字段组成表达式)
  • 七、AS:给查询结果设置别名
    • [7.1 基本语法](#7.1 基本语法)
    • [7.2 AS 可以省略吗?](#7.2 AS 可以省略吗?)
  • 八、DISTINCT:查询结果去重
    • [8.1 为什么需要去重?](#8.1 为什么需要去重?)
    • [8.2 DISTINCT 基本语法](#8.2 DISTINCT 基本语法)
    • [8.3 多列 DISTINCT](#8.3 多列 DISTINCT)
  • 九、WHERE:条件查询(重点)
    • [9.1 WHERE 基本语法](#9.1 WHERE 基本语法)
    • [9.2 常见比较运算符](#9.2 常见比较运算符)
  • [十、WHERE 基础查询案例](#十、WHERE 基础查询案例)
    • [10.1 查询英语不及格的学生](#10.1 查询英语不及格的学生)
    • [10.2 查询语文成绩在 80~90 分之间的学生](#10.2 查询语文成绩在 80~90 分之间的学生)
    • [10.3 查询数学成绩为指定值的学生](#10.3 查询数学成绩为指定值的学生)
  • 十一、LIKE:模糊匹配
    • [11.1 LIKE 的两个通配符](#11.1 LIKE 的两个通配符)
    • [11.2 查询姓孙的学生](#11.2 查询姓孙的学生)
    • [11.3 查询姓孙且姓名为两个字的学生](#11.3 查询姓孙且姓名为两个字的学生)
    • [11.4 LIKE 的其他示例](#11.4 LIKE 的其他示例)
  • [十二、WHERE 中使用表达式](#十二、WHERE 中使用表达式)
    • [12.1 比较两个字段](#12.1 比较两个字段)
    • [12.2 查询总分低于 200 的学生](#12.2 查询总分低于 200 的学生)
  • 十三、AND、OR、NOT:逻辑运算符
    • [13.1 基本含义](#13.1 基本含义)
    • [13.2 查询语文成绩大于 80 且不姓孙的学生](#13.2 查询语文成绩大于 80 且不姓孙的学生)
    • [13.3 综合条件查询](#13.3 综合条件查询)
  • [十四、NULL 的查询与比较](#十四、NULL 的查询与比较)
    • [14.1 为什么不能使用 = NULL?](#14.1 为什么不能使用 = NULL?)
    • [14.2 IS NULL](#14.2 IS NULL)
    • [14.3 IS NOT NULL](#14.3 IS NOT NULL)
    • [14.4 NULL 安全比较运算符](#14.4 NULL 安全比较运算符)
  • [十五、ORDER BY:查询结果排序](#十五、ORDER BY:查询结果排序)
    • [15.1 ORDER BY 基本语法](#15.1 ORDER BY 基本语法)
    • [15.2 按数学成绩升序排序](#15.2 按数学成绩升序排序)
    • [15.3 按数学成绩降序排序](#15.3 按数学成绩降序排序)
    • [15.4 多字段排序](#15.4 多字段排序)
    • [15.5 按表达式排序](#15.5 按表达式排序)
    • [15.6 WHERE 与 ORDER BY 结合](#15.6 WHERE 与 ORDER BY 结合)
  • 十六、LIMIT:限制结果与分页查询
    • [16.1 为什么需要 LIMIT?](#16.1 为什么需要 LIMIT?)
    • [16.2 LIMIT 基本语法](#16.2 LIMIT 基本语法)
    • [16.3 LIMIT 与 OFFSET](#16.3 LIMIT 与 OFFSET)
    • [16.4 分页案例](#16.4 分页案例)
    • [16.5 分页公式](#16.5 分页公式)
    • [16.6 LIMIT 的另一种写法](#16.6 LIMIT 的另一种写法)
  • 十七、UPDATE:修改表中的数据
    • [17.1 UPDATE 基本语法](#17.1 UPDATE 基本语法)
    • [17.2 修改单个字段](#17.2 修改单个字段)
    • [17.3 同时修改多个字段](#17.3 同时修改多个字段)
    • [17.4 在原有数值基础上修改](#17.4 在原有数值基础上修改)
    • [17.5 更新总分最低的三名学生](#17.5 更新总分最低的三名学生)
    • [17.6 修改全表数据](#17.6 修改全表数据)
  • 十八、DELETE:删除记录
    • [18.1 DELETE 基本语法](#18.1 DELETE 基本语法)
    • [18.2 删除指定记录](#18.2 删除指定记录)
    • [18.3 DELETE 不加 WHERE](#18.3 DELETE 不加 WHERE)
    • [18.4 删除所有记录后,自增长会怎样?](#18.4 删除所有记录后,自增长会怎样?)
  • 十九、TRUNCATE:清空整张表
    • [19.1 TRUNCATE 基本语法](#19.1 TRUNCATE 基本语法)
    • [19.2 TRUNCATE 实验](#19.2 TRUNCATE 实验)
    • [19.3 DELETE、TRUNCATE、DROP 的区别](#19.3 DELETE、TRUNCATE、DROP 的区别)
  • [二十、INSERT ... SELECT:插入查询结果](#二十、INSERT ... SELECT:插入查询结果)
  • 二十一、聚合函数:COUNT、SUM、AVG、MAX、MIN
    • [21.1 常见聚合函数](#21.1 常见聚合函数)
    • [21.2 COUNT(*):统计总记录数](#21.2 COUNT(*):统计总记录数)
    • [21.3 COUNT(字段名)](#21.3 COUNT(字段名))
    • [21.4 COUNT(DISTINCT)](#21.4 COUNT(DISTINCT))
    • [21.5 SUM:求和](#21.5 SUM:求和)
    • [21.6 SUM 没有匹配记录时的结果](#21.6 SUM 没有匹配记录时的结果)
    • [21.7 AVG:平均值](#21.7 AVG:平均值)
    • [21.8 MAX:最大值](#21.8 MAX:最大值)
    • [21.9 MIN:最小值](#21.9 MIN:最小值)
  • [二十二、GROUP BY:分组查询(重点)](#二十二、GROUP BY:分组查询(重点))
    • [22.1 为什么需要分组?](#22.1 为什么需要分组?)
    • [22.2 GROUP BY 基本语法](#22.2 GROUP BY 基本语法)
    • [22.3 创建员工测试表](#22.3 创建员工测试表)
    • [22.4 查询每个部门的平均工资](#22.4 查询每个部门的平均工资)
    • [22.5 同时统计平均工资和最高工资](#22.5 同时统计平均工资和最高工资)
    • [22.6 多字段分组](#22.6 多字段分组)
  • 二十三、HAVING:对分组结果进行筛选
    • [23.1 为什么需要 HAVING?](#23.1 为什么需要 HAVING?)
    • [23.2 HAVING 基本语法](#23.2 HAVING 基本语法)
    • [23.3 HAVING 可以使用别名](#23.3 HAVING 可以使用别名)
    • [23.4 WHERE 与 HAVING 的区别](#23.4 WHERE 与 HAVING 的区别)
  • [二十四、SQL 查询的逻辑执行顺序](#二十四、SQL 查询的逻辑执行顺序)
  • 二十五、常见错误与注意事项
    • [25.1 UPDATE 忘记 WHERE](#25.1 UPDATE 忘记 WHERE)
    • [25.2 DELETE 忘记 WHERE](#25.2 DELETE 忘记 WHERE)
    • [25.3 使用 = NULL](#25.3 使用 = NULL)
    • [25.4 在 WHERE 中使用 SELECT 别名](#25.4 在 WHERE 中使用 SELECT 别名)
    • [25.5 DISTINCT 的去重范围理解错误](#25.5 DISTINCT 的去重范围理解错误)
    • [25.6 分页查询没有 ORDER BY](#25.6 分页查询没有 ORDER BY)
    • [25.7 COUNT(*) 与 COUNT(column) 混淆](#25.7 COUNT(*) 与 COUNT(column) 混淆)
    • [25.8 把 TRUNCATE 当成普通 DELETE](#25.8 把 TRUNCATE 当成普通 DELETE)
  • 二十六、综合实战:学生成绩统计系统
    • [26.1 查询所有学生总分](#26.1 查询所有学生总分)
    • [26.2 查询总分最高的三名学生](#26.2 查询总分最高的三名学生)
    • [26.3 查询数学成绩大于 90 的学生](#26.3 查询数学成绩大于 90 的学生)
    • [26.4 查询语文和英语都大于 80 的学生](#26.4 查询语文和英语都大于 80 的学生)
    • [26.5 统计每个班级的人数](#26.5 统计每个班级的人数)
    • [26.6 统计每个班级的数学平均分](#26.6 统计每个班级的数学平均分)
    • [26.7 查询数学平均分大于 85 的班级](#26.7 查询数学平均分大于 85 的班级)
    • [26.8 查询每个班级的最高总分](#26.8 查询每个班级的最高总分)
  • [二十七、SQL 基础查询练习题](#二十七、SQL 基础查询练习题)
  • [二十八、常用 SQL 命令速查](#二十八、常用 SQL 命令速查)
  • 二十九、总结
      • [1. 数据插入](#1. 数据插入)
      • [2. 数据查询](#2. 数据查询)
      • [3. 数据修改与删除](#3. 数据修改与删除)
      • [4. 聚合统计](#4. 聚合统计)
      • [5. 分组查询](#5. 分组查询)

前言

在前面的学习中,我们已经掌握了 MySQL 的基本概念、数据库与表的创建,以及字段类型和约束的使用。

例如,我们已经能够创建下面这样一张学生表:

sql 复制代码
CREATE TABLE student (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20) NOT NULL,
    age TINYINT UNSIGNED,
    score DECIMAL(5,2)
);

但是,创建好表之后,还需要考虑更多实际问题:

  • 如何向表中插入一条或多条记录?
  • 如何查询满足特定条件的数据?
  • 如何对查询结果进行排序和分页?
  • 如何修改或删除指定记录?
  • 如何统计学生数量、平均分、最高分?
  • 如何按照班级或者部门进行分组统计?

这些问题涉及 MySQL 中最基础、使用频率也最高的一组操作:CRUD。

本篇将系统介绍 MySQL 表的增删改查,并进一步学习聚合函数和分组查询。


一、CRUD 的基本概念

1.1 什么是 CRUD?

CRUD 是数据库中四种基本数据操作的简称。

缩写 英文 含义 对应 SQL
C Create 创建数据 INSERT
R Retrieve 读取数据 SELECT
U Update 修改数据 UPDATE
D Delete 删除数据 DELETE

例如,对于学生管理系统:

sql 复制代码
-- 插入学生
INSERT INTO student(name, age)
VALUES ('张三', 18);

-- 查询学生
SELECT * FROM student;

-- 修改学生
UPDATE student
SET age = 19
WHERE name = '张三';

-- 删除学生
DELETE FROM student
WHERE name = '张三';

这四类操作构成了大多数数据库应用的基础。

需要注意,CRUD 中的 Create 通常是指创建记录,而 CREATE TABLE 是创建表结构,两者属于不同层面的操作。

1.2 准备实验数据库

首先连接 MySQL:

bash 复制代码
mycli -h 127.0.0.1 -P 3306 -u root

创建专门的实验数据库:

sql 复制代码
CREATE DATABASE IF NOT EXISTS lab06
CHARACTER SET utf8
COLLATE utf8_general_ci;

切换数据库:

sql 复制代码
USE lab06;

检查当前数据库:

sql 复制代码
SELECT DATABASE();

后续所有实验均在 lab06 中完成。


二、INSERT:向表中插入数据

2.1 INSERT 基本语法

MySQL 使用 INSERT 向表中插入数据。

基本语法:

sql 复制代码
INSERT INTO 表名
(字段1, 字段2, ...)
VALUES
(值1, 值2, ...);

逐个解释:

关键字 作用
INSERT 插入
INTO 指定要插入数据的表
表名 目标数据表
字段列表 指定插入哪些字段
VALUES 指定实际插入的数据

例如:

sql 复制代码
INSERT INTO student(name, age)
VALUES ('张三', 18);

表示向 student 表插入一条记录:

text 复制代码
name = 张三
age = 18

其中,字符串通常使用单引号包围,整数可以直接填写。

2.2 创建实验表

sql 复制代码
CREATE TABLE students (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    sn INT NOT NULL UNIQUE COMMENT '学号',
    name VARCHAR(20) NOT NULL,
    qq VARCHAR(20)
);

各字段含义:

字段 作用
id 自增长主键
sn 学号,不允许重复
name 学生姓名,不允许 NULL
qq QQ 号码,允许 NULL

这里需要注意:

sql 复制代码
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT

表示 id 是一个非负整数主键,并且可以自动增长。

sql 复制代码
sn INT NOT NULL UNIQUE

表示 sn 必须提供,而且不能重复。

2.3 单行数据:全列插入

sql 复制代码
INSERT INTO students
VALUES (100, 10000, '唐三藏', NULL);

因为没有指定字段列表,所以插入值必须与表定义中的字段数量和顺序对应。

对应关系如下:

字段 值
id 100
sn 10000
name 唐三藏
qq NULL

继续插入:

sql 复制代码
INSERT INTO students
VALUES (101, 10001, '孙悟空', '11111');

查询:

sql 复制代码
SELECT * FROM students;

预期结果:

text 复制代码
+-----+-------+-----------+-------+
| id  | sn    | name      | qq    |
+-----+-------+-----------+-------+
| 100 | 10000 | 唐三藏    | NULL  |
| 101 | 10001 | 孙悟空    | 11111 |
+-----+-------+-----------+-------+

这里的 NULL 表示该字段目前没有已知值。

它不是字符串 'NULL',也不是空字符串 ''。

2.4 指定字段插入

如果不想给所有字段赋值,可以明确指定要插入的字段。

例如:

sql 复制代码
INSERT INTO students(sn, name)
VALUES (10002, '猪悟能');

由于 id 具有 AUTO_INCREMENT 属性,因此 MySQL 可以自动生成 id。

而 qq 允许 NULL,所以可以省略。

指定字段插入的优点是:不需要严格依赖表中字段的原始顺序。

在实际项目中,通常更推荐明确指定字段名,以减少表结构变化带来的影响。

2.5 一次插入多条数据

sql 复制代码
INSERT INTO students(id, sn, name)
VALUES
(102, 20001, '曹孟德'),
(103, 20002, '孙仲谋');

这里:

sql 复制代码
VALUES
(102, 20001, '曹孟德'),
(103, 20002, '孙仲谋');

表示一次插入两条记录。

每组括号是一条记录,记录之间使用逗号分隔。

执行后,MySQL 可能显示:

text 复制代码
Query OK, 2 rows affected

其中:

text 复制代码
2 rows affected

表示本次操作影响了两行数据。

2.6 主键或唯一键冲突

如果尝试插入已经存在的主键:

sql 复制代码
INSERT INTO students(id, sn, name)
VALUES (100, 10010, '唐大师');

会出现:

text 复制代码
ERROR 1062 (23000):
Duplicate entry '100' for key 'PRIMARY'

原因是:

text 复制代码
id = 100

已经存在。

同样,如果插入重复学号:

sql 复制代码
INSERT INTO students(sn, name)
VALUES (20001, '曹阿瞒');

也会违反 UNIQUE 约束。

这说明:

PRIMARY KEY 和 UNIQUE 不仅影响查询和索引,还会对 INSERT 操作进行数据合法性检查。


三、INSERT ON DUPLICATE KEY UPDATE:插入否则更新

3.1 为什么需要插入否则更新?

假设我们需要更新一名学生的信息。

业务需求是:

  • 如果记录不存在,就插入。
  • 如果主键或唯一键发生冲突,就更新已有记录。

MySQL 提供了:

sql 复制代码
INSERT ... ON DUPLICATE KEY UPDATE

这种写法也常被称为 Upsert。

3.2 基本语法

sql 复制代码
INSERT INTO 表名(字段列表)
VALUES (字段值列表)
ON DUPLICATE KEY UPDATE
    字段1 = 新值1,
    字段2 = 新值2;

例如:

sql 复制代码
INSERT INTO students(id, sn, name)
VALUES (100, 10010, '唐大师')
ON DUPLICATE KEY UPDATE
    sn = 10010,
    name = '唐大师';

由于 id = 100 已经存在,因此不会插入一条新的 id = 100 的记录,而是修改冲突的原记录。

3.3 影响行数的含义

MySQL 常见的返回情况:

影响行数 含义
0 存在冲突,但更新后值没有实际变化
1 没有冲突,成功插入
2 存在冲突,已有记录发生更新

这里的返回值是常见客户端配置下的行为,具体还可能受到客户端连接选项影响。

可以使用:

sql 复制代码
SELECT ROW_COUNT();

查看紧邻上一条 DML 语句影响的行数。

例如:

sql 复制代码
INSERT INTO students(id, sn, name)
VALUES (100, 10010, '唐大师')
ON DUPLICATE KEY UPDATE
    sn = 10010,
    name = '唐大师';

SELECT ROW_COUNT();

若已有记录被实际更新,通常返回:

text 复制代码
2

需要注意,ROW_COUNT() 应在需要检查的操作之后立即执行。


四、REPLACE:替换记录

4.1 REPLACE 的作用

除了插入和更新,MySQL 还支持:

sql 复制代码
REPLACE INTO

它的核心行为是:

  • 没有唯一键冲突:插入新记录。
  • 存在主键或唯一键冲突:删除冲突记录,再插入新记录。

例如:

sql 复制代码
REPLACE INTO students(sn, name)
VALUES (20001, '曹阿瞒');

如果学号 20001 已经存在,MySQL 会执行替换操作。

4.2 REPLACE 与 ON DUPLICATE KEY UPDATE 的区别

特性 ON DUPLICATE KEY UPDATE REPLACE
无冲突 插入新记录 插入新记录
存在冲突 更新已有记录 删除冲突记录后插入
是否可能改变自增主键 通常不需要 可能
对其他关联关系的影响 主要是更新语义 可能触发删除、插入相关行为

特别注意:

REPLACE 并不是简单的 UPDATE。

如果表中存在外键、触发器、自增长主键等,REPLACE 可能产生与普通 UPDATE 不同的效果。

例如,当唯一键冲突且没有显式指定自增长 id 时,替换后的记录可能获得一个新的 id。

因此,真实业务开发中应根据需求选择使用,不要随意将 REPLACE 当作 UPDATE 的替代品。


五、SELECT:查询表中的数据

SELECT 是 MySQL 中最常用的命令之一。

5.1 SELECT 基本语法

sql 复制代码
SELECT 字段列表
FROM 表名
WHERE 条件
ORDER BY 排序字段
LIMIT 数量;

各部分含义如下:

关键字 作用
SELECT 指定查询内容
FROM 指定数据来源
WHERE 设置筛选条件
ORDER BY 对结果排序
LIMIT 限制结果数量

这些子句并不是每次都必须全部出现。

例如:

sql 复制代码
SELECT * FROM students;

只需要 SELECT 和 FROM 就可以查询整张表。

5.2 准备考试成绩表

创建:

sql 复制代码
CREATE TABLE exam_result (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20) NOT NULL COMMENT '同学姓名',
    chinese FLOAT DEFAULT 0.0 COMMENT '语文成绩',
    math FLOAT DEFAULT 0.0 COMMENT '数学成绩',
    english FLOAT DEFAULT 0.0 COMMENT '英语成绩'
);

插入测试数据:

sql 复制代码
INSERT INTO exam_result(name, chinese, math, english)
VALUES
('唐三藏', 67, 98, 56),
('孙悟空', 87, 78, 77),
('猪悟能', 88, 98, 90),
('曹孟德', 82, 84, 67),
('刘玄德', 55, 85, 45),
('孙权', 70, 73, 78),
('宋公明', 75, 65, 30);

查询:

sql 复制代码
SELECT * FROM exam_result;

初始数据如下:

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

后续 SELECT 实验均基于这组初始数据。


六、SELECT 查询指定列

6.1 全列查询

sql 复制代码
SELECT * FROM exam_result;

其中:

sql 复制代码
*

表示查询所有字段。

虽然这种写法非常方便,但实际业务代码中不建议长期依赖 SELECT *。

原因包括:

  1. 查询不需要的列会增加数据传输量。
  2. 表结构发生变化时,返回列可能改变。
  3. 某些查询原本可以只通过索引完成,查询所有列可能需要额外读取数据。

对于初学阶段的表结构检查,SELECT * 仍然非常实用。

6.2 指定列查询

sql 复制代码
SELECT id, name, english
FROM exam_result;

表示只查询:

text 复制代码
id
name
english

三个字段。

需要注意:SELECT 中字段的排列顺序可以由我们自己决定,不必与建表顺序一致。

例如:

sql 复制代码
SELECT english, name, id
FROM exam_result;

也完全合法。

6.3 查询表达式

SELECT 不仅可以查询字段,还可以计算表达式。

例如:

sql 复制代码
SELECT id, name, 10
FROM exam_result;

这里的 10 是一个常量。

因此每条记录的第三列都会显示 10。

还可以进行加法:

sql 复制代码
SELECT id, name, english + 10
FROM exam_result;

表示查询每名学生的英语成绩加 10 后的结果。

例如,唐三藏英语成绩为 56,结果就是:

text 复制代码
56 + 10 = 66

注意:

这条 SELECT 不会修改数据库中保存的英语成绩。

它只是计算查询结果。

6.4 使用多个字段组成表达式

计算每名学生的总成绩:

sql 复制代码
SELECT
    id,
    name,
    chinese + math + english
FROM exam_result;

其中:

sql 复制代码
chinese + math + english

表示语文、数学和英语三门成绩之和。

例如:

text 复制代码
唐三藏:67 + 98 + 56 = 221
孙悟空:87 + 78 + 77 = 242
猪悟能:88 + 98 + 90 = 276

但是,查询结果中的列名会非常长。

因此可以使用别名。


七、AS:给查询结果设置别名

7.1 基本语法

sql 复制代码
SELECT 字段或表达式 AS 别名
FROM 表名;

例如:

sql 复制代码
SELECT
    id,
    name,
    chinese + math + english AS total
FROM exam_result;

其中:

sql 复制代码
AS total

表示把计算结果列命名为 total。

也可以使用中文别名:

sql 复制代码
SELECT
    id,
    name,
    chinese + math + english AS 总分
FROM exam_result;

此时,查询结果列名就会显示为:

text 复制代码
总分

7.2 AS 可以省略吗?

可以。

下面两种写法在这里是等价的:

sql 复制代码
SELECT name AS 姓名
FROM exam_result;
sql 复制代码
SELECT name 姓名
FROM exam_result;

虽然 AS 可以省略,但在较复杂的 SQL 中,显式写出 AS 往往具有更好的可读性。


八、DISTINCT:查询结果去重

8.1 为什么需要去重?

执行:

sql 复制代码
SELECT math
FROM exam_result;

可能得到:

text 复制代码
98
78
98
84
85
73
65

可以发现,98 出现了两次。

如果我们只想知道数学成绩出现了哪些不同的分数,可以使用 DISTINCT。

8.2 DISTINCT 基本语法

sql 复制代码
SELECT DISTINCT math
FROM exam_result;

结果:

text 复制代码
98
78
84
85
73
65

注意:

DISTINCT 是对查询结果中的行进行去重,不是修改原表。

8.3 多列 DISTINCT

例如:

sql 复制代码
SELECT DISTINCT chinese, math
FROM exam_result;

表示对:

text 复制代码
(chinese, math)

这个组合进行去重。

只有两个字段的值都相同,才会被视为重复结果。

因此,多列 DISTINCT 并不是对每个字段分别去重。


九、WHERE:条件查询(重点)

在实际开发中,我们通常不需要查询所有记录。

例如:

  • 查询英语不及格的学生。
  • 查询语文成绩在 80~90 分之间的学生。
  • 查询姓孙的学生。
  • 查询总分低于 200 的学生。

这些操作需要使用 WHERE。

9.1 WHERE 基本语法

sql 复制代码
SELECT 字段列表
FROM 表名
WHERE 条件;

其中:

sql 复制代码
WHERE

表示筛选满足条件的记录。

9.2 常见比较运算符

运算符 含义
> 大于
>= 大于等于
< 小于
<= 小于等于
= 等于
!= 不等于
<> 不等于
<=> NULL 安全的相等比较
BETWEEN ... AND ... 范围判断
IN (...) 是否属于指定集合
IS NULL 判断是否为 NULL
IS NOT NULL 判断是否不为 NULL
LIKE 模式匹配

需要注意,SQL 使用:

sql 复制代码
=

表示相等比较,而不是 C/C++ 中的 ==。


十、WHERE 基础查询案例

10.1 查询英语不及格的学生

需求:

查询英语成绩小于 60 分的学生姓名和英语成绩。

sql 复制代码
SELECT name, english
FROM exam_result
WHERE english < 60;

预期结果:

name english
唐三藏 56
刘玄德 45
宋公明 30

这里:

sql 复制代码
english < 60

就是筛选条件。

10.2 查询语文成绩在 80~90 分之间的学生

第一种写法:

sql 复制代码
SELECT name, chinese
FROM exam_result
WHERE chinese >= 80
AND chinese <= 90;

其中:

sql 复制代码
AND

表示"并且"。

所以:

sql 复制代码
chinese >= 80 AND chinese <= 90

要求两个条件同时成立。

第二种写法:

sql 复制代码
SELECT name, chinese
FROM exam_result
WHERE chinese BETWEEN 80 AND 90;

其中:

sql 复制代码
BETWEEN 80 AND 90

表示范围在 80 到 90 之间,包含 80 和 90。

10.3 查询数学成绩为指定值的学生

假设要查询数学成绩为:

text 复制代码
58、59、98、99

的学生。

可以使用 OR:

sql 复制代码
SELECT name, math
FROM exam_result
WHERE math = 58
   OR math = 59
   OR math = 98
   OR math = 99;

这里的:

sql 复制代码
OR

表示"或者"。

只要任意一个条件成立,就满足筛选要求。

但是这种写法比较繁琐。

因此可以使用:

sql 复制代码
SELECT name, math
FROM exam_result
WHERE math IN (58, 59, 98, 99);

其中:

sql 复制代码
IN (...)

表示判断某个值是否属于指定值集合。


十一、LIKE:模糊匹配

LIKE 常用于字符串模式匹配。

11.1 LIKE 的两个通配符

通配符 含义
% 匹配零个或多个任意字符
_ 匹配恰好一个任意字符

11.2 查询姓孙的学生

sql 复制代码
SELECT name
FROM exam_result
WHERE name LIKE '孙%';

其中:

sql 复制代码
'孙%'

表示字符串以"孙"开头,后面可以有任意数量的字符。

因此能够匹配:

text 复制代码
孙悟空
孙权

11.3 查询姓孙且姓名为两个字的学生

sql 复制代码
SELECT name
FROM exam_result
WHERE name LIKE '孙_';

其中:

sql 复制代码
_

表示恰好匹配一个字符。

因此只能匹配:

text 复制代码
孙权

不能匹配:

text 复制代码
孙悟空

因为"孙悟空"包含三个字符。

11.4 LIKE 的其他示例

以"张"开头:

sql 复制代码
WHERE name LIKE '张%'

以"三"结尾:

sql 复制代码
WHERE name LIKE '%三'

包含"悟"字:

sql 复制代码
WHERE name LIKE '%悟%'

排除姓孙的记录:

sql 复制代码
WHERE name NOT LIKE '孙%'

需要注意:

LIKE 对大小写是否敏感,还会受到字段校验规则的影响。


十二、WHERE 中使用表达式

12.1 比较两个字段

查询语文成绩高于英语成绩的学生:

sql 复制代码
SELECT name, chinese, english
FROM exam_result
WHERE chinese > english;

这里的:

sql 复制代码
chinese > english

比较的是两个字段,而不是字段和固定常量。

12.2 查询总分低于 200 的学生

sql 复制代码
SELECT
    name,
    chinese + math + english AS 总分
FROM exam_result
WHERE chinese + math + english < 200;

预期结果:

name 总分
刘玄德 185
宋公明 170

注意:

在同一个查询层级中,通常不能直接在 WHERE 中使用 SELECT 定义的列别名。

例如:

sql 复制代码
SELECT
    name,
    chinese + math + english AS 总分
FROM exam_result
WHERE 总分 < 200;

这种写法通常会报错。

原因与 SQL 的逻辑处理顺序有关:

WHERE 条件的逻辑处理早于 SELECT 列别名的生成。

因此应当在 WHERE 中使用原始表达式:

sql 复制代码
WHERE chinese + math + english < 200

十三、AND、OR、NOT:逻辑运算符

13.1 基本含义

运算符 作用
AND 所有条件都需要成立
OR 至少一个条件成立
NOT 对条件结果取反

例如:

sql 复制代码
WHERE chinese > 80
AND english > 60

表示语文大于 80,并且英语大于 60。

sql 复制代码
WHERE chinese > 80
OR english > 80

表示语文或者英语至少有一门大于 80。

13.2 查询语文成绩大于 80 且不姓孙的学生

sql 复制代码
SELECT name, chinese
FROM exam_result
WHERE chinese > 80
AND name NOT LIKE '孙%';

预期结果:

name chinese
猪悟能 88
曹孟德 82

13.3 综合条件查询

需求:

查询满足以下任意一种情况的学生:

  1. 姓孙,并且姓名只有两个字。
  2. 总分大于 200,语文小于数学,同时英语大于 80。

SQL:

sql 复制代码
SELECT
    name,
    chinese,
    math,
    english,
    chinese + math + english AS 总分
FROM exam_result
WHERE name LIKE '孙_'
   OR (
       chinese + math + english > 200
       AND chinese < math
       AND english > 80
   );

这里使用括号:

sql 复制代码
(...)

明确指定多个逻辑条件的组合关系。

需要注意,在 MySQL 中,AND 的优先级高于 OR。

当查询条件较复杂时,建议显式使用括号,避免出现逻辑错误。


十四、NULL 的查询与比较

14.1 为什么不能使用 = NULL?

假设学生 QQ 号码可能为空。

如果执行:

sql 复制代码
SELECT name, qq
FROM students
WHERE qq = NULL;

无法按预期找到 NULL 记录。

因为:

sql 复制代码
NULL = NULL

的结果不是 TRUE,而是 NULL,也就是未知。

14.2 IS NULL

查询 QQ 号码未知的学生:

sql 复制代码
SELECT name, qq
FROM students
WHERE qq IS NULL;

14.3 IS NOT NULL

查询 QQ 号码已知的学生:

sql 复制代码
SELECT name, qq
FROM students
WHERE qq IS NOT NULL;

14.4 NULL 安全比较运算符

MySQL 提供:

sql 复制代码
<=>

用于 NULL 安全比较。

例如:

sql 复制代码
SELECT
    NULL = NULL,
    NULL <=> NULL;

结果:

text 复制代码
NULL = NULL     → NULL
NULL <=> NULL   → 1

其中:

text 复制代码
1

表示 TRUE。

而:

sql 复制代码
SELECT NULL <=> 1;

返回:

text 复制代码
0

表示 FALSE。

实际开发中,最常见的判断方式仍然是 IS NULL 和 IS NOT NULL。


十五、ORDER BY:查询结果排序

15.1 ORDER BY 基本语法

sql 复制代码
SELECT 字段列表
FROM 表名
ORDER BY 字段 ASC;

其中:

  • ORDER BY:对查询结果排序。
  • ASC:升序,从小到大。
  • DESC:降序,从大到小。

如果没有指定排序方向,默认采用 ASC。

15.2 按数学成绩升序排序

sql 复制代码
SELECT name, math
FROM exam_result
ORDER BY math ASC;

也可以省略 ASC:

sql 复制代码
SELECT name, math
FROM exam_result
ORDER BY math;

15.3 按数学成绩降序排序

sql 复制代码
SELECT name, math
FROM exam_result
ORDER BY math DESC;

表示数学成绩高的学生排在前面。

15.4 多字段排序

例如:

先按数学成绩降序,如果数学成绩相同,再按英语成绩升序,最后按语文成绩升序。

sql 复制代码
SELECT name, math, english, chinese
FROM exam_result
ORDER BY math DESC,
         english ASC,
         chinese ASC;

这里有一个重要规则:

ORDER BY 多个字段时,排序优先级按照字段出现顺序决定。

例如:

text 复制代码
数学成绩相同
    ↓
比较英语成绩
    ↓
英语成绩也相同
    ↓
比较语文成绩

15.5 按表达式排序

查询总分,并按照总分降序排列:

sql 复制代码
SELECT
    name,
    chinese + math + english AS 总分
FROM exam_result
ORDER BY 总分 DESC;

这里可以直接使用 SELECT 定义的别名:

sql 复制代码
ORDER BY 总分 DESC

这是因为 ORDER BY 在逻辑处理上位于 SELECT 之后。

15.6 WHERE 与 ORDER BY 结合

查询姓孙或姓曹的学生,并按照数学成绩降序排列:

sql 复制代码
SELECT name, math
FROM exam_result
WHERE name LIKE '孙%'
   OR name LIKE '曹%'
ORDER BY math DESC;

这里先确定满足 WHERE 条件的记录,再对结果排序。

需要特别注意:

如果没有使用 ORDER BY,SQL 查询结果的行顺序没有保证。

即使多次执行看起来顺序相同,也不能依赖这种现象。


十六、LIMIT:限制结果与分页查询

16.1 为什么需要 LIMIT?

假设一张用户表包含 100 万条记录。

如果执行:

sql 复制代码
SELECT * FROM users;

可能返回大量数据。

这会造成:

  • 网络传输开销增大。
  • 客户端显示缓慢。
  • 查询消耗更多资源。

因此,我们通常需要限制查询结果数量。

16.2 LIMIT 基本语法

sql 复制代码
SELECT *
FROM exam_result
LIMIT 3;

表示最多返回 3 条记录。

但是如果希望结果具有稳定顺序,应当配合 ORDER BY。

例如:

sql 复制代码
SELECT *
FROM exam_result
ORDER BY id
LIMIT 3;

16.3 LIMIT 与 OFFSET

完整语法:

sql 复制代码
LIMIT 数量 OFFSET 偏移量

其中:

  • LIMIT:返回多少条记录。
  • OFFSET:跳过多少条记录。

注意:OFFSET 从 0 开始。

例如:

sql 复制代码
LIMIT 3 OFFSET 0

表示从第一条开始,返回最多三条记录。

16.4 分页案例

假设每页显示 3 条学生记录。

第一页:

sql 复制代码
SELECT id, name, math, english, chinese
FROM exam_result
ORDER BY id
LIMIT 3 OFFSET 0;

返回 id 为 1、2、3 的记录。

第二页:

sql 复制代码
SELECT id, name, math, english, chinese
FROM exam_result
ORDER BY id
LIMIT 3 OFFSET 3;

返回 id 为 4、5、6 的记录。

第三页:

sql 复制代码
SELECT id, name, math, english, chinese
FROM exam_result
ORDER BY id
LIMIT 3 OFFSET 6;

返回 id 为 7 的记录。

因为总共只有 7 条记录,所以第三页只有 1 条。

16.5 分页公式

假设:

  • 页码为 (p)
  • 每页记录数为 (n)

那么:

O F F S E T = ( p − 1 ) × n OFFSET=(p-1)\times n OFFSET=(p−1)×n

例如:

第 5 页,每页 20 条记录:

O F F S E T = ( 5 − 1 ) × 20 = 80 OFFSET=(5-1)\times20=80 OFFSET=(5−1)×20=80

对应 SQL:

sql 复制代码
SELECT *
FROM exam_result
ORDER BY id
LIMIT 20 OFFSET 80;

16.6 LIMIT 的另一种写法

MySQL 还支持:

sql 复制代码
LIMIT 3, 5

其中:

  • 3:起始偏移量。
  • 5:返回记录数。

等价于:

sql 复制代码
LIMIT 5 OFFSET 3

对于初学者而言,推荐第二种写法,因为含义更加直观。

实际开发注意事项

OFFSET 分页在数据量非常大时,可能产生较高的扫描和跳过成本。

对于大规模数据,后续可以进一步研究基于索引游标的分页方式,例如:

sql 复制代码
SELECT *
FROM exam_result
WHERE id > 100
ORDER BY id
LIMIT 20;

但现阶段先掌握 LIMIT 与 OFFSET 即可。


十七、UPDATE:修改表中的数据

17.1 UPDATE 基本语法

sql 复制代码
UPDATE 表名
SET 字段 = 新值
WHERE 条件;

其中:

关键字 作用
UPDATE 修改数据
表名 要修改的表
SET 指定新值
WHERE 筛选需要修改的记录

UPDATE 最危险的错误之一,就是忘记 WHERE。

如果没有 WHERE,可能修改整张表。

下面的操作会改变前面实验中的考试成绩,因此相关统计结果也会随之变化。

17.2 修改单个字段

将孙悟空的数学成绩修改为 80:

sql 复制代码
UPDATE exam_result
SET math = 80
WHERE name = '孙悟空';

逐个解释:

sql 复制代码
UPDATE exam_result

表示修改 exam_result 表。

sql 复制代码
SET math = 80

表示将 math 字段设置为 80。

sql 复制代码
WHERE name = '孙悟空'

表示只修改姓名为孙悟空的记录。

修改后查询:

sql 复制代码
SELECT name, math
FROM exam_result
WHERE name = '孙悟空';

预期:

text 复制代码
孙悟空  80

17.3 同时修改多个字段

将曹孟德的数学成绩修改为 60,语文成绩修改为 70:

sql 复制代码
UPDATE exam_result
SET math = 60,
    chinese = 70
WHERE name = '曹孟德';

这里的:

sql 复制代码
SET math = 60,
    chinese = 70

表示一次更新多个字段。

不同字段之间使用逗号分隔。

17.4 在原有数值基础上修改

假设某个学生原来的成绩是 80。

现在希望增加 10 分,可以执行:

sql 复制代码
UPDATE exam_result
SET math = math + 10
WHERE name = '孙悟空';

这里:

sql 复制代码
math = math + 10

表示将原来的数学成绩加 10,再保存回 math 字段。

注意,SQL 不支持直接用 C/C++ 中的:

cpp 复制代码
math += 10;

代替上述更新语法。

17.5 更新总分最低的三名学生

需求:

将总成绩最低的三名学生的数学成绩增加 30 分。

先查看当前总分:

sql 复制代码
SELECT
    name,
    math,
    chinese + math + english AS 总分
FROM exam_result
ORDER BY 总分 ASC
LIMIT 3;

然后:

sql 复制代码
UPDATE exam_result
SET math = math + 30
ORDER BY chinese + math + english ASC, id ASC
LIMIT 3;

这条 SQL 的作用:

  1. 按当前总分升序排序。
  2. 当总分相同时,再按照 id 升序排序。
  3. 选择前三条记录。
  4. 将这三条记录的数学成绩分别增加 30 分。

更新后,原来的总分排序可能发生变化。

所以不能认为更新前总分最低的三名学生,在更新之后依然排名最低。

17.6 修改全表数据

如果执行:

sql 复制代码
UPDATE exam_result
SET chinese = chinese * 2;

由于没有 WHERE 条件,因此会修改整张表。

每名学生的语文成绩都变成原来的两倍。

例如:

text 复制代码
67 → 134
87 → 174
88 → 176

这种语句必须谨慎使用。

真实业务环境建议先执行对应的 SELECT,确认影响范围,再进行 UPDATE。

例如:

sql 复制代码
SELECT *
FROM exam_result
WHERE id = 1;

确认目标记录之后再执行修改:

sql 复制代码
UPDATE exam_result
SET chinese = chinese * 2
WHERE id = 1;

十八、DELETE:删除记录

18.1 DELETE 基本语法

sql 复制代码
DELETE FROM 表名
WHERE 条件;

其中:

  • DELETE:删除记录。
  • FROM:指定目标表。
  • WHERE:指定要删除的记录。

18.2 删除指定记录

删除孙悟空的考试成绩:

sql 复制代码
DELETE FROM exam_result
WHERE name = '孙悟空';

执行后:

sql 复制代码
SELECT *
FROM exam_result
WHERE name = '孙悟空';

将无法查询到对应记录。

需要注意:

DELETE 删除的是表中的记录,而不是表结构。

18.3 DELETE 不加 WHERE

如果执行:

sql 复制代码
DELETE FROM exam_result;

将删除 exam_result 中的全部记录。

但是表结构仍然保留。

因此:

sql 复制代码
DESC exam_result;

依然可以查看表结构。

DELETE FROM 表名 不等于 DROP TABLE 表名。

前者删除记录,后者删除整张表。

18.4 删除所有记录后,自增长会怎样?

创建测试表:

sql 复制代码
CREATE TABLE for_delete (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20)
);

插入:

sql 复制代码
INSERT INTO for_delete(name)
VALUES ('A'), ('B'), ('C');

查询:

sql 复制代码
SELECT * FROM for_delete;

预期结果:

text 复制代码
1 A
2 B
3 C

删除所有记录:

sql 复制代码
DELETE FROM for_delete;

再插入:

sql 复制代码
INSERT INTO for_delete(name)
VALUES ('D');

在这个连续执行的实验中,新记录的 id 通常是:

text 复制代码
4

因为普通 DELETE 不会自动把 AUTO_INCREMENT 计数器重置为起始值。


十九、TRUNCATE:清空整张表

19.1 TRUNCATE 基本语法

sql 复制代码
TRUNCATE TABLE 表名;

例如:

sql 复制代码
TRUNCATE TABLE for_truncate;

表示清空整张表中的数据。

19.2 TRUNCATE 实验

先创建:

sql 复制代码
CREATE TABLE for_truncate (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20)
);

插入:

sql 复制代码
INSERT INTO for_truncate(name)
VALUES ('A'), ('B'), ('C');

查询:

sql 复制代码
SELECT * FROM for_truncate;

结果:

text 复制代码
1 A
2 B
3 C

执行:

sql 复制代码
TRUNCATE TABLE for_truncate;

再次查询:

sql 复制代码
SELECT * FROM for_truncate;

结果为空。

继续插入:

sql 复制代码
INSERT INTO for_truncate(name)
VALUES ('D');

在这个实验中,新的自增长 id 通常重新从 1 开始。

19.3 DELETE、TRUNCATE、DROP 的区别

特性 DELETE TRUNCATE DROP TABLE
删除指定记录 支持 WHERE 不支持 不支持
清空全表 支持 支持 删除整张表
保留表结构 是 是 否
自动重置自增长计数器 通常不会 通常会 表已删除
操作性质 DML DDL DDL
普通 InnoDB 事务能否回滚 可以,满足事务条件时 不能 不能

需要强调:

TRUNCATE 通常不会像逐行 DELETE 那样处理每一行,因此清空大量数据时往往更高效。

但是它属于 DDL 操作,通常会发生隐式提交,不能依靠普通事务回滚恢复数据。

TRUNCATE 和 DROP 都应视为高风险操作。


二十、INSERT ... SELECT:插入查询结果

20.1 基本语法

MySQL 不仅支持通过 VALUES 插入数据,还支持将查询结果插入另一张表。

语法:

sql 复制代码
INSERT INTO 目标表
SELECT 查询字段
FROM 来源表;

这表示:

先执行 SELECT,再将查询结果写入目标表。

20.2 实战:删除重复记录

假设某张表存在重复数据:

sql 复制代码
CREATE TABLE duplicate_table (
    id INT,
    name VARCHAR(20)
);

插入:

sql 复制代码
INSERT INTO duplicate_table
VALUES
(100, 'aaa'),
(100, 'aaa'),
(200, 'bbb'),
(200, 'bbb'),
(200, 'bbb'),
(300, 'ccc');

查询:

sql 复制代码
SELECT * FROM duplicate_table;

结果:

text 复制代码
100 aaa
100 aaa
200 bbb
200 bbb
200 bbb
300 ccc

我们希望保留:

text 复制代码
100 aaa
200 bbb
300 ccc

第一步:创建一张结构相同的空表

sql 复制代码
CREATE TABLE no_duplicate_table
LIKE duplicate_table;

其中:

sql 复制代码
LIKE duplicate_table

表示按照已有表的结构创建新表。

该语句不会自动复制原表的记录。

第二步:查询去重数据并插入

sql 复制代码
INSERT INTO no_duplicate_table
SELECT DISTINCT *
FROM duplicate_table;

这里:

  • SELECT DISTINCT *:查询去重后的完整记录。
  • INSERT INTO:将查询结果写入新表。

执行后,新表中只包含三条不同的记录。

第三步:重命名表

先确认:

sql 复制代码
SELECT * FROM no_duplicate_table;

结果正确后,执行:

sql 复制代码
RENAME TABLE
    duplicate_table TO old_duplicate_table,
    no_duplicate_table TO duplicate_table;

这条语句将原表改名为 old_duplicate_table,再将去重后的表改名为 duplicate_table。

MySQL 支持在一条 RENAME TABLE 语句中完成多个重命名操作。

这样可以避免先删除原表、再创建新表所产生的中间状态。

第四步:验证

sql 复制代码
SELECT * FROM duplicate_table;

预期结果:

text 复制代码
100 aaa
200 bbb
300 ccc

原来的数据仍保留在:

sql 复制代码
old_duplicate_table

中,方便检查。

注意:

这种方法适用于当前简单的重复记录实验。

如果真实业务表包含外键、触发器、复杂索引或其他依赖关系,不能直接照搬这种替换方案,需要先确认所有表结构与依赖能够正确保留。


二十一、聚合函数:COUNT、SUM、AVG、MAX、MIN

前面的 SELECT 主要用于查询具体记录。

但实际业务还需要进行统计。

例如:

  • 有多少名学生?
  • 数学平均分是多少?
  • 英语最高分是多少?
  • 数学最低分是多少?

这些功能可以通过聚合函数实现。

21.1 常见聚合函数

函数 作用
COUNT 统计数量
SUM 求和
AVG 计算平均值
MAX 获取最大值
MIN 获取最小值

21.2 COUNT(*):统计总记录数

sql 复制代码
SELECT COUNT(*)
FROM students;

这里:

sql 复制代码
COUNT(*)

表示统计查询结果中的记录数量。

如果当前有 4 条记录,那么结果为:

text 复制代码
4

也可以使用:

sql 复制代码
SELECT COUNT(1)
FROM students;

在这里同样表示统计记录数。

21.3 COUNT(字段名)

sql 复制代码
SELECT COUNT(qq)
FROM students;

这个 SQL 与 COUNT(*) 不同。

COUNT(qq) 只统计 qq 不为 NULL 的记录数量。

例如:

name qq
唐大师 NULL
孙悟空 11111
孙仲谋 NULL
曹阿瞒 NULL

那么:

sql 复制代码
COUNT(*)

返回 4。

而:

sql 复制代码
COUNT(qq)

返回 1。

因此:

COUNT(*) 统计记录数,COUNT(字段) 统计该字段非 NULL 的值的数量。

21.4 COUNT(DISTINCT)

统计数学成绩有多少种不同的分数:

sql 复制代码
SELECT COUNT(DISTINCT math)
FROM exam_result;

其中:

sql 复制代码
DISTINCT

表示去重。

因此:

sql 复制代码
COUNT(math)

统计非 NULL 的数学成绩数量。

而:

sql 复制代码
COUNT(DISTINCT math)

统计去重后的数学成绩数量。

21.5 SUM:求和

查询数学成绩总和:

sql 复制代码
SELECT SUM(math)
FROM exam_result;

如果前面的 UPDATE、DELETE 实验全部按顺序执行,当前表中的数据已经发生变化。

例如在相应更新、删除操作完成之后,数学成绩的总和可以是:

text 复制代码
569

注意,不同实验执行顺序会影响结果。

如果希望重新得到最初的成绩统计,应使用单独的测试表或重新准备初始数据。

21.6 SUM 没有匹配记录时的结果

执行:

sql 复制代码
SELECT SUM(math)
FROM exam_result
WHERE math < 60;

如果没有任何记录符合条件,结果通常是:

text 复制代码
NULL

而不是数字 0。

这是因为 SUM 没有可参与求和的数值。

需要特别区分:

sql 复制代码
COUNT(*)

在没有匹配记录时返回 0。

而:

sql 复制代码
SUM(math)

在没有可聚合数值时返回 NULL。

21.7 AVG:平均值

计算数学平均分:

sql 复制代码
SELECT AVG(math)
FROM exam_result;

计算三门成绩的平均总分:

sql 复制代码
SELECT
    AVG(chinese + math + english) AS 平均总分
FROM exam_result;

其中:

sql 复制代码
AVG(...)

表示求平均值。

如果某条记录参与计算的表达式结果为 NULL,该 NULL 不参与 AVG 的计算。

21.8 MAX:最大值

查询英语最高分:

sql 复制代码
SELECT MAX(english)
FROM exam_result;

例如:

text 复制代码
90

21.9 MIN:最小值

查询数学成绩大于 70 的记录中的最低数学成绩:

sql 复制代码
SELECT MIN(math)
FROM exam_result
WHERE math > 70;

这里先通过 WHERE 筛选数学成绩大于 70 的记录,再对筛选结果求最小值。


二十二、GROUP BY:分组查询(重点)

22.1 为什么需要分组?

假设有一张员工表:

员工 部门 工资
张三 10 1500
李四 10 1800
王五 20 3000
赵六 20 2500
小陈 30 1200

现在我们希望统计:

每个部门的平均工资是多少?

如果只使用:

sql 复制代码
SELECT AVG(sal)
FROM emp_demo;

只能计算所有员工的整体平均工资。

但是无法得到每个部门各自的平均工资。

因此需要 GROUP BY。

22.2 GROUP BY 基本语法

sql 复制代码
SELECT 字段, 聚合函数
FROM 表名
GROUP BY 分组字段;

其中:

sql 复制代码
GROUP BY

表示按照指定字段对记录进行分组。

22.3 创建员工测试表

sql 复制代码
CREATE TABLE emp_demo (
    id INT PRIMARY KEY,
    ename VARCHAR(20),
    deptno INT,
    job VARCHAR(20),
    sal DECIMAL(10,2)
);

插入数据:

sql 复制代码
INSERT INTO emp_demo
VALUES
(1, '张三', 10, 'DEV', 1500),
(2, '李四', 10, 'DEV', 1800),
(3, '王五', 20, 'DEV', 3000),
(4, '赵六', 20, 'QA', 2500),
(5, '小陈', 30, 'QA', 1200);

字段含义:

字段 作用
id 员工编号
ename 员工姓名
deptno 部门编号
job 岗位
sal 工资

22.4 查询每个部门的平均工资

sql 复制代码
SELECT
    deptno,
    AVG(sal) AS 平均工资
FROM emp_demo
GROUP BY deptno;

执行过程可以理解为:

第一步,按照部门编号进行分组:

text 复制代码
部门 10:
张三 1500
李四 1800

部门 20:
王五 3000
赵六 2500

部门 30:
小陈 1200

第二步,对每个分组分别计算平均值。

结果:

deptno 平均工资
10 1650.00
20 2750.00
30 1200.00

22.5 同时统计平均工资和最高工资

sql 复制代码
SELECT
    deptno,
    AVG(sal) AS 平均工资,
    MAX(sal) AS 最高工资
FROM emp_demo
GROUP BY deptno;

结果:

deptno 平均工资 最高工资
10 1650.00 1800.00
20 2750.00 3000.00
30 1200.00 1200.00

这里说明:

GROUP BY 可以配合多个聚合函数使用。

22.6 多字段分组

如果希望按照:

text 复制代码
部门 + 岗位

进行分组,可以执行:

sql 复制代码
SELECT
    deptno,
    job,
    AVG(sal) AS 平均工资,
    MIN(sal) AS 最低工资
FROM emp_demo
GROUP BY deptno, job;

这里:

sql 复制代码
GROUP BY deptno, job

表示相同部门、相同岗位的记录被分为同一组。

例如:

text 复制代码
部门10 + DEV
部门20 + DEV
部门20 + QA
部门30 + QA

这与单独按照部门分组不同。


二十三、HAVING:对分组结果进行筛选

23.1 为什么需要 HAVING?

假设现在要求:

查询平均工资低于 2000 的部门。

我们首先想到:

sql 复制代码
SELECT
    deptno,
    AVG(sal)
FROM emp_demo
GROUP BY deptno;

这可以得到所有部门的平均工资。

但是,如果想只保留平均工资低于 2000 的部门,就需要进一步过滤分组结果。

此时应该使用 HAVING。

23.2 HAVING 基本语法

sql 复制代码
SELECT
    deptno,
    AVG(sal) AS 平均工资
FROM emp_demo
GROUP BY deptno
HAVING AVG(sal) < 2000;

结果:

deptno 平均工资
10 1650.00
30 1200.00

其中:

sql 复制代码
HAVING AVG(sal) < 2000

表示只保留平均工资小于 2000 的分组。

23.3 HAVING 可以使用别名

MySQL 5.7 支持在 HAVING 中使用 SELECT 定义的聚合结果别名。

例如:

sql 复制代码
SELECT
    deptno,
    AVG(sal) AS myavg
FROM emp_demo
GROUP BY deptno
HAVING myavg < 2000;

这里:

sql 复制代码
myavg

代表:

sql 复制代码
AVG(sal)

23.4 WHERE 与 HAVING 的区别

这是非常重要的一组概念。

特性 WHERE HAVING
作用阶段 分组之前 分组之后
主要筛选对象 原始记录 分组结果
是否适合直接筛选聚合函数 不适合 适合
是否必须与 GROUP BY 一起使用 否 否,但经常配合使用

例如:

sql 复制代码
SELECT
    deptno,
    AVG(sal) AS 平均工资
FROM emp_demo
WHERE sal > 1000
GROUP BY deptno
HAVING AVG(sal) < 2000;

执行逻辑是:

  1. WHERE 筛选工资大于 1000 的员工。
  2. GROUP BY 按部门分组。
  3. AVG 计算每个部门的平均工资。
  4. HAVING 筛选平均工资小于 2000 的分组。

因此:

WHERE 负责过滤原始记录,HAVING 主要用于过滤分组后的结果。


二十四、SQL 查询的逻辑执行顺序

我们平时书写 SQL 时,通常按照:

sql 复制代码
SELECT
FROM
WHERE
GROUP BY
HAVING
ORDER BY
LIMIT

的顺序。

但是,它们的逻辑处理顺序并不完全相同。

对于当前学习的单表查询,可以理解为:

text 复制代码
FROM
  ↓
WHERE
  ↓
GROUP BY
  ↓
HAVING
  ↓
SELECT
  ↓
DISTINCT
  ↓
ORDER BY
  ↓
LIMIT

例如:

sql 复制代码
SELECT
    deptno,
    AVG(sal) AS avg_salary
FROM emp_demo
WHERE sal > 1000
GROUP BY deptno
HAVING AVG(sal) < 2000
ORDER BY avg_salary DESC
LIMIT 2;

可以按照下面的方式理解:

第一步:FROM

确定数据来自 emp_demo。

第二步:WHERE

筛选工资大于 1000 的员工。

第三步:GROUP BY

按照部门编号分组。

第四步:HAVING

只保留平均工资低于 2000 的部门。

第五步:SELECT

生成需要返回的部门编号和平均工资。

第六步:ORDER BY

按照平均工资降序排列。

第七步:LIMIT

最多返回两条结果。

需要说明:

这里的执行顺序是一种用于理解 SQL 语义的逻辑顺序,并不是 MySQL 存储引擎实际执行每个物理操作的固定顺序。

真实数据库会通过查询优化器选择执行计划。


二十五、常见错误与注意事项

25.1 UPDATE 忘记 WHERE

危险 SQL:

sql 复制代码
UPDATE students
SET name = '张三';

这可能修改整张表中的所有记录。

建议先使用 SELECT 验证目标数据:

sql 复制代码
SELECT *
FROM students
WHERE id = 100;

确认后再执行 UPDATE。

25.2 DELETE 忘记 WHERE

危险 SQL:

sql 复制代码
DELETE FROM students;

这会删除整张表中的记录。

实际业务开发中,应当在删除前确认筛选条件。

25.3 使用 = NULL

错误:

sql 复制代码
SELECT *
FROM students
WHERE qq = NULL;

正确:

sql 复制代码
SELECT *
FROM students
WHERE qq IS NULL;

25.4 在 WHERE 中使用 SELECT 别名

通常不能直接写:

sql 复制代码
SELECT
    chinese + math + english AS total
FROM exam_result
WHERE total > 200;

应该写:

sql 复制代码
SELECT
    chinese + math + english AS total
FROM exam_result
WHERE chinese + math + english > 200;

25.5 DISTINCT 的去重范围理解错误

sql 复制代码
SELECT DISTINCT chinese, math
FROM exam_result;

去重的是两列的组合,而不是分别去重。

25.6 分页查询没有 ORDER BY

sql 复制代码
SELECT *
FROM exam_result
LIMIT 10 OFFSET 10;

没有指定排序规则时,不应认为结果具有固定顺序。

建议:

sql 复制代码
SELECT *
FROM exam_result
ORDER BY id
LIMIT 10 OFFSET 10;

25.7 COUNT(*) 与 COUNT(column) 混淆

sql 复制代码
COUNT(*)

统计所有符合条件的记录。

sql 复制代码
COUNT(qq)

统计 qq 不为 NULL 的记录。

如果字段存在 NULL,两者的结果可能不同。

25.8 把 TRUNCATE 当成普通 DELETE

TRUNCATE 通常会清空整表并重置自增长计数器。

由于它属于 DDL 操作,不应该把它当作可以普通回滚的 DELETE。


二十六、综合实战:学生成绩统计系统

下面将前面的知识组合起来,完成一个简单的成绩统计任务。

为避免受前面 UPDATE、DELETE 实验影响,使用新的数据库表:

sql 复制代码
CREATE TABLE score_demo (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20) NOT NULL,
    class_id INT NOT NULL,
    chinese INT,
    math INT,
    english INT
);

插入数据:

sql 复制代码
INSERT INTO score_demo
(name, class_id, chinese, math, english)
VALUES
('张三', 1, 85, 90, 88),
('李四', 1, 78, 92, 80),
('王五', 1, 95, 87, 91),
('赵六', 2, 65, 70, 72),
('小明', 2, 88, 85, 90),
('小红', 2, 92, 94, 96);

26.1 查询所有学生总分

sql 复制代码
SELECT
    id,
    name,
    chinese + math + english AS total
FROM score_demo;

26.2 查询总分最高的三名学生

sql 复制代码
SELECT
    name,
    chinese + math + english AS total
FROM score_demo
ORDER BY total DESC, id ASC
LIMIT 3;

26.3 查询数学成绩大于 90 的学生

sql 复制代码
SELECT name, math
FROM score_demo
WHERE math > 90;

26.4 查询语文和英语都大于 80 的学生

sql 复制代码
SELECT name, chinese, english
FROM score_demo
WHERE chinese > 80
AND english > 80;

26.5 统计每个班级的人数

sql 复制代码
SELECT
    class_id,
    COUNT(*) AS student_count
FROM score_demo
GROUP BY class_id;

26.6 统计每个班级的数学平均分

sql 复制代码
SELECT
    class_id,
    AVG(math) AS avg_math
FROM score_demo
GROUP BY class_id;

26.7 查询数学平均分大于 85 的班级

sql 复制代码
SELECT
    class_id,
    AVG(math) AS avg_math
FROM score_demo
GROUP BY class_id
HAVING AVG(math) > 85;

26.8 查询每个班级的最高总分

sql 复制代码
SELECT
    class_id,
    MAX(chinese + math + english) AS max_total
FROM score_demo
GROUP BY class_id;

通过这组实验,可以进一步理解:

  • SELECT 用于确定输出数据。
  • WHERE 用于筛选记录。
  • ORDER BY 用于排序。
  • LIMIT 用于控制结果数量。
  • COUNT、AVG、MAX 用于统计。
  • GROUP BY 用于分组。
  • HAVING 用于筛选分组结果。

二十七、SQL 基础查询练习题

掌握以上内容后,可以进一步进行 SQL 专项练习。

建议按照由易到难的顺序:

基础查询:

  • 批量插入数据。
  • 查询指定字段。
  • 使用 DISTINCT 去重。
  • 按照工资降序排列。
  • 查询最新入职的员工。
  • 查询排名倒数第三的入职员工。

统计与分组:

  • 统计每个部门员工数量。
  • 统计每个岗位的平均工资。
  • 查询工资变化次数超过指定数量的员工。
  • 统计每个部门当前管理者的薪资情况。
  • 查询出现次数不少于两次的岗位名称。

LeetCode SQL 练习:

  • Duplicate Emails:查找重复邮箱。
  • Big Countries:查询符合条件的国家。
  • Nth Highest Salary:查询第 N 高的工资。

这些题目主要考察:

text 复制代码
SELECT
WHERE
DISTINCT
ORDER BY
LIMIT
COUNT
GROUP BY
HAVING

熟练完成这些题目之后,就能够应对大量基础 SQL 查询需求。


二十八、常用 SQL 命令速查

SQL 命令 核心作用
INSERT INTO 插入数据
VALUES 指定插入值
ON DUPLICATE KEY UPDATE 冲突时更新已有记录
REPLACE INTO 插入或替换记录
SELECT 查询数据
DISTINCT 查询结果去重
AS 设置列别名
WHERE 筛选记录
AND 逻辑与
OR 逻辑或
NOT 逻辑非
BETWEEN ... AND ... 范围匹配
IN (...) 集合匹配
LIKE 模式匹配
IS NULL 判断 NULL
IS NOT NULL 判断非 NULL
ORDER BY 排序
ASC 升序
DESC 降序
LIMIT 限制返回数量
OFFSET 指定偏移量
UPDATE 修改记录
DELETE 删除记录
TRUNCATE 清空整表
INSERT ... SELECT 插入查询结果
COUNT 统计数量
SUM 求和
AVG 求平均值
MAX 求最大值
MIN 求最小值
GROUP BY 分组
HAVING 筛选分组结果

二十九、总结

本篇系统学习了 MySQL 中最常用的数据操作与基础查询语法。

主要可以分成以下几个部分。

1. 数据插入

通过:

sql 复制代码
INSERT INTO

向表中添加记录。

通过:

sql 复制代码
ON DUPLICATE KEY UPDATE

处理主键或唯一键冲突时的更新需求。

2. 数据查询

通过:

sql 复制代码
SELECT

查询指定数据。

通过:

sql 复制代码
WHERE

筛选满足条件的记录。

通过:

sql 复制代码
ORDER BY

完成排序。

通过:

sql 复制代码
LIMIT

完成结果数量限制和基础分页。

3. 数据修改与删除

通过:

sql 复制代码
UPDATE

修改记录。

通过:

sql 复制代码
DELETE

删除指定记录。

通过:

sql 复制代码
TRUNCATE

清空整表。

其中最需要注意的是:

UPDATE 和 DELETE 一旦缺少 WHERE,可能影响整张表。

因此,实际开发中必须养成先检查、再修改的习惯。

4. 聚合统计

通过:

sql 复制代码
COUNT()
SUM()
AVG()
MAX()
MIN()

完成常见统计需求。

5. 分组查询

通过:

sql 复制代码
GROUP BY

按照指定字段分组。

通过:

sql 复制代码
HAVING

对分组结果进行条件筛选。

在实际业务系统中,这些语法经常结合使用。

例如:

sql 复制代码
SELECT
    class_id,
    COUNT(*) AS student_count,
    AVG(math) AS avg_math
FROM score_demo
WHERE math >= 60
GROUP BY class_id
HAVING COUNT(*) >= 2
ORDER BY avg_math DESC
LIMIT 10;

这条 SQL 同时涉及:

  • WHERE 条件过滤。
  • GROUP BY 分组。
  • COUNT 和 AVG 聚合。
  • HAVING 分组筛选。
  • ORDER BY 排序。
  • LIMIT 限制结果。

掌握这些基础查询操作,是后续学习多表连接、索引优化、事务管理以及 C/C++ 数据库开发的重要前提。

相关推荐
Z21-X1 小时前
云原生运维学习笔记:ECShop全链路容器化部署与Docker底层资源隔离深度剖析
运维·笔记·学习·云原生
Pniubi2 小时前
MySQL靠什么实现事务的?事务隔离级别有哪些?
java·数据库·mysql
数据库小学妹3 小时前
pgbouncer 事务池:prepared statement、临时表与会话状态
数据库·后端·postgresql
aiks_yuan3 小时前
MySQL 死锁怎么排查:看懂 INNODB STATUS 死锁日志,先查后插为什么必死锁(MySQL 8.2 实测)
mysql
陈希1233 小时前
Linux 网络诊断工具实战:ping、curl、tcpdump、mtr、dig 用法详解
linux·网络·tcpdump
进击的荆棘3 小时前
Linux系统——线程同步与互斥之线程池和安全
linux·运维·服务器·安全·单例模式·线程
刘大望3 小时前
不用买开发板:用 QEMU 走通嵌入式 Linux 全流程(三)存储与网络实地验证
linux·qemu·嵌入式linux·alpine
Lucifer三思而后行4 小时前
最近折腾了一下金仓数据库的备份恢复
数据库·备份恢复
蜗牛一步步往上爬4 小时前
第六章 BMC常用的调试工具
服务器