SQL语句的执行顺序

SQL 不同于与其他编程语言的最明显特征是处理代码的顺序。在大数编程语言中,代码按编码顺序被处理,但是在SQL语言中,第一个被处理的子句是FROM子句,尽管SELECT语句第一个出现,但是几乎总是最后被处理。 
每个步骤都会产生一个虚拟表,该虚拟表被用作下一个步骤的输入。这些虚拟表对调用者(客户端应用程序或者外部查询)不可用。只是最后一步生成的表才会返回 给调用者。如果没有在查询中指定某一子句,将跳过相应的步骤。下面是对应用于SQL server 2000和SQL Server 2005的各个逻辑步骤的简单描述。

( 8 ) SELECT ( 9 ) DISTINCT ( 11 ) < Top Num > < select list > 
( 1 ) FROM [ left_table ] 
( 3 ) < join_type > JOIN < right_table > 
( 2 ) ON < join_condition > 
( 4 ) WHERE < where_condition > 
( 5 ) GROUP BY < group_by_list > 
( 6 ) WITH < CUBE | RollUP > 
( 7 ) HAVING < having_condition > 
( 10 ) ORDER BY < order_by_list >

逻辑查询处理阶段简介 
FROM:对FROM子句中的前两个表执行笛卡尔积(Cartesian product)(交叉联接),生成虚拟表VT1

ON:对VT1应用ON筛选器。只有那些使为真的行才被插入VT2。

