SPARSE列的使用

稀疏列是对 Null 值采用优化的存储方式的普通列.稀疏列减少了 Null 值的空间需求,但代价是检索非 Null 值的开销增加.当至少能够节省 20%  40% 的空间时,才应考虑使用稀疏列.稀疏列和列集是通过使用 CREATE TABLE ALTER TABLE 语句定义的.

稀疏列可以与列集和筛选索引一起使用

1.列集:INSERTUPDATE  DELETE 语句可以通过名称来引用稀疏列.但是,您也可以查看并处理表中组合为一个 XML 列的所有稀疏列.此列称为列集.

2.筛选索引:因为稀疏列有许多 Null 值行,所以尤其适用于筛选索引.稀疏列的筛选索引可以仅仅对已填充值的行编制索引.这会创建一个更小、更有效的索引.

稀疏列和筛选索引使应用程序( Windows SharePoint Services)可以通过 SQL Server 2008 有效地存储和访问大量的用户定义属性.

注意:

下面的数据类型不能指定为 SPARSE

geography

text

geometry

timestamp

image

user-defined data types

ntext

 

创建存储过程时的WITH RECOMPILE 选项

RECOMPILE:

指示数据库引擎不缓存该过程的计划,该过程在运行时编译.如果指定了 FOR REPLICATION,则不能使用此选项.对于CLR 存储过程,不能指定 RECOMPILE.

若要指示数据库引擎放弃存储过程内单个查询的计划,请使用 RECOMPILE 查询提示.如果非典型值或临时值仅用于属于存储过程的查询子集,则使用 RECOMPILE 查询提示.

 

查询提示:

查询提示指定应在整个查询中使用指示的提示.查询提示影响到语句中的所有运算符.如果主查询中涉及 UNION,则只有涉及 UNION 运算符的最后一个查询才能有 OPTION 子句.查询提示作为 OPTION 子句的一部分指定.如果一个或多个查询提示导致查询优化器不能生成有效计划,则引发 8622 错误.

注意:由于 SQL Server 查询优化器通常会为查询选择最佳执行计划,因此我们建议资深开发人员和数据库管理员只有在不得已时才可使用提示.

SCOPE_IDENTITY 函数使用

返回插入到同一作用域中的标识列内的最后一个标识值.一个范围是一个模块:存储过程、触发器、函数或批处理.因此,如果两个语句处于同一个存储过程、函数或批处理中,则它们位于相同的作用域中.

语法:SCOPE_IDENTITY()

返回类型:numeric(38,0)


SCOPE_IDENTITYIDENT_CURRENT  @@IDENTITY 是相似的函数,因为它们都返回插入到标识列中的值.

IDENT_CURRENT 不受作用域和会话的限制,而受限于指定的表.IDENT_CURRENT 返回为任何会话和作用域中的特定表所生成的值.

SCOPE_IDENTITY  @@IDENTITY 返回在当前会话中的任何表内所生成的最后一个标识值.但是,SCOPE_IDENTITY 只返回插入到当前作用域中的值;@@IDENTITY 不受限于特定的作用域.

例如,有两个表 T1  T2,并且在 T1 上定义了 INSERT 触发器.当将某行插入 T1 ,触发器被激发,并在 T2 中插入一行.该方案演示了两个作用域:在 T1 上的插入,以及在 T2 通过触发器的插入.

假设 T1  T2 都有标识列,@@IDENTITY  SCOPE_IDENTITY 将在 T1 上的 INSERT 语句的最后返回不同的值.@@IDENTITY 将返回在当前会话中的任何作用域内插入的最后一个标识列的值.这是在 T2 中插入的值.SCOPE_IDENTITY() 将返回在 T1 中插入的 IDENTITY .这是在同一个作用域内发生的最后的插入.如果在任何 INSERT 语句作用于作用域中的标识列之前调用 SCOPE_IDENTITY() 函数,则该函数将返回 Null.

如果语句和事务失败,它们会更改表的当前标识,从而使标识列中的值出现不连贯现象.即使未提交试图向表中插入值的事务,也永远无法回滚标识值.例如,如果因 IGNORE_DUP_KEY 冲突而导致 INSERT 语句失败,表的当前标识值仍然会增加.

 

