Если CTE определен в запросе и никогда не используется, он издает звук?

Ответы:

21

Похоже, что нет, но это действительно относится только к вложенным CTE.

Создайте две временные таблицы:

CREATE TABLE #t1 (id INT);
INSERT #t1 ( id )
VALUES ( 1 );

CREATE TABLE #t2 (id INT);
INSERT #t2 ( id )
VALUES ( 1 );

Запрос 1:

WITH your_mom AS (
    SELECT TOP 1 *
    FROM #t1 AS t 
),
also_your_mom AS (
    SELECT TOP 1 *
    FROM #t2 AS t
)
SELECT *
FROM your_mom;

Запрос 2:

WITH your_mom AS (
    SELECT TOP 1 *
    FROM #t1 AS t 
),
also_your_mom AS (
    SELECT TOP 1 *
    FROM #t2 AS t
)
SELECT *
FROM also_your_mom;

Планы запроса:

NUTS

Это накладные расходы, но ненужная часть запроса устраняется очень рано (во время синтаксического анализа в этом случае; стадия упрощения в более сложных случаях), поэтому дополнительная работа действительно минимальна и не способствует потенциально дорогостоящим затратным затратам. оптимизация.

Эрик Дарлинг
источник
28

+1 Эрику, но хотел добавить две вещи (которые не сработали в комментарии):

  1. Вам даже не нужно смотреть на планы выполнения, чтобы увидеть, что они игнорируются, когда не используются. Следующее должно привести к ошибке «делить на 0», но не из-за того, что cte2вообще не выбрано:

    ;WITH cte1 AS
    (
      SELECT 1 AS [Bob]
    ),
    cte2 AS (
      SELECT 1 / 0 AS [Err]
      FROM cte1
    )
    SELECT *
    FROM   cte1;
  2. CTE можно игнорировать, даже если они являются единственным CTE, и даже если они выбраны из, если логически все строки будут исключены в любом случае. Ниже приведен случай, когда оптимизатор запросов заранее знает, что строки не могут быть возвращены из CTE, поэтому он даже не потрудился выполнить его:

    ;WITH cte AS
    (
      SELECT 1 / 0 AS [Bob]
    )
    SELECT TOP (1) [object_id]
    FROM   sys.objects
    UNION ALL
    SELECT cte.[Bob]
    FROM   cte
    WHERE  1 = 0;

Что касается производительности, неиспользуемый CTE анализируется и компилируется (или, по крайней мере, компилируется в приведенном ниже случае), поэтому он не игнорируется на 100%, но стоимость должна быть незначительной, и о ней не стоит беспокоиться.

При только парсинге нет ошибки:

SET PARSEONLY ON;

;WITH cte1 AS
(
  SELECT obj.[NotHere]
  FROM   sys.objects obj
)
SELECT TOP (1) so.[name]
FROM   sys.objects so

GO
SET PARSEONLY OFF;
GO

При выполнении всего, кроме выполнения, возникает проблема:

GO
SET NOEXEC ON;
GO

;WITH cte1 AS
(
  SELECT obj.[NotHere]
  FROM   sys.objects obj
)
SELECT TOP (1) so.[name]
FROM   sys.objects so

GO
SET NOEXEC OFF;
GO
/*
Msg 207, Level 16, State 1, Line XXXXX
Invalid column name 'NotHere'.
*/
Соломон Руцкий
источник
Хотел бы я отметить более одного ответа как правильный, но Эрик опередил вас. :) Но ваш ответ очень информативен и великолепен, спасибо!
JD
Что если CTE находятся в представлении, а представление вложено более 3 раз? Нет ли точки, когда оптимизатор сдается и запускает все?
Зикато
@ Zikato Понятия не имею, но это отличный вопрос. Вы должны иметь возможность настроить тест без особых усилий, создав представление с использованием трюка деления на ноль, который я показал в первых двух примерах. Пожалуйста, дайте мне знать результаты, так как мне очень интересно сейчас об этом сценарии :-).
Соломон Руцкий
@SolomonRutzky Чтобы быть справедливым, я действительно проверял это, но это не было окончательно. Я создал представление из вашего примера cte и вложил его 5 раз, но, поскольку он все время сканирует и не очень сложен, оптимизатор справился с этим хорошо. Я хотел бы проверить это более тщательно в будущем и скрыть это за более сложной логикой. Я дам Вам знать.
Зикато
@ Zikato Интересно. Не уверен, что будет считаться «сложным», но да, мой пример очень упрощен. Когда вы говорите «вложенные 5 раз», вы имеете в виду другие представления / процессы, которые вызывают друг друга, и это было 5 глубоких, или в подзапросах / CTE? Я думаю, что есть вероятность, что вложенность достаточного количества уровней может пропустить его, но не из-за того, что на него не ссылаются, а из-за того, что более высокий уровень вложенности не использует его, и что предполагается для более низких уровней. Я видел, как хитрость NEWID()использования представления в UDF может возвращать одно и то же значение из нескольких вызовов из-за его кэширования оптимизатором.
Соломон Руцкий