从零起步学习MySQL 第四章:DQL查询全解析

对于后端开发者、数据库初学者来说,MySQL中的DQL(数据查询语言)是日常工作中最常用、最核心的知识点之一。不同于DML(数据操纵语言)的"增删改",DQL专注于"查数据"------如何快速、准确地从数据库中提取所需信息,直接决定了系统的性能和开发效率。今天,我们就从概念到实战,一步步拆解DQL的核心用法,帮你彻底吃透MySQL查询技巧。

一、先搞懂:DQL到底是什么?

DQL的全称是Data Query Language,即数据查询语言,它的核心作用就是从数据库表中查询符合条件的数据,是MySQL中使用频率最高的语言模块。

这里我们可以用一个简单的类比理解DQL与DML的区别:

  • DML(INSERT、UPDATE、DELETE):相当于"修改数据仓库",负责往仓库里放数据、改数据、删数据;

  • DQL(核心是SELECT语句):相当于"从仓库里找东西",根据你的需求,精准找到想要的数据,不会对原有数据做任何修改。

简单来说,只要你需要从数据库中"查东西",就离不开DQL,而SELECT语句,就是DQL的灵魂。

二、核心框架:SELECT语句的基本结构与执行顺序

很多新手写SELECT语句时容易混乱,不知道先写什么、后写什么,其实只要记住它的基本结构和执行顺序,就能轻松上手。

SELECT语句的完整结构(7个核心部分):

sql 复制代码
SELECT [字段列表]
FROM [表名]
WHERE [筛选条件]
GROUP BY [分组字段]
HAVING [分组后的筛选条件]
ORDER BY [排序字段]
LIMIT [分页限制]

这里有一个关键知识点------SELECT语句的执行顺序,很多人会误以为执行顺序和书写顺序一致,其实不然,正确的执行顺序是:

FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT

举个通俗的例子:你要从"学生表"中找"18-22岁的计算机系学生,按年龄降序排列,取前10条",执行逻辑就是:先找到学生表(FROM),筛选出18-22岁的学生(WHERE),再按专业分组(如果有需要),过滤分组后的结果(如果有需要),然后选择要显示的字段(SELECT),按年龄降序排序(ORDER BY),最后取前10条(LIMIT)。

记住这个执行顺序,能帮你快速排查查询语句的错误,也能更精准地优化查询性能。

三、基础查询:从零开始写第一个SELECT语句

基础查询是DQL的入门,主要包括查询所有字段、给字段起别名、去重查询三种常见场景,适合新手快速上手。

1. 查询所有字段

最简单的查询方式,使用"*"表示查询表中的所有字段:

sql 复制代码
SELECT * FROM student;

注意 :这种方式虽然简单,但不建议在生产环境中使用。因为"*"会查询表中所有字段,包括不需要的字段,不仅会增加数据库的查询压力、影响性能,还会降低代码的可维护性(比如表结构修改后,查询结果可能出现异常)。

推荐写法:明确写出需要查询的字段名,精准高效:

sql 复制代码
SELECT id, name, age, department FROM student;

2. 给字段起别名

当字段名过于简洁、不直观,或者在多表查询中出现字段名重复时,我们可以给字段起一个别名,方便阅读和使用,使用关键字"AS"(可省略)。

sql 复制代码
-- 给name起别名为"姓名",age起别名为"年龄"
SELECT name AS 姓名, age AS 年龄 FROM student;
-- 省略AS,效果一致
SELECT name 姓名, age 年龄 FROM student;

别名的核心作用的是"简化显示",尤其是在前端展示数据时,用中文别名会更直观,多表查询时,也能避免字段名冲突。

3. 去重查询

当表中存在重复数据时,我们可以使用"DISTINCT"关键字去除重复值,只保留唯一的记录。

sql 复制代码
-- 查询所有不重复的专业(去除重复的department值)
SELECT DISTINCT department FROM student;

注意:DISTINCT会对所有指定的字段一起去重,比如"SELECT DISTINCT department, age FROM student",会去除"专业+年龄"都相同的重复记录,而不是只对department去重。

四、条件查询:精准筛选你想要的数据(WHERE)

基础查询只能查询所有或指定字段的全部数据,而实际开发中,我们往往需要筛选出符合特定条件的数据,这就需要用到WHERE子句。WHERE子句的核心是"筛选条件",由运算符和值组成。

