多个join的SQL查询执行计划的逻辑顺序

Pan*_*tea 6 query t-sql sql-server-2016

我知道执行 SQL 查询的逻辑顺序是:

FROM
ON
JOIN
WHERE
GROUP BY
WITH CUBE or WITH ROLLUP
HAVING
SELECT
DISTINCT
ORDER BY
TOP
Run Code Online (Sandbox Code Playgroud)

如果查询中有多个连接会发生什么,例如,如果我们有这样的查询:

SELECT *
FROM user_branch T1
INNER JOIN dimcustomer2 T2
   ON T1.BRANCH_CODE = T2.BRANCH_CODE
INNER JOIN customer_guarantee T3
   ON T3.CUSTOMER_NUM = T2.CUSTOMER_NUM
Run Code Online (Sandbox Code Playgroud)

一些示例数据:

customer_guarantee:    CUSTOMER_NUM      BRANCH_CODE
                      -------------------------------
                           A                X
                           B                X
                           C                Y
                           D                Z



 user_branch:           USER_ID          BRANCH_CODE    
                      --------------------------------
                           U1               Y



 dimcustomer2:         CUSTOMER_NUM      BRANCH_CODE      
                      --------------------------------
                           A                Y
                           B                Y
                           C                Y
                           D                Z
Run Code Online (Sandbox Code Playgroud)

这将如何执行?哪个连接会先执行?如果查询中有不同类型的连接怎么办?在这种情况下执行连接的顺序是什么?提前致谢。

And*_*y M 11

确定连接逻辑顺序的一种方法是将示例中的第一个内连接替换为左外连接:

SELECT *
FROM user_branch T1
LEFT  JOIN dimcustomer2 T2
   ON T1.BRANCH_CODE = T2.BRANCH_CODE
INNER JOIN customer_guarantee T3
   ON T3.CUSTOMER_NUM = T2.CUSTOMER_NUM
Run Code Online (Sandbox Code Playgroud)

让我们假设 中的某些行在 中T1没有匹配项T2。更具体地说,让我们假设这些是三个表:

SELECT *
FROM user_branch T1
LEFT  JOIN dimcustomer2 T2
   ON T1.BRANCH_CODE = T2.BRANCH_CODE
INNER JOIN customer_guarantee T3
   ON T3.CUSTOMER_NUM = T2.CUSTOMER_NUM
Run Code Online (Sandbox Code Playgroud)

这里有两个连接以及它们执行顺序的两种可能性。

1.左连接,然后内连接

如果首先计算左连接,则其结果将在行不匹配的T2列中为空T1

T1.BRANCH_CODE T2.BARNCH_CODE T2.CUSTOMER_NUM
-------------- -------------- ---------------
11 11 230
12 12 235
13               (空)          (空) 
14               (空)          (空)
15 15 260

T3在使用T2列的条件上使用内部连接进一步连接该结果将消除不匹配 - 因此,相应的T1行, - 因为空值不能满足连接的等于条件:

T1.BRANCH_CODE T2.BARNCH_CODE T2.CUSTOMER_NUM T3.CUSTOMER_NUM
-------------- -------------- --------------- ------- --------
11 11 230 230
12 12 235 235
15 15 260 260

这样一些T1行将从最终结果集中排除。

2. INNER JOIN,然后LEFT JOIN

现在,如果先执行内连接,那么它将生成一个结果集,其中包含来自T2T3匹配内连接条件的行:

T2.BARNCH_CODE T2.CUSTOMER_NUM T3.CUSTOMER_NUM
--------------- --------------- ---------------
11 230 230
12 235 235
15 260 260

然后将此结果集外连接到T1T1在外侧,您将获得最终结果,其中包含来自T1与外连接条件匹配的T2-T3内连接的所有行:

T1.BRANCH_CODE T2.BARNCH_CODE T2.CUSTOMER_NUM T3.CUSTOMER_NUM
-------------- -------------- --------------- ------- --------
11 11 230 230
12 12 235 235
13               (空)          (空)           (空) 
14               (空)          (空)           (空)
15 15 260 260

因此,第二种解释意味着所有T1行都应该出现在结果中。


由于这两种解释给出了如此不同的结果,很明显只有一种解释是正确的。执行查询,您将看到它实际上是第一个。这意味着从逻辑上讲,连接按照它们在FROM子句中指定的顺序执行

语法变化

请注意,上述结论适用于最传统的连接语法,即:

FROM
  T1
  ... JOIN T2 ON ...
  ... JOIN T3 ON ...
  ...
