一次数据访问层的改进之路(续一之分页篇)

本文介绍了一种基于min-max法的高效SQL分页查询方法,通过动态生成SQL语句实现优雅的分页查询封装,并提供了详细的实现代码及效率测试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一 次数据访问层的改进之路(续一之分页篇)

前言:上次代码打不开,今天换了一种代码展现发式。

在上篇一 次数据访问层的改进之路 中,我们经过改进后查询列表得到的编码结果类似下面:

1 OleDbParameter[] paras = new ACEParameterHelper().AddParameter< string >( "@memoType" , memoType)
2                                                           .GetParameters();
3 List<DataDictItemInfo> list = null ;
4 list = ACEAdvanceHelper.GetList<DataDictItemInfo>( "select * from Fk_data_dict_item t where t.memo=@memoType and t.parent_id=0" , conn, paras);
5 return list;

代码还算简洁,能满足基本的查询要求!

但是实际应用中,这是不够的,这个组件主要还是要写sql语句的,写sql语句的一大繁琐事情就是写分页sql语句。

我们的问题就是:

  
如何分页?

 

1.分页方法选择:not in法和min-max法。

01 //not in法
02 //说明:该法效率地下,越往后面翻,效率越地下
03 //测试:13万数据,下面的分页查询,耗时40s左 右
04 select top 10 * from LandRightInfo where id not in (select top 10000 id from LandRightInfo)
05  
06 //min-max法
07 //说明:该法效率最好
08 //测试:13万数据,下面的分页查询,耗时1s左 右,而且不随翻页效率有大的降低
09 select top 10 * from LandRightInfo where id > (select max(id) from (select top 10000 id from LandRightInfo))
10 //或者
11 select top 10 * from LandRightInfo where id < (select min(id) from (select top 10000 id from LandRightInfo))
12 //或者加上排序
13 select top 10 * from LandRightInfo where id > (select max(id) from (select top 10000 id from LandRightInfo order by id desc)) order by id desc

比较一下,我采用了min-max法来分页。

 

2.分析min-max语句的特点

就以这个为例子:

1 select top 10 * from LandRightInfo where id > (select max(id) from (select top 10000 id from LandRightInfo order by id desc)) order by id desc

作用:查询第100页,第10001-10010条记录

10,代表的就是pageSize

10000,代表的就是当前页数之前页数不显示的数据列表项

提取一下:

1 select top pageSize * from LandRightInfo where orderKey > (select max(orderKey) from (select top (pageIndex-1)*pageSize id from LandRightInfo))

这是个基本的模型。

第一个问题,经测试,这个模型有个问题,当第一页的时候,有问题,sql语句有错误。

所以,当pageIndex=1的时候,得单独写个模型,直接这样:

1 select top pageSize * from LandRightInfo

第二个问题,加排序条件,规则就是内查询和外查询(不包括min-max查询)的最后面都加上order  by语句

1 select top pageSize * from LandRightInfo where orderKey > (select max(orderKey) from (select top (pageIndex-1)*pageSize id from LandRightInfo order by id desc)) order by id desc

第三个问题,加查询条件,加在order by语句前面,两个order  by之前都要加

这里有个技巧,你直接加"where ..."的,就写的很死,不够灵活,咱们可以写成"where 1=1 ",再在后面Append条件"and ...",就可以多条件查询了。

代码就没什么贴的了。

 

3.如何动态生成这样的min-max语句

根据上面的分析和思路,我们也是很容易生成这样的语句的,方法如下:

01 private string CreatePageSql()
02 {
03      //select top 10 * from LandRightInfo where id > (select max(id) from (select top 100000 id from LandRightInfo))
04      StringBuilder result = new StringBuilder( "select top " );
05      result.AppendFormat( "{0} * from {1} where " , pageSize, tableName);
06  
07      if (pageIndex == 1)
08      {
09          if (orderKey != null )
10          {
11              result.AppendFormat( "1=1 {0} order by {1} {2}" , condition, orderKey, orderType);
12          }
13      }
14      else
15      {
16          //含排序操作:字段,类 型
17          result.Append(orderKey);
18  
19          int removePageNum = (pageIndex - 1) * pageSize;
20          //排序类型处理:升序,降序
21          if (orderType == "desc" )
22          {
23              result.AppendFormat( "< (select min({0}) from (select top {1} {2} from {3} where 1=1 {4} order by {5} desc))" , orderKey, removePageNum, orderKey, tableName, condition, orderKey);
24              result.AppendFormat( " {0}" , condition);
25              result.AppendFormat( " order by {0} desc" , orderKey);
26          }
27          else
28          {
29              result.AppendFormat( "> (select max({0}) from (select top {1} {2} from {3} where 1=1 {4}))" , orderKey, removePageNum, orderKey, tableName, condition);
30              result.AppendFormat( " {0}" , condition);
31          }
32      }
33  
34      return result.ToString();
35 }