1. 常见比较运算符

比较运算符用于判断字段值与指定值之间的关系,是WHERE子句中最常用的运算符,具体如下:

运算符 含义 示例
= 等于 WHERE age = 20(查询年龄为20的学生)
<> 或 != 不等于 WHERE department != 'CS'(查询非计算机系学生)
>、<、>=、<= 大于、小于、大于等于、小于等于 WHERE age >= 18(查询成年学生)
BETWEEN ... AND ... 在某个范围内(包含边界值) WHERE age BETWEEN 18 AND 22(18≤年龄≤22)
IN (...) 在指定集合中 WHERE department IN ('CS', 'Math')(计算机系或数学系)
LIKE 模糊匹配 WHERE name LIKE '张%'(查询姓张的学生)
IS NULL / IS NOT NULL 判断字段是否为空 WHERE email IS NULL(查询未填写邮箱的学生)

这里重点说一下模糊匹配"LIKE"的用法:

  • %:匹配任意长度的字符(包括0个字符),比如"张%"可以匹配"张三""张三丰""张小明";

  • :匹配单个字符,比如"张"只能匹配"张三""张伟"(两个字的姓张的名字)。

2. 逻辑运算符

当需要多个筛选条件同时生效时,就需要用到逻辑运算符,将多个条件连接起来,常用的有3个:

运算符 含义 示例
AND 并且(多个条件同时满足) WHERE age >= 18 AND department = 'CS'(成年的计算机系学生)
OR 或者(多个条件满足一个即可) WHERE age < 18 OR department = 'Math'(未成年或数学系学生)
NOT 取反(不满足某个条件) WHERE NOT age BETWEEN 18 AND 22(年龄不在18-22之间)

注意:逻辑运算符的优先级是 NOT > AND > OR,如果有复杂的条件组合,建议使用括号()明确优先级,避免查询结果出错。

五、排序查询:让结果更有规律(ORDER BY)

查询出来的数据默认是无序的,我们可以使用ORDER BY子句对查询结果按指定字段排序,让数据更易读。

排序的核心参数:

  • ASC:升序排序(默认值,可省略),比如按年龄从大到小排列;

  • DESC:降序排序,比如按年龄从大到小排列。

sql 复制代码
-- 按年龄降序排序(从大到小)
SELECT * FROM student ORDER BY age DESC;
-- 按年龄升序排序(从小到大,省略ASC)
SELECT * FROM student ORDER BY age;

实际开发中,我们也可以按多个字段排序,先按第一个字段排序,第一个字段相同的情况下,再按第二个字段排序:

sql 复制代码
-- 先按专业升序,同一专业内按年龄降序
SELECT * FROM student ORDER BY department ASC, age DESC;

六、聚合函数:对数据进行统计分析(Aggregate Functions)

当需要对查询到的数据进行统计(比如计数、求和、求平均值)时,就需要用到聚合函数。聚合函数会对一组数据进行计算,返回一个单一的值,常用的聚合函数有5个:

函数 功能 示例
COUNT() 计数(统计符合条件的记录数) COUNT(*) AS 总人数(统计学生总数)
SUM() 求和(计算指定字段的总和) SUM(score) AS 总分(统计某门课程的总分)
AVG() 求平均值 AVG(age) AS 平均年龄(计算学生平均年龄)
MAX() 求最大值 MAX(age) AS 最大年龄(查询最大年龄)
MIN() 求最小值 MIN(age) AS 最小年龄(查询最小年龄)

示例:统计学生表中的总人数和平均年龄:

sql 复制代码
SELECT COUNT(*) AS 人数, AVG(age) AS 平均年龄 FROM student;

注意:聚合函数默认对整个表的数据进行计算,如果需要按分组统计,需要结合GROUP BY子句使用。

七、分组查询:按指定规则分组统计(GROUP BY / HAVING)

分组查询的核心是"GROUP BY"子句,它可以将数据按指定字段分组,然后对每个分组进行聚合统计。而"HAVING"子句则用于对分组后的结果进行筛选,相当于"分组后的WHERE"。

1. 基本用法

示例:按专业分组,统计每个专业的学生人数:

sql 复制代码
SELECT department, COUNT(*) AS 人数
FROM student
GROUP BY department;

这里的逻辑是:先按department(专业)分组,将相同专业的学生归为一组,然后对每个组执行COUNT(*)计数,最终得到每个专业的学生人数。

