Поделиться через


WITH обобщенное_табличное_выражение (Transact-SQL)

Задается временно именованный результирующий набор, называемый обобщенным табличным выражением (ОТВ). Он получается при выполнении простого запроса и определяется в области выполнения одиночной инструкции SELECT, INSERT, UPDATE, MERGE или DELETE. Это предложение может использоваться также в инструкции CREATE VIEW как часть определяющей ее инструкции SELECT. Обобщенное табличное выражение может включать ссылки на само себя. Такое выражение называется рекурсивным обобщенным табличным выражением.

Значок ссылки на разделСоглашение о синтаксисе в Transact-SQL

Синтаксис

[ WITH <common_table_expression> [ ,...n ] ]

<common_table_expression>::=
        expression_name [ ( column_name [ ,...n ] ) ]
    AS
        ( CTE_query_definition )

Аргументы

  • expression_name
    Действительный идентификатор обобщенного табличного выражения. Имя аргумента expression_name должно отличаться от имени другого обобщенного табличного выражения, определенного в том же предложении WITH <общее_табличное_выражение>, но expression_name может совпадать с именем основной таблицы или представления. Любая ссылка на аргумент expression_name в запросе использует обобщенное табличное выражение, но не базовый объект.

  • column_name
    Задается имя столбца в обобщенном табличном выражении. Дублированные имена в определении одного обобщенного табличного выражения не допускаются. Количество заданных имен столбцов должно совпадать с количеством столбцов в результирующем наборе CTE_query_definition. Список имен столбцов необязателен только в том случае, если всем результирующим столбцам в определении запроса присвоены уникальные имена.

  • CTE_query_definition
    Задается инструкция SELECT, результирующий набор которой заполняет обобщенное табличное выражение. Инструкция SELECT для CTE_query_definition должна удовлетворять таким же требованиям, что и при создании представления, за исключением того, что CTE-выражение не может определять другое CTE-выражение. Дополнительные сведения см. в разделах «Примечания» и CREATE VIEW (Transact-SQL).

    Если определено несколько параметров CTE_query_definition, определения запроса должны быть соединены одним из следующих операторов: UNION ALL, UNION, INTERSECT или EXCEPT. Дополнительные сведения об использовании определений рекурсивных CTE-выражений запросов см. в следующем разделе «Примечания» и в разделе Рекурсивные запросы, использующие обобщенные табличные выражения.

Замечания

Рекомендации по созданию и использованию обобщенных табличных выражений

Следующие рекомендации относятся к нерекурсивным обобщенным табличным выражениям. Рекомендации, применимые к рекурсивным обобщенным табличным выражениям, см. в расположенном ниже разделе «Рекомендации по определению и использованию рекурсивных обобщенных табличных выражений».

  • За обобщенным табличным выражением должна следовать одиночная инструкция SELECT, INSERT, UPDATE, MERGE или DELETE, ссылающаяся на некоторые или на все столбцы обобщенного табличного выражения. CTE может задаваться также в инструкции CREATE VIEW как часть определяющей инструкции SELECT представления.

  • Несколько определений запросов CTE-выражений могут быть определены в нерекурсивных CTE-выражениях. Определения могут объединяться одним из следующих операторов: UNION ALL, UNION, INTERSECT или EXCEPT.

  • CTE-выражения могут иметь ссылки сами на себя, а также на CTE-выражения, определенные до этого в том же предложении WITH. Ссылки на определяемые далее CTE-выражения недопустимы.

  • Задание в одном CTE-выражении нескольких предложений WITH недопустимо. Например, если CTE_query_definition содержит вложенный запрос, этот вложенный запрос не может содержать вложенное предложение WITH, определяющее другое CTE-выражение.

  • Следующие предложения не могут использоваться в CTE_query_definition:

    • COMPUTE или COMPUTE BY

    • ORDER BY (за исключением случаев задания предложения TOP)

    • INTO

    • Предложение OPTION с подсказками в запросе

    • FOR XML

    • FOR BROWSE

  • Если CTE-выражение используется в инструкции, являющейся частью пакета, за инструкцией, стоящей перед ней, должен следовать символ точки с запятой.

  • Запрос, ссылающийся на CTE-выражение, может использоваться для определения курсора.

  • В CTE-выражении могут быть ссылки на таблицы, находящиеся на удаленных серверах.

  • При выполнении CTE-выражения между подсказками, ссылающимися на CTE-выражение, может быть конфликт с другими подсказками, обнаруживаемыми, когда CTE-выражения обращаются к их базовым таблицам так же, как подсказки обращаются к представлениям в запросах. Когда это происходит, запрос возвращает ошибку. Дополнительные сведения см. в разделе Разрешение представлений.

  • Если обобщенное табличное выражение указывается в качестве объекта инструкции UPDATE, должны совпадать все ссылки на это выражение в инструкции. Например, если для обобщенного табличного выражения в предложении FROM назначается псевдоним, то этот псевдоним должен использоваться для всех остальных ссылок на обобщенное табличное выражение. Неоднозначные ссылки на обобщенное табличное выражение могут вызвать непредвиденную работу соединений и нежелательные результаты запроса. Дополнительные сведения см. в разделе UPDATE (Transact-SQL).

