热门标签 | HotTags
当前位置:  开发笔记 > 数据库 > 正文

仿orm自动生成分页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
    {
        ///


        /// 数据库类别
        ///

        public DBType dbType = DBType.Oracle;
        ///
        /// 逆序分页行数,总行数大于MaxRow,则会生成逆序分页SQL
        ///

        public int MaxRow = 1000;//临时测试,把值弄小点

        ///


        /// 匹配SQL语句中Select字段
        ///

        private Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?        ///
        /// 匹配SQL语句中Order By字段
        ///

        private Regex rxOrderBy = new Regex(@"\b(?ORDER\s+BY\s+(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+)(?:\s+(?ASC|DESC))?(?:\s*,\s*(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
        ///
        /// 匹配SQL语句中Distinct
        ///

        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[6];
            // Extract the columns from "SELECT FROM"
            var m = rxColumns.Match(sql);
            if (!m.Success)
                return null;

            // Save column list and replace with COUNT(*)
            Group g = m.Groups[1];
            sqlInfo[2] = sql.Substring(g.Index);

            if (rxDistinct.IsMatch(sqlInfo[2]))
                sqlInfo[0] = sql.Substring(0, g.Index) + "COUNT(" + m.Groups[1].ToString().Trim() + ") " + sql.Substring(g.Index + g.Length);
            else
                sqlInfo[0] = sql.Substring(0, g.Index) + "COUNT(*) " + sql.Substring(g.Index + g.Length);


            // Look for an "ORDER BY " clause
            m = rxOrderBy.Match(sqlInfo[0]);
            if (!m.Success)
            {
                sqlInfo[3] = null;
            }
            else
            {
                g = m.Groups[0];
                sqlInfo[3] = g.ToString();
                //统计的SQL 移除order
                sqlInfo[0] = sqlInfo[0].Substring(0, g.Index) + sqlInfo[0].Substring(g.Index + g.Length);
                //存储排序信息
                sqlInfo[4] = m.Groups["ordersql"].Value;//order by xxx
                sqlInfo[5] = m.Groups["order"].Value;//desc

                //select部分 移除order
                sqlInfo[2] = sqlInfo[2].Replace(sqlInfo[3], string.Empty);
            }

            return sqlInfo;
        }


        ///


        /// 生成逆序分页Sql语句
        ///

        ///
        ///
        ///
        ///
        ///
        public void CreatePageSqlReverse(string sql,ref string[] sqls, int start, int limit, int total = 0)
        {
            //如果总行数不多或分页的条数位于前半部分,没必要逆序分页
            if (total <100 || start <= total / 2)
            {
                return;
            }

            //sql正则分析过后的数组有5个值,若未分析,此处分析
            if (sqls == null || sqls.Length == 6)
            {
                sqls = SplitSqlForPaging(sql);
                if (sqls == null)
                {
                    //无法解析的SQL语句
                    throw new Exception("can't parse sql to pagesql ,the sql is " + sql);
                }
            }

            //如果未定义排序规则,则无需做逆序分页计算
            if (string.IsNullOrEmpty(sqls[5]))
            {
                return;
            }

            //逆序分页检查
            string sqlOrder = sqls[3];
            int end = start + limit;

            //获取逆序排序的sql
            string sqlOrderChange = string.Compare(sqls[5], "desc", true) == 0 ?
                string.Format("{0} ASC ", sqls[4]) :
                string.Format("{0} DESC ", sqls[4]);

            /*理论
             * 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[2], sqlOrderChange);
            }
            else if (dbType == DBType.SqlServer)
            {
                //组织分页SQL语句
                pageSql.AppendFormat("SELECT PageTab.* FROM ( SELECT TOP @PageEnd ROW_NUMBER() over ({0}) RN , {1}  ) PageTab ",
                    sqlOrderChange,
                    sqls[2]);

                //如果查询不是第一页,则需要判断起始行号
                if (start > 1)
                {
                    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[2], sqlOrderChange);

                //如果查询不是第一页,则需要判断起始行号
                if (start > 1)
                {
                    pageSql.Insert(0, "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[2], sqlOrderChange);
            }

            //恢复排序
            pageSql.Append(sqlOrder);

            //存储生成的分页SQL语句 
            sqls[1] = pageSql.ToString();

            //临时测试
            sqls[1] = sqls[1].Replace("@", "").Replace(":", "").Replace("PageStart", ++start + "").Replace("PageEnd", end + "").Replace("PageLimit", limit + "");

            Console.WriteLine("【count】{0}", sqls[0]);
            Console.WriteLine("【page】{0}", sqls[1]);
            Console.WriteLine();
        }

        ///


        /// 生成常规Sql语句
        ///

        ///
        ///
        ///
        ///
        ///
        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[2];
            }

            //组织分页SQL语句
            var pageSql = new StringBuilder();

            var end = start + limit;
            if (dbType == DBType.SqlServer2000)
            {
                pageSql.AppendFormat("SELECT TOP @PageEnd {0} {1}", sqls[2], sqls[3]);

                if (start > 1)
                {
                    var orderChange = string.IsNullOrEmpty(sqls[5]) ? null :
                        string.Compare(sqls[5], "desc", true) == 0 ?
                        string.Format("{0} ASC ", sqls[4]) :
                        string.Format("{0} DESC ", sqls[4]);
                    pageSql.Insert(0, "SELECT TOP 100 PERCENT  * FROM (SELECT TOP @PageLimit * FROM ( ");
                    pageSql.AppendFormat(" ) PageTab {0} ) PageTab2 {1}", orderChange, sqls[3]);
                }
            }
            else if (dbType == DBType.SqlServer)
            {
                pageSql.AppendFormat(" Select top @PageEnd ROW_NUMBER() over ({0}) RN , {1}",
                    string.IsNullOrEmpty(sqls[3]) ? "ORDER BY (SELECT NULL)" : sqls[3],
                    sqls[2]);

                //如果查询不是第一页,则需要判断起始行号
                if (start > 1)
                {
                    pageSql.Insert(0, "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 > 1)
                {
                    pageSql.Insert(0, "select * from ( ");
                    pageSql.Append(" ) Where RN>= :PageStart ");
                }
            }
            else if (dbType == DBType.SQLite)
            {
                pageSql.AppendFormat("{0} limit @PageStart,@PageLimit", sql, start, limit);
            }

            //存储生成的分页SQL语句 
            sqls[1] = pageSql.ToString();

            //临时测试
            sqls[1] = sqls[1].Replace("@", "").Replace(":", "").Replace("PageStart", ++start + "").Replace("PageEnd", end + "").Replace("PageLimit", limit + "");

            Console.WriteLine("【count】{0}", sqls[0]);
            Console.WriteLine("【page】{0}", sqls[1]);
            Console.WriteLine();
        }
    }

1.交换2个整数用了这样的算法。交换a和b,a=a+b;b=a-b;b=a-b;这是原来找工作的时候被考到的,如果在不使用第三方变量的情况下交换2个整数。

2.Sql2000下由于是使用top进行分页,除非条件一条数据都查不到,否则在分页start和limit参数超过了总行数时,也会查询出数据。

3.拆分Sql语句,参考了PetaPoco的部分源代码。

4.我的应用场景则是在dbhelp类,某个方法传递sql,start,limit参数即可对sql查询出来的结果进行分页。其中start:查询结果的起始行号(不包括它),limit:需要取出的行数。如 start:0,limit:15 则是取出前15条数据。


推荐阅读
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • PHP 编程疑难解析与知识点汇总
    本文详细解答了 PHP 编程中的常见问题,并提供了丰富的代码示例和解决方案,帮助开发者更好地理解和应用 PHP 知识。 ... [详细]
  • 根据最新发布的《互联网人才趋势报告》,尽管大量IT从业者已转向Python开发,但随着人工智能和大数据领域的迅猛发展,仍存在巨大的人才缺口。本文将详细介绍如何使用Python编写一个简单的爬虫程序,并提供完整的代码示例。 ... [详细]
  • TCP长连接设备管理平台:架构与功能概览
    本文介绍了基于TCP长连接的设备管理平台的设计理念、技术选型及主要功能模块。最初,项目旨在实现简单的协议测试,但随着需求扩展,逐步演变为一个完整的前后端分离系统。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • Søren Kierkegaard famously stated that life can only be understood in retrospect but must be lived moving forward. This perspective delves into the intricate relationship between our lived experiences and our reflections on them. ... [详细]
  • 计算机网络复习:第五章 网络层控制平面
    本文探讨了网络层的控制平面,包括转发和路由选择的基本原理。转发在数据平面上实现,通过配置路由器中的转发表完成;而路由选择则在控制平面上进行,涉及路由器中路由表的配置与更新。此外,文章还介绍了ICMP协议、两种控制平面的实现方法、路由选择算法及其分类等内容。 ... [详细]
  • 本文将介绍如何使用 Go 语言编写和运行一个简单的“Hello, World!”程序。内容涵盖开发环境配置、代码结构解析及执行步骤。 ... [详细]
  • 线性Kalman滤波器在多自由度车辆悬架主动控制中的应用研究
    本文探讨了线性Kalman滤波器(LKF)在不同自由度(2、4、7)的车辆悬架系统中进行主动控制的应用。通过详细的仿真分析,展示了LKF在提升悬架性能方面的潜力,并总结了调参过程中的关键要点。 ... [详细]
  • 本文探讨了Hive中内部表和外部表的区别及其在HDFS上的路径映射,详细解释了两者的创建、加载及删除操作,并提供了查看表详细信息的方法。通过对比这两种表类型,帮助读者理解如何更好地管理和保护数据。 ... [详细]
  • 本文介绍了一种有效的方法来检测硬盘上的视频文件是否损坏或存在缺帧问题。虽然一些常见的搜索引擎并未提供专门的工具,但通过使用FFmpeg等专业软件,可以全面验证各种视频格式的数据完整性,包括较为古老的AVI格式。 ... [详细]
  • 本次考试于2016年10月25日上午7:50至11:15举行,主要涉及数学专题,特别是斐波那契数列的性质及其在编程中的应用。本文将详细解析考试中的题目,并提供解题思路和代码实现。 ... [详细]
  • 爱奇艺视频下载指南
    随着百度在视频领域的不断扩展,爱奇艺的内容库日益丰富,涵盖了大量新番动画、电影、电视剧和综艺节目。本文将详细介绍如何通过爱奇艺客户端下载视频,帮助用户轻松实现离线观看。 ... [详细]
  • 脑机接口(BCI)技术正逐步将科幻变为现实,从帮助听障人士恢复听力到使瘫痪者重新站立,甚至可能将多年的学习过程压缩至瞬间。本文探讨了这一前沿技术的现状、挑战及其未来前景。 ... [详细]
author-avatar
周铭
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有