2. HAVING与WHERE的核心区别

很多新手会混淆HAVING和WHERE,其实两者的核心区别在于"筛选时机不同":

  • WHERE:在分组前筛选记录,筛选的是原始数据,不能使用聚合函数;

  • HAVING:在分组后筛选结果,筛选的是分组后的统计结果,可以使用聚合函数。

示例:按专业分组,统计学生人数大于10的专业:

sql 复制代码
SELECT department, COUNT(*) AS 人数
FROM student
GROUP BY department
HAVING COUNT(*) > 10;

如果把这里的HAVING换成WHERE,会报错------因为WHERE不能使用聚合函数COUNT(*),而HAVING可以筛选分组后的统计结果。

八、分页查询:解决大数据量查询问题(LIMIT)

当表中的数据量很大时,一次性查询所有数据会占用大量的数据库资源,也会导致前端加载缓慢,这时就需要用到分页查询------只查询当前页需要的数据,减少数据传输量。

MySQL中分页的语法是:

sql 复制代码
LIMIT [偏移量], [行数]

参数说明:

  • 偏移量:从第几条数据开始查询(默认从0开始,即第一条数据的偏移量是0);

  • 行数:本次查询要返回的记录数。

示例:

sql 复制代码
-- 查询前10条数据(偏移量0,取10条)
SELECT * FROM student LIMIT 0, 10;
-- 查询第11到20条数据(偏移量10,取10条)
SELECT * FROM student LIMIT 10, 10;

在Java后端开发中(比如Spring Boot + MyBatis),分页查询非常常见,通常会将偏移量和行数封装成参数,动态拼接SQL:

sql 复制代码
SELECT * FROM student LIMIT #{offset}, #{pageSize};

其中,offset(偏移量)=(当前页码-1)× pageSize(每页行数),比如第2页、每页10条,偏移量就是10。

九、连接查询:多表关联查询(JOIN)

实际开发中,数据往往分散在多个表中(比如学生表、课程表、成绩表),我们需要通过表与表之间的关联关系,查询出跨表的数据,这就需要用到连接查询。MySQL中最常用的连接查询有3种:内连接、左连接、右连接。

为了方便理解,我们假设存在两个表:student(学生表,包含id、name、department)和course(课程表,包含id、course_name、student_id),其中course表的student_id与student表的id关联。

1. 内连接(INNER JOIN)

内连接是最常用的连接方式,它只返回两个表中"匹配的行"------即两个表中关联字段相等的记录,不匹配的记录会被过滤掉。

sql 复制代码
SELECT s.name, c.course_name
FROM student s  -- 给student表起别名s
INNER JOIN course c  -- 给course表起别名c
ON s.id = c.student_id;  -- 关联条件:学生id等于课程表中的学生id

上述语句会查询出"有课程记录的学生"及其对应的课程名称,没有选课的学生、没有对应学生的课程,都不会被查询出来。

2. 左连接(LEFT JOIN)

左连接会返回"左表(LEFT JOIN左边的表)的所有行",即使右表没有匹配的行,右表的字段会显示为NULL。

sql 复制代码
SELECT s.name, c.course_name
FROM student s
LEFT JOIN course c
ON s.id = c.student_id;

上述语句会查询出所有学生的信息,即使某个学生没有选课(右表course没有匹配的记录),该学生的name会显示,course_name会显示为NULL。

3. 右连接(RIGHT JOIN)

右连接与左连接相反,会返回"右表(RIGHT JOIN右边的表)的所有行",即使左表没有匹配的行,左表的字段会显示为NULL。

sql 复制代码
SELECT s.name, c.course_name
FROM student s
RIGHT JOIN course c
ON s.id = c.student_id;

上述语句会查询出所有课程的信息,即使某门课程没有对应的学生(左表student没有匹配的记录),该课程的course_name会显示,name会显示为NULL。

十、子查询:嵌套查询(Subquery)

子查询是指"一个SELECT语句嵌套在另一个查询语句中",嵌套的SELECT语句称为"子查询",外层的SELECT语句称为"主查询"。子查询的核心作用是"用子查询的结果作为主查询的条件或数据源"。

1. 作为条件使用

示例:查询"计算机系(CS)的学生姓名",其中计算机系的id需要从department表中查询:

