SQL查询的执行顺序

48

我对此查询的执行顺序感到困惑,请解释一下。我不确定连接何时应用,函数何时调用,新列何时添加Case,以及何时添加序列号。请解释所有这些的执行顺序。

select Row_number() OVER(ORDER BY (SELECT 1))  AS 'Serial Number', 
    EP.FirstName,Ep.LastName,[dbo].[GetBookingRoleName](ES.UserId,EP.BookingRole) as RoleName,  
    (select top 1 convert(varchar(10),eventDate,103)from [3rdi_EventDates] where EventId=13) as EventDate,
    (CASE [dbo].[GetBookingRoleName](ES.UserId,EP.BookingRole)  
            WHEN    '90 Day Client' THEN 'DC'
            WHEN    'Association Client'  THEN  'DC'
            WHEN    'Autism Whisperer'    THEN  'DC'
            WHEN    'CampII'             THEN   'AD' 
            WHEN    'Captain'              THEN 'AD' 
            WHEN    'Chiropractic Assistant' THEN 'AD'
            WHEN    'Coaches'               THEN 'AD'
            END) as Category from [3rdi_EventParticipants] as EP  
    inner join [3rdi_EventSignup] as ES on EP.SignUpId = ES.SignUpId  
    where EP.EventId = 13
    and userid in (  
    select distinct userid from userroles  
    --where roleid not in(6,7,61,64) and roleid not in(1,2))  
    where roleid not in(19, 20, 21, 22) and roleid not in(1,2))

这是从上面查询调用的函数。

CREATE function [dbo].[GetBookingRoleName]  
(  
 @UserId as integer,
 @BookingId as integer
)  
RETURNS varchar(20)  
as  
begin  
declare @RoleName varchar(20)  

if @BookingId = -1
Select Top 1 @RoleName=R.RoleName From UserRoles UR inner join Roles R on UR.RoleId=R.RoleId Where UR.UserId=@UserId and R.RoleId not in(1,2)  
else
Select @RoleName= RoleName From Roles where RoleId = @BookingId

return @RoleName  
end