示例

A.  @@IDENTITY  SCOPE_IDENTITY 用于触发器

下面的示例创建两个表,TZ  TY,并对 TZ 创建一个 INSERT 触发器.当将某行插入表 TZ 中时,触发器 (Ztrig)将激发并在 TY 中插入一行.

USE tempdb

GO

CREATE TABLE TZ (

Z_id int IDENTITY(1,1)PRIMARY KEY,

Z_name varchar(20) NOT NULL)

 

INSERT TZ

VALUES (‘Lisa’)

INSERT TZ

VALUES (‘Mike’)

INSERT TZ

VALUES (‘Carla’)

 

SELECT * FROM TZ

 

–Result set: This is how table TZ looks.

 

Z_id Z_name

————-

1 Lisa

2 Mike

3 Carla

 

CREATE TABLE TY (

Y_id int IDENTITY(100,5)PRIMARY KEY,

Y_name varchar(20) NULL)

 

INSERT TY (Y_name)

VALUES (‘boathouse’)

INSERT TY (Y_name)

VALUES (‘rocks’)

INSERT TY (Y_name)

VALUES (‘elevator’)

 

SELECT * FROM TY

–Result set: This is how TY looks:

 

Y_id Y_name

—————

100 boathouse

105 rocks

110 elevator

 

/*Create the trigger that inserts a row in table TY

when a row is inserted in table TZ.*/

CREATE TRIGGER Ztrig

ON TZ

FOR INSERT AS

BEGIN

INSERT TY VALUES (”)

END

 

/*FIRE the trigger and determine what identity values you obtain

with the @@IDENTITY and SCOPE_IDENTITY functions.*/

INSERT TZ VALUES (‘Rosalie’)

 

SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY]

GO

SELECT @@IDENTITY AS [@@IDENTITY]

GO

 

下面是结果集:

SCOPE_IDENTITY

4

/*SCOPE_IDENTITY 返回同一个作用域中的最后一个标识值.这对于表 TZ 是插入操作.*/

@@IDENTITY

115

/*@@IDENTITY 返回由触发器插入到 TY 的最后一个标识值.之所以激发,是因为以前对 TZ 执行了插入.*/

B.  @@IDENTITY  SCOPE_IDENTITY() 用于复制

下面的示例说明如何针对为合并复制发布的数据库中的插入内容使用 @@IDENTITY  SCOPE_IDENTITY().示例中的两个表都在 AdventureWorks2008R2 示例数据库中,其中 Person.ContactType 未发布,Sales.Customer已发布.合并复制将把触发器添加到已发布的表中.因此,@@IDENTITY 可以从复制系统表中的插入内容而非用户表中的插入内容返回值.

Person.ContactType 表的最大标识值为 20.如果在该表中插入一行,@@IDENTITY  SCOPE_IDENTITY() 将返回相同的值.

USE AdventureWorks2008R2;

GO

INSERT INTO Person.ContactType ([Name]) VALUES (‘Assistant to the Manager’);

GO

SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY];

GO

SELECT @@IDENTITY AS [@@IDENTITY];

GO

 

下面是结果集:

SCOPE_IDENTITY

21

@@IDENTITY

21

Sales.Customer 表的最大标识值为 29483.如果在此表中插入一行,@@IDENTITY  SCOPE_IDENTITY() 将返回不同值.SCOPE_IDENTITY() 从用户表的插入内容返回值, @@IDENTITY 从复制系统表中的插入内容返回值.请对需要访问插入的标识值的应用程序使用 SCOPE_IDENTITY().

INSERT INTO Sales.Customer ([TerritoryID],[PersonID]) VALUES (8,NULL);

GO

SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY];

GO

SELECT @@IDENTITY AS [@@IDENTITY];

GO

 

下面是结果集:

SCOPE_IDENTITY

29484

@@IDENTITY

89

SqlServer中日期和时间数据类型及函数

日期和时间数据类型

下表列出了 Transact-SQL 的日期和时间数据类型.

