В чем разница между 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)), определенное в его объявлении.

Когда я выполняю оба CTE, я не вижу никакой разницы в плане выполнения.

Мне просто интересно узнать:

  • Какая разница, если я не указываю имена столбцов в определении CTE?
  • Почему я должен / не должен указывать имена столбцов при создании CTE?
  • Повлияло ли это на план выполнения запроса случайно? (Насколько я видел, это не имеет никакого значения.)
Ketan
источник

Ответы:

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];
Shaneis
источник
1
по существу, версия без столбцов совпадает с версией со столбцом, за исключением того, что SQL должен «вывести» имена столбцов из запроса.
KutuluMike
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.

исходное имя / псевдонимы : псевдоним необходимо назначать только в том случае, если вы выполняете вычисления или хотите / должны переименовать столбец

dnoeth
источник
1
«легче обнаружить», возможно, немного субъективно. Я предпочитаю иметь псевдонимы столбцов в начале строки, например SomeAlias = SomeFunction(SomeColumn), с одним определением столбца на строку. Это позволяет просто сканировать в левой части списка столбцов, чтобы найти тот, который вы ищете.
Макс Вернон
1
@MaxVernon: Это верно, и добавление пустых строк между вычислениями, охватывающими несколько строк, также помогает. На самом деле, я в основном тоже опускаю список столбцов ...
dnoeth
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);это было бы приятно для отладки!
Андрей М