从上面的方法可以看出,排序的字段OrderKey是必须的。

好了,生成sql的工具也有了。

 

4.如何以优雅的代码封装这个分页方法?

我相信很多人,都会写这样的封装代码:

01 //传一大堆参数,满足CreatePageSql()方法里变量的调用
02 public List<T> GetList<T>( int pageIndex, int pageSize, string OrderKey,...)
03 {
04         List<T> list = null ;
05  
06         string sql = CreatePageSql();
07         list = ACEAdvanceHelper.GetList<T>(sql, conn, paras);
08         conn.Close();
09         return list;
10 }

自从见识了linq一起其他的一些ORM框架后,我突然觉得这种方法,代码看上去很一般,谈不上优雅,可读性并不强(个人观点)。

我觉得用一种类似函数式编程的方式给每个参数赋值更形象更美丽,可读性也更好,具体的我也不分析了,直接上类代码:

001 public class ACEPageHelper
002 {
003      /// <summary>
004      /// 总记录数
005      /// </summary>
006      private int recordCounts = 0;
007      /// <summary>
008      /// 页数
009      /// </summary>
010      private int pageCounts = 0;
011  
012      private string tableName;
013  
014      private int pageSize = 0;
015      private int pageIndex = 0;
016  
017      private string condition = "" ;
018  
019      private string orderKey = null ;
020      private string orderType = null ;
021  
022      private OleDbConnection conn = null ;
023      private OleDbParameter[] paras = null ;
024  
025      // 注意每个方法返回的类型都是当前类
026      public ACEPageHelper Connect(OleDbConnection conn)
027      {
028          this .conn = conn;
029          return this ;
030      }
031  
032      public ACEPageHelper Parameter(OleDbParameter[] paras)
033      {
034          this .paras = paras;
035          return this ;
036      }
037      public ACEPageHelper TableName( string tableName)
038      {
039          this .tableName = tableName;
040          return this ;
041      }
042      public ACEPageHelper PageSize( int pageSize)
043      {
044          this .pageSize = pageSize;
045          return this ;
046      }
047  
048      public ACEPageHelper PageIndex( int pageIndex)
049      {
050          this .pageIndex = pageIndex;
051          return this ;
052      }
053  
054      public ACEPageHelper Condition( string condition)
055      {
056          this .condition += " and " ;
057          this .condition += condition;
058          return this ;
059      }
060  
061      public ACEPageHelper OrderKey( string orderKey)
062      {
063          this .orderKey = orderKey;
064          return this ;
065      }
066  
067      public ACEPageHelper Asc()
068      {
069          this .orderType = "asc" ;
070          return this ;
071      }
072  
073      public ACEPageHelper Desc()
074      {
075          this .orderType = "desc" ;
076          return this ;
077      }
078  
079      public List<T> GetList<T>()
080      {
081          List<T> list = null ;
082  
083          string sql = CreatePageSql();
084          list = ACEAdvanceHelper.GetList<T>(sql, conn, paras);
085          conn.Close();
086          return list;
087  
088      }
089  
090      private string CreatePageSql()
091      {
092          //select top 10 * from LandRightInfo where id > (select max(id) from (select top 100000 id from LandRightInfo))
093          StringBuilder result = new StringBuilder( "select top " );
094          result.AppendFormat( "{0} * from {1} where " , pageSize, tableName);
095  
096          if (pageIndex == 1)
097          {
098              if (orderKey != null )
099              {
100                  result.AppendFormat( "1=1 {0} order by {1} {2}" , condition, orderKey, orderType);
101              }
102          }
103          else
104          {
105              //含排序操作: 字段,类型
106              result.Append(orderKey);
107  
108              int removePageNum = (pageIndex - 1) * pageSize;
109              //排序类型处理:升序,降序
110              if (orderType == "desc" )
111              {
112                  result.AppendFormat( "< (select min({0}) from (select top {1} {2} from {3} where 1=1 {4} order by {5} desc))" , orderKey, removePageNum, orderKey, tableName, condition, orderKey);
113                  result.AppendFormat( " {0}" , condition);
114                  result.AppendFormat( " order by {0} desc" , orderKey);
115              }
116              else
117              {
118                  result.AppendFormat( "> (select max({0}) from (select top {1} {2} from {3} where 1=1 {4}))" , orderKey, removePageNum, orderKey, tableName, condition);
119                  result.AppendFormat( " {0}" , condition);
120              }
121          }
122  
123          return result.ToString();
124      }
125  
126      /// <summary>
127      /// 计算:总记录数
128      /// </summary>
129      public int GetCounts()
130      {
131          StringBuilder sql = new StringBuilder( "select " );
132          sql.AppendFormat( "count({0})" , orderKey);
133          sql.AppendFormat( " from {0} where 1=1 " , tableName);
134          sql.Append(condition);
135  
136          int result = Convert.ToInt32(ACECommonHelper.ExcuteScalar(sql.ToString(), conn, paras));
137          conn.Close();
138          this .recordCounts = result;
139          return result;
140      }
141  
142      /// <summary>
143      /// 计算:页数
144      /// </summary>
145      /// <returns></returns>
146      public int GetPages()
147      {
148          int result = 0;
149  
150          if (recordCounts == 0)
151          {
152              GetCounts();
153          }
154  
155          if (recordCounts % pageSize == 0)
156          {
157              result = recordCounts / pageSize;
158          }
159          else
160          {
161              result = (recordCounts / pageSize) + 1;
162          }
163  
164          return result;
165      }
166  
167 }