离题了,但是如果你放弃那个UDF并将逻辑内联,你很可能会更好。 - Martin Smith
截至2022年,维基百科有一篇关于SQL语法的不错文章(https://en.wikipedia.org/wiki/SQL_syntax#Queries),其中在查询部分对SQL执行顺序进行了解释。 - Brandt
7个回答

85

查询通常按照以下方式进行处理(SQL Server)。我不知道其他关系型数据库是否也是这样处理的。

FROM [MyTable]
    ON [MyCondition]
  JOIN [MyJoinedTable]
 WHERE [...]
 GROUP BY [...]
HAVING [...]
SELECT [...]
 ORDER BY [...]

我同意!简短而精辟的解释。这个答案得到了我的支持。 - Devendra Lattu
这不是正确的。 - The Impaler
@TheImpaler你能详细说明一下吗?对于像我这样的随意读者来说,很难知道如何理解你的评论。 - littleO
2
@littleO 这个答案描述了一种理解数据库如何执行查询的好方法;这对于新学生更容易理解SQL非常有用,作为一种教学工具。然而,SQL是一种声明性语言,不像C、Java、Python等命令式语言。这意味着真实世界中的生产数据库可以自由地重新构造查询,重写它,颠倒它,并以任何顺序执行它。只有一个天真的数据库会像这个答案所解释的那样执行查询;任何中高端数据库都会使用各种肮脏的技巧使查询变得非常快。 - The Impaler
@TheImpaler,有趣的事实是,命令式语言也有一定的声明性。以int a = 2; int b = 4*23-12+a;为例,人们可能认为编译后的二进制代码在执行时会进行乘法和加法运算,但实际上编译器会解决所有的算术运算,而二进制代码只包含硬编码的b=82。 - Lucas Alonso
显示剩余2条评论

70

SQL是一种声明式语言。查询的结果必须与以下过程评估的结果相同(来自Microsoft):

SELECT语句的逻辑处理顺序

以下步骤显示了SELECT语句的逻辑处理顺序或绑定顺序,确定在一个步骤中定义的对象何时可供后续步骤中的子句使用。例如,如果查询处理器可以绑定(访问)FROM子句中定义的表或视图,则这些对象及其列将对所有后续步骤可用。相反,因为SELECT子句是第8步,所以在该子句中定义的任何列别名或派生列都不能被前面的子句引用。但是,它们可以被后续子句(如ORDER BY子句)引用。请注意,语句的实际物理执行是由查询处理器确定的,顺序可能与此列表不同。

  1. FROM
  2. ON
  3. JOIN
  4. WHERE
  5. GROUP BY
  6. WITH CUBE或WITH ROLLUP
  7. HAVING
  8. SELECT
  9. DISTINCT
  10. ORDER BY
  11. TOP

优化器可以自由选择任何顺序以产生最佳执行时间。给定任何SQL查询,基本上不可能有人假装知道执行顺序。如果添加有关所涉及的模式(精确表和索引定义)和估计的基数(数据大小和键的选择性)的详细信息,则可以对可能的执行顺序进行猜测。

最终,唯一正确的“顺序”是实际执行计划中描述的顺序。请参见使用SQL Server Profiler事件类显示执行计划使用SQL Server Management Studio显示图形化执行计划

然而,完全不同的事情是查询、子查询和表达式如何投射到“有效性”中。例如,如果在SELECT投影列表中有一个别名表达式,您可以在WHERE子句中使用该别名吗?就像这样:

SELECT a+b as c
FROM t
WHERE c=...;

在where子句中使用c别名是无效的。查询形成语法树,树的下层不能引用树中定义的高层内容。这不一定是执行顺序,更多的是语法解析问题。这相当于在C#中编写以下代码:

void Select (int a, int b)
{
   if (c = ...) then {...}
   int c = a+b;
}

就像在C#中,由于变量c在定义之前被使用,所以此代码无法编译。与此类似,上面的SELECT语句也无法正确编译,因为别名c在树中的引用位置比实际定义位置低。
不幸的是,与C/C#语言解析的众所周知规则不同,SQL查询树的构建规则有些神秘。在Single SQL Statement Processing中简要提到了它们,但我不知道任何关于如何创建它们,什么顺序是有效的,什么顺序是无效的的详细讨论。我并不是说没有好的资料,我相信一些好的SQL书籍会涵盖这个主题。
请注意,语法树的顺序与SQL文本的可视顺序不匹配。例如,ORDER BY子句通常是SQL文本中的最后一个,但作为语法树,它位于所有其他内容的上方(它对SELECT的输出进行排序,所以可以说它位于SELECT的列上方),因此是有效的引用c别名。
SELECT a+b as c
FROM t
ORDER BY c;

但是在我的上述查询中,当我的函数被调用时呢?在之前获取连接结果后,如果在那之前它从哪里获取参数值,以及执行“(select top 1 convert(varchar(10),eventDate,103)from [3rdi_EventDates] where EventId=13)”的时候,以及新列的那种情况何时被执行。 - NoviceToDotNet
唯一找出答案的方法是查看实际执行计划。 - Remus Rusanu
27
在互联网和SO上,有太多的答案声称“SQL优化器会施展其魔力 - 没有人能知道给定查询将如何执行”。但是正如你后来意识到的那样,这是不正确的!是的,优化器确实会随机处理 - 但显然有某些从句的执行顺序。否则,你怎么能依赖一个查询呢?请将您的更新移到顶部,这样寻找答案的人就不会看到您错误的答案,并离开该线程... - Don Cheadle
All-at-once - Lukasz Szozda
你能否更新一下这个答案,让社区更清楚明白吗?目前,用户必须阅读一堆东西,然后在底部告诉你,那些东西并不完全正确。这不是一种很好的格式。 - doublefelix
这个答案是正确的。SQL语句的实际执行顺序事先是不知道的。优化器会在运行时决定如何执行该语句。 - The Impaler

13

SQL查询语句是声明式的而非命令式的,因此您不知道哪个语句会首先执行,但由于SQL由SQL查询引擎评估,大多数SQL引擎遵循类似的过程以获得结果。您可能需要了解查询引擎内部的工作原理才能理解某些SQL执行行为。

Julia Evens写了一篇很好的文章来解释这个问题,值得一读:

https://jvns.ca/blog/2019/10/03/sql-queries-don-t-start-with-select/

输入图像说明


9

SQL是一种声明性语言,意味着它告诉SQL引擎要做什么,而不是如何做。这与命令式语言(如C)形成对比,在命令式语言中,如何执行某些操作是明确规定的。

这意味着并非所有语句都会按预期执行。特别需要注意的是布尔表达式,它们可能无法按照书写顺序从左到右进行求值。例如,以下代码不能保证不会出现除以零的错误:

SELECT 'null' WHERE 1 = 1 OR 1 / 0 = 0

这是因为查询优化器会选择执行语句的最佳(最有效)方法。这意味着,例如,在应用转换谓词之前,可能会加载和过滤值,从而导致错误。请参见上面第二个链接的示例。
请参见:这里这里

链接已经失效。 - Alex
我刚刚检查过,它们两个都还能用。 - IamIC

4
“执行顺序”可能是 SQL 查询的一个不好的思维模型。实际上很难编写一个只依赖于执行顺序的查询语句(这是一件好事)。相反,您应该将所有加入和条件子句视为同时进行(几乎像一个模板)。
话虽如此,您可以运行显示执行计划,这应该能够帮助您了解它。
然而,由于不清楚您为什么想要知道执行顺序,我猜测您正在尝试获得这个查询的思维模型,以便以某种方式修复它。以下是我“翻译”查询的方式,尽管我对这种分析做得很好,但精度方面存在一些灰色地带。
FROM 和 WHERE 子句
  • 给我所有的活动参与者行。 from [3rdi_EventParticipants]

  • 同时给我所有与报名ID匹配的活动报名行,这些报名行在SignUpID上与活动参与者行相匹配。 inner join 3rdi_EventSignup] as ES on EP.SignUpId = ES.SignUpId

  • 但仅限于第13个活动。 EP.EventId = 13

  • 并且仅当用户ID在用户角色表中有一条记录,其中角色ID不是1、2、19、20、21、22时才符合条件。 userid in (
    select distinct userid from userroles
    --where roleid not in(6,7,61,64) and roleid not in(1,2))
    where roleid not in(19, 20, 21, 22) and roleid not in(1,2))

