2004-11-16 + 分页的讨论

为什么要分页?这个问题的答案似乎谁都知道,但是在这里还是要说一下:之所以需要分页,是因为我们“不想”把数据库里的“所有资料”“一次性”的“发送到client端让用户看”。注意加重的那几个词,许多人就是因为不知道这几个词所以才不会分页。
分页的关键并不在于在client端将数据以“分页”的形式表现出来,而是如何将数据库里的资料按照当前页的索引分批的发送到client端。经常可以看到周围的人先用一个“select *  from xx”的办法把所有资料从数据库要出来,然后再对数据进行处理。这样就违背了上面所说的分页的目的(并且,既然把数据都要出来了,为什么不全部给用户看呢?完全的资源浪费啊)。
另外的一个问题是经常会看到有人问:repeater和datalist怎么分页?从这个问题可以看出的一点是,提问者应该会用datagrid进行普通的分页。但是datagrid的普通分页用的是上面的那种方法,所以实际的用途不大。我觉得如果从分页的角度看这三个控件,其差别应该不大:这三个控件都是起展示数据的作用,而没有任何内置的智能用来分页。
最后一个误区是会有人使用dataadapter的Fill(DataSet,int,int,string),方法来进行分页,这个的问题也和上面datagrid普通分页的问题差不多,也不可取。
说了这么多,现在应该能明白正确的分页办法了。即“只从数据库查找当前页需要的数据”,我们可以使用sql语句来完成这个任务。
方法1
思路大家应该可以想到,按照一个索引找出该页的最大序号和最小序号,然后查找记录的时候查找位于这两个序号之间的记录即可。难点就在于怎么找最大序号和最小序号,使用数据库虽然可以指定一个identity按1递增来担当序号,但是由于平常的删除添加等操作,这个序号早就不是按每个递增1的顺序来排列。为了可以使用这个办法来分页,我们需要在查询的时候先创建一个临时表,然后给这个表指定一个递增字段,因为是临时按照数据库里的数据创建的表,所以不存在序号“断号”的问题。其实现的sp如下:

create proc GetAuthors

@Author_Last_Name as varchar(100) = null,

@StartRow as int = null,

@StopRow as int = null

AS

---- 建立有标识符列的table变量

declare @t_table table

(

[rownum] [int] IDENTITY (1, 1) Primary key NOT NULL ,

[Author_Last_Name] [varchar] (40) ,

[Author_First_Name] [varchar] (20) ,

[phone] [char] (12) ,

[address] [varchar] (40) ,

[city] [varchar] (20) ,

[state] [char] (2) ,

[zip] [char] (5)

)

---- 在返回指定的@StopRow行数之后停止处理查询

Set RowCount @StopRow

---- 插入到table变量中

insert @t_table

(

[Author_Last_Name],[Author_First_Name],[phone],[address],[city],[state],[zip]

)

SELECT [Author_Last_Name],[Author_First_Name],[phone],[address],[city],[state],[zip]

FROM authors

WHERE Author_Last_Name like '%' + @Author_Last_Name + '%'

ORDER BY Author_Last_Name

---- 返回到正确的结果

SELECT * FROM @t_table WHERE rownum >= @StartRow

ORDER BY rownum

GO

这个sp是孟子大哥做的,如果用google搜分页,应该就可以找到这个的详细说明,这是其中的一个 http://www.ccw.com.cn/htm/center/prog/02_11_11_4.asp

方法2
如果大家嫌这个办法太麻烦,那么还有一个办法来完成这项工作,这个办法也是我一直在用的,其思路为:排除不需要的序号,剩下的序号就是我们需要的。这个说法听起来很吓人,但是实际做起来却并不复杂。我们知道在sql里有一个top关键字,可以提取前n条记录,该方法就使用了这个办法。假设每页10条记录,现在要查找第6页的话
select top 10 * from yourtable where id not in (select top 50 id from yourtable)
注意这是一个复合查询,先把不需要的序号排除(注意这里排除的并不是全部不需要的序号,而是上半部分),然后从剩下的序号里找出前面的n个,这些就是当前页需要的记录。
下面这个sp是我最近写的一个新闻系统用的,大家看一看

ALTER PROCEDURE GetNewsInfosByCategory
@category varchar(20),
@pageSize int,
@currentPageIndex int
AS
begin
declare @sql nvarchar(180)
set @sql='select top ' + convert(varchar(10),@pageSize) + ' * from newsinfo where category = ''' + @category + ''' and nid not in (select top ' + convert(varchar(10),@pageSize *(@currentPageIndex-1) ) + ' nid from newsinfo where category = ''' + @category + ''' order by nid desc) order by nid desc'
execute sp_executesql @sql
end


用了上面的思路来完成,注意这个是先拼接了一个sql语句,然后再执行,因为在sp中像top等位置是不能使用变量的,所以才需要拼接。但是大家要注意拼接所带来的安全问题。除了要验证输入外,还要注意控制sql语句的长度,以防止sql injection攻击。

以上是两种分页办法,除了这些外还有一些其他的途径可以达到分页的目的,比如eric写的这个,看这里  http://blog.csdn.net/ericfine

最后回到一开始,来讨论一下repeater、datalist、datagrid怎么分页。
既然我们每次得到的都是当前页的记录,那给这三个控件进行分页还有困难吗?我们要做的,只是将返回的数据绑定过去,然后,是做一个用于分页的导航连接,像这面这样的

首页 前页 后页 尾页 页次:1 /1 页 10 个记录/页 共2 个记录?? 转到 -G O-->

然后把这个连接放到控件的下面,当要跳转到某一页时使用url参数
http://localhost/aspx/Astrophel/UI/Pages/List.aspx?page=2

至于这个连接怎么做,可能个人有个人的办法。我是把这个做成了一个用户控件的形式,这样用起来方便。具体的代码就不在这里给出了,因为是用户控件,所以和项目内其他部分的关联比较多。其实我写的那个论坛Astrophel的分页就是用的这个,感兴趣的可以找来看看。
有关分页的内容就暂时写到这里,希望这篇笔记可以对大家有所帮助!

posted on 2006-07-03 18:45  Notus|南色的风  阅读(350)  评论(3编辑  收藏  举报