当前位置:首页 > 问答 > 正文

MSSQL里那些执行顺序到底是咋回事,慢慢捋一捋看看理解不理解

你写的SQL语句,比如一个长长的带着WHERE、GROUP BY、HAVING、ORDER BY的SELECT语句,就像是递给厨师(也就是SQL Server数据库引擎)的一张菜谱,但厨师做菜的顺序,并不完全按照你写菜谱的先后顺序来,他有一套自己固定的、高效的“烹饪流程”。

根据微软官方的技术文档(比如在描述SELECT语句逻辑处理顺序的部分),这个内在的顺序是这样的:

  1. FROM
  2. ON
  3. JOIN
  4. WHERE
  5. GROUP BY
  6. HAVING
  7. SELECT
  8. DISTINCT
  9. ORDER BY
  10. TOP/OFFSET-FETCH

怎么样?是不是和你写的顺序不太一样?最反直觉的地方可能就是 SELECT子句并不是第一步执行的,它排得还挺靠后的,而 ORDER BY几乎是最后才做的事

下面我们就用一个具体的“做菜”例子,来一步步拆解这个顺序,你就明白为啥要这么安排了。

假设我们有两张表:顾客表(Customers)和订单表(Orders),我们想完成一个查询:找出在2023年下过订单的、来自“北京”的顾客,并计算他们每个人的订单总金额,最后只显示总金额大于500元的顾客,并按总金额从高到低排序。

我们写的“菜谱”(SQL)可能长这样:

SELECT
    c.顾客姓名,
    SUM(o.订单金额) AS 总金额
FROM 顾客表 AS c
INNER JOIN 订单表 AS o ON c.顾客ID = o.顾客ID
WHERE c.城市 = '北京'
    AND o.订单日期 >= '2023-01-01'
GROUP BY c.顾客姓名
HAVING SUM(o.订单金额) > 500
ORDER BY 总金额 DESC;

厨师(数据库引擎)开始按照他的流程做菜了:

第一步:FROM 和 JOIN(准备食材和初步处理) 厨师首先会走进仓库,找到你指定的两张桌子:顾客表订单表,这就是FROM步骤,他根据ON后面的条件(c.顾客ID = o.顾客ID),把两张表像拼拼图一样连接(JOIN)在一起,想象一下,他把每个顾客的信息,和属于这个顾客的所有订单记录,一行一行地配对好,临时组成一个巨大的“顾客-订单明细表”,这一步产生了一个非常大的中间结果,包含了所有顾客和他们所有的订单,不管是不是北京的,也不管是不是2023年的。

第二步:WHERE(筛选食材) 厨师开始对这个巨大的中间结果进行筛选,他拿出筛子,根据WHERE条件,只留下那些“城市是北京”订单日期在2023年之后”的记录,那些不符合条件的行,比如上海的顾客、或者2022年的订单,就被直接扔掉了,这一步非常重要,它提前减少了需要处理的数据量,让后面的步骤更轻松。WHERE是在分组(GROUP BY)之前发生的。

第三步:GROUP BY(给食材分组) 经过筛选后,剩下的都是2023年北京顾客的订单记录了,厨师开始GROUP BY(分组),他按照“顾客姓名”把这些记录分成一堆一堆的,叫“张三”的所有订单记录放在一堆,“李四”的放在另一堆,每一堆里都包含了一个顾客的多条订单记录,但还没有进行任何计算。

第四步:HAVING(对分组结果进行筛选) 分组完成后,厨师开始对每一“堆”数据进行计算(比如我们要求的SUM求和),计算完后,他再看HAVING条件:SUM(o.订单金额) > 500,他会检查每一堆的总金额,如果总金额大于500,这一堆就保留;如果小于等于500,就把整堆都扔掉。这里的关键是:HAVING是作用于分组后的结果,而WHERE是作用于分组前的每一条具体记录。

第五步:SELECT(决定最终端上桌的菜样) 好了,经过层层筛选,剩下的都是我们想要的“顾客堆”了,厨师才开始处理SELECT子句,他按照你的要求,从每一堆里提取出你需要的信息:顾客姓名,以及计算好的总金额,并且给总金额起了一个别名“总金额”,在这一步,你还可以使用一些函数来处理数据,或者构造新的计算列。

第六步:ORDER BY 和 TOP(摆盘和上菜顺序) 厨师要按照ORDER BY的要求,将最终的结果集按照“总金额”从大到小(DESC)进行排序,这就好比把做好的菜按照价格高低漂亮地排成一排,如果有TOP子句,他会在排序后只取出最前面的几道菜。

总结一下核心理解:

  • 顺序是固定的逻辑处理顺序,不是你写的顺序。 你写的SQL是“声明式”的,你只是告诉数据库你想要什么结果,而它自己会按照最优的流程去执行。
  • WHERE 和 HAVING 的根本区别:这是最容易混淆的点。WHERE在分组前过滤,过滤的是原始记录行(row),HAVING在分组后过滤,过滤的是分组(group),凡是能用WHERE解决的问题,就不要用HAVING,因为WHERE提前过滤效率更高,只有当过滤条件依赖于聚合函数(如SUM, AVG, COUNT)的结果时,才必须用HAVING。
  • 为什么SELECT在后面? 因为在前面的步骤(如WHERE、GROUP BY)中,可能还不需要知道所有列的信息,只需要关心参与条件和分组的列即可,等到所有脏活累活(连接、过滤、分组)都干完了,再“投影”出你需要的列,这样更高效。

希望通过这个一步步的“做菜”比喻,能让你对MSSQL内部那个看似神秘的执行顺序有一个清晰直观的理解。

MSSQL里那些执行顺序到底是咋回事,慢慢捋一捋看看理解不理解