Sql 投影中相关子查询排序的影响

Sql 投影中相关子查询排序的影响,sql,sql-server,performance,tsql,subquery,Sql,Sql Server,Performance,Tsql,Subquery,我注意到SQLServer(本例中为SQLServer2008)在select语句中处理相关子查询的方式有点出乎意料。我的假设是,查询计划不应该仅仅受select语句的projection子句中写入子查询(或列)的顺序的影响。然而,情况似乎并非如此 考虑以下两个查询,除了CTE中子查询的顺序外,它们是相同的: --query 1: subquery for Color is second WITH vw AS ( SELECT p.[ID], (SELECT TOP(1) [FirstNa

我注意到SQLServer(本例中为SQLServer2008)在select语句中处理相关子查询的方式有点出乎意料。我的假设是,查询计划不应该仅仅受select语句的projection子句中写入子查询(或列)的顺序的影响。然而,情况似乎并非如此

考虑以下两个查询,除了CTE中子查询的顺序外,它们是相同的:

--query 1: subquery for Color is second
WITH vw AS
(
 SELECT p.[ID],
  (SELECT TOP(1) [FirstName] FROM [Preference] WHERE p.ID = ID AND [FirstName] IS NOT NULL ORDER BY [LastModified] DESC) [FirstName],
  (SELECT TOP(1) [Color] FROM [Preference] WHERE p.ID = ID AND [Color] IS NOT NULL ORDER BY [LastModified] DESC) [Color]
 FROM Person p
)
SELECT ID, Color, FirstName
FROM vw
WHERE Color = 'Gray';


--query 2: subquery for Color is first
WITH vw AS
(
 SELECT p.[ID],
  (SELECT TOP(1) [Color] FROM [Preference] WHERE p.ID = ID AND [Color] IS NOT NULL ORDER BY [LastModified] DESC) [Color],
  (SELECT TOP(1) [FirstName] FROM [Preference] WHERE p.ID = ID AND [FirstName] IS NOT NULL ORDER BY [LastModified] DESC) [FirstName]
 FROM Person p
)
SELECT ID, Color, FirstName
FROM vw
WHERE Color = 'Gray';
如果查看这两个查询计划,您将看到每个子查询都使用了外部联接,联接的顺序与子查询的编写顺序相同。有一个过滤器应用于颜色外部连接的结果,以过滤掉颜色不是“灰色”的行。(奇怪的是,SQL会对颜色子查询使用外部联接,因为我对颜色子查询的结果有一个非null约束,但是OK。)

颜色过滤器将删除大多数行。结果是查询2比查询1便宜得多,因为第二次联接涉及的行更少。撇开所有的理由不谈,这是一种预期的行为吗?SQL server是否应该选择在查询计划中尽早移动筛选器,而不管子查询的编写顺序如何


编辑:我只是想澄清一下,我探讨这个场景有一个合理的理由。我可能需要创建一个包含类似构造子查询的视图,现在很明显,基于从视图投影的这些列的任何过滤都会因为列的顺序而在性能上有所不同

由于TOP操作符在这里起作用,查询优化器对统计信息明显视而不见,因此它将寻找关于如何最好地使用它的其他线索,例如首先实例化CTE的相关部分


这是一个外部联接,因为如果不返回任何内容,子查询将被用作NULL,并且系统将首先实例化它。如果您使用的是聚合而不是TOP,您可能会得到一个稍有不同但更一致的计划。

这里有一个可能表现更好的替代版本:

With Colors As
    (
    Select Id, [Color]
        , ROW_NUMBER() OVER ( PARTITION BY ID ORDER BY [LastModified] DESC ) As Num
    From Preference
    Where [Color] Is Not Null
    )
    , Names As
    (
    Select Id, [FirstName]
        , ROW_NUMBER() OVER ( PARTITION BY ID ORDER BY [LastModified] DESC ) As Num
    From Preference
    Where [FirstName] Is Not Null
    )
Select
From Person As P
    Join Colors As C
        On C.Id = P.Id
            And C.Num = 1
    Left Join Names As N
        On N.Id = P.Id
            And N.Num = 1
Where C.[Color]= 'Grey'
另一种更为简洁的解决方案,可能也可能不起作用:

With RankedItems
    (
    Select Id, [Color], [FirstName]
        , ROW_NUMBER() OVER ( PARTITION BY ID ORDER BY Case When [Color] Is Not Null 1 Else 0 End DESC, [LastModified] DESC ) As ColorRank
        , ROW_NUMBER() OVER ( PARTITION BY ID ORDER BY Case When [FirstName] Is Not Null 1 Else 0 End DESC, [LastModified] DESC ) As NameRank
    From Preference
    )
Select
From Person As P
    Join RankedItems As RI
        On RI.Id = P.Id
            And RI.ColorRank = 1
    Left Join RankedItems As RI2
        On RI2.Id = P.Id
            And RI2.NameRank = 1
Where RI.[Color]= 'Grey'

为什么要使用相关子查询呢?为什么不改为使用联接?如果每个子查询使用两个CTE表而不是一个CTE表,那么查询计划是什么样子的。@HLGEM我通常会使用联接,但在本例中,我只对另一个表的前1(或0)值感兴趣,其中可能有很多。@Thomas我认为我不能将cte用于子查询,因为它们是相关的子查询。我不能参数化CTE(虽然如果你可以的话,这会很好),所以我会遇到我在下面gbn的查询中指出的相同问题。你可能更喜欢使用row_number来获得结果,因为它可以有效地替代TOP。是的,我理解你关于统计的观点,但是,优化器不应该假设任何过滤在最坏的情况下都会留下所有行,但在最好的情况下会删除许多行吗?此外,如果您使用外部联接构造一个更典型的查询,然后在联接列上放置一个非null约束,那么优化器足够聪明,可以使用内部联接。为什么不在这种情况下使用呢?在这种情况下,我也不能轻松地使用聚合,因为SQL中没有空的聚合聚合。select子句中的标量子查询永远不能过滤数据集,因此它被实现为外部联接。您在其他地方过滤它的事实意味着它可能首先通过内部联接过滤掉空值,但这样做没有好处,QO选择使用外部联接(这通常更快,因为系统不必从其工作集中删除行).我知道你不能在这种特殊情况下使用聚合。如果T-SQL实现了一个FIRST(Field)OVER(ORDER BY…)函数就好了,但它还没有。谢谢Thomas,我现在正在检查您的第一个查询,它看起来很不错。到目前为止,生成的计划似乎与顺序无关,并且筛选的联接始终作为内部联接首先执行。您的第一个查询在IO性能方面似乎比我的原始查询更具扩展性,因为SQL扫描后续联接,而不是使用重复的索引查找。第二个查询更简洁(虽然没有那么清楚),但不幸的是,它需要对case语句的结果进行昂贵的排序。通过对ID LastModified DESC进行聚类,我可以很容易地避免第一个查询中所需的排序。