Run Code Online (Sandbox Code Playgroud)

您的示例与该模式匹配,因此结论也适用于它。然而,值得一提的是,我们的结论并不适用,或者至少不那么直接。

1.嵌套的JOIN语法

从语法上讲,可以在另一个连接中指定一个连接,如下所示:

FROM
  T1
  JOIN
    T2
    JOIN T3 ON ..
  ON ...
Run Code Online (Sandbox Code Playgroud)

在上述情况下,JOIN T2之前遇到过JOIN T3。然而,前一个 join 的声明在这一点上并不完整:它的ON子条款是最后的,并且只在JOIN T3 ON ...部分之后进行逻辑评估。所以在这种情况下,T2首先加入到T3,然后加入的结果加入到T1

你仍然可以争辩说我们的结论站在这里,尽管在这种情况下它并不那么明确。我们得出结论,连接是按照它们在子句中指定的顺序进行评估的FROM。在这个例子中,我们在解析FROM子句时遇到的第一个连接在第二个连接时尚未完全指定。

2. 混合逗号连接和常规连接

在引入显式JOIN语法之前,连接是这样指定的:

FROM
  T1,
  T2,
  T3
WHERE
  <joining conditions, filter conditions>
Run Code Online (Sandbox Code Playgroud)

大多数(如果不是全部)平台(包括 SQL Server)仍然支持这种类型的联接,有时也称为逗号联接。

如果没有连接条件,逗号连接本质上就是交叉连接。连接条件使其成为内部连接。但是,您可以看到,这种情况下的连接条件来自一个完全不同的子句,即WHERE子句。

现在,SQL Server 允许您在同一FROM子句中混合使用逗号连接和常规连接。当您混合使用这样的连接时:

FROM
  T1,
  T2
  JOIN T3 ON ... ,
  T4 ...
Run Code Online (Sandbox Code Playgroud)

SQL Server 将在将它们全部交叉连接在一起之前独立于其他项目评估每个单独的逗号分隔项。因此,在上述情况下,T2 JOIN T3 ON ...连接将在其结果交叉连接之前进行评估T1(通过可能找到该WHERE子句的任何连接条件进一步过滤)。我们的结论在这里完全不适用。但是,您可以看到在这种情况下使用了非常不同的语法。

我在 Stack Overflow 上的回答中更详细地讨论了混合语法:The multi-part identifier could not be bound

  • 我的猜测是 OP 的看法可能有所不同。而且我认为即使不正确,也可以想象第二个连接在 T2 和 T3 之间,而不是在 T1-T2 连接的结果和 T3 表之间。事实上,我认为这就是为什么首先提出这个问题的原因。 (5认同)
  • 第二个连接不可能在第一个连接之前解析,因为它引用了来自第一个连接的列。我发现 _logically execution_ 的整个概念不正确,因为查询执行属于 _physical_ 域。 (3认同)

mus*_*cio 9

恐怕“逻辑执行”这个词没有多大意义;根据定义,查询执行是结果集的物理具体化。我认为您所说的“逻辑执行”是查询编译,即分析查询语法和语义并准备查询计划以实现所述语义的阶段。

查询中的联接表1始终从左到右(或从上到下)计算:

select ... from t_a                   -- evaluated first
           join t_b                   -- evaluated second
             on t_a.c1 = t_b.c3
           join t_x                   -- evaluated third
             on t_b.c4 = t_x.c5
           ...
Run Code Online (Sandbox Code Playgroud)

如果您尝试在ON子句中引用属于稍后包含在评估序列中的表的列,您可以自己验证这一点。这将无法编译:

select ... from t_a
           join t_b
             on t_a.c1 = t_c.c8       -- t_c is not known yet
           join t_c                   
           ...
Run Code Online (Sandbox Code Playgroud)

查询被解析后,执行计划可以按照维护查询语义的任何顺序执行连接。正如手册所说

FROM关键字后的表源顺序不影响返回的结果集。

这个问答有点相关。


1 - 一个joined_table子句由两个table_sources 和它们对应的ON子句组成。

  • 语言定义会告诉您输入的结果/输出/值是什么。这是通过根据子表达式的组成输入给出结果/输出/值来完成的。该结果规范称为“逻辑评估/执行”,涉及的表达式树遍历顺序称为“逻辑评估/执行顺序”。虽然如果问题是使用该术语提出的,如果它为那些不知道它的人定义它会有所帮助。除了必须返回指定的结果之外,它与“物理”“执行”无关。 (4认同)