一、引言:从"存数据"到"查数据"的飞跃
在前面的博客中,我们学习了MySQL的安装和基本操作。但数据库的真正价值,并不在于"存储"数据,而在于高效、灵活地"检索"数据。
想象一下:你手中有一张包含上万条记录的员工表,老板让你"找出工资高于平均水平的市场部员工,按入职时间排序,只显示前10名"。如果手动翻找,恐怕要花费几个小时。而使用数据查询语言(DQL,Data Query Language),只需一行SQL语句,几秒钟就能得到结果。
DQL是SQL语言中最核心、最常用的部分,它负责从数据库中查询数据。本篇博客将系统讲解DQL的完整语法,从基础查询到复杂的多表连接、子查询,配合大量实例,帮助大家真正掌握数据检索的精髓。
二、数据准备
为了便于演示,我们使用经典的"员工"数据库(myemployees.sql),包含以下四张表:
-
employees(员工表):employee_id, first_name, last_name, email, phone_number, job_id, salary, commission_pct, manager_id, department_id, hiredate
-
departments(部门表):department_id, department_name, manager_id, location_id
-
jobs(工种表):job_id, job_title, min_salary, max_salary
-
locations(位置表):location_id, street_address, postal_code, city, state_province, country_id
三、基础查询(SELECT)
3.1 语法结构
sql
SELECT 查询列表 FROM 表名;
-
查询列表可以是:字段、常量、表达式、函数
-
查询结果是一个虚拟的表格
3.2 常见操作
sql
-- 查询单个字段
SELECT last_name FROM employees;
-- 查询多个字段
SELECT employee_id, first_name, last_name FROM employees;
-- 查询所有字段(*)
SELECT * FROM employees;
-- 查询常量值
SELECT 66;
SELECT 'bigdata';
-- 查询表达式
SELECT 100 * 2;
-- 查询函数
SELECT VERSION();
-- 起别名(AS 或空格)
SELECT first_name AS 名, last_name 姓 FROM employees;
-- 去重(DISTINCT)
SELECT DISTINCT department_id FROM employees;
-- 拼接字符串(CONCAT)
SELECT CONCAT(last_name, first_name) AS 姓名 FROM employees;
特别注意 :MySQL中的+号只能用于数值相加,不能拼接字符串。拼接必须使用CONCAT()函数。对于NULL值,使用IFNULL()处理。
四、条件查询(WHERE)
4.1 语法
sql
SELECT 查询列表
FROM 表名
WHERE 筛选条件;
4.2 筛选条件分类
| 类型 | 运算符/关键字 |
|---|---|
| 条件表达式 | > < >= <= = != <> |
| 逻辑表达式 | AND OR NOT |
| 模糊查询 | LIKE BETWEEN AND IN IS NULL |
4.3 案例演示
sql
-- 条件表达式:工资大于12000
SELECT * FROM employees WHERE salary > 12000;
-- 逻辑表达式:部门编号不在90~110之间,或工资高于15000
SELECT * FROM employees
WHERE (department_id < 90 OR department_id > 110) OR salary > 15000;
-- LIKE:姓名中包含字符a
SELECT * FROM employees WHERE first_name LIKE '%a%';
-- LIKE:第三个字符为e,第五个为a
SELECT first_name, salary FROM employees
WHERE first_name LIKE '__e_a%';
-- 转义:查询姓中第二个字符为_的员工(使用\或ESCAPE)
SELECT last_name FROM employees
WHERE last_name LIKE '_\_%'; -- 或 '_$_%' ESCAPE '$'
-- BETWEEN AND:工资在10000到20000之间(包含临界值)
SELECT * FROM employees WHERE salary BETWEEN 10000 AND 20000;
-- IN:工种编号为指定值
SELECT first_name, job_id FROM employees
WHERE job_id IN ('IT_PROG', 'AD_VP', 'AD_PRES');
-- IS NULL / IS NOT NULL:查询没有奖金的员工
SELECT first_name, commission_pct FROM employees
WHERE commission_pct IS NULL;
-- 安全等于(<=>):可判断NULL和普通值
SELECT * FROM employees WHERE salary <=> 12000;
五、排序查询(ORDER BY)
5.1 语法
sql
SELECT 查询列表
FROM 表名
[WHERE 筛选条件]
ORDER BY 排序列表 [ASC | DESC];
-
ASC升序(默认),DESC降序
-
支持单个字段、多个字段、表达式、别名、函数
5.2 案例
sql
-- 按工资降序
SELECT * FROM employees ORDER BY salary DESC;
-- 按入职时间先后(升序)
SELECT * FROM employees WHERE department_id >= 90 ORDER BY hiredate ASC;
-- 按年薪排序(使用别名)
SELECT *, salary * 12 * (1 + IFNULL(commission_pct,0)) AS 年薪
FROM employees ORDER BY 年薪 DESC;
-- 按姓名长度排序(函数)
SELECT LENGTH(CONCAT(last_name,first_name)) 字节长度,
CONCAT(last_name,first_name) 姓名, salary
FROM employees ORDER BY 字节长度 DESC;
-- 多字段排序(先工资升序,再员工编号降序)
SELECT * FROM employees ORDER BY salary ASC, employee_id DESC;
六、常见函数
函数分为单行函数 (处理一行数据)和分组函数(统计多行)。
6.1 字符函数
| 函数 | 作用 | 示例 |
|---|---|---|
LENGTH(str) |
返回字节数 | LENGTH('hello') → 5 |
CONCAT(str1,str2) |
拼接 | CONCAT('a','b') → 'ab' |
UPPER(str) / LOWER(str) |
大小写转换 | |
SUBSTR(str,pos,len) |
截取子串(起始索引1) | SUBSTR('abcde',2,2) → 'bc' |
INSTR(str,sub) |
返回子串首次位置 | INSTR('abc','b') → 2 |
TRIM(str) |
去除两端空格或指定字符 | TRIM('s' FROM 'sssql') → 'sql' |
LPAD(str,len,pad) / RPAD |
左/右填充 | |
REPLACE(str,old,new) |
替换 |
6.2 数学函数
-
ROUND(number, decimals):四舍五入 -
CEIL(number)/FLOOR(number):向上/下取整 -
TRUNCATE(number, decimals):截断 -
MOD(N, M):取余
6.3 日期函数
sql
-- 当前日期时间
SELECT NOW(), CURDATE(), CURTIME();
-- 提取年月日等
SELECT YEAR(NOW()), MONTH(NOW()), DAY(NOW()), HOUR(NOW());
-- 字符串转日期
SELECT STR_TO_DATE('4-3 1992', '%c-%d %Y');
-- 日期格式化
SELECT DATE_FORMAT(NOW(), '%y年%m月%d日');
6.4 流程控制函数
sql
-- IF函数:如果条件成立返回value1,否则value2
SELECT IF(2<3, '大', '小');
-- CASE简单形式:等值判断
SELECT salary,
CASE department_id
WHEN 30 THEN salary * 1.1
WHEN 40 THEN salary * 1.2
ELSE salary
END AS 新工资
FROM employees;
-- CASE搜索形式:条件判断
SELECT salary,
CASE
WHEN salary > 20000 THEN 'A'
WHEN salary > 15000 THEN 'B'
WHEN salary > 10000 THEN 'C'
ELSE 'D'
END AS 工资级别
FROM employees;
七、分组函数(聚合函数)
| 函数 | 说明 |
|---|---|
SUM() |
求和(数值型) |
AVG() |
平均值(数值型) |
MAX() / MIN() |
最大/最小值(任意类型) |
COUNT() |
计数(任意类型) |
特点:
-
自动忽略
NULL值 -
可与
DISTINCT搭配去重 -
COUNT(*)统计行数,推荐使用
sql
SELECT SUM(salary), AVG(salary), MAX(salary), MIN(salary), COUNT(salary)
FROM employees;
-- 去重统计
SELECT COUNT(DISTINCT salary) FROM employees;
-- 注意:与分组函数同查的字段,必须出现在GROUP BY中
SELECT AVG(salary), employee_id FROM employees; -- 错误
八、分组查询(GROUP BY)
8.1 语法
sql
SELECT 分组函数, 列(要求出现在GROUP BY后)
FROM 表名
[WHERE 筛选条件] -- 分组前筛选
GROUP BY 分组列表
[HAVING 分组后筛选] -- 分组后筛选
[ORDER BY 排序];
8.2 案例
sql
-- 查询每个部门的平均工资
SELECT AVG(salary), department_id
FROM employees
GROUP BY department_id;
-- 查询哪个部门的员工个数大于2(分组后筛选使用HAVING)
SELECT COUNT(*), department_id
FROM employees
GROUP BY department_id
HAVING COUNT(*) > 2;
-- 查询领导编号>102的每个领导手下最低工资>5000的领导编号
SELECT MIN(salary), manager_id
FROM employees
WHERE manager_id > 102
GROUP BY manager_id
HAVING MIN(salary) > 5000;
-- 按姓名长度分组,筛选个数>5
SELECT COUNT(*) AS c, LENGTH(CONCAT(last_name,first_name)) len_name
FROM employees
GROUP BY len_name
HAVING c > 5;
-- 多字段分组(每个部门每个工种的平均工资)
SELECT AVG(salary), department_id, job_id
FROM employees
GROUP BY job_id, department_id;
-- 添加排序
SELECT AVG(salary) AS a, department_id, job_id
FROM employees
WHERE department_id IS NOT NULL
GROUP BY job_id, department_id
HAVING a > 10000
ORDER BY a DESC;
九、连接查询(多表查询)
当查询涉及多个表时,需要通过连接条件将表关联起来。
9.1 SQL99标准语法
sql
SELECT 查询列表
FROM 表1 别名
[INNER | LEFT | RIGHT] JOIN 表2 别名
ON 连接条件
[WHERE 筛选]
[GROUP BY ...]
[HAVING ...]
[ORDER BY ...];
9.2 内连接(INNER JOIN)
只返回两个表中匹配的行。
sql
-- 等值连接:员工名和部门名
SELECT first_name, department_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id;
-- 三表连接:员工名、部门名、工种名
SELECT first_name, department_name, job_title
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id
INNER JOIN jobs j ON e.job_id = j.job_id;
-- 非等值连接:员工的工资级别
SELECT salary, grade_level
FROM employees e
INNER JOIN job_grades g ON e.salary BETWEEN g.lowest_sal AND g.highest_sal;
-- 自连接:员工与其上级
SELECT e.first_name 员工, m.first_name 上级
FROM employees e
INNER JOIN employees m ON e.manager_id = m.employee_id;
9.3 外连接(LEFT / RIGHT JOIN)
返回主表全部记录,从表没有匹配则显示NULL。左外连接左表为主,右外连接右表为主。
sql
-- 查询没有男朋友的女神(左外)
SELECT b.name, bo.*
FROM beauty b
LEFT OUTER JOIN boys bo ON b.boyfriend_id = bo.id
WHERE bo.id IS NULL;
-- 查询没有员工的部门(右外)
SELECT d.*, e.employee_id
FROM employees e
RIGHT OUTER JOIN departments d ON d.department_id = e.department_id
WHERE e.employee_id IS NULL;
9.4 交叉连接(CROSS JOIN)
即笛卡尔积,一般很少单独使用。
sql
SELECT b.*, bo.* FROM beauty b CROSS JOIN boys bo;
十、子查询
子查询是嵌套在其他SQL语句中的SELECT查询,分为标量子查询(一行一列)、列子查询(一列多行)、行子查询(一行多列)和表子查询(多行多列)。
10.1 WHERE / HAVING 后面的子查询
sql
-- 标量子查询:查询工资比Abel高的人
SELECT * FROM employees
WHERE salary > (SELECT salary FROM employees WHERE last_name = 'Abel');
-- 列子查询(IN / ANY / ALL)
-- 查询location_id为1400或1700的部门的所有员工
SELECT first_name FROM employees
WHERE department_id IN (
SELECT DISTINCT department_id FROM departments
WHERE location_id IN (1400, 1700)
);
-- ANY:比IT_PROG工种任一工资低的员工
SELECT first_name, salary
FROM employees
WHERE salary < ANY (SELECT DISTINCT salary FROM employees WHERE job_id = 'IT_PROG');
-- ALL:比IT_PROG所有工资都低的员工
SELECT first_name, salary
FROM employees
WHERE salary < ALL (SELECT DISTINCT salary FROM employees WHERE job_id = 'IT_PROG');
-- 行子查询:查询员工编号最小且工资最高的员工
SELECT * FROM employees
WHERE (employee_id, salary) = (
SELECT MIN(employee_id), MAX(salary) FROM employees
);
10.2 SELECT 后面的子查询(标量)
sql
-- 查询每个部门的员工个数
SELECT d.*, (SELECT COUNT(*) FROM employees e WHERE e.department_id = d.department_id) 个数
FROM departments d;
10.3 FROM 后面的子查询(表子查询)
sql
-- 查询每个部门平均工资的工资等级
SELECT ag_dep.*, g.grade_level
FROM (
SELECT AVG(salary) ag, department_id
FROM employees
GROUP BY department_id
) ag_dep
INNER JOIN job_grades g ON ag_dep.ag BETWEEN g.lowest_sal AND g.highest_sal;
10.4 EXISTS 后面的相关子查询
sql
-- 查询有员工的部门
SELECT department_name
FROM departments d
WHERE EXISTS (SELECT * FROM employees e WHERE d.department_id = e.department_id);
十一、分页查询(LIMIT)
用于分页显示,必须放在SQL语句最后。
sql
SELECT 查询列表
FROM 表名
...
LIMIT [offset,] size;
-- offset:起始索引(从0开始),size:条目数
-- 查询前5条
SELECT * FROM employees LIMIT 5; -- 等价于 LIMIT 0,5
-- 查询第11~25条(第3页,每页10条)
SELECT * FROM employees LIMIT 10, 15;
-- 公式:(page-1)*size, size
十二、联合查询(UNION)
将多个查询结果合并成一个结果集,要求列数和类型一致。UNION默认去重,UNION ALL保留重复。
sql
-- 查询部门编号>90或邮箱包含a的员工
SELECT * FROM employees WHERE email LIKE '%a%'
UNION
SELECT * FROM employees WHERE department_id > 90;
十三、总结
| 知识点 | 核心语法 | 适用场景 |
|---|---|---|
| 基础查询 | SELECT ... FROM ... |
简单数据提取 |
| 条件查询 | WHERE + 条件 |
数据筛选 |
| 排序查询 | ORDER BY |
结果排序 |
| 常用函数 | 字符、数学、日期、流程控制 | 数据变换 |
| 分组函数 | SUM/AVG/MAX/MIN/COUNT |
统计计算 |
| 分组查询 | GROUP BY + HAVING |
分组统计 |
| 连接查询 | INNER/LEFT/RIGHT JOIN |
多表关联 |
| 子查询 | 嵌套SELECT |
复杂条件依赖 |
| 分页查询 | LIMIT |
数据分页展示 |
| 联合查询 | UNION |
合并结果集 |