数据库之SQL语句执行顺序
一、前言
在SQL语句中有很多的关键字,比如SELECT、FROM、JOIN、LEFT JOIN、RIGHT JOIN、FULL JOIN、ON、WHERE、GROUP、HAVING、ORDER BY、TOP、LIMIT、UNION、UNION ALL、MAX、MIN、COUNT。这些关键字有条件过滤、分组、排序、聚合函数、合并结果集,由这些关键字组成的SQL语句是按照什么样的顺序进行查询结果数据?
二、SQL逻辑查询语句执行顺序
如下定义一个查询的伪代码,每一个关键字注释一个序列号,其中1-6的序号表示执行先后顺序信息,相同序号内部的细分则是该序号内部的执行顺序,其他则是该序号并行次序执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | --查询组合字段 (5) select (5-2) distinct (5-3) top (<top_specification>)(5-1)<select_list> --连表 (1) from (1-J)<left_table><join_type> join <right_table> on <on_predicate> (1-A)<left_table><apply_type> apply <right_table_expression> as <alias> (1-P)<left_table> pivot (<pivot_specification>) as <alias> (1-U)<left_table> unpivot (<unpivot_specification>) as <alias> --查询条件 (2) where <where_pridicate> --分组 (3) group by <group_by_specification> --分组条件 (4) having <having_predicate> --排序 (6) order by <order_by_list> |
ps:join_type(表连接类型)、apply_type(表运算符类型)、pivot(行转列)、unpivot(列转行)。left join/right join/full join的空缺字段使用NULL值进行补齐。
关键字执行描述
<1>FROM:对FROM子句中的前两个表执行笛卡尔积(Cartesian product)(交叉联接),生成虚拟表VT1
<2>ON:对VT1应用ON筛选器。只有那些使<join_condition>为真的行才被插入VT2。
<3>OUTER(JOIN):如果指定了OUTER JOIN(相对于CROSS JOIN 或(INNER JOIN),保留表(preserved table:左外部联接把左表标记为保留表,右外部联接把右表标记为保留表,完全外部联接把两个表都标记为保留表)中未找到匹配的行将作为外部行添加到 VT2,生成VT3.如果FROM子句包含两个以上的表,则对上一个联接生成的结果表和下一个表重复执行步骤1到步骤3,直到处理完所有的表为止。
<4>WHERE:对VT3应用WHERE筛选器。只有使<where_condition>为true的行才被插入VT4.
<5>GROUP BY:按GROUP BY子句中的列列表对VT4中的行分组,生成VT5.
<6>CUBE|ROLLUP:把超组(Suppergroups)插入VT5,生成VT6.
<7>HAVING:对VT6应用HAVING筛选器。只有使<having_condition>为true的组才会被插入VT7.
<8>SELECT:处理SELECT列表,产生VT8.
<9>DISTINCT:将重复的行从VT8中移除,产生VT9.
<10>ORDER BY:将VT9中的行按ORDER BY 子句中的列列表排序,生成游标(VC10).
<11>TOP:从VC10的开始处选择指定数量或比例的行,生成表VT11,并返回调用者。
ps:每一步执行都会产生相应的虚拟表,作为下一个步骤的结果集,直到完成所以SQL语句查询虚拟表为最终结果集。
三、实例分析
1、创建学生表和学生选课的成绩表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `sno` int NOT NULL AUTO_INCREMENT COMMENT '学生号' , `sname` varchar (10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '姓名' , `ssex` int NOT NULL COMMENT '性别' , `sage` int NOT NULL COMMENT '年龄' , PRIMARY KEY (`sno`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb3; INSERT INTO `student` VALUES ( '1' , '张三' , '1' , '18' ); INSERT INTO `student` VALUES ( '2' , '李四' , '2' , '19' ); INSERT INTO `student` VALUES ( '3' , '王五' , '1' , '20' ); INSERT INTO `student` VALUES ( '4' , '赵六' , '1' , '21' ); DROP TABLE IF EXISTS `score`; CREATE TABLE `score` ( `sno` int NOT NULL COMMENT '学生号' , `cno` int NOT NULL COMMENT '课程号' , `mark` decimal (10,0) NOT NULL COMMENT '分数' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; INSERT INTO `score` VALUES ( '1' , '1' , '60' ); INSERT INTO `score` VALUES ( '2' , '1' , '70' ); INSERT INTO `score` VALUES ( '1' , '2' , '80' ); INSERT INTO `score` VALUES ( '2' , '2' , '90' ); INSERT INTO `score` VALUES ( '4' , '1' , '100' ); INSERT INTO `score` VALUES ( '5' , '1' , '0' ); |
2、执行FROM语句生成笛卡尔积结果,会得到一个虚拟表(VT1)
select * from test.student join test.score
ps:依据学生表的4条数据和课程成绩表的6条数据,产生笛卡尔积的4*6=24条数据,其结果作为下一关键字的筛选数据。
3、执行ON关键字,ON关键字是过滤条件,过滤对应字段不符合ON条件的数据,比如a.sno=b.sno表示学生表学生号等于课程成绩表的学生号,不等于就过滤。
1 | select * from test.student a join test.score b on a.sno=b.sno |
ps:通过ON关键字在笛卡尔积的产生VT1虚拟表的基础上过滤数据生成新的虚拟表VT2。
4、使用外部行(左连接、右连接、全连接)
1 | select * from test.student a left join test.score b on a.sno=b.sno |
ps:使用外部行的工作就是在VT2表的基础上添加保留表中被过滤条件过滤掉的数据,非保留表中的数据被赋予NULL值,最后生成虚拟表VT3。
5、执行where过滤条件
1 | select * from test.student a left join test.score b on a.sno=b.sno where a.ssex=1 |
ps:对虚拟表VT3的结果集中学生性别字段为不为1的数据过滤,最后生成虚拟表VT4。
6、执行GROUP BY分组语句
1 | select a.sno, max (a.sname), count (a.sno), sum (b.mark) from test.student a left join test.score b on a.sno=b.sno where a.ssex=1 group by a.sno |
ps:对虚拟表VT4的学生号进行分组,结果表示学生选课的数量count(a.sno),此时,我们就得到了一个VT5虚拟表。
7、执行HAVING过滤条件
1 2 | select a.sno, max (a.sname), count (a.sno), sum (b.mark) from test.student a left join test.score b on a.sno=b.sno where a.ssex=1 group by a.sno having count (a.sno)=1 |
ps:配合分组VT5虚拟表的结果集,对count(a.sno)选课数量进行过滤,选择选择一门课的数据,生成虚拟表VT6。
8、SELECT列表,对虚拟表VT6的数据选择指定字段的作为结果返回虚拟表VT7
9、执行DISTINCT,对虚拟表VT6的结果集按照DISTINCT操作的列增加了一个唯一索引,以此来除重复数据,其一样是生成虚拟表
10、执行ORDER BY子句
1 2 | select a.sno, max (a.sname), count (a.sno), sum (b.mark) from test.student a left join test.score b on a.sno=b.sno where a.ssex=1 GROUP BY a.sno HAVING count (a.sno)=1 order by sum (b.mark) desc |
ps:对虚拟表VT7的结果集中字段sum(b.mark)进行排序,生成虚拟表VT8,直到所有关键字执行完成,返回查询的结果集。
四、总结
1、SQL语句查询顺序的过程是不断生成各种虚拟表,每一步的结果都是提供给下一步的虚拟表。
2、SQL语句的执行过程,在分析器中会对语句的执行效率进行优化语句,所以其执行过程不一定是按照编写的SQL语句执行。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?