/// <summary>按唯一数字最大最小分页,性能很好。必须指定一个数字型排序列。</summary> /// <param name="builder">查询生成器</param> /// <param name="startRowIndex">开始行,0表示第一行</param> /// <param name="maximumRows">最大返回行数,0表示所有行</param> /// <returns>分页SQL</returns> static SelectBuilder MaxMin(SelectBuilder builder, Int64 startRowIndex, Int64 maximumRows) { if (builder.Keys == null || builder.Keys.Length != 1) { throw new ArgumentNullException("Key", "TopNotIn分页算法要求指定单一主键列!" + builder.ToString()); } // 分页标准 Select (20,10,ID Desc) // Select Top 10 * From Table Where ID>(Select max(ID) From (Select Top 20 ID From Table Order By ID) Order By ID Desc) Order By ID Desc var builder1 = builder.Clone().Top(startRowIndex, builder.Key); var builder2 = builder1.AsChild("XCode_T0", true); builder2.Column = String.Format("{0}({1})", builder.IsDesc ? "Min" : "Max", builder.Key); SelectBuilder builder3 = null; if (maximumRows < 1) { builder3 = builder.CloneWithGroupBy("XCode_T1", true); } else { builder3 = builder.Clone().Top(maximumRows); } // 如果本来有Where字句,加上And,当然,要区分情况按是否有必要加圆括号 builder3.AppendWhereAnd("{0}{1}({2})", builder.Key, builder.IsDesc ? "<" : ">", builder2); return(builder3); }
/// <summary>最经典的NotIn分页,通用但是效率最差。只需指定一个排序列。</summary> /// <param name="builder">查询生成器</param> /// <param name="startRowIndex">开始行,0表示第一行</param> /// <param name="maximumRows">最大返回行数,0表示所有行</param> /// <returns>分页SQL</returns> static SelectBuilder TopNotIn(SelectBuilder builder, Int64 startRowIndex, Int64 maximumRows) { if (builder.Keys == null || builder.Keys.Length != 1) { throw new ArgumentNullException("Key", "TopNotIn分页算法要求指定单一主键列!" + builder.ToString()); } // 分页标准 Select (20,10,ID) // 1,取目标页之前的20行 // 2,排除前20行之后取10行 // Select Top 10 * From Table Where ID Not In(Select Top 20 ID From Table) // 构建Select Top 20 ID From Table SelectBuilder builder1 = builder.Clone().Top(startRowIndex, builder.Key); SelectBuilder builder2 = null; if (maximumRows < 1) { builder2 = builder.CloneWithGroupBy("XCode_T0"); } else { builder2 = builder.Clone().Top(maximumRows); } builder2.AppendWhereAnd("{0} Not In({1})", builder.Key, builder1.ToString()); return(builder2); }
/// <summary>最经典的NotIn分页,通用但是效率最差。只需指定一个排序列。</summary> /// <param name="builder">查询生成器</param> /// <param name="startRowIndex">开始行,0表示第一行</param> /// <param name="maximumRows">最大返回行数,0表示所有行</param> /// <returns>分页SQL</returns> static SelectBuilder TopNotIn(SelectBuilder builder, Int64 startRowIndex, Int64 maximumRows) { if (builder.Keys == null || builder.Keys.Length != 1) throw new ArgumentNullException("Key", "TopNotIn分页算法要求指定单一主键列!" + builder.ToString()); // 分页标准 Select (20,10,ID) // 1,取目标页之前的20行 // 2,排除前20行之后取10行 // Select Top 10 * From Table Where ID Not In(Select Top 20 ID From Table) // 构建Select Top 20 ID From Table var builder1 = builder.Clone().Top(startRowIndex, builder.Key); SelectBuilder builder2 = null; if (maximumRows < 1) builder2 = builder.CloneWithGroupBy("XCode_T0"); else builder2 = builder.Clone().Top(maximumRows); builder2.AppendWhereAnd("{0} Not In({1})", builder.Key, builder1); // 结果列处理 builder2.Column = builder.Column; // 如果结果列包含有“.”,即有形如tab1.id、tab2.name之类的列时设为获取子查询的全部列 if ((!string.IsNullOrEmpty(builder2.Column)) && builder2.Column.Contains(".")) { builder2.Column = "*"; } return builder2; }
/// <summary>分页算法</summary> /// <remarks> /// builder里面必须含有排序,否则就要通过key指定主键,否则大部分算法不能使用,会导致逻辑数据排序不正确。 /// 其实,一般数据都是按照聚集索引排序,而聚集索引刚好也就是主键。 /// 所以,只要设置的Key顺序跟主键顺序一致,就没有问题。 /// 如果,Key指定了跟主键不一致的顺序,那么查询语句一定要指定同样的排序。 /// </remarks> /// <param name="builder"></param> /// <param name="startRowIndex"></param> /// <param name="maximumRows"></param> /// <param name="isSql2005"></param> /// <param name="queryCountCallback">查询总记录数的委托,近供DoubleTop使用</param> /// <returns></returns> public static SelectBuilder PageSplit(SelectBuilder builder, Int64 startRowIndex, Int64 maximumRows, Boolean isSql2005, Func<SelectBuilder, Int64> queryCountCallback = null) { // 从第一行开始,不需要分页 if (startRowIndex <= 0) { if (maximumRows < 1) return builder; else return builder.Clone().Top(maximumRows); } if (builder.Keys == null || builder.Keys.Length < 1) throw new XCodeException("分页算法要求指定排序列!" + builder.ToString()); // 其实,一般数据都是按照聚集索引排序,而聚集索引刚好也就是主键 // 所以,只要设置的Key顺序跟主键顺序一致,就没有问题 // 如果,Key指定了跟主键不一致的顺序,那么查询语句一定要指定同样的排序 //// 如果不指定排序,只能使用TopNotIn,另外三种都会影响结果的顺序 //if (String.IsNullOrEmpty(builder.OrderBy)) //{ // // 取前面页(经试验Access字符串主键大概在200行以下)并且没有排序的时候,TopNotIn算法应该是最快的 // if (startRowIndex < 200) return TopNotIn(builder, startRowIndex, maximumRows); //} if (isSql2005) return RowNumber(builder, startRowIndex, maximumRows); // 必须有排序,且排序字段必须就是数字主键 if (builder.IsInt && builder.KeyIsOrderBy) return MaxMin(builder, startRowIndex, maximumRows); if (maximumRows > 0) return DoubleTop(builder, startRowIndex, maximumRows, queryCountCallback); return TopNotIn(builder, startRowIndex, maximumRows); }
/// <summary>根据条件把普通查询SQL格式化为分页SQL。</summary> /// <param name="builder">查询生成器</param> /// <param name="startRowIndex">开始行,0表示第一行</param> /// <param name="maximumRows">最大返回行数,0表示所有行</param> /// <returns>分页SQL</returns> public SelectBuilder PageSplit(SelectBuilder builder, Int64 startRowIndex, Int64 maximumRows) { if (startRowIndex <= 0 && maximumRows <= 0) { return(builder); } // 2016年7月2日 HUIYUE 取消分页SQL缓存,此部分缓存提升性能不多,但有可能会造成分页数据不准确,感觉得不偿失 return(Db.PageSplit(builder.Clone(), startRowIndex, maximumRows)); }
///// <summary>分页算法</summary> ///// <remarks> ///// builder里面必须含有排序,否则就要通过key指定主键,否则大部分算法不能使用,会导致逻辑数据排序不正确。 ///// 其实,一般数据都是按照聚集索引排序,而聚集索引刚好也就是主键。 ///// 所以,只要设置的Key顺序跟主键顺序一致,就没有问题。 ///// 如果,Key指定了跟主键不一致的顺序,那么查询语句一定要指定同样的排序。 ///// </remarks> ///// <param name="builder"></param> ///// <param name="startRowIndex"></param> ///// <param name="maximumRows"></param> ///// <param name="queryCountCallback">查询总记录数的委托,近供DoubleTop使用</param> ///// <returns></returns> //public static SelectBuilder PageSplit_Sql2012(SelectBuilder builder, Int64 startRowIndex, Int64 maximumRows, Func<SelectBuilder, Int64> queryCountCallback = null) //{ // // 从第一行开始,不需要分页 // // 2012.11.08 注释掉首页使用SELECT TOP的方式,此方式在对有重复数据出现的字段排序时, // // 与Row_Number()的规则不一致,导致出现第一、二页排序出现重复记录。 // // 具体可百度一下【结合TOP N和Row_Number()分页因Order by排序规则不同引起的bug】 // if (startRowIndex <= 0) // { // if (maximumRows < 1) // { // return builder; // } // else if (builder.KeyIsOrderBy) // { // return builder.Clone().Top(maximumRows); // } // } // if (builder.Keys == null || builder.Keys.Length < 1) throw new XCodeException("分页算法要求指定排序列!" + builder.ToString()); // //ms sql 2012 分页算法的写法 // //var str = $"select * from {builder.Table} where type = 'p' order by {builder.OrderBy} offset {startRowIndex} rows fetch next {maximumRows} rows only"; // builder.Column = "*"; // builder.AppendWhereAnd($"type='p'"); // // todo ordrby后面还需要添加 offset {startRowIndex} rows fetch next {maximumRows} rows only,但是未找到地方 // return builder; //} /// <summary>分页算法</summary> /// <remarks> /// builder里面必须含有排序,否则就要通过key指定主键,否则大部分算法不能使用,会导致逻辑数据排序不正确。 /// 其实,一般数据都是按照聚集索引排序,而聚集索引刚好也就是主键。 /// 所以,只要设置的Key顺序跟主键顺序一致,就没有问题。 /// 如果,Key指定了跟主键不一致的顺序,那么查询语句一定要指定同样的排序。 /// </remarks> /// <param name="builder"></param> /// <param name="startRowIndex"></param> /// <param name="maximumRows"></param> /// <param name="isSql2005"></param> /// <param name="queryCountCallback">查询总记录数的委托,近供DoubleTop使用</param> /// <returns></returns> public static SelectBuilder PageSplit(SelectBuilder builder, Int64 startRowIndex, Int64 maximumRows, Boolean isSql2005, Func <SelectBuilder, Int64> queryCountCallback = null) { // 从第一行开始,不需要分页 // 2012.11.08 注释掉首页使用SELECT TOP的方式,此方式在对有重复数据出现的字段排序时, // 与Row_Number()的规则不一致,导致出现第一、二页排序出现重复记录。 // 具体可百度一下【结合TOP N和Row_Number()分页因Order by排序规则不同引起的bug】 if (startRowIndex <= 0) { if (maximumRows < 1) { return(builder); } else if (builder.KeyIsOrderBy) { return(builder.Clone().Top(maximumRows)); } } if (builder.Keys == null || builder.Keys.Length < 1) { throw new XCodeException("分页算法要求指定排序列!" + builder.ToString()); } // 其实,一般数据都是按照聚集索引排序,而聚集索引刚好也就是主键 // 所以,只要设置的Key顺序跟主键顺序一致,就没有问题 // 如果,Key指定了跟主键不一致的顺序,那么查询语句一定要指定同样的排序 //// 如果不指定排序,只能使用TopNotIn,另外三种都会影响结果的顺序 //if (String.IsNullOrEmpty(builder.OrderBy)) //{ // // 取前面页(经试验Access字符串主键大概在200行以下)并且没有排序的时候,TopNotIn算法应该是最快的 // if (startRowIndex < 200) return TopNotIn(builder, startRowIndex, maximumRows); //} if (isSql2005) { return(RowNumber(builder, startRowIndex, maximumRows)); } // 必须有排序,且排序字段必须就是数字主键 if (builder.IsInt && builder.KeyIsOrderBy) { return(MaxMin(builder, startRowIndex, maximumRows)); } if (maximumRows > 0) { return(DoubleTop(builder, startRowIndex, maximumRows, queryCountCallback)); } return(TopNotIn(builder, startRowIndex, maximumRows)); }
public override SelectBuilder PageSplit(SelectBuilder builder, Int64 startRowIndex, Int64 maximumRows) { //return MSPageSplit.PageSplit(builder, startRowIndex, maximumRows, IsSQL2005, b => CreateSession().QueryCount(b)); if (startRowIndex <= 0) { if (maximumRows < 1) { return(builder); } else if (builder.KeyIsOrderBy) { return(builder.Clone().Top(maximumRows)); } } if (builder.Keys == null || builder.Keys.Length < 1) { throw new XCodeException("分页算法要求指定排序列!" + builder.ToString()); } // 如果包含分组,则必须作为子查询 var builder1 = builder.CloneWithGroupBy("XCode_T0", true); //builder1.Column = String.Format("{0}, row_number() over(Order By {1}) as rowNumber", builder.ColumnOrDefault, builder.OrderBy ?? builder.KeyOrder); // 不必追求极致,把所有列放出来 builder1.Column = "*, row_number() over(Order By {0}) as rowNumber".F(builder.OrderBy ?? builder.KeyOrder); var builder2 = builder1.AsChild("XCode_T1", true); // 结果列处理 //builder2.Column = builder.Column; //// 如果结果列包含有“.”,即有形如tab1.id、tab2.name之类的列时设为获取子查询的全部列 //if ((!string.IsNullOrEmpty(builder2.Column)) && builder2.Column.Contains(".")) //{ // builder2.Column = "*"; //} // 不必追求极致,把所有列放出来 builder2.Column = "*"; // row_number()直接影响了排序,这里不再需要 builder2.OrderBy = null; if (maximumRows < 1) { builder2.Where = String.Format("rowNumber>={0}", startRowIndex + 1); } else { builder2.Where = String.Format("rowNumber Between {0} And {1}", startRowIndex + 1, startRowIndex + maximumRows); } return(builder2); }
/// <summary>最经典的NotIn分页,通用但是效率最差。只需指定一个排序列。</summary> /// <param name="builder">查询生成器</param> /// <param name="startRowIndex">开始行,0表示第一行</param> /// <param name="maximumRows">最大返回行数,0表示所有行</param> /// <returns>分页SQL</returns> static SelectBuilder TopNotIn(SelectBuilder builder, Int64 startRowIndex, Int64 maximumRows) { if (builder.Keys == null || builder.Keys.Length != 1) throw new ArgumentNullException("Key", "TopNotIn分页算法要求指定单一主键列!" + builder.ToString()); // 分页标准 Select (20,10,ID) // 1,取目标页之前的20行 // 2,排除前20行之后取10行 // Select Top 10 * From Table Where ID Not In(Select Top 20 ID From Table) // 构建Select Top 20 ID From Table SelectBuilder builder1 = builder.Clone().Top(startRowIndex, builder.Key); SelectBuilder builder2 = null; if (maximumRows < 1) builder2 = builder.CloneWithGroupBy("XCode_T0"); else builder2 = builder.Clone().Top(maximumRows); builder2.AppendWhereAnd("{0} Not In({1})", builder.Key, builder1.ToString()); return builder2; }
/// <summary>最经典的NotIn分页,通用但是效率最差。只需指定一个排序列。</summary> /// <param name="builder">查询生成器</param> /// <param name="startRowIndex">开始行,0表示第一行</param> /// <param name="maximumRows">最大返回行数,0表示所有行</param> /// <returns>分页SQL</returns> static SelectBuilder TopNotIn(SelectBuilder builder, Int64 startRowIndex, Int64 maximumRows) { if (builder.Keys == null || builder.Keys.Length != 1) { throw new ArgumentNullException("Key", "TopNotIn分页算法要求指定单一主键列!" + builder.ToString()); } // 分页标准 Select (20,10,ID) // 1,取目标页之前的20行 // 2,排除前20行之后取10行 // Select Top 10 * From Table Where ID Not In(Select Top 20 ID From Table) // 构建Select Top 20 ID From Table var builder1 = builder.Clone().Top(startRowIndex, builder.Key); SelectBuilder builder2 = null; if (maximumRows < 1) { builder2 = builder.CloneWithGroupBy("XCode_T0", true); } else { builder2 = builder.Clone().Top(maximumRows); } builder2.AppendWhereAnd("{0} Not In({1})", builder.Key, builder1); // 结果列处理 builder2.Column = builder.Column; // 如果结果列包含有“.”,即有形如tab1.id、tab2.name之类的列时设为获取子查询的全部列 if ((!string.IsNullOrEmpty(builder2.Column)) && builder2.Column.Contains(".")) { builder2.Column = "*"; } return(builder2); }
/// <summary>构造分页SQL</summary> /// <remarks> /// 两个构造分页SQL的方法,区别就在于查询生成器能够构造出来更好的分页语句,尽可能的避免子查询。 /// MS体系的分页精髓就在于唯一键,当唯一键带有Asc/Desc/Unkown等排序结尾时,就采用最大最小值分页,否则使用较次的TopNotIn分页。 /// TopNotIn分页和MaxMin分页的弊端就在于无法完美的支持GroupBy查询分页,只能查到第一页,往后分页就不行了,因为没有主键。 /// </remarks> /// <param name="builder">查询生成器</param> /// <param name="startRowIndex">开始行,0表示第一行</param> /// <param name="maximumRows">最大返回行数,0表示所有行</param> /// <returns>分页SQL</returns> public override SelectBuilder PageSplit(SelectBuilder builder, Int64 startRowIndex, Int64 maximumRows) { // 从第一行开始,不需要分页 if (startRowIndex <= 0) { if (maximumRows > 0) { builder.Limit += String.Format(" limit {0}", maximumRows); } return(builder); } if (maximumRows < 1) { throw new NotSupportedException("不支持取第几条数据之后的所有数据!"); } // 不要修改原来的查询语句 builder = builder.Clone(); builder.Limit += String.Format(" limit {0}, {1}", startRowIndex, maximumRows); return(builder); }
/// <summary>分页算法</summary> /// <remarks> /// builder里面必须含有排序,否则就要通过key指定主键,否则大部分算法不能使用,会导致逻辑数据排序不正确。 /// 其实,一般数据都是按照聚集索引排序,而聚集索引刚好也就是主键。 /// 所以,只要设置的Key顺序跟主键顺序一致,就没有问题。 /// 如果,Key指定了跟主键不一致的顺序,那么查询语句一定要指定同样的排序。 /// </remarks> /// <param name="builder"></param> /// <param name="startRowIndex"></param> /// <param name="maximumRows"></param> /// <param name="isSql2005"></param> /// <param name="queryCountCallback">查询总记录数的委托,近供DoubleTop使用</param> /// <returns></returns> public static SelectBuilder PageSplit(SelectBuilder builder, Int64 startRowIndex, Int64 maximumRows, Boolean isSql2005, Func<SelectBuilder, Int64> queryCountCallback = null) { // 从第一行开始,不需要分页 // 2012.11.08 注释掉首页使用SELECT TOP的方式,此方式在对有重复数据出现的字段排序时, // 与Row_Number()的规则不一致,导致出现第一、二页排序出现重复记录。 // 具体可百度一下【结合TOP N和Row_Number()分页因Order by排序规则不同引起的bug】 if (startRowIndex <= 0) { if (maximumRows < 1) { return builder; } else if (builder.KeyIsOrderBy) { return builder.Clone().Top(maximumRows); } } if (builder.Keys == null || builder.Keys.Length < 1) throw new XCodeException("分页算法要求指定排序列!" + builder.ToString()); // 其实,一般数据都是按照聚集索引排序,而聚集索引刚好也就是主键 // 所以,只要设置的Key顺序跟主键顺序一致,就没有问题 // 如果,Key指定了跟主键不一致的顺序,那么查询语句一定要指定同样的排序 //// 如果不指定排序,只能使用TopNotIn,另外三种都会影响结果的顺序 //if (String.IsNullOrEmpty(builder.OrderBy)) //{ // // 取前面页(经试验Access字符串主键大概在200行以下)并且没有排序的时候,TopNotIn算法应该是最快的 // if (startRowIndex < 200) return TopNotIn(builder, startRowIndex, maximumRows); //} if (isSql2005) return RowNumber(builder, startRowIndex, maximumRows); // 必须有排序,且排序字段必须就是数字主键 if (builder.IsInt && builder.KeyIsOrderBy) return MaxMin(builder, startRowIndex, maximumRows); if (maximumRows > 0) return DoubleTop(builder, startRowIndex, maximumRows, queryCountCallback); return TopNotIn(builder, startRowIndex, maximumRows); }
/// <summary>双Top分页,因为没有使用not in,性能比NotIn要好。语句必须有排序,不需额外指定排序列</summary> /// <param name="builder"></param> /// <param name="startRowIndex"></param> /// <param name="maximumRows"></param> /// <param name="queryCountCallback">查询总记录数的委托,近供DoubleTop使用</param> /// <returns></returns> static SelectBuilder DoubleTop(SelectBuilder builder, Int64 startRowIndex, Int64 maximumRows, Func <SelectBuilder, Int64> queryCountCallback) { if (builder.Keys == null) { throw new ArgumentNullException("Key", "DoubleTop分页算法要求指定排序列!" + builder.ToString()); } // 采用DoubleTop分页,最后一页可能有问题,需要特殊处理 if (queryCountCallback != null) { // 查询总记录数,计算是否最后一页 var count = queryCountCallback(builder); // 数据不足 if (count <= startRowIndex) { return(null); } // 刚好相等的就不必处理了 if (startRowIndex + maximumRows > count) { maximumRows = count - startRowIndex; } } // 分页标准 Select (20,10,ID Desc) // 1,按原始排序取20+10行,此时目标页位于底部 // 2,倒序过来取10行,得到目标页,但是顺序是反的 // 3,再倒序一次 // 显然,原始语句必须有排序,否则无法倒序。另外,也不能处理maximumRows<1的情况 // Select * From (Select Top 10 * From (Select Top 20+10 * From Table Order By ID Desc) Order By ID Asc) Order By ID Desc // 找到排序,优先采用排序字句来做双Top排序 var orderby = builder.OrderBy ?? builder.KeyOrder; var keys = SelectBuilder.Split(orderby, out var isdescs); // 把排序反过来 var isdescs2 = new Boolean[keys.Length]; for (var i = 0; i < keys.Length; i++) { if (isdescs != null && isdescs.Length > i) { isdescs2[i] = !isdescs[i]; } else { isdescs2[i] = true; } } var reversekeyorder = SelectBuilder.Join(keys, isdescs2); // 构建Select Top 20 * From Table Order By ID Asc var builder1 = builder.Clone().AppendColumn(keys).Top(startRowIndex + maximumRows); // 必须加一个排序,否则会被优化掉而导致出错 if (String.IsNullOrEmpty(builder1.OrderBy)) { builder1.OrderBy = builder1.KeyOrder; } var builder2 = builder1.AsChild("XCode_T0", true).Top(maximumRows); // 要反向排序 builder2.OrderBy = reversekeyorder; var builder3 = builder2.AsChild("XCode_T1", true); // 结果列处理 builder3.Column = builder.Column; // 如果结果列包含有“.”,即有形如tab1.id、tab2.name之类的列时设为获取子查询的全部列 if ((!string.IsNullOrEmpty(builder3.Column)) && builder3.Column.Contains(".")) { builder3.Column = "*"; } // 让结果正向排序 builder3.OrderBy = orderby; return(builder3); }
/// <summary>按唯一数字最大最小分页,性能很好。必须指定一个数字型排序列。</summary> /// <param name="builder">查询生成器</param> /// <param name="startRowIndex">开始行,0表示第一行</param> /// <param name="maximumRows">最大返回行数,0表示所有行</param> /// <returns>分页SQL</returns> static SelectBuilder MaxMin(SelectBuilder builder, Int64 startRowIndex, Int64 maximumRows) { if (builder.Keys == null || builder.Keys.Length != 1) throw new ArgumentNullException("Key", "TopNotIn分页算法要求指定单一主键列!" + builder.ToString()); // 分页标准 Select (20,10,ID Desc) // Select Top 10 * From Table Where ID>(Select max(ID) From (Select Top 20 ID From Table Order By ID) Order By ID Desc) Order By ID Desc var builder1 = builder.Clone().Top(startRowIndex, builder.Key); var builder2 = builder1.AsChild("XCode_T0"); builder2.Column = String.Format("{0}({1})", builder.IsDesc ? "Min" : "Max", builder.Key); SelectBuilder builder3 = null; if (maximumRows < 1) builder3 = builder.CloneWithGroupBy("XCode_T1"); else builder3 = builder.Clone().Top(maximumRows); // 如果本来有Where字句,加上And,当然,要区分情况按是否有必要加圆括号 builder3.AppendWhereAnd("{0}{1}({2})", builder.Key, builder.IsDesc ? "<" : ">", builder2); return builder3; }
/// <summary>双Top分页,因为没有使用not in,性能比NotIn要好。语句必须有排序,不需额外指定排序列</summary> /// <param name="builder"></param> /// <param name="startRowIndex"></param> /// <param name="maximumRows"></param> /// <param name="queryCountCallback">查询总记录数的委托,近供DoubleTop使用</param> /// <returns></returns> static SelectBuilder DoubleTop(SelectBuilder builder, Int64 startRowIndex, Int64 maximumRows, Func<SelectBuilder, Int64> queryCountCallback) { if (builder.Keys == null) throw new ArgumentNullException("Key", "DoubleTop分页算法要求指定排序列!" + builder.ToString()); // 采用DoubleTop分页,最后一页可能有问题,需要特殊处理 if (queryCountCallback != null) { // 查询总记录数,计算是否最后一页 var count = queryCountCallback(builder); // 数据不足 if (count <= startRowIndex) return null; // 刚好相等的就不必处理了 if (startRowIndex + maximumRows > count) maximumRows = count - startRowIndex; } // 分页标准 Select (20,10,ID Desc) // 1,按原始排序取20+10行,此时目标页位于底部 // 2,倒序过来取10行,得到目标页,但是顺序是反的 // 3,再倒序一次 // 显然,原始语句必须有排序,否则无法倒序。另外,也不能处理maximumRows<1的情况 // Select * From (Select Top 10 * From (Select Top 20+10 * From Table Order By ID Desc) Order By ID Asc) Order By ID Desc // 找到排序,优先采用排序字句来做双Top排序 var orderby = builder.OrderBy ?? builder.KeyOrder; Boolean[] isdescs = null; var keys = SelectBuilder.Split(orderby, out isdescs); // 把排序反过来 var isdescs2 = new Boolean[keys.Length]; for (int i = 0; i < keys.Length; i++) { if (isdescs != null && isdescs.Length > i) isdescs2[i] = !isdescs[i]; else isdescs2[i] = true; } var reversekeyorder = SelectBuilder.Join(keys, isdescs2); // 构建Select Top 20 * From Table Order By ID Asc var builder1 = builder.Clone().AppendColumn(keys).Top(startRowIndex + maximumRows); // 必须加一个排序,否则会被优化掉而导致出错 if (String.IsNullOrEmpty(builder1.OrderBy)) builder1.OrderBy = builder1.KeyOrder; var builder2 = builder1.AsChild("XCode_T0").Top(maximumRows); // 要反向排序 builder2.OrderBy = reversekeyorder; var builder3 = builder2.AsChild("XCode_T1"); // 结果列处理 builder3.Column = builder.Column; // 如果结果列包含有“.”,即有形如tab1.id、tab2.name之类的列时设为获取子查询的全部列 if ((!string.IsNullOrEmpty(builder3.Column)) && builder3.Column.Contains(".")) { builder3.Column = "*"; } // 让结果正向排序 builder3.OrderBy = orderby; return builder3; }