《ASPNet中DataGrid基于Access的快速分页法详解.docx》由会员分享,可在线阅读,更多相关《ASPNet中DataGrid基于Access的快速分页法详解.docx(15页珍藏版)》请在冰豆网上搜索。
DataGrid是一个功能非常强大的ASP.NETWeb服务器端控件,它除了能够方便地按各种方式格式化显示表格中的数据,还可以对表格中的数据进行动态的排序、编辑和分页。
”的SQL语句从数据库表中取出所有的记录到DataSet中,DataGrid控件绑定到该DataSet之后,它的自动分页功能会帮你从该DataSet中筛选出当前分页的数据并显示出来,其他没有用的数据将被丢弃。 还有一种方法是使用自定义分页功能,先将DataGrid的AllowCustomPaging属性设置为True,再利用DataAdapter的Fill方法将数据的筛选工作提前到填充DataSet时,而不是让DataGrid帮你筛选:
publicintFill(
DataSetdataSet,//要填充的DataSet。
intstartRecord,//从其开始的从零开始的记录号。
intmaxRecords,//要检索的最大记录数。
stringsrcTable//用于表映射的源表的名称。
);
该方法首先将来自查询处的结果填充到DataSet中,再将不需要显示的数据丢弃。
当然,自定义分页功能需要完成的事情还不止这些,本文将在后面详细介绍。
以上两种方法的工作原理都是先从数据库中取出所有的记录,然后筛选出有用的数据显示出来。
可见,两种方法的效率基本上是一致的,因为它们在数据访问阶段并没有采取有效的措施来减少Access对磁盘的访问次数。
对于小数量的记录,这种开销可能是比较小的,如果针对大量数据的分页,开销将会非常巨大,从而导致分页的速度非常的慢。
换句话说,就算每个DataGrid分页面要显示的数据只是一个拥有几万条记录的数据库表的其中10条,每次DataGrid进行分页时还是要从该表中取出所有的记录。
很多人已经意识到了这个问题,并提出了解决方法:
用自定义分页,每次只从数据库中取出要显示的数据。
这样,我们需要在SQL语句上下功夫了。
由于Access不支持真正的存储过程,在编写分页算法上就没有SQLServer那么自由了。
SQLServer可以在存储过程中利用临时表来实现高效率的分页算法,受到了广泛的采用。
而对于Access,我们必须想办法在一条SQL语句内实现最高效的算法。
用一条SQL语句取得某段数据的方法有好几种。
算法不同,效率也就不同。
我经过粗略的测试,发现效率最差的SQL语句执行时耗费的时间大概是效率最高的SQL语句的3倍!
而且这个数值会随着记录总数的增加而增加。
下面将介绍其中两条常用的SQL语句。
为了方便接下来的讨论,我们先约定如下:
变量
说明
变量
说明
@PageSize
每页显示的记录总数
@MiddleIndex
中间页的索引
@PageCount
分页总数
@LastIndex
最后一页的索引
@RecordCount
数据表的记录总数
@TableName
数据库表名称
@PageIndex
当前页的索引
@PrimaryKey
主键字段名称
@FirstIndex
第一页的索引
@QueryFields
要查询的字段集
变量
定义
@PageCount
(int)Math.Ceiling((double)@RecordCount/@PageSize)
@FirstIndex
0
@LastIndex
@PageCount–1
@MiddleIndex
(int)Math.Ceiling((double)@PageCount/2)–1
先让我们看看效率最差的SQL语句:
SELECTTOP@PageSize*FROM@TableName
WHERE@PrimaryKeyNOTIN(
SELECTTOP@PageSize*@PageIndex@PrimaryKeyFROM@TableName
ORDERBY@PrimaryKeyASC
)ORDERBY@PrimaryKeyASC
这条SQL语句慢就慢在NOTIN这里,主SELECT语句遍历的每个@PrimaryKey的值都要跟子SELECT语句的结果集中的每一个@PrimaryKey的值进行比较,这样时间复杂度非常大。
这里不得不提醒一下大家,平时编写SQL语句时应该尽量避免使用NOTIN语句,因为它往往会增加整个SQL语句的时间复杂度。
另一种是使用了两个TOP和三个ORDERBY的SQL语句,如下所示:
SELECT*FROM(
SELECTTOP@PageSize*FROM(
SELECTTOP@PageSize*(@PageIndex+1)*FROM@TableName
ORDERBY@PrimaryKeyASC
)TableAORDERBY@PrimaryKeyDESC
)TableBORDERBY@PrimaryKeyASC
这条SQL语句空间复杂度比较大。
如果要显示的分页面刚好是最后一页,那么它的效率比直接SELECT出所有的记录还要低。
因此,对于分页算法,我们还应该具体情况具体分析,不能一概而论。
下面将简单介绍一下相关概念,如果您对主键和索引非常熟悉,可以直接跳过。
有关主键和索引的概念
在ACCESS中,一个表的主键(PRIMARYKEY,又称主索引)必然是唯一索引(UNIQUEINDEX),它的值是不会重复的。
除此之外,索引依据索引列的值进行排序,每个索引记录包含着一个指向它所引用的数据行的指针,这对ORDERBY的执行非常有帮助。
我们可以利用主键这两个特点来实现对某条记录的定位,从而快速地取出某个分页上要显示的记录。
举个例子,假设主键字段为INTEGER型,在数据库表中,记录的索引已经按主键字段的值升序排好(默认情况下),那么主键字段值为“11”的记录的索引,肯定刚好在值为“12”的记录的索引前面(假设数据库表中存在主键的值为“12”的记录)。
如果主键字段不具备UNIQUE约束,数据库表中将有可能存在两个或两个以上主键字段的值为“11”的记录,这样就无法确定这些记录之间的前后位置了。
下面就让我们看看如何利用主键来进行数据的分段查询吧。
快速分页法的原理
其实该分页法是从其他方法衍生而来的。
本人对原来的方法认真地分析,发现通过优化和改进可以非常有效地提高它的效率。
原算法本身效率很高,但缺乏对具体问题的具体分析。
同一个分页算法,可能在取第一页的数据时效率非常高,但是在取最后一页的数据时可能反而效率更低。
经过分析,我们可以把分页算法的效率状态分为四种情况:
(1)@PageIndex<=@FirstIndex
(2)@FirstIndex<@PageIndex<=@MiddleIndex
(3)@MiddleIndex<@PageIndex<@LastIndex
(4)@PageIndex>=@LastIndex
状态
(1)和(4)分别表示第一页和最后一页。
它们属于特殊情况,我们不必对其使用特殊算法,直接用TOP就可以解决了,不然会把问题复杂化,反而降低了效率。
对于剩下的两种状态,如果分页总数为偶数,我们可以看作是从数据库表中删掉第一页和最后一页的记录,再把剩下的按前后位置平分为两部分,即前面的一部分,也就是状态
(2),后面的为另一部分,也就是状态(3);如果分页总数为奇数,则属于中间页面的记录归于前面的部分。
这四种状态分别对应着四组SQL语句,每组SQL语句由升序和降序两条SQL语句组成。
下面是一个数据库表,左边第一列是虚拟的,不属于该数据库表结构的一部分,它表示相应记录所在的分页索引。
该表将用于接下来的SQL语句的举例中:
PageIndex
ItemId
ProductId
Price
0
001
0011
$12
002
0011
$13
1
003
0012
$13
004
0012
$11
2
005
0013
$14
006
0013
$12
3
007
0011
$13
008
0012
$15
4
009
0013
$12
010
0013
$11
由表可得:
@PageSize=2,@RecordCount=10,@PageCount=5
升序的SQL语句
(1)@PageIndex<=@FirstIndex
取第一页的数据是再简单不过了,我们只要用TOP@PageSize就可以取出第一页要显示的记录了。
SELECTTOP@PageSize@QueryFieldsFROM@TableNameWHERE@ConditionORDERBY@PrimaryKeyASC
(2)@FirstIndex<@PageIndex<=@MiddleIndex
把取数据表前半部分记录和取后半部分记录的SQL语句分开写,可以有效地改善性能。
后面我再详细解释这个原因。
现在看看取前半部分记录的SQL语句。
先取出当前页之前的所有记录的主键值,再从中选出最大值,然后取出主键值大于该最大值的前@PageSize条记录。
这里@PrimaryKey的数据类型可以不是INTEGER类型,CHAR、VARCHAR等其他类型照样可以。
SELECTTOP@PageSize@QueryFields
FROM@TableName
WHERE@PrimaryKey>(
SELECTMAX(@PrimaryKey)FROM(
SELECTTOP@PageSize*@PageIndex@PrimaryKey
FROM@TableName
WHERE@Condition
ORDERBY@PrimaryKeyASC
)TableA
)WHERE@Condition
ORDERBY@PrimaryKeyASC
例如:
@PageIndex=1,红-->黄-->蓝
(3)@MiddleIndex<@PageIndex<@LastIndex
接下来看看取数据库表中后半部分记录的SQL语句。
该语句跟前面的语句算法的原理是一样的,只是方法稍微不同。
先取出当前页之后的所有记录的主键值,再从中选出最小值,然后取出主键值小于该最小值的前@PageSize条记录。
SELECT*FROM(
SELECTTOP@PageSize@QueryFields
FROM@TableName
WHERE@PrimaryKey<(
SELECTMIN(@PrimaryKey)FROM(
SELECTTOP(@RecordCount-@PageSize*(@PageIndex+1))@PrimaryKey
FROM@TableName
WHERE@Condition
ORDERBY@PrimaryKeyDESC
)TableA
)WHERE@Condition
ORDERBY@PrimaryKeyDESC
)TableB
ORDERBY@PrimaryKeyASC
之所以把取数据表前半部分记录和取后半部分记录的SQL语句分开写,是因为使用取前半部分记录的SQL语句时,当前页前面的记录数目随页数递增,而我们还要从这些记录中取出它们的主键字段的值再从中选出最大值。
这样一来,分页速度将随着页数的增加而减慢。
因此我没有这样做,而是在当前页索引大于中间页索引时(@MiddleIndex<@PageIndex)选用了分页速度随着页数的增加而加快的算法。
由此可见,假设把所有分页面划分为前面、中间和后面三部分,则最前面和最后面的分页速度最快,最中间的分页速度最慢。
例如:
@PageIndex=3,红-->黄-->蓝
(4)@PageIndex>=@LastIndex
取最后一页的记录可以简单地使用类似状态
(1)的做法:
SELECT*FROM(
SELECTTOP@PageSize@QueryFields
FROM@TableName
WHERE@Condition
ORDERBY@PrimaryKeyDESC
)TableAORDERBY@PrimaryKeyASC
不过,这样产生的最后一页不一定是实际意义上的最后一页。
因为最后一页的记录数未必刚好跟@PageSize相等,而上面的SQL语句是直接取得倒数的@PageSize条记录。
如果想要精确地取得最后一页的记录,应该在先计算出该页的记录数,作为TOP语句的条件:
SELECT*FROM(
SELECTTOP(@RecordCount-@PageSize*@LastIndex)@QueryFields
FROM@TableNameWHERE@Condition
ORDERBY@PrimaryKeyDESC
)TableAORDERBY@PrimaryKeyASC
降序的SQL语句
降序的SQL语句跟升序的大同小异,这里就不在罗嗦了J
(1)@PageIndex<=@FirstIndex
SELECTTOP@PageSize@QueryFieldsFROM@TableNameWHERE@ConditionORDERBY@PrimaryKeyDESC
(2)@FirstIndex<@PageIndex<=@MiddleIndex
SELECTTOP@PageSize@QueryFields
FROM@TableName
WHERE@PrimaryKey<(
SELECTMIN(@PrimaryKey)FROM(
SELECTTOP@PageSize*@PageIndex@PrimaryKey
FROM@TableName
WHERE@Condition
ORDERBY@PrimaryKeyDESC
)TableA
)WHERE@Condition
ORDERBY@PrimaryKeyDESC
(3)@MiddleIndex<@PageIndex<@LastIndex
SELECT*FROM(
SELECTTOP@PageSize@QueryFields
FROM@TableName
WHERE@PrimaryKey>(
SELECTMAX(@PrimaryKey)FROM(
SELECTTOP(@RecordCount-@PageSize*(@PageIndex+1))@PrimaryKey
FROM@TableName
WHERE@Condition
ORDERBY@PrimaryKeyASC
)TableA
)WHERE@Condition
ORDERBY@PrimaryKeyASC
)TableBORDERBY@PrimaryKeyDESC
(4)@PageIndex>=@LastIndex
SELECT*FROM(SELECTTOP(@RecordCount-@PageSize*@LastIndex)@QueryFields
FROM@TableNameWHERE@ConditionORDERBY@PrimaryKeyASC
)TableAORDERBY@PrimaryKeyDESC
如何动态产生上述的SQL语句?
看了上面的SQL语句之后,相信大家已经基本明白该分页法的原理了。
下面,我们将要设计一个动态生成SQL语句的类FastPaging。
该类有一个公有静态方法,它根据您给出的条件动态生成SQL语句,作为方法的返回值。
//产生根据指定字段排序并分页查询的SELECT语句。
publicstaticStringPaging(
intpageSize,//每页要显示的记录的数目。
intpageIndex,//要显示的页的索引。
intrecordCount,//数据表中的记录总数。
StringtableName,//要查询的数据表。
StringqueryFields,//要查询的字段。
StringprimaryKey,//主键字段。
boolascending,//是否为升序排列。
Stringcondition//查询的筛选条件。
){
StringBuildersb=newStringBuilder();
intpageCount=GetPageCount(recordCount,pageSize);//分页的总数
intmiddleIndex=GetMidPageIndex(pageCount);//中间页的索引
intfirstIndex=0;//第一页的索引
intlastIndex=pageCount-1;//最后一页的索引
if(pageIndex<=firstIndex){
//代码略
}elseif(pageIndex>firstIndex&&pageIndex<=middleIndex){
sb.Append("SELECTTOP").Append(pageSize).Append("")
.Append(queryFields).Append("FROM").Append(tableName)
.Append("WHERE").Append(primaryKey);
if(ascending)
sb.Append(">(").Append("SELECTMAX(");
else
sb.Append("<(").Append("SELECTMIN(");
sb.Append(primaryKey).Append(")FROM(SELECTTOP")
.Append(pageSize*pageIndex).Append("").Append(primaryKey)
.Append("FROM").Append(tableName);
if(condition!
=String.Empty)
sb.Append("WHERE").Append(condition);
sb.Append("ORDERBY").Append(primaryKey).Append("")
.Append(GetSortType(ascending)).Append(")TableA)");
if(condition!
=String.Empty)
sb.Append("AND").Append(condition);
sb.Append("ORDERBY").Append(primaryKey).Append("")
.Append(GetSortType(ascending));
}
elseif(pageIndex>middleIndex&&pageIndex//代码略
}elseif(pageIndex>=lastIndex){
//代码略
}
returnsb.ToString();
}
除了Paging方法还有另外几个方法:
//根据记录总数和分页大小计算分页数。
publicstaticintGetPageCount(intrecordCount,intpageSize){
return(int)Math.Ceiling((double)recordCount/pageSize);
}
//计算中间页的页索引。
publicstaticintGetMidPageIndex(intpageCount){
return(int)Math.Ceiling((double)pageCount/2)-1;
}
//获取排序的方式("ASC"表示升序,"DESC"表示降序)
publicstaticStringGetSortType(boolascending){
return(ascending?
"ASC":
"DESC");
}
//获取一个布尔值,该值指示排序的方式是否为升序。
publicstaticboolIsAscending(StringorderType){
return((orderType.ToUpper()=="DESC")?
false:
true);
}
让DataGrid工作起来
有了上面的类,实现分页的工作就简单多了。
首先,我们要将DataGrid的AllowPaging属性和AllowCustomPaging属性为True,除此之外,为了体现出升序和降序的功能,还需要将AllowSorting属性也设置为True。
然后在每次分页时,我们需要产生一个OleDbDataReader对象或DataView对象绑定到DataGrid,作为DataGrid的数据源。
这里需要用FastPaging类的Paging方法根据条件产生一个SQL语句,并赋给OleDbCommand对象的CommandText属性:
cmd.CommandText=FastPaging.Paging(
DataGrid1.PageSize,
(int)ViewState["CurrentPa
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
-
ASPNet
DataGrid
基于
Access
快速
分页
详解
冰豆网所有资源均是用户自行上传分享,仅供网友学习交流,未经上传用户书面授权,请勿作他用。