Рекомендации по созданию и использованию рекурсивных обобщенных табличных выражений

Следующие рекомендации применимы к определению рекурсивных обобщенных табличных выражений.

  • Определение рекурсивного CTE-выражения должно содержать по крайней мере два определения CTE-выражений запросов, закрепленный элемент и рекурсивный член. Могут быть определены несколько закрепленных элементов и рекурсивных членов, однако все определения запросов закрепленного элемента должны быть поставлены перед первым определением рекурсивного члена. Все определения CTE-выражений запросов являются членами указателя, если только они не ссылаются на само CTE-выражение.

  • Закрепленные элементы должны объединяться одним из следующих операторов установки: UNION ALL, UNION, INTERSECT или EXCEPT. UNION ALL является единственным оператором установки, который может находиться между последним закрепленным элементом и первым рекурсивным членом, а также при объединении нескольких рекурсивных членов.

  • Количество столбцов членов указателя и рекурсивных членов должно совпадать.

  • Тип данных столбца в рекурсивном члене должен совпадать с типом данных соответствующего столбца в закрепленном элементе.

  • Предложение FROM рекурсивного члена должно ссылаться на CTE-выражение expression_name только один раз.

  • Следующие элементы недопустимы в CTE_query_definition рекурсивного члена:

    • SELECT DISTINCT

    • GROUP BY

    • HAVING

    • Скалярный агрегат

    • TOP

    • LEFT, RIGHT, OUTER JOIN (INNER JOIN допускается)

    • Вложенные запросы

    • Подсказка, применимая к рекурсивной ссылке на CTE-выражение в определении CTE_query_definition

Следующие рекомендации применимы к использованию рекурсивных обобщенных табличных выражений.

  • Все столбцы, возвращаемые рекурсивным CTE-выражением, могут содержать значения NULL, независимо от того, могут ли иметь значения NULL столбцы, возвращаемые участвующими инструкциями SELECT.

  • Неправильно составленное рекурсивное ОТВ может привести к бесконечному циклу. Например, если определение запроса рекурсивного члена возвращает одинаковые значения как для родительского, так и для дочернего столбца, то образуется бесконечный цикл. Для предотвращения бесконечного цикла можно ограничить количество уровней рекурсии, допустимых для определенной инструкции, с помощью подсказки MAXRECURSION и значения в диапазоне от 0 до 32767 в предложении OPTION инструкции INSERT, UPDATE, MERGE, DELETE или SELECT. Это дает возможность контролировать выполнение инструкции до тех пор, пока не будет разрешена проблема с кодом, из-за которой происходит зацикливание программы. Значение по умолчанию уровня сервера равно 100. При указании 0 не применяется никакого ограничения. В одной инструкции может быть указано только одно значение MAXRECURSION. Дополнительные сведения см. в разделе Подсказки в запросах (Transact-SQL).

  • Представление, содержащее рекурсивное обобщенное табличное выражение, не может использоваться для обновления данных.

  • Курсоры могут определяться на запросах при помощи CTE-выражений. CTE-выражение является аргументом select_statement, который определяет результирующий набор курсора. Для рекурсивных CTE-выражений допустимы только однонаправленные и статические курсоры (курсоры моментального снимка). Если в рекурсивном CTE-выражении указан курсор другого типа, тип курсора преобразуется в статический.

  • В CTE-выражении могут быть ссылки на таблицы, находящиеся на удаленных серверах. Если на удаленный сервер имеются ссылки в рекурсивном члене CTE-выражения, создается буфер для каждой удаленной таблицы, так что к таблицам может многократно осуществляться локальный доступ. Если это запрос CTE, Index Spool/Lazy Spools отображается в плане запроса и будет иметь дополнительный предикат WITH STACK. Это один из способов подтверждения надлежащей рекурсии.

  • В среде SQL Server 2008 нельзя использовать аналитические и статистические функции в рекурсивной части обобщенных табличных выражений.