怎么个优雅法,接着往下看。

 

5.使用上面的类以及效率测试

基本使用(最后一页):

01 using (OleDbConnection conn = new OleDbConnection( "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|AccessHelper_390602.mdb;" ))
02 {
03      List<LandRightInfo> list = null ;
04      ACEPageHelper pageData= new ACEPageHelper();
05      list = pageData.Connect(conn)
06                     .TableName( "LandRightInfo" )
07                     .PageSize(10)
08                     .PageIndex(39061)
09                     .OrderKey( "id" )
10                     .GetList<LandRightInfo>();
11      Response.Write( "总结果数/页数:" + pageData.GetCounts() + "条/" +pageData.GetPages()+ "页<br/>" );
12 }

加个条件:

1 list = pageData.Connect(conn)
2                      .TableName( "LandRightInfo" )
3                      .PageSize(50)
4                      .PageIndex(1)
5                      .OrderKey( "LandCode" )
6                      .Condition( "LandCode like '%5%'" ) //条件可以不断的通过Condition添加
7                   .Condition( "id like '%5%'" )
8                      .Desc()
9                      .GetList<LandRightInfo>();

一目了然啊,每页多少条,第几页,查询条件是什么,升序还是降序,这代码看上去就带劲啊,呵呵。

参数化查询:

01 //使用上篇中讲到的简化参数的写法
02 OleDbParameter[] paras = new ACEParameterHelper()
03                                   .AddParameter< int >( "@id" , 1000)
04                                   .GetParameters();
05 list = pageData.Connect(conn)
06                      .TableName( "LandRightInfo" )
07                      .Parameter(paras) //传参数Parameter()方法
08                .PageSize(10)
09                      .PageIndex(37)
10                      .OrderKey( "id" )
11                      .Condition( "LandCode like '%5%'" )
12                      .Condition( "id <@id" ) //条件中带参数
13                .GetList<LandRightInfo>();

注:max,min函数也可应用于文本字段,所以这里的OrderKey也可以是文本字段,效率还行

目前,这种方法只能处理单表,夺标复杂的查询还不行,但这是一个好的开始,而且这个函数的思想也是可以应用到其他数据库的分页查询中去,我觉得这篇 博客的实用性还是挺强的,个人推荐一下。

完美了,查询出来的速度也是飞快飞快的。

查询语句:

01 list = pageData.Connect(conn)
02                      .TableName( "LandRightInfo" )
03                      .PageSize(10)
04                      .PageIndex(30000)
05                      .OrderKey( "id" )
06                      .GetList<LandRightInfo>();
07 //第一页生成的sql语句
08 {select top 10 * from LandRightInfo where 1=1  order by id }
09 //第二页生成的sql语句
10 {select top 10 * from LandRightInfo where id> (select max(id) from (select top 10 id from LandRightInfo where 1=1 )) }
11 //其他带参数生成的sql语句
12 {select top 10 * from LandRightInfo where id> (select max(id) from (select top 360 id from LandRightInfo where 1=1  and LandCode like '%5%' and id <@id))  and LandCode like '%5%' and id <@id}
13 //非id而是文本类型的sql语句
14 {select top 50 * from LandRightInfo where LandCode< (select min(LandCode) from (select top 50 LandCode from LandRightInfo where 1=1  and LandCode like '%5%' and id like '%5%' order by LandCode desc))  and LandCode like '%5%' and id like '%5%' order by LandCode desc}

---------------39万数据测试第一页:=3.5s-----------------
总结果数/页数:390602条 /39061页
起始时间:1:34:41
结束时间:1:34:45
耗时Ticks:35462029

---------------39万数据测试第2页:<1s-----------------
总结果数/页数:390602条 /39061页
起始时间:1:32:54
结束时间:1:32:54
耗时Ticks:630036

---------------39万数据测试第30000页:<1s-----------------
总结果数/页 数:390602条/39061页
起始时间:1:44:19
结束时间:1:44:20
耗时Ticks:6630379

希望大家多提宝贵意见,多多支持!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值