sql 复制代码
SELECT name
FROM student
WHERE department_id = (
    -- 子查询:查询计算机系的id
    SELECT id FROM department WHERE name = 'CS'
);

这里的子查询会先执行,得到计算机系的id,然后主查询根据这个id,查询出对应的学生姓名。

2. 作为虚拟表使用

子查询的结果可以作为一个"虚拟表",供主查询使用,这种情况下需要给子查询起一个别名。

sql 复制代码
-- 查询男生的平均年龄,先通过子查询筛选出男生,再计算平均年龄
SELECT AVG(age) AS 男生平均年龄
FROM (SELECT * FROM student WHERE gender='M') AS male_students;

这里的子查询"SELECT * FROM student WHERE gender='M'"会筛选出所有男生,作为虚拟表male_students,主查询再对这个虚拟表计算平均年龄。

十一、DQL与Java后端结合:实战落地

对于后端开发者来说,学会DQL不仅要会写SQL,还要知道如何在Java项目中使用DQL查询数据。常见的Java后端框架(Spring Boot、MyBatis、JDBC)中,DQL主要用于实现"查询接口"(比如/user/list、/student/getById),通过ORM框架执行SQL,并将查询结果封装成Java对象。

示例(MyBatis Mapper接口):

sql 复制代码
// 通过年龄查询用户列表,参数为age,返回List<User>对象
@Select("SELECT id, name, age, email FROM user WHERE age > #{age}")
List<User> findUsersByAge(@Param("age") int age);

当后端接口被调用时(比如传入age=20),MyBatis会执行对应的SQL:

sql 复制代码
SELECT id, name, age, email FROM user WHERE age > 20;

并将查询结果自动封装成User对象的列表,返回给前端。

十二、DQL实战建议:避坑+优化

掌握了DQL的基础用法后,想要写出高效、规范的查询语句,还需要注意以下几点:

  1. 多练习组合查询:实际开发中,很少有单一的查询场景,多练习"WHERE + GROUP BY + HAVING + ORDER BY"的组合用法,熟悉执行顺序,提升查询逻辑能力;

  2. 避免使用SELECT *:尤其是在大表中,SELECT *会查询多余字段,增加数据库压力,建议明确写出需要的字段名;

  3. 牢记执行顺序:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT,排查SQL错误时,按执行顺序分析,能快速找到问题;

  4. 合理使用分页:大数据量查询时,必须使用LIMIT分页,避免一次性查询所有数据;

  5. 连接查询避坑:多表连接时,一定要写清楚关联条件(ON子句),避免出现笛卡尔积(查询结果暴增);

  6. 子查询优化:简单的子查询可以使用,但复杂的子查询(比如多层嵌套)建议替换为连接查询,提升查询性能。

总结

DQL是MySQL中最核心、最常用的语言,从基础的SELECT查询,到条件、排序、分组、分页,再到多表连接、子查询,每一个知识点都对应着实际开发中的场景。对于新手来说,建议先掌握基础用法,多写SQL练习,再逐步学习优化技巧,理解执行顺序和底层逻辑。

其实DQL并不难,只要多练、多思考,就能熟练运用,写出高效、规范的查询语句,为后端开发打下坚实的基础。后续我也会分享更多DQL优化技巧和实战案例,欢迎持续关注~

相关推荐
原来是猿2 小时前
MYSQL【库操作】
数据库·mysql
皮皮哎哟3 小时前
嵌入式数据库从入门到精通
linux·数据库·sqlite3·sqlite3_open
爱吃羊的老虎3 小时前
【后端】MySQL 主从复制原理深度解析
数据库·mysql
blues92573 小时前
MySQL 篇 - Java 连接 MySQL 数据库并实现数据交互
java·数据库·mysql
曹牧3 小时前
C#:控制函数执行时间
数据库·c#
ApacheSeaTunnel3 小时前
(三)ODS/明细层落地设计要点:把数据接入层打造成“稳定可运维”的基础设施
数据库·数据仓库·数据湖·白鲸开源
猹叉叉(学习版)3 小时前
【ASP.NET CORE】 6. 中间件
数据库·笔记·后端·中间件·c#·asp.net·.netcore
高铭杰3 小时前
Postgresql源码(152)Transaction Redo (RM_XACT_ID = 1)
数据库·postgresql·xact
Zzzzmo_3 小时前
【MySQL】视图
数据库·mysql