信息发布系统 Jquery+MVC架构开发(8)DAL层的补充
在这一层中,应用了sql server CTE,关于cte,在这里补充一下:
CTE (Common Table Expression),是从sql server 2005开始支持的一种表达式,它是一种临时结果集,与派生表类似,仅在查询期间有效。与派生表不同的是,cte可以调用自身,从而实现递归。此外,还可以在同一查询中引用多次。
下面是CTE的语法:
[ WITH [ ,n ] ]
::=
expression_name [ ( column_name [ ,n ] ) ]
AS
( CTE_query_definition )
至少有一个定位点成员和一个递归成员,当然,你可以定义多个定位点成员和递归成员,但所有定位点成员必须在递归成员的前面
定位点成员之间必须使用UNION ALL、UNION、INTERSECT、EXCEPT集合运算符,最后一个定位点成员与递归成员之间必须使用UNION ALL,递归成员之间也必须使用UNION ALL连接
定位点成员和递归成员中的字段数量和类型必须完全一致
递归成员的FROM子句只能引用一次CTE对象
递归成员中不允许出现下列项
SELECT DISTINCT
GROUP BY
HAVING
标量聚合
TOP
LEFT、RIGHT、OUTER JOIN(允许出现 INNER JOIN)
子查询
注:
派生表是一个查询结果生成的表,类似于临时表。
派生表可以简化查询,避免使用临时表。相比手动生成临时性能更优越。派生表与其他表一样出现在查询的FROM子句中
select * from (select * from athors) temp
temp 就是派生表
Every derived table must have its own alias(每个派生表必须有自己的别名)
派生出来的表必须要是一个有效的表.因此,它必须遵守以下几条规则:
1. 所有列必须要有名称
2. 列名称必须是要唯一
3. 不允许使用ORDER BY(除非指定了TOP)
我们在分页算法中应用了CTE,如下:
strSql.Append(@" SELECT count(1) as maxcount from Info " + strwhere.ToString() + "; ");
strSql.Append(@" WITH Row AS
(SELECT ROW_NUMBER() OVER(ORDER BY InfoId) AS rownumber, InfoId FROM Info (NOLOCK) "
+ strwhere.ToString() + ") ");
strSql.Append("select InfoId,infoname,InfoContent,TypeId,PictureUrl,CreateId,CreateDate,ModifyDate,AttachMentUrl,IsTop from Info inner join Row on Info.InfoId=Row.InfoId WHERE rownumber BETWEEN @StartNum AND @StartNum + @pageSize - 1 ");
详细的代码见我的文章:
http://blog.csdn.net/hliq5399/article/details/6629032
大家可以看到应用了CTE的分页算法简洁了很多。
其实CTE的用处很多,一个常见的应用还可以用它来实现递归:
在网上找了个例子,就不自己写了,原文如下:
http://www.cnblogs.com/downmoon/archive/2009/10/23/1588405.html
表结构如下:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
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
内容导航
再插入一些测试数据
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
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,就简单多了。
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
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语句:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
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的分类包含所有子分类:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
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。
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
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
查询结果同上,略去。这里我们看出,有时候,好的表结构设计相当重要。
有人很关心性能问题。目前没有测试过。稍后会附上百万级测试报告。不过,有两点理解邀月忘了补充:
一、CTE其实是面向对象的,运行的基础是CLR。一个很好的说明是With查询语句中是区分字段的大小写的。即"C_Code"和"c_Code"是不一样的,后者会报错。这与普通的SQL语句不同。
二、 这个应用示例重在简化业务逻辑,即便是性能不佳,但对临时表\表变量\游标等传统处理方式是一种业务层次上的简化或者说是优化。