先看下面一个嵌套的查询语句: 复制代码 代码如下: select * from person.StateProvince where CountryRegionCode in (select CountryRegionCode from person.CountryRegion where Name like "C%")
上面的查询语句使用了一个子查询。虽然这条SQL语句并不复杂,但如果嵌套的层次过多,会使SQL语句非常难以阅读和维护。因此,也可以使用表变量的方式来解决这个问题,SQL语句如下: 复制代码 代码如下: declare @t table(CountryRegionCode nvarchar(3)) insert into @t(CountryRegionCode) (select CountryRegionCode from person.CountryRegion where Name like "C%") select * from person.StateProvince where CountryRegionCode in (select * from @t)
虽然上面的SQL语句要比第一种方式更复杂,但却将子查询放在了表变量@t中,这样做将使SQL语句更容易维护,但又会带来另一个问题,就是性能的损失。由于表变量实际上使用了临时表,从而增加了额外的I/O开销,因此,表变量的方式并不太适合数据量大且频繁查询的情况。为此,在SQL Server 2005中提供了另外一种解决方案,这就是公用表表达式(CTE),使用CTE,可以使SQL语句的可维护性,同时,CTE要比表变量的效率高得多。 下面是CTE的语法: 复制代码 代码如下: [ WITH <common_table_expression> [ ,n ] ] <common_table_expression>::= expression_name [ ( column_name [ ,n ] ) ] AS ( CTE_query_definition )
现在使用CTE来解决上面的问题,SQL语句如下: 复制代码 代码如下: with cr as ( select CountryRegionCode from person.CountryRegion where Name like "C%" ) select * from person.StateProvince where CountryRegionCode in (select * from cr)
其中cr是一个公用表表达式,该表达式在使用上与表变量类似,只是SQL Server 2005在处理公用表表达式的方式上有所不同。 在使用CTE时应注意如下几点: 1. CTE后面必须直接跟使用CTE的SQL语句(如select、insert、update等),否则,CTE将失效。如下面的SQL语句将无法正常使用CTE: 复制代码 代码如下: with cr as ( select CountryRegionCode from person.CountryRegion where Name like "C%" ) select * from person.CountryRegion -- 应将这条SQL语句去掉 -- 使用CTE的SQL语句应紧跟在相关的CTE后面 -- select * from person.StateProvince where CountryRegionCode in (select * from cr)
2. CTE后面也可以跟其他的CTE,但只能使用一个with,多个CTE中间用逗号(,)分隔,如下面的SQL语句所示: 复制代码 代码如下: with cte1 as ( select * from table1 where name like "abc%" ), cte2 as ( select * from table2 where id > 20 ), cte3 as ( select * from table3 where price < 100 ) select a.* from cte1 a, cte2 b, cte3 c where a.id = b.id and a.id = c.id
3. 如果CTE的表达式名称与某个数据表或视图重名,则紧跟在该CTE后面的SQL语句使用的仍然是CTE,当然,后面的SQL语句使用的就是数据表或视图了,如下面的SQL语句所示: 复制代码 代码如下: -- table1是一个实际存在的表 with table1 as ( select * from persons where age < 30 ) select * from table1 -- 使用了名为table1的公共表表达式 select * from table1 -- 使用了名为table1的数据表
4. CTE 可以引用自身,也可以引用在同一 WITH 子句中预先定义的 CTE。不允许前向引用。 5. 不能在 CTE_query_definition 中使用以下子句: (1)COMPUTE 或 COMPUTE BY (2)ORDER BY(除非指定了 TOP 子句) (3)INTO (4)带有查询提示的 OPTION 子句 (5)FOR XML (6)FOR BROWSE 6. 如果将 CTE 用在属于批处理的一部分的语句中,那么在它之前的语句必须以分号结尾,如下面的SQL所示: 复制代码 代码如下: declare @s nvarchar(3) set @s = "C%" ; -- 必须加分号 with t_tree as ( select CountryRegionCode from person.CountryRegion where Name like @s ) select * from person.StateProvince where CountryRegionCode in (select * from t_tree)
为了描述方便,邀月特地列举了一个常见的自关联Table 表结构如下: 表结构 复制代码 代码如下: CREATE TABLE [dbo].[CategorySelf]( [PKID] [int] IDENTITY(1,1) NOT NULL, [C_Name] [nvarchar](50) NOT NULL, [C_Level] [int] NOT NULL, [C_Code] [nvarchar](255) NULL, [C_Parent] [int] NOT NULL, [InsertTime] [datetime] NOT NULL, [InsertUser] [nvarchar](50) NULL, [UpdateTime] [datetime] NOT NULL, [UpdateUser] [nvarchar](50) NULL, [SortLevel] [int] NOT NULL, [CurrState] [smallint] NOT NULL, [F1] [int] NOT NULL, [F2] [nvarchar](255) NULL
CONSTRAINT [PK_OBJECTCATEGORYSELF] PRIMARY KEY CLUSTERED ( [PKID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
GO
再插入一些测试数据 复制代码 代码如下: Insert INSERT INTO [CategorySelf]([C_Name],[C_Level] ,[C_Code],[C_Parent] ,[InsertTime] ,[InsertUser] ,[UpdateTime] ,[UpdateUser] ,[SortLevel] ,[CurrState] ,[F1] ,[F2]) select "分类1",1,"0",0,GETDATE(),"testUser",DATEADD(dd,1,getdate()),"CrackUser",13,0,1,"邀月备注" union all select "分类2",1,"0",0,GETDATE(),"testUser",DATEADD(dd,78,getdate()),"CrackUser",12,0,1,"邀月备注" union all select "分类3",1,"0",0,GETDATE(),"testUser",DATEADD(dd,6,getdate()),"CrackUser",10,0,1,"邀月备注" union all select "分类4",2,"1",1,GETDATE(),"testUser",DATEADD(dd,75,getdate()),"CrackUser",19,0,1,"邀月备注" union all select "分类5",2,"2",2,GETDATE(),"testUser",DATEADD(dd,3,getdate()),"CrackUser",17,0,1,"邀月备注" union all select "分类6",3,"1/4",4,GETDATE(),"testUser",DATEADD(dd,4,getdate()),"CrackUser",16,0,1,"邀月备注" union all select "分类7",3,"1/4",4,GETDATE(),"testUser",DATEADD(dd,5,getdate()),"CrackUser",4,0,1,"邀月备注" union all select "分类8",3,"2/5",5,GETDATE(),"testUser",DATEADD(dd,6,getdate()),"CrackUser",3,0,1,"邀月备注" union all select "分类9",4,"1/4/6",6,GETDATE(),"testUser",DATEADD(dd,7,getdate()),"CrackUser",5,0,1,"邀月备注" union all select "分类10",4,"1/4/6",6,GETDATE(),"testUser",DATEADD(dd,7,getdate()),"CrackUser",63,0,1,"邀月备注" union all select "分类11",4,"1/4/6",6,GETDATE(),"testUser",DATEADD(dd,8,getdate()),"CrackUser",83,0,1,"邀月备注" union all select "分类12",4,"2/5/8",8,GETDATE(),"testUser",DATEADD(dd,10,getdate()),"CrackUser",3,0,1,"邀月备注" union all select "分类13",4,"2/5/8",8,GETDATE(),"testUser",DATEADD(dd,15,getdate()),"CrackUser",1,0,1,"邀月备注"
一个典型的应用场景是:在这个自关联的表中,查询以PKID为2的分类包含所有子分类。也许很多情况下,我们不得不用临时表/表变量/游标等。现在我们有了CTE,就简单多了 复制代码 代码如下: CTEDemo1 WITH SimpleRecursive(C_Name, PKID, C_Code,C_Parent) AS (SELECT C_Name, PKID, C_Code,C_Parent FROM CategorySelf WHERE PKID = 2 UNION ALL SELECT p.C_Name, p.PKID, p.C_Code,p.C_parent FROM CategorySelf P INNER JOIN SimpleRecursive A ON A.PKID = P.C_Parent ) SELECT sr.C_Name as C_Name, c.C_Name as C_ParentName,sr.C_Code as C_ParentCode FROM SimpleRecursive sr inner join CategorySelf c on sr.C_Parent=c.PKID
查询结果如下:C_Name C_ParentName C_ParentCode 分类5 分类2 2 分类8 分类5 2/5 分类12 分类8 2/5/8 分类13 分类8 2/5/8 感觉怎么样?如果我只想查询第二层,而不是默认的无限查询下去, 可以在上面的SQL后加一个选项 Option(MAXRECURSION 5),注意5表示到第5层就不往下找了。如果只想找第二层,但实际结果有三层,此时会出错, Msg 530, Level 16, State 1, Line 1 The statement terminated. The maximum recursion 1 has been exhausted before statement completion. 此时可以通过where条件来解决,而保证不出错,看如下SQL语句: CTEDemo2 复制代码 代码如下: WITH SimpleRecursive(C_Name, PKID, C_Code,C_Parent,Sublevel) AS (SELECT C_Name, PKID, C_Code,C_Parent,0 FROM CategorySelf WHERE PKID = 2 UNION ALL SELECT p.C_Name, p.PKID, p.C_Code,p.C_parent,Sublevel+1 FROM CategorySelf P INNER JOIN SimpleRecursive A ON A.PKID = P.C_Parent ) SELECT sr.C_Name as C_Name, c.C_Name as C_ParentName,sr.C_Code as C_ParentCode FROM SimpleRecursive sr inner join CategorySelf c on sr.C_Parent=c.PKID where SubLevel<=2
查询结果: C_Name C_ParentName C_ParentCode 分类5 分类2 2 分类8 分类5 2/5 当然,我们不是说CTE就是万能的。通过好的表设计也可以某种程度上解决特定的问题。下面用常规的SQL实现上面这个需求。 注意:上面表中有一个字段很重要,就是C_Code,编码 ,格式如"1/2",“2/5/8"表示该分类的上级分类是1/2,2/5/8 这样,我们查询就简单多,查询以PKID为2的分类包含所有子分类: 复制代码 代码如下: SELECT C_Name as C_Name, (Select top 1 C_Name from CategorySelf s where c.C_Parent=s.PKID) as C_ParentName,C_Code as C_ParentCode from CategorySelf c where C_Code like "2/%"
查询以PKID为2的分类包含所有子分类,且级别不大于3 复制代码 代码如下: SELECT C_Name as C_Name, (Select top 1 C_Name from CategorySelf s where c.C_Parent=s.PKID) as C_ParentName,C_Code as C_ParentCode from CategorySelf c where C_Code like "2/%" and C_Level<=3