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、如下定义一个查询的伪代码,每一个关键字注释一个序列号,其中1-6的序号表示执行先后顺序信息,相同序号内部的细分则是该序号内部的执行顺序,其他则是该序号并行次序执行。

--查询组合字段
(5)select (5-2) distinct(5-3) top()(5-1)
--连表
(1)from (1-J) join  on 
(1-A) apply  as 
(1-P) pivot () as 
(1-U) unpivot () as 
--查询条件
(2)where 
--分组
(3)group by 
--分组条件
(4)having
--排序
(6)order by

ps:join_type(表连接类型)、apply_type(表运算符类型)、pivot(行转列)、unpivot(列转行)。left join/right join/full join的空缺字段使用NULL值进行补齐。

2、关键字执行描述
<1>FROM:对FROM子句中的前两个表执行笛卡尔积(Cartesian product)(交叉联接),生成虚拟表VT1
<2>ON:对VT1应用ON筛选器。只有那些使为真的行才被插入VT2。
<3>OUTER(JOIN):如果指定了OUTER JOIN(相对于CROSS JOIN 或(INNER JOIN),保留表(preserved table:左外部联接把左表标记为保留表,右外部联接把右表标记为保留表,完全外部联接把两个表都标记为保留表)中未找到匹配的行将作为外部行添加到 VT2,生成VT3.如果FROM子句包含两个以上的表,则对上一个联接生成的结果表和下一个表重复执行步骤1到步骤3,直到处理完所有的表为止。
<4>WHERE:对VT3应用WHERE筛选器。只有使为true的行才被插入VT4.
<5>GROUP BY:按GROUP BY子句中的列列表对VT4中的行分组,生成VT5.
<6>CUBE|ROLLUP:把超组(Suppergroups)插入VT5,生成VT6.
<7>HAVING:对VT6应用HAVING筛选器。只有使为true的组才会被插入VT7.
<8>SELECT:处理SELECT列表,产生VT8.
<9>DISTINCT:将重复的行从VT8中移除,产生VT9.
<10>ORDER BY:将VT9中的行按ORDER BY 子句中的列列表排序,生成游标(VC10).
<11>TOP:从VC10的开始处选择指定数量或比例的行,生成表VT11,并返回调用者。

ps:每一步执行都会产生相应的虚拟表,作为下一个步骤的结果集,直到完成所以SQL语句查询虚拟表为最终结果集。

三、实例分析

1、创建学生表和学生选课的成绩表

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表示学生表学生号等于课程成绩表的学生号,不等于就过滤

select * from  test.student a join test.score b on a.sno=b.sno

ps:通过ON关键字在笛卡尔积的产生VT1虚拟表的基础上过滤数据生成新的虚拟表VT2。 

4、使用外部行(左连接、右连接、全连接)

select * from  test.student a left join test.score b on a.sno=b.sno

 ps:使用外部行的工作就是在VT2表的基础上添加保留表中被过滤条件过滤掉的数据,非保留表中的数据被赋予NULL值,最后生成虚拟表VT3。

5、执行where过滤条件

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分组语句

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过滤条件

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子句

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语句执行。