使用SQL分页查询,能够有效的提高数据库查询的效率,以及返回客户端数据的最小化。
几种数据库的分页SQL都不一样,SQLite和MySql的比较相近,而Oracle稍稍要绕一点,MsSql的最难处理。
一、SQLite和MySql分页
SQLite和MySql使用Limit和Offset关键字就能够实现分页。
/// <summary> /// 对命令文本进行分段处理,使之能够返回小范围内的数据。 /// </summary> /// <param name="commandText">命令文本。</param> /// <param name="segment">数据分段对象。</param> /// <returns>处理后的分段命令文本。</returns> /// <exception cref="SegmentNotSupportedException">当前数据库或版本不支持分段时,引发该异常。</exception> public virtual string Segment(string commandText, IDataSegment segment) { commandText = string.Format(@" SELECT T.* FROM ( {0} ) T LIMIT {1}{2}", commandText, segment.Length != 0 ? segment.Length : 1000, segment.Start != null ? " OFFSET " + (segment.Start - 1) : ""); return commandText; }
二、Oracle分页
Oracle的分页是有技巧的,比如以前是这样写的:
/// <summary> /// 对命令文本进行分段处理,使之能够返回小范围内的数据。 /// </summary> /// <param name="commandText">命令文本。</param> /// <param name="segment">数据分段对象。</param> /// <returns>处理后的分段命令文本。</returns> /// <exception cref="SegmentNotSupportedException">当前数据库或版本不支持分段时,引发该异常。</exception> public virtual string Segment(string commandText, IDataSegment segment) { commandText = string.Format(@" SELECT T.* FROM ( SELECT T.*, ROWNUM ROW_NUM FROM ({0}) T ) T WHERE {1}", commandText, segment.Condition("ROW_NUM")); return commandText; }
以上的segment.Condition是对ROW_NUM进行Between拼装:
/// <summary> /// 构造 <see cref="IDataSegment"/> 的分页条件。 /// </summary> /// <param name="segment"></param> /// <param name="fieldName">分页列名称。</param> /// <returns></returns> internal static string Condition(this IDataSegment segment, string fieldName) { if (segment.Start != null && segment.End != null) { return string.Format("{0} BETWEEN {1} AND {2}", fieldName, segment.Start, segment.End); } if (segment.Start != null && segment.End == null) { return string.Format("{0} >= {1}", fieldName, segment.Start); } if (segment.Start == null && segment.End != null) { return string.Format("{0} <= {1}", fieldName, segment.End); } return "1 = 1"; }
最近在分析Oracle分页语句的时候,发现,Oracle的分页在外面嵌套了两层,如果将ROW_NUM的小于条件提进一层,则查询的速度将会提高90%左右。修改分页如下:
/// <summary> /// 对命令文本进行分段处理,使之能够返回小范围内的数据。 /// </summary> /// <param name="commandText">命令文本。</param> /// <param name="segment">数据分段对象。</param> /// <returns>处理后的分段命令文本。</returns> /// <exception cref="SegmentNotSupportedException">当前数据库或版本不支持分段时,引发该异常。</exception> public virtual string Segment(string commandText, IDataSegment segment) { //** rownnum <= n 放在内层能够提高10倍的速度! commandText = string.Format(@" SELECT T.* FROM ( SELECT T.*, ROWNUM ROW_NUM FROM ({0}) T {1} ) T {2}", commandText, segment.End != null ? "WHERE ROWNUM <= " + segment.End : string.Empty, segment.Start != null ? "WHERE ROW_NUM >= " + segment.Start: string.Empty); return commandText; }
这个优化各位可以去百度相关的介绍,这里就不再细说了。
三、MsSql分页
MsSql在2005版本以前是不支持分页语句的,只有写个存储过程来使用几个top嵌套来实现分页,从2005开始,MsSql也提供了ROW_NUMBER() OVER()这样的分页关键字,但是处理还是很麻烦,因为SQL中的排序字段必须提到OVER中来。
/// <summary> /// 对命令文本进行分段处理,使之能够返回小范围内的数据。 /// </summary> /// <param name="commandText">命令文本。</param> /// <param name="segment">数据分段对象。</param> /// <returns>处理后的分段命令文本。</returns> /// <exception cref="SegmentNotSupportedException">当前数据库或版本不支持分段时,引发该异常。</exception> public virtual string Segment(string commandText, IDataSegment segment) { var regxOrder = new Regex(@"order\s+by ([\W|\w])+", RegexOptions.IgnoreCase); var regAlias = new Regex(@"(\S+)?\."); //如果有排序 if (regxOrder.IsMatch(commandText)) { var matchs = regxOrder.Matches(commandText); //去除子句中的Order并移到OVER后 commandText = string.Format(@" SELECTT.* FROM ( SELECT T.*, ROW_NUMBER() OVER ({2}) AS ROW_NUM FROM ({0}) T ) T WHERE {1}", regxOrder.Replace(commandText, "").Trim(), segment.Condition("ROW_NUM"), regAlias.Replace(matchs[matchs.Count - 1].Value, "")); } else { commandText = string.Format(@" SELECT T.* FROM ( SELECT T.*, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS ROW_NUM FROM ({0}) T ) T WHERE {1}", commandText, segment.Condition("ROW_NUM")); } return commandText; }