选择子句

  • 为每一行提供一个唯一的ID Row_number() OVER(ORDER BY (SELECT 1)) AS '序号',

  • 参与者的名字 EP.FirstName

  • 参与者的姓氏 Ep.LastName

  • 预订角色名称 GetBookingRoleName

  • 去事件日期中查找第一个EventId等于13的事件日期 (select top 1 convert(varchar(10),eventDate,103)from [3rdi_EventDates] where EventId=13) as EventDate

  • 最后将GetBookingRoleName翻译成Category。我没有相应的表格,因此需要手动映射 (CASE [dbo].[GetBookingRoleName](ES.UserId,EP.BookingRole)
    WHEN '90 Day Client' THEN 'DC' WHEN 'Association Client' THEN 'DC' WHEN 'Autism Whisperer' THEN 'DC' WHEN 'CampII' THEN 'AD' WHEN 'Captain' THEN 'AD' WHEN 'Chiropractic Assistant' THEN 'AD' WHEN 'Coaches' THEN 'AD' END) as Category

所以这里有几点需要注意。当您选择 TOP 时,您没有按任何内容排序。您应该在那里放置一个 ORDER BY。您也可以将其放在您的 FROM 子句中,例如:
from [3rdi_EventParticipants] as EP  
    inner join [3rdi_EventSignup] as ES on EP.SignUpId = ES.SignUpId,   
       (select top 1 convert(varchar(10),eventDate,103)
       from [3rdi_EventDates] where EventId=13
       Order by eventDate) dates

2
查询文本的评估有一个逻辑顺序,但是数据库引擎可以根据最优选择执行查询组件的顺序。下面列出了逻辑文本解析的顺序。例如,这就是为什么您不能在WHERE子句中使用SELECT子句中的别名的原因。就查询解析过程而言,别名还不存在。

  1. FROM

  2. ON

  3. OUTER

  4. WHERE

  5. GROUP BY

  6. CUBE | ROLLUP

  7. HAVING

  8. SELECT

  9. DISTINCT

  10. ORDER BY

  11. TOP

更多关于此的信息请参见Microsoft文档(查看“SELECT语句的逻辑处理顺序”)


0

简化的T-SQL顺序 -> SELECT语句:

1) FROM 
2) Cartesian product
3) ON
4) Outer rows
5) WHERE
6) GROUP BY
7) HAVING
8) SELECT
9) Evaluation phase in SELECT 
10) DISTINCT
11) ORDER BY
12) TOP 

就像我迄今为止所做的一样 - 在SQLite中适用相同的顺序。

源 => SELECT (Transact-SQL)

当然,也有(罕见的)例外情况。


这个答案相比已经提供的其他答案,对于这个旧问题有什么新的补充? - Shmiel
广告 #2,#9,SQLite。虽然不是很全面的补充,但可能有助于节省一些搜索。 - Pepik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接