仿Orm 自动生成分页SQL
分页的写法
自从用上了Orm,分页这种事就是腰不酸腿不痛了。不过有时候想用纯粹的ado.net来操作,希望返回的数据是原生的DataTable或DbDataReader类似的东西,故研究下怎么生成分页的SQL语句。
平时接触的数据库有sql2000-2008,Oracle,SQLite 。 分页逻辑,Oracle和SQLite相对好写,就SQL事多,Sql2000下只能用top,排序2次,而Sql2005+就可以使用ROW_NUMBER()分析函数了,据说Sql2012对分页又有了改进,暂时用不上那么高的版本,所以没做。先看看目前这4种数据库的分页写法:
-- Oracle
SELECT * FROM (
SELECT ROWNUM RN, PageTab.* FROM
(
SELECT * FROM User_Tables order by id desc
) PageTab where ROWNUM <= 3010
) Where RN>= 3001 -- SQLite
select * from User_Tables order by id desc limit 3001,10 -- SQL2000
SELECT TOP 100 PERCENT * FROM (
SELECT TOP 10 * FROM (
SELECT TOP 3010 * from User_Tables order by id desc ) PageTab order by id ASC
) PageTab2 order by id desc -- SQL2005+
Select PageTab.* from (
Select top 3010 ROW_NUMBER() over (order by id desc) RN , * from User_Tables
) PageTab Where RN >= 3001
其中针对 Oracle和Sql2005+的分页写法做个说明。
Oracle使用ROWNUM要比Row_Number()要快。sql示例中均是查询 [3001,3010] 区间的数据,在Sql语句中,尽可能在子查询中减少查询的结果集行数,然后针对排序过后的行号,在外层查询中做条件筛选。 如Oracle写法中 子查询有ROWNUM <= 3010 ,Sql2005 中有 top 3010 * 。
当然今天要讨论的问题,不是分页语句的性能问题,如果你知道更好更快的写法,欢迎交流。
上面的分页写法,基于的查询sql语句是:
select * from User_Tables order by id desc
首先要从Sql语句中分析出行为,我把该Sql拆成了n部分,然后完成了以上拼接功能。按照模子往里面套数据,难度不大。
逆序分页
我们来描述另外一种场景,刚刚演示的sql是查询 满足条件下行数在[3001,3010]之间的数据,如果说总行数仅仅只有3500行,那么结果则是需要查询出3010行数据,并取出最后10条,而前面3000条数据,是没用的。
所以借鉴以前的经验,姑且叫它 逆序分页 。在知道总行数的前提下,我们可以进行分析,是否需要逆序分页,因为逆序分页得到分页Sql语句,也是需要时间的,并非所有的情况都有必要这么做。之前有假设,数据仅仅有3500行,我们期望取出 按照id 倒叙排序后的[3001,3010]数据,换种方式理解,若按照id升序,我们期望取出的数据则是[491,500] 这个区间,然后将这个数据,再按照id倒叙排序,也就是我们需要的数据了。
理论知识差不多就说完了,需要了解更多的话,百度一下,你就知道。下面是代码,有点长,展开当心:
public enum DBType
{
SqlServer2000,
SqlServer,
Oracle,
SQLite
} public class Page
{
/// <summary>
/// 数据库类别
/// </summary>
public DBType dbType = DBType.Oracle;
/// <summary>
/// 逆序分页行数,总行数大于MaxRow,则会生成逆序分页SQL
/// </summary>
public int MaxRow = ;//临时测试,把值弄小点 /// <summary>
/// 匹配SQL语句中Select字段
/// </summary>
private Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?<!,\s+)\bFROM\b", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
/// <summary>
/// 匹配SQL语句中Order By字段
/// </summary>
private Regex rxOrderBy = new Regex(@"\b(?<ordersql>ORDER\s+BY\s+(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+)(?:\s+(?<order>ASC|DESC))?(?:\s*,\s*(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
/// <summary>
/// 匹配SQL语句中Distinct
/// </summary>
private Regex rxDistinct = new Regex(@"\ADISTINCT\s", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
private string[] SplitSqlForPaging(string sql)
{
/*存储分析过的SQL信息 依次为:
* 0.countsql
* 1.pageSql(保留位置此处不做分析)
* 2.移除了select的sql
* 3.order by 字段 desc
* 4.order by 字段
* 5.desc
*/
var sqlInfo = new string[];
// Extract the columns from "SELECT <whatever> FROM"
var m = rxColumns.Match(sql);
if (!m.Success)
return null; // Save column list and replace with COUNT(*)
Group g = m.Groups[];
sqlInfo[] = sql.Substring(g.Index); if (rxDistinct.IsMatch(sqlInfo[]))
sqlInfo[] = sql.Substring(, g.Index) + "COUNT(" + m.Groups[].ToString().Trim() + ") " + sql.Substring(g.Index + g.Length);
else
sqlInfo[] = sql.Substring(, g.Index) + "COUNT(*) " + sql.Substring(g.Index + g.Length); // Look for an "ORDER BY <whatever>" clause
m = rxOrderBy.Match(sqlInfo[]);
if (!m.Success)
{
sqlInfo[] = null;
}
else
{
g = m.Groups[];
sqlInfo[] = g.ToString();
//统计的SQL 移除order
sqlInfo[] = sqlInfo[].Substring(, g.Index) + sqlInfo[].Substring(g.Index + g.Length);
//存储排序信息
sqlInfo[] = m.Groups["ordersql"].Value;//order by xxx
sqlInfo[] = m.Groups["order"].Value;//desc //select部分 移除order
sqlInfo[] = sqlInfo[].Replace(sqlInfo[], string.Empty);
} return sqlInfo;
} /// <summary>
/// 生成逆序分页Sql语句
/// </summary>
/// <param name="sql"></param>
/// <param name="sqls"></param>
/// <param name="start"></param>
/// <param name="limit"></param>
/// <param name="total"></param>
public void CreatePageSqlReverse(string sql,ref string[] sqls, int start, int limit, int total = )
{
//如果总行数不多或分页的条数位于前半部分,没必要逆序分页
if (total < || start <= total / )
{
return;
} //sql正则分析过后的数组有5个值,若未分析,此处分析
if (sqls == null || sqls.Length == )
{
sqls = SplitSqlForPaging(sql);
if (sqls == null)
{
//无法解析的SQL语句
throw new Exception("can't parse sql to pagesql ,the sql is " + sql);
}
} //如果未定义排序规则,则无需做逆序分页计算
if (string.IsNullOrEmpty(sqls[]))
{
return;
} //逆序分页检查
string sqlOrder = sqls[];
int end = start + limit; //获取逆序排序的sql
string sqlOrderChange = string.Compare(sqls[], "desc", true) == ?
string.Format("{0} ASC ", sqls[]) :
string.Format("{0} DESC ", sqls[]); /*理论
* total:10000 start:9980 limit:10
* 则 end:9990 分页条件为 RN >= 9980+1 and RN <= 9990
* 逆序调整后
* start = total - start = 20
* end = total - end = 10
* 交换start和end,分页条件为 RN >= 10+1 and RN<= 20
*/
//重新计算start和end
start = total - start;
end = total - end;
//交换start end
start = start + end;
end = start - end;
start = start - end; //定义分页SQL
var pageSql = new StringBuilder(); if (dbType == DBType.SqlServer2000)
{
pageSql.AppendFormat("SELECT TOP @PageLimit * FROM ( SELECT TOP @PageEnd {0} {1} ) ", sqls[], sqlOrderChange);
}
else if (dbType == DBType.SqlServer)
{
//组织分页SQL语句
pageSql.AppendFormat("SELECT PageTab.* FROM ( SELECT TOP @PageEnd ROW_NUMBER() over ({0}) RN , {1} ) PageTab ",
sqlOrderChange,
sqls[]); //如果查询不是第一页,则需要判断起始行号
if (start > )
{
pageSql.Append("Where RN >= :PageStart ");
}
}
else if (dbType == DBType.Oracle)
{
pageSql.AppendFormat("SELECT ROWNUM RN, PageTab.* FROM ( Select {0} {1} ) PageTab where ROWNUM <= :PageEnd ", sqls[], sqlOrderChange); //如果查询不是第一页,则需要判断起始行号
if (start > )
{
pageSql.Insert(, "SELECT * FROM ( ");
pageSql.Append(" ) ");
pageSql.Append(" WHERE RN>= :PageStart ");
}
}
else if (dbType == DBType.SQLite)
{
pageSql.AppendFormat("SELECT * FROM ( SELECT {0} {1} limit @PageStart,@PageLimit ) PageTab ", sqls[], sqlOrderChange);
} //恢复排序
pageSql.Append(sqlOrder); //存储生成的分页SQL语句
sqls[] = pageSql.ToString(); //临时测试
sqls[] = sqls[].Replace("@", "").Replace(":", "").Replace("PageStart", ++start + "").Replace("PageEnd", end + "").Replace("PageLimit", limit + ""); Console.WriteLine("【count】{0}", sqls[]);
Console.WriteLine("【page】{0}", sqls[]);
Console.WriteLine();
} /// <summary>
/// 生成常规Sql语句
/// </summary>
/// <param name="sql"></param>
/// <param name="sqls"></param>
/// <param name="start"></param>
/// <param name="limit"></param>
/// <param name="createCount"></param>
public void CreatePageSql(string sql, out string[] sqls, int start, int limit, bool createCount = false)
{
//需要输出的sql数组
sqls = null; //生成count的SQL语句 SqlServer生成分页,必须通过正则拆分
if (createCount || dbType == DBType.SqlServer || dbType == DBType.SqlServer2000)
{
sqls = SplitSqlForPaging(sql);
if (sqls == null)
{
//无法解析的SQL语句
throw new Exception("can't parse sql to pagesql ,the sql is " + sql);
}
}
else
{
sqls = new string[];
} //组织分页SQL语句
var pageSql = new StringBuilder(); var end = start + limit;
if (dbType == DBType.SqlServer2000)
{
pageSql.AppendFormat("SELECT TOP @PageEnd {0} {1}", sqls[], sqls[]); if (start > )
{
var orderChange = string.IsNullOrEmpty(sqls[]) ? null :
string.Compare(sqls[], "desc", true) == ?
string.Format("{0} ASC ", sqls[]) :
string.Format("{0} DESC ", sqls[]);
pageSql.Insert(, "SELECT TOP 100 PERCENT * FROM (SELECT TOP @PageLimit * FROM ( ");
pageSql.AppendFormat(" ) PageTab {0} ) PageTab2 {1}", orderChange, sqls[]);
}
}
else if (dbType == DBType.SqlServer)
{
pageSql.AppendFormat(" Select top @PageEnd ROW_NUMBER() over ({0}) RN , {1}",
string.IsNullOrEmpty(sqls[]) ? "ORDER BY (SELECT NULL)" : sqls[],
sqls[]); //如果查询不是第一页,则需要判断起始行号
if (start > )
{
pageSql.Insert(, "Select PageTab.* from ( ");
pageSql.Append(" ) PageTab Where RN >= @PageStart");
}
}
else if (dbType == DBType.Oracle)
{
pageSql.Append("select ROWNUM RN, PageTab.* from ");
pageSql.AppendFormat(" ( {0} ) PageTab ", sql);
pageSql.Append(" where ROWNUM <= :PageEnd "); //如果查询不是第一页,则需要判断起始行号
if (start > )
{
pageSql.Insert(, "select * from ( ");
pageSql.Append(" ) Where RN>= :PageStart ");
}
}
else if (dbType == DBType.SQLite)
{
pageSql.AppendFormat("{0} limit @PageStart,@PageLimit", sql, start, limit);
} //存储生成的分页SQL语句
sqls[] = pageSql.ToString(); //临时测试
sqls[] = sqls[].Replace("@", "").Replace(":", "").Replace("PageStart", ++start + "").Replace("PageEnd", end + "").Replace("PageLimit", limit + ""); Console.WriteLine("【count】{0}", sqls[]);
Console.WriteLine("【page】{0}", sqls[]);
Console.WriteLine();
}
}
具体实现
其他
1.交换2个整数用了这样的算法。交换a和b,a=a+b;b=a-b;b=a-b;这是原来找工作的时候被考到的,如果在不使用第三方变量的情况下交换2个整数。
2.Sql2000下由于是使用top进行分页,除非条件一条数据都查不到,否则在分页start和limit参数超过了总行数时,也会查询出数据。另外,在使用Top时,若想使用top @ncount ,必须写成 top (@ncount) ,目测sql2005+支持,sql2000下未知,没有测试环境。
3.拆分Sql语句,参考了PetaPoco的部分源代码。
4.我的应用场景则是在dbhelp类,某个方法传递sql,start,limit参数即可对sql查询出来的结果进行分页。其中start:查询结果的起始行号(不包括它),limit:需要取出的行数。如 start:0,limit:15 则是取出前15条数据。
仿Orm 自动生成分页SQL的更多相关文章
- Entity Framewrok 7beta7中不同版本sql server自动生成分页sql语句的问题
在EF中,使用linq进行分页是很方便的,假如我们有一个EMP表,结构如下: public class Emp { [Key] public Guid No { get; set; } public ...
- [转]双TOP二分法生成分页SQL类(支持MSSQL、ACCESS)
本文转自:http://www.cnblogs.com/jitian/archive/2011/03/22/1991961.html 博客开张,先发以前的几个老物件儿,以前写下来的,现在发上来权当记录 ...
- SAP 查询分析器,查询报表自动生成,SQL查询测试实现说明(转)
在日常的SAP开发和应用中,经常需要通过查询SAP数据表来处理日常业务,比如:数据对账.报表SQL测试.SAP查询功能开发等.通过开发SAP查询分析器,SAP实施和开发人员,可以在较短的时间内查询到需 ...
- @InsertProvider 根据bean属性,自动生成插入sql语句
以Test为例,用mybatis的@InsertProvider的注解插入数据的时候,每次都要写类似于 Mapper类 @Mapper public interface TestDao { @Inse ...
- PHP自动生成分页链接
page.class.php <?php class Page { // 分页栏每页显示的页数 public $rollPage = 5; // 页数跳转时要带的参数 public $param ...
- jquery自动生成分页控件 - pagetest.js
/* pagenum:当前页数 theallnum:总条数 themeiye:每页显示多少条 */ function pagetest(pagenum, theallnum, themeiye) { ...
- entity framework 查看自动生成的sql
public MesDbContext() : base("name=mysql") { Database.Log = new Action<string>(msg = ...
- 报表性能优化方案之单数据集分页SQL实现层式报表
1.概述 我们知道,行式引擎按页取数只适用于Oracle,mysql,hsql和sqlserver2008及以上数据库,其他数据库,如access,sqlserver2005,sqlite等必须编写分 ...
- 一个自动生成插入与更新SQL语句的小类
无需关注字段类型,只要传入字段名与值的集合,自动生成Ms sql server SQL语句.详见Test()方法 using System; namespace Fan.iData.SqlUtilit ...
随机推荐
- MySQL安装在Linux
利用Alt+p工具将下载好的Linux版本的mysql软件加载到根目录. 1. 将下载好的MySQL文件MySQL-5.6.41-1.el6.i686.rpm-bundle.tar放到 根目录下的mk ...
- realloc函数的用法
realloc(void *__ptr, size_t __size):更改已经配置的内存空间,即更改由malloc()函数分配的内存空间的大小. 如果将分配的内存减少,realloc仅仅是改变索引的 ...
- python——PIL(图像处理库)
PIL(Python Imaging Library,python图像处理库)提供了通用的图像处理功能,以及大量有用的基本图像操作,如图像缩放,裁剪,旋转,颜色转换等. 1.打开图像并显示 from ...
- Java线程和多线程(七)——ThreadLocal
Java中的ThreadLocal是用来创建线程本地变量用的.我们都知道,访问某个对象的所有线程都是能够共享对象的状态的,所以这个对象状态就不是线程安全的.开发者可以通过使用同步来保证线程安全,但是如 ...
- java 值传递 和 引用传递
参考:(http://www.cnblogs.com/woshimrf/p/5263018.html) 参考:(http://www.cnblogs.com/binyue/p/3862276.html ...
- android studio 首字母提示 设置 大小写敏感
在使用Android studo 编写程序时, 刚开始,关键字提示 首字母 设置了 大小写敏感,小写字母只能提示小写字母开头的,大写字母只能提示大写字母开始的,比较麻烦,在网上搜了下,解决办法如下: ...
- html5兼容处理&sublime text3配置html5环境
1.为了兼容低版本的浏览器解析不了hmtl5标签,要在html文件中head内引入html5shiv.min.js文件 <!--[if lt IE 9]> <script src=& ...
- 剑指Offer - 九度1507 - 不用加减乘除做加法
剑指Offer - 九度1507 - 不用加减乘除做加法2013-11-29 20:00 题目描述: 写一个函数,求两个整数之和,要求在函数体内不得使用+.-.*./四则运算符号. 输入: 输入可能包 ...
- Python学习-KindEditor-富文本编辑框
1.进入官网 2.下载 官网下载:http://kindeditor.net/down.php 本地下载:http://files.cnblogs.com/files/wupeiqi/kindedit ...
- (转)KlayGE游戏引擎 :高效的GBUFFER管理方式
转载请注明出处为KlayGE游戏引擎,本文的永久链接为http://www.klayge.org/?p=3304 个顶点.这样的数据对GPU来说是很头疼的.所以引擎往往需要在Buffer上做一些工作来 ...