数据类型

格式

范围

精确度

存储大小(以字节为单位)

用户定义的秒的小数精度

时区偏移量

time

hh:mm:ss[.nnnnnnn]

00:00:00.0000000 23:59:59.9999999

100 纳秒

5

date

YYYY-MM-DD

0001-01-01  9999-12-31

3

smalldatetime

YYYY-MM-DD hh:mm:ss

1900-01-01  2079-06-06

分钟

4

datetime

YYYY-MM-DD hh:mm:ss[.nnn]

1753-01-01  9999-12-31

0.00333

8

datetime2

YYYY-MM-DD hh:mm:ss[.nnnnnnn]

0001-01-01 00:00:00.0000000 9999-12-31 23:59:59.9999999

100 纳秒

8

datetimeoffset

YYYY-MM-DD hh:mm:ss[.nnnnnnn] [+|-]hh:mm

0001-01-01 00:00:00.0000000 9999-12-31 23:59:59.9999999(以UTC 时间表示)

100 纳秒

10

 

注意:Transact-SQL rowversion 数据类型不是日期或时间数据类型.timestamp  rowversion 的同义词,但不推荐使用.

 

日期和时间函数

用来获取系统日期和时间值的函数

所有系统日期和时间值均得自运行 SQL Server 实例的计算机的操作系统.
精度较高的系统日期和时间函数

SQL Server 2008 使用 GetSystemTimeAsFileTime() Windows API 来获取日期和时间值.精确程度取决于运行 SQL Server 实例的计算机硬件和 Windows 版本. API 的精度固定为 100 纳秒.可通过使用 GetSystemTimeAdjustment() Windows API 来确定该精确度.

函数

语法

返回值

返回数据类型

确定性

SYSDATETIME

SYSDATETIME ()

返回包含计算机的日期和时间的 datetime2(7),SQL Server 的实例正在该计算机上运行.时区偏移量未包含在内.

datetime2(7)

不具有确定性

SYSDATETIMEOFFSET

SYSDATETIMEOFFSET ( )

返回包含计算机的日期和时间的datetimeoffset(7),SQL Server 的实例正在该计算机上运行.时区偏移量包含在内.

datetimeoffset(7)

不具有确定性

SYSUTCDATETIME

SYSUTCDATETIME ( )

返回包含计算机的日期和时间的 datetime2(7),SQL Server 的实例正在该计算机上运行.日期和时间作为 UTC 时间(通用协调时间)返回.

datetime2(7)

不具有确定性


精度较低的系统日期和时间函数

函数

语法

返回值

返回数据类型

确定性

CURRENT_TIMESTAMP

CURRENT_TIMESTAMP

返回包含计算机的日期和时间的datetime2(7) ,SQL Server的实例正在该计算机上运行.时区偏移量未包含在内.

datetime

不具有确定性

GETDATE

GETDATE ( )

返回包含计算机的日期和时间的datetime2(7) ,SQL Server的实例正在该计算机上运行.时区偏移量未包含在内.

datetime

不具有确定性

GETUTCDATE

GETUTCDATE ( )

返回包含计算机的日期和时间的datetime2(7) ,SQL Server的实例正在该计算机上运行.日期和时间作为 UTC 时间(通用协调时间)返回.

datetime

不具有确定性

用来获取日期和时间部分的函数

函数

语法

返回值

返回数据类型

确定性

DATENAME

DATENAME ( datepartdate )

返回表示指定日期的指定datepart 的字符串.

nvarchar

不具有确定性

DATEPART

DATEPART ( datepartdate )

返回表示指定 date 的指定datepart 的整数.

int

不具有确定性

DAY

DAY ( date )

返回表示指定 date 部分的整数.

int

具有确定性

MONTH

MONTH ( date )

返回表示指定 date 部分的整数.

int

具有确定性

YEAR

YEAR ( date )

返回表示指定 date 部分的整数.

int

具有确定性

用来获取日期和时间差的函数

函数

语法

返回值

返回数据类型

确定性

DATEDIFF

DATEDIFF ( datepart ,startdate , enddate )