OUTER(JOIN):如 果指定了OUTER JOIN(相对于CROSS JOIN 或(INNER JOIN),保留表(preserved table:左外部联接把左表标记为保留表,右外部联接把右表标记为保留表,完全外部联接把两个表都标记为保留表)中未找到匹配的行将作为外部行添加到 VT2,生成VT3.如果FROM子句包含两个以上的表,则对上一个联接生成的结果表和下一个表重复执行步骤1到步骤3,直到处理完所有的表为止。

WHERE:对VT3应用WHERE筛选器。只有使为true的行才被插入VT4.

GROUP BY:按GROUP BY子句中的列列表对VT4中的行分组,生成VT5.

CUBE|ROLLUP:把超组(Suppergroups)插入VT5,生成VT6.

HAVING:对VT6应用HAVING筛选器。只有使为true的组才会被插入VT7.

SELECT:处理SELECT列表,产生VT8.

DISTINCT:将重复的行从VT8中移除,产生VT9.

ORDER BY:将VT9中的行按ORDER BY 子句中的列列表排序,生成游标(VC10).

TOP:从VC10的开始处选择指定数量或比例的行,生成表VT11,并返回调用者。

从这个顺序中我们不难发现,所有的 查询语句都是从from开始执行的,在执行过程中,每个步骤都会为下一个步骤生成一个虚拟表,这个虚拟表将作为下一个执行步骤的输入。 
第一步:首先对from子句中的前两个表执行一个笛卡尔乘积,此时生成虚拟表 vt1(选择相对小的表做基础表) 
第二步:接下来便是应用on筛选器,on 中的逻辑表达式将应用到 vt1 中的各个行,筛选出满足on逻辑表达式的行,生成虚拟表 vt2 
第三步:如果是outer join 那么这一步就将添加外部行,left outer jion 就把左表在第二步中过滤的添加进来,如果是right outer join 那么就将右表在第二步中过滤掉的行添加进来,这样生成虚拟表 vt3 
第四步:如果 from 子句中的表数目多余两个表,那么就将vt3和第三个表连接从而计算笛卡尔乘积,生成虚拟表,该过程就是一个重复1-3的步骤,最终得到一个新的虚拟表 vt3。 
第五步:应用where筛选器,对上一步生产的虚拟表引用where筛选器,生成虚拟表vt4,在这有个比较重要的细节不得不说一下,对于包含outer join子句的查询,就有一个让人感到困惑的问题,到底在on筛选器还是用where筛选器指定逻辑表达式呢?on和where的最大区别在于,如果在on应用逻辑表达式那么在第三步outer join中还可以把移除的行再次添加回来,而where的移除的最终的。举个简单的例子,有一个学生表(班级,姓名)和一个成绩表(姓名,成绩),我现在需要返回一个x班级的全体同学的成绩,但是这个班级有几个学生缺考,也就是说在成绩表中没有记录。为了得到我们预期的结果我们就需要在on子句指定学生和成绩表的关系(学生.姓名=成绩.姓名)那么我们是否发现在执行第二步的时候,对于没有参加考试的学生记录就不会出现在vt2中,因为他们被on的逻辑表达式过滤掉了,但是我们用left outer join就可以把左表(学生)中没有参加考试的学生找回来,因为我们想返回的是x班级的所有学生,如果在on中应用学生.班级='x'的话,left outer join会把x班级的所有学生记录找回(感谢网友康钦谋__康钦苗的指正),所以只能在where筛选器中应用学生.班级='x' 因为它的过滤是最终的。 
第六步:group by 子句将中的唯一的值组合成为一组,得到虚拟表vt5。如果应用了group by,那么后面的所有步骤都只能得到的vt5的列或者是聚合函数(count、sum、avg等)。原因在于最终的结果集中只为每个组包含一行。这一点请牢记。 
第七步:应用cube或者rollup选项,为vt5生成超组,生成vt6. 
第八步:应用having筛选器,生成vt7。having筛选器是第一个也是为唯一一个应用到已分组数据的筛选器。 
第九步:处理select子句。将vt7中的在select中出现的列筛选出来。生成vt8. 
第十步:应用distinct子句,vt8中移除相同的行,生成vt9。事实上如果应用了group by子句那么distinct是多余的,原因同样在于,分组的时候是将列中唯一的值分成一组,同时只为每一组返回一行记录,那么所以的记录都将是不相同的。 
第十一步:应用order by子句。按照order_by_condition排序vt9,此时返回的一个游标,而不是虚拟表。sql是基于集合的理论的,集合不会预先对他的行排序,它只是成员的逻辑集合,成员的顺序是无关紧要的。对表进行排序的查询可以返回一个对象,这个对象包含特定的物理顺序的逻辑组织。这个对象就叫游标。正因为返回值是游标,那么使用order by 子句查询不能应用于表表达式。排序是很需要成本的,除非你必须要排序,否则最好不要指定order by,最后,在这一步中是第一个也是唯一一个可以使用select列表中别名的步骤。 
第十二步:应用top选项。此时才返回结果给请求者即用户。 

注:步骤10,按ORDER BY子句中的列列表排序上步返回的行,返回游标VC10.这一步是第一步也是唯一一步可以使用SELECT列表中的列别名的步骤。这一步不同于其它步骤的 是,它不返回有效的表,而是返回一个游标。SQL是基于集合理论的。集合不会预先对它的行排序,它只是成员的逻辑集合,成员的顺序无关紧要。对表进行排序 的查询可以返回一个对象,包含按特定物理顺序组织的行。ANSI把这种对象称为游标。理解这一步是正确理解SQL的基础。

因为这一步不返回表(而是返回游标),使用了ORDER BY子句的查询不能用作表表达式。表表达式包括:视图、内联表值函数、子查询、派生表和共用表达式。它的结果必须返回给期望得到物理记录的客户端应用程序。例如,下面的派生表查询无效,并产生一个错误:

  select * from (select orderid,customerid from orders order by orderid) as d

下面的视图也会产生错误

  create view my_view as select *from orders order by orderid

在SQL中,表表达式中不允许使用带有ORDER BY子句的查询,而在T—SQL中却有一个例外(应用TOP选项)

      所以要记住,不要为表中的行假设任何特定的顺序。换句话说,除非你确定要有序行,否则不要指定ORDER BY 子句。排序是需要成本的,SQL Server需要执行有序索引扫描或使用排序运行符。

     好像自已在书写 SQL 语句时由于不清楚各个关键字的执行顺序, 往往组织的 SQL 语句缺少很好的逻辑, 凭感觉 "拼凑" ( 不好意思, 如果您的 SQL 语句也经常 "拼凑", 那您是不是得好好反省一下呢?, 呵呵). 确实是爽了自己, 可苦了机器, 服务器还需要在我们的杂乱无章的 SQL 语句中寻找它下一句需要执行的关键字在哪里. 效率嘛, 由于我们的感觉神经对秒以下的变化实在不敏感, 暂且就认为自已写的 SQL 顺序无关紧要, "反正没什么变化!", 呵呵.其实服务器对每句 SQL 解析时间都会有详细记录的, 大家可以看一下自已按习惯写的 SQL 和按标准顺序写的SQL解析时间差别有多大.
   因此, 建议大家在平时工作中 SQL 语句按标准顺序写, 一是专业, 二是实用, 呵呵, 不过我觉得最主要的是心里感觉舒服.
   标准的 SQL 的解析顺序为:
   (1).FROM 子句, 组装来自不同数据源的数据
   (2).WHERE 子句, 基于指定的条件对记录进行筛选
   (3).GROUP BY 子句, 将数据划分为多个分组
   (4).使用聚合函数进行计算
   (5).使用 HAVING 子句筛选分组
   (6).计算所有的表达式
   (7).使用 ORDER BY 对结果集进行排序

   举例说明: 在学生成绩表中 (暂记为 tb_Grade), 把 "考生姓名"内容不为空的记录按照 "考生姓名" 分组, 并且筛选分组结果, 选出 "总成绩" 大于 600 分的.
   标准顺序的 SQL 语句为: 
   select 考生姓名, max(总成绩) as max总成绩
   from tb_Grade
   where 考生姓名 is not null
   group by 考生姓名
   having max(总成绩) > 600
   order by max总成绩
   
   在上面的示例中 SQL 语句的执行顺序如下:
   (1). 首先执行 FROM 子句, 从 tb_Grade 表组装数据源的数据
   (2). 执行 WHERE 子句, 筛选 tb_Grade 表中所有数据不为 NULL 的数据
   (3). 执行 GROUP BY 子句, 把 tb_Grade 表按 "学生姓名" 列进行分组
   (4). 计算 max() 聚集函数, 按 "总成绩" 求出总成绩中最大的一些数值
   (5). 执行 HAVING 子句, 筛选课程的总成绩大于 600 分的.
   (7). 执行 ORDER BY 子句, 把最后的结果按 "Max 成绩" 进行排序.

posted @ 2017-04-16 05:05  李慕白520  阅读(195)  评论(0编辑  收藏  举报