Примеры

А. Создание простого обобщенного табличного выражения

В следующем примере несколько служащих предоставляют отчеты непосредственно каждому руководителю в Adventure Works Cycles.

USE AdventureWorks;
GO
WITH DirReps(ManagerID, DirectReports) AS 
(
    SELECT ManagerID, COUNT(*) 
    FROM HumanResources.Employee AS e
    WHERE ManagerID IS NOT NULL
    GROUP BY ManagerID
)
SELECT ManagerID, DirectReports 
FROM DirReps 
ORDER BY ManagerID;
GO

Б. Использование обобщенного табличного выражения для ограничения общего и среднего количества отчетов

В следующем примере показано среднее количество служащих, отсылающих доклады руководителям.

WITH DirReps (Manager, DirectReports) AS 
(
    SELECT ManagerID, COUNT(*) AS DirectReports
    FROM HumanResources.Employee
    GROUP BY ManagerID
) 
SELECT AVG(DirectReports) AS [Average Number of Direct Reports]
FROM DirReps 
WHERE DirectReports>= 2 ;
GO

В. Неоднократное обращение к обобщенному табличному выражению

В следующем примере представлено общее количество заказов на продажу и дата последнего заказа на продажу в таблице SalesOrderHeader для каждого продавца. В выполняющейся инструкции ссылка на CTE-выражение выполняется два раза: один раз для возврата выбранных столбцов для продавцов и повторно для получения подобной подробной информации для руководителей продавцов. И данные о менеджере по продажам, и данные о его руководителе возвращаются в одной строке.

USE AdventureWorks;
GO
WITH Sales_CTE (SalesPersonID, NumberOfOrders, MaxDate)
AS
(
    SELECT SalesPersonID, COUNT(*), MAX(OrderDate)
    FROM Sales.SalesOrderHeader
    GROUP BY SalesPersonID
)
SELECT E.EmployeeID, OS.NumberOfOrders, OS.MaxDate,
    E.ManagerID, OM.NumberOfOrders, OM.MaxDate
FROM HumanResources.Employee AS E
    JOIN Sales_CTE AS OS
    ON E.EmployeeID = OS.SalesPersonID
    LEFT OUTER JOIN Sales_CTE AS OM
    ON E.ManagerID = OM.SalesPersonID
ORDER BY E.EmployeeID;
GO

Г. Использование рекурсивного обобщенного табличного выражения для отображения нескольких уровней рекурсии

В следующем примере представлен иерархический список руководителей и служащих, отчитывающихся перед ними.

USE AdventureWorks;
GO
WITH DirectReports(ManagerID, EmployeeID, EmployeeLevel) AS 
(
    SELECT ManagerID, EmployeeID, 0 AS EmployeeLevel
    FROM HumanResources.Employee
    WHERE ManagerID IS NULL
    UNION ALL
    SELECT e.ManagerID, e.EmployeeID, EmployeeLevel + 1
    FROM HumanResources.Employee e
        INNER JOIN DirectReports d
        ON e.ManagerID = d.EmployeeID 
)
SELECT ManagerID, EmployeeID, EmployeeLevel 
FROM DirectReports ;
GO

Д. Использование рекурсивного обобщенного табличного выражения для отображения двух уровней рекурсии

В следующем примере представлены руководители и отчитывающиеся перед ними служащие. Количество возвращаемых уровней ограничено двумя.