返回两个指定日期之间所跨的日期或时间 datepart 边界的数目.

int

具有确定性

用来修改日期和时间值的函数

函数

语法

返回值

返回数据类型

确定性

DATEADD

DATEADD (datepartnumber date )

通过将一个时间间隔与指定date 的指定 datepart相加,返回一个新的datetime .

date 参数的数据类型.

具有确定性

SWITCHOFFSET

SWITCHOFFSET(DATETIMEOFFSET ,time_zone)

SWITCH OFFSET 更改DATETIMEOFFSET 值的时区偏移量并保留 UTC .

具有DATETIMEOFFSET的小数精度的datetimeoffset

具有确定性

TODATETIMEOFFSET

TODATETIMEOFFSET (expression ,time_zone)

TODATETIMEOFFSET datetime2 值转换为datetimeoffset.datetime2 值被解释为指定 time_zone 的本地时间.

具有 datetime 参数的小数精度的datetimeoffset

具有确定性

用来设置或获取会话格式的函数

函数

语法

返回值

返回数据类型

确定性

@@DATEFIRST

@@DATEFIRST

返回对会话进行 SET DATEFIRST操作所得结果的当前值.

tinyint

不具有确定性

SET DATEFIRST

SET DATEFIRST {number |@number_var }

将一周的第一天设置为从 1  7 的一个数字.

不适用

不适用

SET DATEFORMAT

SET DATEFORMAT {format |@format_var }

