WITH CTE和WITH CTE(<column_names>)有什么区别?


11

如在MSDN上使用通用表表达式中所示,可以将CTE定义为:

WITH expression_name [ ( column_name [,...n] ) ]
AS
( CTE_query_definition )

并像这样使用它:

SELECT <column_list> FROM expression_name;

假设我有2个CTE

with cte1 as(
select name from Table1
)

with cte2(name) as(
select name from Table1
)

由于内部查询相同,因此两个CTE的查询输出结果相同。两者之间的唯一区别是cte2(name)在其声明中定义了列name()。

当我执行两个CTE时,执行计划没有任何区别。

我只是好奇地知道:

  • 如果我在CTE定义中未指定任何列名,会有什么区别?
  • 为什么在创建CTE时应该/不应该指定列名?
  • 它是否会偶然影响查询执行计划?(据我所知,这没有任何区别。)

Answers:


25

您几乎已经对一个问题有了答案。

MSDN页面中,引号后直接有一行说明了这一点:

CTE的基本语法结构为:

WITH expression_name [(column_name [,... n])]

(CTE_query_definition)

仅当在查询定义中提供所有结果列的唯一名称时,列名称列表才是可选的。

(已强调)

这意味着在某些情况下您需要指定列名称:

  • 这将工作:

    WITH [test_table] ([NoName], [CAST], [Function]) 
    AS
    (
        SELECT 
            1
          , CAST('1' AS CHAR(1))
          , dbo.CastToChar(1)
    )
    SELECT * FROM [test_table];
  • 就像这样:

    WITH [test_table]  
    AS
    (
        SELECT 
            1 as [NoName]
          , CAST('1' AS CHAR(1)) as [CAST]
          , dbo.CastToChar(1) as [Function]
    )
    SELECT * FROM [test_table];
  • 但这不会,因为它没有列的不同名称:

    WITH [test_table] 
    AS
    (
        SELECT 
            1
          , CAST('1' AS CHAR(1))
          , dbo.CastToChar(1)
    )
    SELECT * FROM [test_table];

1
本质上,没有列的版本与带有列的版本相同,只不过SQL必须从查询中“推断”列名。
KutuluMike '16

10

有趣的是,我更喜欢在CTE中命名列,而不是在WITH CTE (xxx) AS1子句中命名,因为您永远不会无意间使名称与列内容不匹配。

以下面的示例为例:

;WITH MyCTE (x, y)
AS 
(
    SELECT mt.y
         , mt.x
    FROM MySchema.MyTable mt
)
SELECT MyCTE.x
     , MyCTE.y
FROM MyCTE;

这显示什么?它显示y标题为的列中的内容以及标题x为的x列中的内容y

有了这种认识,我再也没有(xxx) AS子句中指定列名,而是这样做:

;WITH MyCTE
AS 
(
    SELECT Alias1 = mt.y
         , Alias2 = mt.x
    FROM MySchema.MyTable mt
)
SELECT MyCTE.Alias1
     , MyCTE.Alias2
FROM MyCTE;

这消除了有关列定义是什么的所有疑问。

在完全不相关的旁注中;引用对象名称时始终指定架构名称,并以分号结束语句


7

最终,每列都需要一个有效的名称,您可以通过两种方式为其分配名称:

  1. 列清单

    ;WITH cte (foo)
    AS
    ( select col from tab )
    select foo from cte;
  2. 使用原始列名或别名

    ;WITH cte
    AS
    ( select col from tab )
    select col from cte;

当您同时使用别名列列表时

  1. 列列表和别名

    ;WITH cte (foo, bar)
    AS
    ( select col1         -- not valid in outer Select
             col2 as colx -- not valid in outer Select
      from tab )
    select foo, bar from cte;

这类似于视图或派生表的定义,您也可以在其中指定列名列表。

列列表:当您进行大量复杂的计算时,由于名称不会分散在源代码中,因此更容易发现名称。如果您拥有递归的CTE,并且可以为#3中的同一列分配两个不同的名称,则会更容易。

原始名称/别名:仅在执行计算或想要/必须重命名列时才需要分配别名


1
“更容易发现”可能有点主观。我更喜欢在行的开头添加列别名,如中所示SomeAlias = SomeFunction(SomeColumn),每行只有一个列定义。这样可以简单地向下扫描列列表的左侧以找到您要查找的列。
Max Vernon

1
@MaxVernon:是的,在跨多行的计算之间添加空白行也有帮助。实际上,我也基本上忽略了列列表...
dnoeth '16

2
您提到的观点很有趣。定义视图时,我从未在视图名称后使用列列表CREATE VIEW SomeView (ColA, ColB, …) AS …。现在您已经提出了,我正在考虑这样的场景CREATE VIEW MyView (G) AS WITH cte (C) AS (SELECT A AS B FROM MyTable) SELECT E AS F FROM (SELECT C AS D FROM cte) AS s (E);–调试是多么的高兴!
Andriy M
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.