代码改变世界

[置顶]信息发布系统 Jquery+MVC架构开发(8)DAL层的补充

2011-07-26 16:17  乱世文章  阅读(175)  评论(0编辑  收藏  举报

 在这一层中,应用了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_NameC_ParentName C_ParentCode
分类5分类22
分类8分类52/5
分类12分类82/5/8
分类13分类82/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_NameC_ParentNameC_ParentCode
分类5分类22
分类8分类52/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语句不同。

  二、 这个应用示例重在简化业务逻辑,即便是性能不佳,但对临时表\表变量\游标等传统处理方式是一种业务层次上的简化或者说是优化。