设置用于输入 datetime smalldatetime 数据的日期各部分(月//年)的顺序.

不适用

不适用

@@LANGUAGE

@@LANGUAGE

返回当前使用的语言的名称.@@LANGUAGE 不是日期或时间函数.但是,语言设置会影响日期函数的输出.

不适用

不适用

SET LANGUAGE

SET LANGUAGE { [ N ] language |@language_var }

设置会话和系统消息的语言环境.SET LANGUAGE 不是日期或时间函数.但是,语言设置会影响日期函数的输出.

不适用

不适用

sp_helplanguage

sp_helplanguage [ [@language = ]language ]

返回有关所有支持语言日期格式的信息.sp_helplanguage 不是日期或时间存储过程.但是,语言设置会影响日期函数的输出.

不适用

不适用

用来验证日期和时间值的函数

函数

语法

返回值

返回数据类型

确定性

ISDATE

ISDATE (expression )

确定 datetime smalldatetime 输入表达式是否为有效的日期或时间值.

int

只有与 CONVERT 函数一起使用,同时指定了 CONVERT 样式参数且样式不等于 0,100,9  109,ISDATE 才是确定的.

 

日期和时间相关主题

主题

说明

使用日期和时间数据

提供通用于日期和时间数据类型及函数的信息和示例.

CAST  CONVERT (Transact-SQL)

提供有关在日期和时间值与字符串文字及其他日期和时间格式之间进行相互转换的信息.

编写国际化 Transact-SQL 语句

提供使用 Transact-SQL 语句的数据库和数据库应用程序在不同语言之间的可移植性准则,或支持多种语言的数据库和数据库应用程序的可移植性准则.

ODBC 标量函数(Transact-SQL)

提供有关可在 Transact-SQL 语句中使用的 ODBC 标量函数的信息.这包括ODBC 日期和时间函数.

分布式查询的数据类型映射

提供有关以下方面的信息:日期和时间数据类型对具有不同版本的 SQL Server 或不同访问接口的服务器之间的分布式查询有何影响.

Common Table Expression (CTE)

指定临时命名的结果集,这些结果集称为公用表表达式 (CTE).该表达式源自简单查询,并且在单条SELECT,INSERT,UPDATE,MERGE  DELETE 语句的执行范围内定义.该子句也可用在 CREATE VIEW 语句中,作为该语句的 SELECT 定义语句的一部分.公用表表达式可以包括对自身的引用.这种表达式称为递归公用表表达式.

 

语法

[ WITH <common_table_expression> [ ,…] ]

 

<common_table_expression>::=

        expression_name [ ( column_name [ ,…] ) ]

    AS

        ( CTE_query_definition )

参数

expression_name

公用表表达式的有效标识符. expression_name 必须与在同一 WITH <common_table_expression> 子句中定义的任何其他公用表表达式的名称不同, expression_name 可以与基表或基视图的名称相同.在查询中对 expression_name 的任何引用都会使用公用表表达式,而不使用基对象.

column_name

在公用表表达式中指定列名.在一个 CTE 定义中不允许出现重复的名称.指定的列名数必须与CTE_query_definition 结果集中列数匹配.只有在查询定义中为所有结果列都提供了不同的名称时,列名称列表才是可选的.

CTE_query_definition

指定一个其结果集填充公用表表达式的 SELECT 语句.除了 CTE 不能定义另一个 CTE 以外,CTE_query_definition  SELECT 语句必须满足与创建视图时相同的要求.

如果定义了多个 CTE_query_definition,则这些查询定义必须用下列一个集合运算符联接起来:UNION ALL,UNION,EXCEPT  INTERSECT.

注释

创建和使用公用表表达式的准则

下面的准则适用于非递归公用表表达式.

  • CTE 之后必须跟随引用部分或全部 CTE 列的单条 SELECT,INSERT,UPDATE,MERGE  DELETE 语句.也可以在 CREATE VIEW 语句中将 CTE 指定为视图中 SELECT 定义语句的一部分.
  • 可以在非递归 CTE 中定义多个 CTE 查询定义.定义必须与以下集合运算符之一结合使用:UNION ALL,UNION,INTERSECT  EXCEPT.
  • CTE 可以引用自身,也可以引用在同一 WITH 子句中预先定义的 CTE.不允许前向引用.
  • 不允许在一个 CTE 中指定多个 WITH 子句.例如,如果 CTE_query_definition 包含一个子查询,则该子查询不能包括定义另一个 CTE 的嵌套的 WITH 子句.
  • 不能在 CTE_query_definition 中使用以下子句:
    • COMPUTE  COMPUTE BY
    • ORDER BY(除非指定了 TOP 子句)
    • INTO
    • 带有查询提示的 OPTION 子句
    • FOR XML
    • FOR BROWSE
  • 如果将 CTE 用在属于批处理的一部分的语句中,那么在它之前的语句必须以分号结尾.
  • 可以使用引用 CTE 的查询来定义游标.
  • 可以在 CTE 中引用远程服务器中的表.
  • 在执行 CTE ,任何引用 CTE 的提示都可能与该 CTE 访问其基础表时发现的其他提示相冲突,这种冲突与引用查询中的视图的提示所发生的冲突相同.发生这种情况时,查询将返回错误.
  •  CTE  UPDATE 语句的目标时,在该语句中对 CTE 的所有引用都必须匹配.例如,如果在 FROM 子句中向 CTE 分配了一个别名,则该别名必须用于对 CTE 的所有其他引用.不明确的 CTE 引用可能会产生意外的联接行为和意外的查询结果.

 

定义和使用递归公用表表达式的准则

下面的准则适用于定义递归公用表表达式:

  • 递归 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_query_definition 中的 CTE 的递归引用的提示.

 

下面的准则适用于使用递归公用表表达式:

  • 无论参与的 SELECT 语句返回的列的为 Null 性如何,递归 CTE 返回的全部列都可以为空.
  • 如果递归 CTE 组合不正确,可能会导致无限循环.例如,如果递归成员查询定义对父列和子列返回相同的值,则会造成无限循环.可以使用 MAXRECURSION 提示以及在 INSERT,UPDATE,MERGE,DELETE SELECT 语句的 OPTION 子句中的一个 0  32,767 之间的值,来限制特定语句所允许的递归级数,以防止出现无限循环.这样就能够在解决产生循环的代码问题之前控制语句的执行.服务器范围的默认值为100.如果指定 0,则没有限制.每一个语句只能指定一个 MAXRECURSION .
  • 不能使用包含递归公用表表达式的视图来更新数据.
  • 可以使用 CTE 在查询上定义游标.CTE 是定义游标结果集的 select_statement 参数.递归 CTE 只允许使用快速只进游标和静态(快照)游标.如果在递归 CTE 中指定了其他游标类型,则该类型将转换为静态游标类型.
  • 可以在 CTE 中引用远程服务器中的表.如果在 CTE 的递归成员中引用了远程服务器,那么将为每个远程表创建一个假脱机,这样就可以在本地反复访问这些表.如果为 CTE 查询,Index Spool/Lazy Spool 则显示在查询计划中,并具有额外的 WITH STACK 谓词.这是一种确认正确递归的方法.
  • SQL Server 2008 不允许在 CTE 的递归部分中使用分析和聚合函数.

 

 

示例

A. 创建一个简单公用表表达式

以下示例显示直接向 Adventure Works Cycles 的每个经理报告的雇员的数目.

USE AdventureWorks2008R2;
GO

WITH Sales_CTE (SalesPersonID, NumberOfOrders)
AS
(
SELECT SalesPersonID, COUNT(*)
FROM Sales.SalesOrderHeader
WHERE SalesPersonID IS NOT NULL
GROUP BY SalesPersonID
)

SELECT SalesPersonID, NumberOfOrders
FROM Sales_CTE
ORDER BY SalesPersonID;
GO

B. 使用公用表表达式来限制次数和报告平均数

以下示例显示向经理报告的雇员的平均数.

WITH Sales_CTE (SalesPersonID, NumberOfOrders)
AS
(
SELECT SalesPersonID, COUNT(*)
FROM Sales.SalesOrderHeader
WHERE SalesPersonID IS NOT NULL
GROUP BY SalesPersonID
)
SELECT AVG(NumberOfOrders) AS “Average Sales Per Person”
FROM Sales_CTE;
GO

C. 多次引用同一个公用表表达式

以下示例显示 SalesOrderHeader 表中每个销售人员的销售订单的总数和最近的销售订单的日期.CTE 在运行的语句中被引用两次:一次返回为销售人员所选的列,另一次检索销售经理的类似详细信息.销售人员和销售经理的数据都返回在一行中.

USE AdventureWorks2008R2;
GO
— Define the CTE expression name and column list.
WITH Sales_CTE (SalesPersonID, SalesOrderID, SalesYear)
AS
— Define the CTE query.
(
SELECT SalesPersonID, SalesOrderID, YEAR(OrderDate) AS SalesYear
FROM Sales.SalesOrderHeader
WHERE SalesPersonID IS NOT NULL
)
— Define the outer query referencing the CTE name.
SELECT SalesPersonID, COUNT(SalesOrderID) AS TotalSales, SalesYear
FROM Sales_CTE
GROUP BY SalesYear, SalesPersonID
ORDER BY SalesPersonID, SalesYear;
GO

使用递归公用表表达式显示递归的多个级别.

以下示例显示经理以及向经理报告的雇员的层次列表.

USE AdventureWorks2008R2;
GO
WITH DirectReports(ManagerID, EmployeeID, Title, EmployeeLevel) AS
(
SELECT ManagerID, EmployeeID, Title, 0 AS EmployeeLevel
FROM dbo.MyEmployees
WHERE ManagerID IS NULL
UNION ALL
SELECT e.ManagerID, e.EmployeeID, e.Title, EmployeeLevel + 1
FROM dbo.MyEmployees AS e
INNER JOIN DirectReports AS d
ON e.ManagerID = d.EmployeeID
)
SELECT ManagerID, EmployeeID, Title, EmployeeLevel
FROM DirectReports
ORDER BY ManagerID;
GO

E. 使用递归公用表表达式显示递归的两个级别.

以下示例显示经理以及向经理报告的雇员.将返回的级别数目被限制为两个.

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

F. 使用递归公用表表达式显示层次列表

以下示例在示例 C 的基础上添加经理和雇员的名称,以及他们各自的头衔.通过缩进各个级别,突出显示经理和雇员的层次结构.

USE AdventureWorks2008R2;
GO
WITH DirectReports(Name, Title, EmployeeID, EmployeeLevel, Sort)
AS (SELECT CONVERT(varchar(255), e.FirstName + ‘ ‘ + e.LastName),
e.Title,
e.EmployeeID,
1,
CONVERT(varchar(255), e.FirstName + ‘ ‘ + e.LastName)
FROM dbo.MyEmployees AS e
WHERE e.ManagerID IS NULL
UNION ALL
SELECT CONVERT(varchar(255), REPLICATE (‘|    ‘ , EmployeeLevel) +
e.FirstName + ‘ ‘ + e.LastName),
e.Title,
e.EmployeeID,
EmployeeLevel + 1,
CONVERT (varchar(255), RTRIM(Sort) + ‘|    ‘ + FirstName + ‘ ‘ +
LastName)
FROM dbo.MyEmployees AS e
JOIN DirectReports AS d ON e.ManagerID = d.EmployeeID
)
SELECT EmployeeID, Name, Title, EmployeeLevel
FROM DirectReports
ORDER BY Sort;
GO

G. 使用 MAXRECURSION 取消一条语句

可以使用 MAXRECURSION 来防止不合理的递归 CTE 进入无限循环.以下示例特意创建了一个无限循环,然后使用MAXRECURSION 提示将递归级别限制为两级.

USE AdventureWorks2008R2;
GO
–Creates an infinite loop
WITH cte (EmployeeID, ManagerID, Title) as
(
SELECT EmployeeID, ManagerID, Title
FROM dbo.MyEmployees
WHERE ManagerID IS NOT NULL
UNION ALL
SELECT cte.EmployeeID, cte.ManagerID, cte.Title
FROM cte
JOIN  dbo.MyEmployees 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 AdventureWorks2008R2;
GO
WITH cte (EmployeeID, ManagerID, Title)
AS
(
SELECT EmployeeID, ManagerID, Title
FROM dbo.MyEmployees
WHERE ManagerID IS NOT NULL
UNION ALL
SELECT  e.EmployeeID, e.ManagerID, e.Title
FROM dbo.MyEmployees AS e
JOIN cte ON e.ManagerID = cte.EmployeeID
)
SELECT EmployeeID, ManagerID, Title
FROM cte;
GO

 

H. 使用公用表表达式来有选择地执行 SELECT 语句中的递归操作

以下示例显示了为 ProductAssemblyID = 800 生产自行车所需的产品装配和部件层次结构.

USE AdventureWorks2008R2;
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

I.  UPDATE 语句中使用递归 CTE

以下示例为直接或间接用于创建 ProductAssemblyID 800 的所有部件和组件更新 PerAssemnblyQty .公用表表达式将返回用于直接生成 ProductAssemblyID 800 的部件和用于生成这些组件的部件等的列表.只修改公用表表达式所返回的行.

USE AdventureWorks2008R2;
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
)
UPDATE Production.BillOfMaterials
SET PerAssemblyQty = c.PerAssemblyQty * 2
FROM Production.BillOfMaterials AS c
JOIN Parts AS d ON c.ProductAssemblyID = d.AssemblyID
WHERE d.ComponentLevel = 0;

使用多个定位点和递归成员

以下示例使用多个定位点和递归成员来返回指定的人的所有祖先.创建了一个表,并在表中插入值,以建立由递归 CTE 返回的宗谱.

— Genealogy table
IF OBJECT_ID(‘dbo.Person’,’U’) IS NOT NULL DROP TABLE dbo.Person;
GO
CREATE TABLE dbo.Person(ID int, Name varchar(30), Mother int, Father int);
GO
INSERT dbo.Person
VALUES(1, ‘Sue’, NULL, NULL)
,(2, ‘Ed’, NULL, NULL)
,(3, ‘Emma’, 1, 2)
,(4, ‘Jack’, 1, 2)
,(5, ‘Jane’, NULL, NULL)
,(6, ‘Bonnie’, 5, 4)
,(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 dbo.Person
WHERE Name = ‘Bonnie’
UNION
— Second anchor member returns Bonnie’s father.
SELECT Father
FROM dbo.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, dbo.Person
WHERE Generation.ID=Person.ID
)
SELECT Person.ID, Person.Name, Person.Mother, Person.Father
FROM Generation, dbo.Person
WHERE Generation.ID = Person.ID;
GO