今天的课程正式进入 MySQL。主要内容包括 MySQL 的基本概念、DQL 数据查询语言、条件查询、排序查询,以及字符串、数学和日期函数。
课程使用 myemployees 数据库中的 employees 员工表进行练习。表中包含员工编号、姓名、工资、奖金率、部门编号和入职日期等信息,非常适合用来学习 SQL 查询。
一、认识 MySQL 和数据库
数据库可以理解为按照一定结构保存数据的仓库。相比普通文本文件,数据库更适合保存大量数据,并且能够快速完成查询、修改和统计。
MySQL 是一种关系型数据库管理系统。所谓关系型数据库,就是把数据保存到一张张二维表中。
例如员工表:
| employee_id | first_name | salary | department_id |
|---|---|---|---|
| 100 | Steven | 24000 | 90 |
| 101 | Neena | 17000 | 90 |
| 102 | Lex | 17000 | 90 |
其中:
一行表示一条记录;
一列表示一个字段;
表示同一类数据的多条记录保存在同一张表中;
多张表共同组成一个数据库。
SQL 语言可以分成几个部分:
DDL:定义数据库、表等结构;
DML:新增、修改和删除数据;
DQL:查询数据;
DCL:控制用户和权限;
TCL:控制事务。
今天主要学习的是 DQL,核心命令就是 SELECT。
二、连接和选择数据库
在 Linux 中可以先检查 MySQL 服务状态:
systemctl status mysqld
如果服务没有启动,可以执行:
systemctl start mysqld
在终端中登录:
mysql -uroot -p
查看服务器中有哪些数据库:
SHOW DATABASES;
选择课程使用的数据库:
USE myemployees;
查看当前数据库中的表:
SHOW TABLES;
查看员工表的结构:
DESC employees;
在 Navicat 等图形化工具中,也需要先选择连接和数据库,再创建查询窗口执行 SQL。
三、DQL 的基本查询语法
最基础的查询格式是:
SELECT 查询列表
FROM 表名;
SELECT 表示选择哪些内容,FROM 表示从哪张表中查询。
1. 查询单个字段
SELECT last_name
FROM employees;
2. 查询多个字段
SELECT
employee_id,
first_name,
last_name
FROM employees;
字段之间使用英文逗号分隔,最后一个字段后面不写逗号。
3. 查询所有字段
SELECT *
FROM employees;
* 表示所有字段。练习时这样写比较方便,但在实际项目中更推荐写出需要的字段,减少无用数据的传输。
4. 查询常量和表达式
SELECT 不一定必须配合表使用:
SELECT 100;
SELECT 'hello mysql';
SELECT 10 + 20;
还可以在查询表数据时进行计算:
SELECT
first_name,
salary,
salary * 12
FROM employees;
这里的 salary * 12 表示计算员工的基础年薪。
四、为查询结果设置别名
计算结果的默认列名通常不够直观,可以使用别名:
SELECT
first_name AS 姓名,
salary AS 月薪,
salary * 12 AS 年薪
FROM employees;
AS 可以省略:
SELECT salary * 12 年薪
FROM employees;
如果别名中包含空格或特殊字符,应使用反引号:
SELECT salary * 12 AS `annual salary`
FROM employees;
设置别名只会改变查询结果中的显示名称,不会修改原表字段。
五、MySQL 中加号的特点
在某些编程语言中,+ 既可以做加法,也可以拼接字符串。但 MySQL 中的 + 主要用于数学运算。
SELECT 66 + 99;
结果为:
165
当一边是数字字符串时,MySQL 会尝试把字符串转换为数字:
SELECT 66 + '99';
结果仍然是:
165
如果字符串不能正常转换为数字,通常会被转换成 0,并可能产生警告:
SELECT 66 + 'abc';
另外,只要运算中出现 NULL,结果通常也是 NULL:
SELECT NULL + 70;
因此,拼接字符串不能依赖 +,应该使用 CONCAT()。
六、字符串拼接和空值处理
查询员工的完整姓名:
SELECT
CONCAT(first_name, ' ', last_name) AS full_name
FROM employees;
如果希望自动添加分隔符,可以使用:
SELECT
CONCAT_WS('-', '2026', '08', '17') AS current_date;
结果为:
2026-08-17
计算包含奖金的年薪时,需要注意 commission_pct 中可能存在空值。
SELECT
first_name,
salary,
salary * 12 * (1 + commission_pct) AS annual_salary
FROM employees;
当奖金率为 NULL 时,整个计算结果也会变成 NULL。可以使用 IFNULL():
SELECT
first_name,
salary,
salary * 12 * (1 + IFNULL(commission_pct, 0))
AS annual_salary
FROM employees;
IFNULL(commission_pct, 0) 表示:如果奖金率为空,就使用 0 代替。
七、去除重复数据
如果只想查看员工所在的部门编号,可以使用:
SELECT department_id
FROM employees;
但结果中会出现许多重复部门。使用 DISTINCT 可以去重:
SELECT DISTINCT department_id
FROM employees;
DISTINCT 作用于后面所有字段的组合:
SELECT DISTINCT department_id, job_id
FROM employees;
这表示去除"部门编号和职位编号都相同"的重复记录。
八、使用 WHERE 筛选数据
基本语法:
SELECT 字段
FROM 表名
WHERE 条件;
例如查询工资大于 12000 的员工:
SELECT
employee_id,
first_name,
salary
FROM employees
WHERE salary > 12000;
常见比较运算符包括:
| 运算符 | 作用 |
|---|---|
= |
等于 |
!= 或 <> |
不等于 |
> |
大于 |
< |
小于 |
>= |
大于等于 |
<= |
小于等于 |
例如查询部门编号不等于 90 的员工:
SELECT
first_name,
department_id
FROM employees
WHERE department_id <> 90;
九、逻辑运算符
多个条件可以使用 AND、OR 和 NOT 连接。
查询工资大于 8000,并且部门编号为 80 的员工:
SELECT
first_name,
salary,
department_id
FROM employees
WHERE salary > 8000
AND department_id = 80;
查询部门编号为 80 或 90 的员工:
SELECT
first_name,
department_id
FROM employees
WHERE department_id = 80
OR department_id = 90;
排除部门编号为 90 的员工:
SELECT
first_name,
department_id
FROM employees
WHERE NOT department_id = 90;
同时使用 AND 和 OR 时,建议加上小括号,让条件更加明确:
SELECT *
FROM employees
WHERE salary > 10000
AND (department_id = 80 OR department_id = 90);
十、BETWEEN 和 IN
查询员工编号在 100 到 120 之间的记录:
SELECT *
FROM employees
WHERE employee_id BETWEEN 100 AND 120;
BETWEEN 100 AND 120 包含边界,相当于:
WHERE employee_id >= 100
AND employee_id <= 120
查询职位编号属于几个指定值之一的员工:
SELECT
first_name,
job_id
FROM employees
WHERE job_id IN ('IT_PROG', 'AD_VP', 'AD_PRES');
它相当于:
WHERE job_id = 'IT_PROG'
OR job_id = 'AD_VP'
OR job_id = 'AD_PRES'
当同一个字段需要匹配多个确定值时,使用 IN 更简洁。
十一、LIKE 模糊查询
当我们只知道字符串的一部分时,可以使用 LIKE。
% 表示任意数量的任意字符:
SELECT first_name
FROM employees
WHERE first_name LIKE 'S%';
表示查询名字以 S 开头的员工。
SELECT first_name
FROM employees
WHERE first_name LIKE '%a%';
表示名字中包含字母 a。
下划线 _ 表示任意一个字符:
SELECT first_name
FROM employees
WHERE first_name LIKE '_a%';
表示名字的第二个字符是 a。
% 和 _ 是通配符。如果要查询它们本身,需要进行转义。
十二、正确判断 NULL
NULL 表示缺失、未知或不存在,它不等于数字 0,也不等于空字符串。
错误写法:
SELECT *
FROM employees
WHERE commission_pct = NULL;
正确写法:
SELECT *
FROM employees
WHERE commission_pct IS NULL;
查询奖金率不为空的员工:
SELECT *
FROM employees
WHERE commission_pct IS NOT NULL;
MySQL 还提供空值安全比较运算符 <=>:
SELECT *
FROM employees
WHERE commission_pct <=> NULL;
它可以用于普通值和 NULL,但为了提高可读性,判断空值时更推荐使用 IS NULL。
十三、ORDER BY 排序查询
排序语法:
SELECT 字段
FROM 表名
ORDER BY 排序字段 ASC或DESC;
ASC 表示升序,DESC 表示降序。如果不写,默认按照升序排列。
按工资从低到高排序:
SELECT
first_name,
salary
FROM employees
ORDER BY salary ASC;
按工资从高到低排序:
SELECT
first_name,
salary
FROM employees
ORDER BY salary DESC;
也可以按照表达式或别名排序:
SELECT
first_name,
salary,
salary * 12 * (1 + IFNULL(commission_pct, 0))
AS annual_salary
FROM employees
ORDER BY annual_salary DESC;
多字段排序:
SELECT
first_name,
department_id,
salary
FROM employees
ORDER BY department_id ASC, salary DESC;
含义是先按部门升序,同一部门内再按工资降序。
十四、常用字符串函数
1. LENGTH 和 CHAR_LENGTH
SELECT LENGTH('hello');
SELECT LENGTH('你好');
LENGTH() 返回字节数。英文一般一个字符占一个字节,而中文在 utf8mb4 中通常占多个字节。
如果想统计字符个数,可以使用:
SELECT CHAR_LENGTH('你好');
2. 大小写转换
SELECT UPPER('hello');
SELECT LOWER('MYSQL');
3. 截取字符串
SELECT SUBSTR('hello mysql', 7, 5);
MySQL 字符串位置通常从 1 开始。以上语句会从第 7 个字符开始,截取 5 个字符。
4. 查找位置
SELECT INSTR('hello mysql', 'mysql');
返回子字符串第一次出现的位置;没有找到时返回 0。
5. 去除空格
SELECT TRIM(' hello ');
还可以删除指定字符:
SELECT TRIM('x' FROM 'xxxhelloxxx');
6. 左右填充
SELECT LPAD('数加', 4, '*');
SELECT RPAD('数加', 4, '*');
分别表示从左侧和右侧填充字符,直到达到指定长度。
7. 替换字符串
SELECT REPLACE('hello mysql', 'mysql', 'database');
结果为:
hello database
十五、常用数学函数
四舍五入:
SELECT ROUND(3.1415926, 2);
向上取整:
SELECT CEIL(3.14);
SELECT CEIL(-3.14);
向下取整:
SELECT FLOOR(3.14);
SELECT FLOOR(-3.14);
直接截断小数,不进行四舍五入:
SELECT TRUNCATE(123.456, 2);
计算余数:
SELECT MOD(10, 3);
生成 0 到 1 之间的随机小数:
SELECT RAND();
CEIL(-3.14) 的结果是 -3,FLOOR(-3.14) 的结果是 -4。理解时不要简单记成"去掉小数",而要根据数轴方向判断。
十六、常用日期函数
获取当前日期和时间:
SELECT NOW();
只获取当前日期:
SELECT CURDATE();
只获取当前时间:
SELECT CURTIME();
提取日期中的年、月、日:
SELECT YEAR(NOW());
SELECT MONTH(NOW());
SELECT DAY(NOW());
提取时、分、秒:
SELECT HOUR(NOW());
SELECT MINUTE(NOW());
SELECT SECOND(NOW());
计算两个日期相差的天数:
SELECT DATEDIFF('2026-08-20', '2026-08-17');
十七、日期与字符串互相转换
1. STR_TO_DATE
将字符串转换成日期:
SELECT STR_TO_DATE('2026-08-17', '%Y-%m-%d');
查询指定入职日期的员工:
SELECT *
FROM employees
WHERE hiredate = STR_TO_DATE(
'4-3-1992',
'%c-%e-%Y'
);
2. DATE_FORMAT
将日期格式化成字符串:
SELECT DATE_FORMAT(
NOW(),
'%Y年%m月%d日 %H:%i:%s'
);
常用格式符号:
| 格式 | 含义 |
|---|---|
%Y |
四位年份 |
%y |
两位年份 |
%m |
两位月份 |
%c |
数字月份 |
%d |
两位日期 |
%e |
数字日期 |
%H |
24 小时制小时 |
%h |
12 小时制小时 |
%i |
分钟 |
%s |
秒 |
分钟使用 %i,而不是容易想当然写出的 %m,因为 %m 表示月份。
十八、综合查询案例
查询部门编号为 80 或 90、工资不低于 8000 的员工,显示完整姓名和年薪,并按年薪从高到低排列:
SELECT
employee_id,
CONCAT_WS(' ', first_name, last_name) AS full_name,
department_id,
salary,
salary * 12 * (1 + IFNULL(commission_pct, 0))
AS annual_salary,
DATE_FORMAT(hiredate, '%Y-%m-%d') AS hire_date
FROM employees
WHERE department_id IN (80, 90)
AND salary >= 8000
ORDER BY annual_salary DESC;
分析一条查询时,可以按照下面的顺序思考:
从哪张表查询
→ 查询哪些字段
→ 使用什么条件筛选
→ 是否需要计算或调用函数
→ 最后按照什么规则排序
总结
今天学习的核心是 SELECT 查询。最基础的结构是:
SELECT 字段
FROM 表
WHERE 条件
ORDER BY 排序规则;
在此基础上,又学习了比较运算、逻辑运算、BETWEEN、IN、LIKE、空值判断以及排序。函数部分则包括字符串处理、数值计算和日期格式转换。
初学 SQL 时,最容易出现的问题包括:忘记字段之间的逗号、把字符串拼接写成加号、使用 = NULL 判断空值,以及没有注意 AND 和 OR 的优先级。练习时可以先用自然语言描述需求,再分别确定查询表、字段、条件和排序方式,最后组合成完整 SQL。