USE AdventureWorks;
GO
WITH DirectReports(ManagerID, EmployeeID, EmployeeLevel) AS 
(
    SELECT ManagerID, EmployeeID, 0 AS EmployeeLevel
    FROM HumanResources.Employee
    WHERE ManagerID IS NULL
    UNION ALL
    SELECT e.ManagerID, e.EmployeeID, EmployeeLevel + 1
    FROM HumanResources.Employee e
        INNER JOIN DirectReports d
        ON e.ManagerID = d.EmployeeID 
)
SELECT ManagerID, EmployeeID, EmployeeLevel 
FROM DirectReports 
WHERE EmployeeLevel <= 2 ;
GO

Е. Использование рекурсивного обобщенного табличного выражения для отображения иерархического списка

Следующий пример строится на примере с добавлением имен руководителей и служащих и соответствующих им должностей. Иерархия руководителей и служащих дополнительно выделяется при выполнении соответствующих отступов на каждом уровне.

USE AdventureWorks;
GO
WITH DirectReports(Name, Title, EmployeeID, EmployeeLevel, Sort)
AS (SELECT CONVERT(varchar(255), c.FirstName + ' ' + c.LastName),
        e.Title,
        e.EmployeeID,
        1,
        CONVERT(varchar(255), c.FirstName + ' ' + c.LastName)
    FROM HumanResources.Employee AS e
    JOIN Person.Contact AS c ON e.ContactID = c.ContactID 
    WHERE e.ManagerID IS NULL
    UNION ALL
    SELECT CONVERT(varchar(255), REPLICATE ('| ' , EmployeeLevel) +
        c.FirstName + ' ' + c.LastName),
        e.Title,
        e.EmployeeID,
        EmployeeLevel + 1,
        CONVERT (varchar(255), RTRIM(Sort) + '| ' + FirstName + ' ' + 
                 LastName)
    FROM HumanResources.Employee as e
    JOIN Person.Contact AS c ON e.ContactID = c.ContactID
    JOIN DirectReports AS d ON e.ManagerID = d.EmployeeID
    )
SELECT EmployeeID, Name, Title, EmployeeLevel
FROM DirectReports 
ORDER BY Sort;
GO

Ж. Использование подсказки MAXRECURSION для отмены инструкции

Подсказка MAXRECURSION может использоваться для предотвращения входа в бесконечный цикл из-за неверно сформированного рекурсивного CTE-выражения. В следующем примере специально создается бесконечный цикл и используется подсказка MAXRECURSION для ограничения количества уровней рекурсии до двух.

USE AdventureWorks;
GO
--Creates an infinite loop
WITH cte (EmployeeID, ManagerID, Title) as
(
    SELECT EmployeeID, ManagerID, Title
    FROM HumanResources.Employee
    WHERE ManagerID IS NOT NULL
  UNION ALL
    SELECT cte.EmployeeID, cte.ManagerID, cte.Title
    FROM cte 
    JOIN  HumanResources.Employee AS e 
        ON cte.ManagerID = e.EmployeeID
)
--Uses MAXRECURSION to limit the recursive levels to 2
SELECT EmployeeID, ManagerID, Title
FROM cte
OPTION (MAXRECURSION 2);
GO

После исправления ошибки в коде подсказка MAXRECURSION больше не нужна. В следующем примере приводится правильный код.

USE AdventureWorks;
GO
WITH cte (EmployeeID, ManagerID, Title)
AS
(
    SELECT EmployeeID, ManagerID, Title
    FROM HumanResources.Employee
    WHERE ManagerID IS NOT NULL
  UNION ALL
    SELECT  e.EmployeeID, e.ManagerID, e.Title
    FROM HumanResources.Employee AS e
    JOIN cte ON e.ManagerID = cte.EmployeeID
)
SELECT EmployeeID, ManagerID, Title
FROM cte;
GO

З. Использование обобщенного табличного выражения для выборочного прохождения рекурсивной связи в инструкции SELECT

В следующем примере показана иерархия узлов и компонентов продукции, необходимых для создания велосипеда для ProductAssemblyID = 800.

