SQL Server T-SQL: CTE

概念:CTE:Common Table Expression,简称CTE,中文可以叫做,通(公)用表表达式.

用处:处理以前版本中SQL不好现实,不好理解,复杂的查询问题.比如:分页,递归查询...

基本语法:

 

WITH <name of your CTE>(<column names>)
AS
(
<actual query>
)

SELECT * FROM <name of your CTE>

示例一(基本用法):

WITH MyCTE(ID, NAME)
AS
(
    SELECT EmployeeID AS ID,
           FirstName + ' ' + LastName AS NAME
    FROM   HumanResources.vEmployee
)
SELECT * FROM MyCTE

示例二(分页):

WITH MyCTE(ID, NAME, RowID)
AS
(
    SELECT hrids AS ID,
           hr_no + ' ' + hr_name AS NAME,
           ROW_NUMBER() OVER(ORDER BY hrids) AS RowID
    FROM   hi_info_main
)
SELECT * FROM MyCTE WHERE RowID BETWEEN 11 AND 21

示例三(关联CTE):

WITH OrderCountCTE(SalesPersonID, OrderCount)
AS
(
    SELECT 
        SalesPersonID,
        COUNT(1) AS OrderCount
    FROM   
        Sales.SalesOrderHeader
    WHERE  
        SalesPersonID IS NOT NULL
    GROUP BY
           SalesPersonID
)
SELECT sp.SalesPersonID,
       sp.SalesYTD,
       cte.OrderCount
FROM   
    OrderCountCTE cte
    INNER JOIN Sales.SalesPerson sp ON cte.SalesPersonID = sp.SalesPersonID

示例四(使用CTE的删除):

/************************************************************
 * Code formatted by SoftTree SQL Assistant ?v4.0.34
 * Time: 2012/10/16 14:52:48
 ************************************************************/

WITH DuplicateProdCTE
AS
(
    SELECT MIN(Product_ID) AS Product_ID,
           Product_Name
    FROM   Products
    GROUP BY
           Product_Name
    HAVING COUNT(1) > 1
)
DELETE Products
FROM   Products p
       JOIN DuplicateProdCTE cte
            ON  cte.Product_Name = p.Product_Name
            AND p.Product_ID > cte.Product_ID

示例五(递归查询):

参考文章:http://www.cnblogs.com/xfrog/archive/2010/10/10/1847462.html

 WITH lmenu(name,senior,level) as 
 (  
    SELECT NAME,SENIOR,0 level FROM MENU WHERE SENIOR IS NULL --定位成员
    UNION ALL 
    SELECT A.NAME,A.SENIOR,b.level+1 FROM MENU A,lmenu b  --递归成员
    where a.senior = b.name 
)  
 
SELECT *  from lmenu 

递归CTE具有一些限制条件(引自MSDN):

  至少有一个定位点成员和一个递归成员,当然,你可以定义多个定位点成员和递归成员,但所有定位点成员必须在递归成员的前面

  定位点成员之间必须使用UNION ALL、UNION、INTERSECT、EXCEPT集合运算符,最后一个定位点成员与递归成员之间必须使用UNION ALL,递归成员之间也必须使用UNION ALL连接

  定位点成员和递归成员中的字段数量和类型必须完全一致

  递归成员的FROM子句只能引用一次CTE对象

  递归成员中不允许出现下列项 

SELECT DISTINCT   
GROUP BY   
HAVING   
标量聚合   
TOP   
LEFTRIGHTOUTER JOIN(允许出现 INNER JOIN)   
子查询 

非递归 CTE 指南:

   CTE 之后必须跟随引用部分或全部 CTE 列的 SELECTINSERTUPDATE  DELETE 语句。也可以在 CREATE VIEW 语句中将 CTE 指定为视图中 SELECT 定义语句的一部分。即CTE后面必须跟随T-SQL语句,且只能在紧跟CTE后面的语句使用;

  可以在非递归 CTE 中定义多个 CTE 查询定义。定义必须与以下集合运算符之一结合使用:UNION ALLUNIONINTERSECT  EXCEPT。即如下格式:

WITH CTEA AS
(
    ....
),
CTEB AS
(
    ....
)

SELECT * FROM CTEB

  CTE 可以引用自身,也可以引用在同一 WITH 子句中预先定义的 CTE。不允许前向引用。

   不允许在一个 CTE 中指定多个 WITH 子句。例如,如果 CTE_query_definition 包含一个子查询,则该子查询不能包括定义另一个 CTE 的嵌套的 WITH 子句。

  不能在 CTE_query_definition 中使用以下子句:

    COMPUTECOMPUTE BY

    ORDER BY(除非指定了 TOP 子句)

    INTO

    带有查询提示的 OPTION 子句

    FOR XML

    FOR BROWSE

  如果将 CTE 用在属于批处理的一部分的语句中,那么在它之前的语句必须以分号结尾。即在With CTE as () 之前加分号(;)

  可以使用引用 CTE 的查询来定义游标。

  可以在 CTE 中引用远程服务器中的表。

递归 CTE 指南:

   递归 CTE 定义至少必须包含两个 CTE 查询定义,一个定位点成员和一个递归成员。可以定义多个定位点成员和递归成员;但必须将所有定位点成员查询定义置于第一个递归成员定义之前。所有 CTE 查询定义都是定位点成员,但它们引用 CTE 本身时除外。

  定位点成员必须与以下集合运算符之一结合使用:UNION ALLUNIONINTERSECT EXCEPT。在最后一个定位点成员和第一个递归成员之间,以及组合多个递归成员时,只能使用 UNION ALL 集合运算符。

  定位点成员和递归成员中的列数必须一致。

  递归成员中列的数据类型必须与定位点成员中相应列的数据类型一致。

  递归成员的 FROM 子句只能引用一次 CTE expression_name

  在递归成员的 CTE_query_definition 中不允许出现下列项:

SELECT DISTINCT

GROUP BY

HAVING

标量聚合

TOP

LEFTRIGHTOUTER JOIN(允许出现 INNER JOIN)

子查询

应用于对 CTE_query_definition 中的 CTE 的递归引用的提示。

  无论参与的 SELECT 语句返回的列的为空性如何,递归 CTE 返回的全部列都可以为空。

  如果递归 CTE 组合不正确,可能会导致无限循环。例如,如果递归成员查询定义对父列和子列返回相同的值,则会造成无限循环。可以使用 MAXRECURSION 提示以及在INSERTUPDATEDELETE  SELECT 语句的 OPTION 子句中的一个 0  32,767 之间的值,来限制特定语句所允许的递归级数,以防止出现无限循环。这样就能够在解决产生循环的代码问题之前控制语句的执行。服务器范围内的默认值是 100。如果指定 0,则没有限制。每一个语句只能指定一个 MAXRECURSION 值。有关详细信息,请参阅查询提示 (Transact-SQL)

  不能使用包含递归公用表表达式的视图来更新数据。

  可以使用 CTE 在查询上定义游标。递归 CTE 只允许使用快速只进游标和静态(快照)游标。如果在递归 CTE 中指定了其他游标类型,则该类型将转换为静态游标类型。

  可以在 CTE 中引用远程服务器中的表。如果在 CTE 的递归成员中引用了远程服务器,那么将为每个远程表创建一个假脱机,这样就可以在本地反复访问这些表。

使用 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

 

 

 

参考文章:

  http://kenchell.blog.163.com/blog/static/260883092011421104513195/

  http://database.51cto.com/art/201107/274675.htm

  

 

posted @ 2012-10-16 15:20  Devil_Zhang  阅读(433)  评论(0编辑  收藏  举报