SQL语句的执行顺序
ps:如果这篇帖子对于还在找工作和找实习的你有所帮助,可以关注我,给本贴点赞、评论、收藏并订阅专栏;同时不要吝啬您的花花
书写顺序:SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
执行顺序:FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT
SQL语句的执行顺序与书写顺序完全不同,这是很多SQL初学者容易混淆的关键点。其核心遵循“先找数据、再过滤、后展示”的逻辑,简单来说,就是先确定要操作的数据来源,再逐步筛选、处理数据,最后以指定格式呈现结果。明确SQL的实际执行顺序,不仅能帮助我们规范编写SQL语句,还能快速排查查询中的逻辑错误,完整执行顺序(从先到后)详细拆解如下:
- FROM:指定查询的数据源表,这是SQL执行的第一步,也是所有查询操作的基础——先明确要从哪些表中获取数据,无论是单表查询还是多表关联,都需先定位数据源,后续所有操作都围绕这些表展开(先确定操作的表);
- ON:表连接时的过滤条件,仅适用于多表关联场景(如JOIN连接),在执行表连接之前,会先根据ON后的条件筛选出符合关联要求的数据行,减少后续连接的数据量,提升查询效率(多表关联时,先筛选关联条件);
- JOIN:执行表连接操作,结合前面ON指定的关联条件,将多个表中符合条件的数据行进行关联拼接,形成一张临时的联合数据表,为后续的过滤和分组操作提供完整的数据基础(根据ON条件关联多个表);
- WHERE:过滤行数据,对JOIN之后形成的临时表(或单表查询的原始表)进行单行数据筛选,排除不符合条件的单个数据行,需要注意的是,WHERE不能使用聚合函数,仅针对单个数据行的筛选(排除不符合条件的单行数据,不涉及分组);
- GROUP BY:对WHERE过滤后的行数据进行分组,按照指定的字段将数据划分为多个不同的分组,每个分组内的数据具有相同的分组字段值,分组后后续的聚合操作(如COUNT、SUM)会针对每个分组单独执行(按指定字段分组);
- HAVING:过滤分组后的数据,仅作用于GROUP BY分组后的结果,用于排除不符合条件的整个分组,与WHERE不同,HAVING可以使用聚合函数,筛选的是分组层面的数据(排除不符合条件的分组,仅对分组生效);
- SELECT:选择需要展示的列,在完成所有数据筛选和分组后,从处理后的临时表中挑选出需要呈现给用户的字段,也可以在这一步使用聚合函数(如求和、计数)对分组数据进行计算,确定最终要展示的内容(筛选字段,可使用聚合函数);
- DISTINCT:对查询结果去重,属于可选操作,仅在SELECT之后执行,当查询结果中存在重复的行数据时,通过DISTINCT可以保留唯一的行,需要注意的是,DISTINCT会作用于SELECT选择的所有字段(可选,在SELECT之后执行);
- ORDER BY:对最终结果排序,按照指定的字段(可多个字段)对SELECT筛选后的结果进行升序(默认)或降序排列,排序操作必须在分组之后执行,因为只有分组和筛选完成后,才能确定最终的数据集(按指定字段升序/降序);
- LIMIT/OFFSET:限制查询结果的条数,属于可选操作,是SQL执行的最后一步,用于从排序后的结果中截取指定数量的行数据,常用来实现分页查询,OFFSET可配合LIMIT指定截取的起始位置(可选,最后执行)。
补充说明
- 书写顺序(常用):日常编写SQL时,我们习惯的书写顺序与实际执行顺序相反,常用书写顺序为:SELECT → FROM → JOIN → ON → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT,牢记这一书写规范,能让SQL语句更规范、易读,也便于他人理解和维护;
- 关键区别:WHERE 与 HAVING 的核心区别的是筛选范围不同——WHERE 过滤单行数据,作用于分组之前;HAVING 过滤分组数据,作用于分组之后,且HAVING可使用聚合函数而WHERE不能;另外,ORDER BY 必须在 GROUP BY 之后执行,因为只有先完成分组,才能对分组后的结果进行排序(先分组再排序)。
为了让大家更直观地掌握上述执行顺序,避免理论与实践脱节,下面结合一个具体的SQL案例,逐一步拆解每一个执行步骤的具体操作和结果,帮你更清晰地理解执行逻辑。
具体SQL案例(多表关联+分组筛选场景)
假设我们有两张日常办公中常见的表,用于存储学生信息和考试成绩,表结构及测试数据如下,后续查询将围绕这两张表展开:
1. 表结构与测试数据
- 学生表(student):存储学生基本信息,包含字段(student_id:学生ID,name:学生姓名,major:专业,age:年龄)
- 成绩表(score):存储学生考试成绩,包含字段(score_id:成绩ID,student_id:关联学生ID,subject:科目,score:分数)
测试数据(便于后续查询验证):
student表:(1, 张三, 计算机, 20)、(2, 李四, 计算机, 21)、(3, 王五, 数学, 20)、(4, 赵六, 计算机, 20)
score表:(1, 1, 数据库, 85)、(2, 1, Java, 90)、(3, 2, 数据库, 78)、(4, 2, Java, 82)、(5, 3, 高数, 92)、(6, 4, 数据库, 88)、(7, 4, Java, 76)
-- 创建学生表 student
CREATE TABLE student (
student_id INT PRIMARY KEY COMMENT '学生ID(主键)',
name VARCHAR(20) NOT NULL COMMENT '学生姓名',
major VARCHAR(50) COMMENT '所学专业',
age INT COMMENT '年龄'
) COMMENT '学生基本信息表';
-- 创建成绩表 score(关联学生表)
CREATE TABLE score (
score_id INT PRIMARY KEY COMMENT '成绩ID(主键)',
student_id INT NOT NULL COMMENT '关联学生ID',
subject VARCHAR(50) COMMENT '考试科目',
score INT COMMENT '考试分数',
-- 外键约束:保证score表的student_id必须在student表中存在
CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES student(student_id)
) COMMENT '学生考试成绩表';
-- 插入学生表测试数据
INSERT INTO student (student_id, name, major, age)
VALUES
(1, '张三', '计算机', 20),
(2, '李四', '计算机', 21),
(3, '王五', '数学', 20),
(4, '赵六', '计算机', 20);
-- 插入成绩表测试数据
INSERT INTO score (score_id, student_id, subject, score)
VALUES
(1, 1, '数据库', 85),
(2, 1, 'Java', 90),
(3, 2, '数据库', 78),
(4, 2, 'Java', 82),
(5, 3, '高数', 92),
(6, 4, '数据库', 88),
(7, 4, 'Java', 76);
2. 需求与完整SQL语句
需求:查询计算机专业学生的各科平均分,筛选出平均分≥80分的科目,按平均分降序排列,只显示前2个科目。
SELECT subject AS 科目, AVG(score) AS 平均分 FROM student JOIN score ON student.student_id = score.student_id WHERE student.major = '计算机' GROUP BY subject HAVING AVG(score) >= 80 ORDER BY 平均分 DESC LIMIT 2;
3. 案例对应执行顺序拆解(结合上述SQL)
下面将案例SQL与前文提到的10个执行步骤一一对应,清晰展示每一步的具体操作和临时结果:
- FROM:指定数据源表,此处为student表和score表,先定位到这两张表,作为后续所有操作的基础。
- ON:表连接过滤条件,指定student.student_id = score.student_id,筛选出两张表中关联ID一致的数据行,排除关联失败的无效数据(如score表中无对应student_id的记录,此处无此类数据)。
- JOIN:执行表连接,根据ON条件将student表和score表中符合条件的行拼接,形成临时联合表(包含字段:student_id、name、major、age、score_id、subject、score)。
- WHERE:过滤单行数据,筛选出student.major = '计算机'的行,排除非计算机专业的学生数据(此处排除王五的高数成绩相关行),临时表仅保留张三、李四、赵六的相关数据。
- GROUP BY:按subject(科目)分组,将临时表中的数据按“数据库”“Java”两个科目分组,形成两个分组(数据库分组:张三85、李四78、赵六88;Java分组:张三90、李四82、赵六76)。
- HAVING:过滤分组数据,筛选出AVG(score) ≥ 80的分组,计算两个分组的平均分(数据库平均分:(85+78+88)÷3≈83.67;Java平均分:(90+82+76)÷3≈82.67),两个分组均符合条件,全部保留。
- SELECT:选择需要展示的列,将subject命名为“科目”,AVG(score)命名为“平均分”,确定最终要展示的字段及计算结果。
- DISTINCT:此处无去重需求,不执行该步骤(可选步骤,根据需求决定是否使用)。
- ORDER BY:按“平均分”字段降序排列,排序后结果为:数据库(83.67)、Java(82.67)。
- LIMIT 2:限制查询结果条数,截取前2条数据,最终展示结果即为排序后的两个科目及对应平均分。
最终查询结果:
数据库 | 83.67 |
Java | 82.67 |
通过该案例,可清晰看到SQL执行的每一步如何作用于数据,与前文的理论拆解完全对应,能快速掌握执行逻辑的核心要点。
ps:如果这篇帖子对于还在找工作和找实习的你有所帮助,可以关注我,给本贴点赞、评论、收藏并订阅专栏;同时不要吝啬您的花花
《MySQL基础专栏》专为编程新手打造!从SQL核心语法、数据增删改查,到预编译SQL、索引入门、事务基础,层层拆解MySQL必备知识点。专栏摒弃晦涩术语,以通俗讲解+实操案例,带你掌握数据库基础操作,规避SQL注入、性能低效等常见坑,快速搭建MySQL基础体系,轻松应对日常开发中的数据库基础场景。
查看14道真题和解析