USE AdventureWorks;
GO
WITH Parts(AssemblyID, ComponentID, PerAssemblyQty, EndDate, ComponentLevel) AS
(
    SELECT b.ProductAssemblyID, b.ComponentID, b.PerAssemblyQty,
        b.EndDate, 0 AS ComponentLevel
    FROM Production.BillOfMaterials AS b
    WHERE b.ProductAssemblyID = 800
          AND b.EndDate IS NULL
    UNION ALL
    SELECT bom.ProductAssemblyID, bom.ComponentID, p.PerAssemblyQty,
        bom.EndDate, ComponentLevel + 1
    FROM Production.BillOfMaterials AS bom 
        INNER JOIN Parts AS p
        ON bom.ProductAssemblyID = p.ComponentID
        AND bom.EndDate IS NULL
)
SELECT AssemblyID, ComponentID, Name, PerAssemblyQty, EndDate,
        ComponentLevel 
FROM Parts AS p
    INNER JOIN Production.Product AS pr
    ON p.ComponentID = pr.ProductID
ORDER BY ComponentLevel, AssemblyID, ComponentID;
GO

И. Использование рекурсивного CTE-выражения в инструкции UPDATE

В следующем примере значение VacationHours изменяется на 25 процентов для всех служащих, отчитывающихся непосредственно или не непосредственно перед ManagerID 12. Обобщенное табличное выражение возвращает иерархический список служащих, отчитывающихся непосредственно перед ManagerID 12, служащих, отчитывающихся перед этими служащими, и т. д. Изменяются только строки, возвращаемые обобщенным табличным выражением.

USE AdventureWorks;
GO
WITH DirectReports(EmployeeID, NewVacationHours, EmployeeLevel)
AS
(SELECT e.EmployeeID, e.VacationHours, 1
  FROM HumanResources.Employee AS e
  WHERE e.ManagerID = 12
  UNION ALL
  SELECT e.EmployeeID, e.VacationHours, EmployeeLevel + 1
  FROM HumanResources.Employee as e
  JOIN DirectReports AS d ON e.ManagerID = d.EmployeeID
)
UPDATE HumanResources.Employee
SET VacationHours = VacationHours * 1.25
FROM HumanResources.Employee AS e
JOIN DirectReports AS d ON e.EmployeeID = d.EmployeeID;
GO

К. Использование нескольких членов указателя и рекурсивных членов

В следующем примере несколько членов указателя и рекурсивных членов используются для возврата всех предков указанного лица. Создается и заполняется значениями таблица для формирования генеалогии семьи, возвращаемой рекурсивным CTE-выражением.

-- Genealogy table
IF OBJECT_ID('Person','U') IS NOT NULL DROP TABLE Person;
GO
CREATE TABLE Person(ID int, Name varchar(30), Mother int, Father int);
GO
INSERT Person VALUES(1, 'Sue', NULL, NULL);
INSERT Person VALUES(2, 'Ed', NULL, NULL);
INSERT Person VALUES(3, 'Emma', 1, 2);
INSERT Person VALUES(4, 'Jack', 1, 2);
INSERT Person VALUES(5, 'Jane', NULL, NULL);
INSERT Person VALUES(6, 'Bonnie', 5, 4);
INSERT Person VALUES(7, 'Bill', 5, 4);
GO
-- Create the recursive CTE to find all of Bonnie's ancestors.
WITH Generation (ID) AS
(
-- First anchor member returns Bonnie's mother.
    SELECT Mother 
    FROM Person
    WHERE Name = 'Bonnie'
UNION
-- Second anchor member returns Bonnie's father.
    SELECT Father 
    FROM Person
    WHERE Name = 'Bonnie'
UNION ALL
-- First recursive member returns male ancestors of the previous generation.
    SELECT Person.Father
    FROM Generation, Person
    WHERE Generation.ID=Person.ID
UNION ALL
-- Second recursive member returns female ancestors of the previous generation.
    SELECT Person.Mother
    FROM Generation, Person
    WHERE Generation.ID=Person.ID
)
SELECT Person.ID, Person.Name, Person.Mother, Person.Father
FROM Generation, Person
WHERE Generation.ID = Person.ID;
GO

Журнал изменений

Обновленное содержимое

В раздел «Рекомендации по созданию и использованию обобщенных табличных выражений» добавлен пункт, описывающий требования к имени обобщенного табличного выражения, которое является объектом инструкции UPDATE.