求一通用分页SQL(不要存储过程)(C#实现)

智能大石头 2007-02-17 10:44:42
要实现这么一个功能的函数:
输入参数:一个取得记录集的Select SQL,开始行行号,最大返回行数,唯一键列名
返回:返回一个基于给出的SQL的分页SQL

我实现的一个函数如下:
/// <summary>
/// 执行SQL查询,返回分页记录集
/// </summary>
/// <param name="sql">SQL语句</param>
/// <param name="startRowIndex">开始行,1开始</param>
/// <param name="maximumRows">最大返回行数</param>
/// <param name="keyColumn">主键列。用于not in分页</param>
/// <returns></returns>
public virtual DataSet Query(String sql, Int32 startRowIndex, Int32 maximumRows, String keyColumn)
{
// 从第一行开始,不需要分页
if (startRowIndex <= 1)
{
if (maximumRows < 1)
return Query(sql);
else
return Query(String.Format("Select Top {0} * From ({1}) a", maximumRows, sql));
}
if (maximumRows < 1)
sql = String.Format("Select * From ({1}) b Where {2} Not In(Select Top {0} {2} From ({1}) a)", startRowIndex - 1, sql, keyColumn);
else
sql = String.Format("Select Top {0} * From ({1}) b Where {2} Not In(Select Top {3} {2} From ({1}) a)", startRowIndex + maximumRows - 1, sql, keyColumn, startRowIndex - 1);
return Query(sql);
}

在实际应用中发现,对于Access,该方法可行,对于MSSQL,就不一定了。
如果参数中的sql带有order by,而又没有top,那么,这个SQL是不能作为子查询的,上面的方法就行不通了。

后来我又加了下面一个函数:
/// <summary>
/// 为子查询准备。
/// 如果一个SQL用了order by,而没有top时,在SqlServer中是不能作为子查询的。
/// </summary>
/// <param name="sql"></param>
/// <returns></returns>
private String PrepairSqlForSubSelect(String sql)
{
String str = sql.Trim().ToLower();
// Select开头,不是存储过程
if (str.StartsWith("select ") && str.Contains("order by"))
{
str = str.Substring(0, 30);
if (!Regex.IsMatch(str, @"^ *select( +(all|distinct))? +top ", RegexOptions.IgnoreCase))
{
sql = Regex.Replace(sql, @"^ *select +all ", "Select All Top 100000000 ", RegexOptions.IgnoreCase);
sql = Regex.Replace(sql, @"^ *select +distinct ", "Select Top 100000000 ", RegexOptions.IgnoreCase);
sql = Regex.Replace(sql, @"^ *select +", "Select Top 100000000 ", RegexOptions.IgnoreCase);
}
}
return sql;
}

用来检查参数sql是否是这种情况,如果是的话,用正则修改这个sql,在select后面加上Top 100000000。可以解决一部分问题。
后来又发现,如果参数sql中,select后面的选择列中带有ntext类型字段的话,是不能用top。

我现在没办法了,请各位帮帮忙。
存储过程的方法就不用啦,谢谢^_^
...全文
5712 138 打赏 收藏 转发到动态 举报
写回复
用AI写文章
138 条回复
切换为时间正序
请发表友善的回复…
发表回复
hxy102863729 2010-04-21
  • 打赏
  • 举报
回复
标记!
gousunling 2007-03-01
  • 打赏
  • 举报
回复
没有人来了?
自然框架 2007-03-01
  • 打赏
  • 举报
回复
刚整理了一下我的分页控件的思路
http://blog.csdn.net/jyk/archive/2007/03/01/1518370.aspx
有情趣的有空看看。
自然框架 2007-03-01
  • 打赏
  • 举报
回复
原来是这样呀,那么可以考虑使用这个算法

select top 10
  • from
     select top 10
  • from
     
  • (
      select top 20
  • from
  • [TableReplace]
      order by [AddedDate] desc,[ID]
     ) as aa order by [AddedDate] ,[ID] desc
    ) as aa order by [AddedDate] desc ,[ID]


    把这个 [TableReplace] 换成原来的SQL语句试一下;
    或者考虑一下 修改一下 top 20 这个部分,就是最里层的select 当成原来的SQL语句,然后加进去 top 20

    ps:
    top 20 指的是 top PageSize * PageIndex

    不知道有没有看懂。
foyuan 2007-02-28
  • 打赏
  • 举报
回复
create procedure [dbo].[p_pages]
@sqlstr nvarchar(4000), --查询字符串
@start int, --从第N条记录开始
@pagesize int --每页行数
as
declare @rowcount int
set nocount on
declare @P1 int --P1是游标的id
exec sp_cursoropen @P1 output,@sqlstr,@scrollopt=1,@ccopt=1,@rowcount=@rowcount output
exec sp_cursorfetch @P1,16,@start,@pagesize
exec sp_cursorclose @P1
select @rowcount
fengfeiwuwq 2007-02-28
  • 打赏
  • 举报
回复
学习
自然框架 2007-02-28
  • 打赏
  • 举报
回复
{0} {1} {2} {3} 都可以换成子查询的形式。

{2} 可以加 group by 等语句

{1} 可以使用 INNER JOIN 之类的语句。

最方便的还是写成视图的形式。
自然框架 2007-02-28
  • 打赏
  • 举报
回复
要是拆开,就没有在这里讨论的意义了。只用一个sql,目的就是让上层调用者知道:“我只负责返回你需要的这些数据行,别的不返回”。不要让上层调用者为了实现如何把复杂sql拆开而头疼。

=================

不明白,既然才开了就可以解决问题,为什么不才开呢?

>>“我只负责返回你需要的这些数据行,别的不返回”。

拆开了就达不到这个目的吗?

>>如何把复杂sql拆开而头疼。

复杂的SQL语句都写出来了,拆开还会头痛吗?

可能是我没有写过复杂的SQL语句的原因吧,想不懂。

select {0} from {1} where {2} order by {3}

这是一个最基本的SQL语句。(注意没有 top )。

再怎么复杂也是围绕这个转的吧,能跑出这种结构吗?

另外复杂的语句可以请 视图 来帮忙呀。Orcale有没有视图?不会只有MSSQL有视图吧。
bitm 2007-02-28
  • 打赏
  • 举报
回复
mark
gousunling 2007-02-28
  • 打赏
  • 举报
回复
转 大石头 回复:

这就是我所说的解决子查询的精妙方法。
分析SQL是由一条SQL完成的,不过那个SQL现在尚有点问题,我就偷懒一下,麻烦各位高人帮想一个吧。要符合上面代码的,就是SelectRegex这个变量。
gousunling 2007-02-28
  • 打赏
  • 举报
回复
转 大石头 回复:

下面是我写的SqlBuilder的部分代码,主要是如何处理子查询和静态字符串,请大家指正:


#region 导入SQL
/// <summary>
/// 分析一条SQL
/// </summary>
/// <param name="sql"></param>
/// <returns></returns>
public Boolean Parse(String sql)
{
sql = TranSql(sql);

Regex reg = new Regex(SelectRegex, RegexOptions.IgnoreCase);
MatchCollection ms = reg.Matches(sql);
if (ms[0].Success)
{
Match m = ms[0];
ColumnClause = RevTranSql(m.Groups["选择列"].Value);
TableClause = RevTranSql(m.Groups["数据表"].Value);
WhereClause = RevTranSql(m.Groups["条件"].Value);
GroupByClause = RevTranSql(m.Groups["分组"].Value);
HavingClause =RevTranSql( m.Groups["分组条件"].Value);
OrderByClause = RevTranSql(m.Groups["排序"].Value);
}

return Regex.IsMatch(sql, SelectRegex, RegexOptions.IgnoreCase);
}
#endregion

#region 导出SQL
/// <summary>
/// 已重写。获取本Builder所分析的SQL语句
/// </summary>
/// <returns></returns>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append("Select ");
sb.Append(ColumnClause);
sb.Append(" From ");
sb.Append(TableClause);
if (!String.IsNullOrEmpty(WhereClause)) sb.Append(" Where " + WhereClause);
if (!String.IsNullOrEmpty(GroupByClause)) sb.Append(" GroupBy " + GroupByClause);
if (!String.IsNullOrEmpty(HavingClause)) sb.Append(" Having " + HavingClause);
if (!String.IsNullOrEmpty(OrderByClause)) sb.Append(" OrderBy " + OrderByClause);
return sb.ToString();
}
#endregion

#region 辅助函数
/// <summary>
/// SQL转义列表
/// </summary>
private Dictionary<Int32, String> SqlTranList = new Dictionary<Int32, String>();
/// <summary>
/// SQL转义。去除所有子查询,单引号 以及 双引号
/// </summary>
/// <param name="sql"></param>
/// <returns></returns>
private String TranSql(String sql)
{
SqlTranList.Clear();
sql = TranSql(sql, @"\(", @"\)");
sql = TranSql(sql, "'", "'");
sql = TranSql(sql, "\"", "\"");
return sql;
}

private String TranSql(String sql, String start, String end)
{
Regex reg = new Regex(String.Format("{0}.*?{1}", start, end), RegexOptions.IgnoreCase);
MatchCollection ms = reg.Matches(sql);
if (ms.Count < 1) return sql;
String str;
foreach (Match m in ms)
{
if (!String.IsNullOrEmpty(m.Groups[0].Value))
{
str = String.Format("#{0}#", SqlTranList.Count);
SqlTranList.Add(SqlTranList.Count, m.Groups[0].Value);
sql = sql.Replace(m.Groups[0].Value, str);
}
}
return TranSql(sql, start, end);
}

/// <summary>
/// 反转义SQL
/// </summary>
/// <param name="sql"></param>
/// <returns></returns>
private String RevTranSql(String sql)
{
Regex reg = new Regex(@"#\d+#");
if (!reg.IsMatch(sql)) return sql;
foreach (int k in SqlTranList.Keys)
{
sql = sql.Replace(String.Format("#{0}#", k), SqlTranList[k]);
}
return RevTranSql(sql);
}
#endregion
gousunling 2007-02-28
  • 打赏
  • 举报
回复
转 大石头 的回复:

◎jyk

要是拆开,就没有在这里讨论的意义了。只用一个sql,目的就是让上层调用者知道:“我只负责返回你需要的这些数据行,别的不返回”。不要让上层调用者为了实现如何把复杂sql拆开而头疼。

◎heyidan
你的离我要的,差得太远了。基本的top分页方法,我想这里每个人都应该知道吧,在网上,简直就是一抓一大把。

◎hh1984616
你这个方法,将会非常慢,且不能满足我的要求。

◎bluedoctor
你满足于现状了,可能没有碰到复杂语句吧。
gousunling 2007-02-28
  • 打赏
  • 举报
回复
转 大石头 回复:

一个已经运行了两年的系统,因为设计不当,所有取记录集的地方,都是查出一个表的所有数据,现在数据量稍微大了(个别表一万多),很慢。如何通过最少的修改,来达到效果呢?
cooolchen 2007-02-27
  • 打赏
  • 举报
回复
收了
dzq138 2007-02-27
  • 打赏
  • 举报
回复
mark
bluedoctor 2007-02-27
  • 打赏
  • 举报
回复
我的思路是:自动分析SQL
关于效率:请看我的“约束条件”和“SQL复杂度分析”
关于子查询是否可行:经过项目实践,至少目前没有发现任何问题

--------------------------------------
以下是 nnhy(大石头) 的回复
@bluedoctor
你给出的第一个,是存储过程,不符合要求.
第二个,的确是c#,往我要求的方向走了,只是,你这样分析,岂不是对输入的sql要求很高?还有,我看不懂strWhere是用来干什么的.


--------------------------------------
以下是 vivikcat的回复

@bluedoctor

刚看了你的blog,呵呵,想法和我差不多.只是,你难道不觉得,对每条输入的sql都使用这个方法,会对性能有极大的损耗么?

对于你的编码,有一点点建议,尽可能使用StringBuilder来代替字符串连接.

文中你提到"实际上,我们关心的只是最外层的 ORDER BY 子句".我也有同感,呵呵,对于复杂的语句,仅仅需要分析最外面一层就可以了.对于这点,我已经有一个极完美的解决方法,并且测试通过.你的实现方法,我看得不是很明白,给你提一些特殊情况吧.包含子查询是一般情况,而如果要对varchar字段进行查询,需要用到'要匹配Order By的字符串'之类的字符串常量的时候,你的程序,是如何解决的?也就是select from等出现在字符串里面.

很感谢你的意见.




will521 2007-02-27
  • 打赏
  • 举报
回复
关键在于
strSql = "Select Top " + s_model.PageSize + " " + s_model.Fields + " From " + s_model.TableName + " " + strCondition + " Order By " + s_model.OrderField + " " + s_model.OrderType;

===================================================================
中国DotNet程序员俱乐部 -- www.willsft.com

-- 中国最专业的DotNet技术社区!

技术支持群:3354034、34857327 、24694039、
19055417、35202852、2684939、1621937、35112134
===================================================================
tgv_mic 2007-02-27
  • 打赏
  • 举报
回复
好贴!
开阔眼界!
ls212 2007-02-27
  • 打赏
  • 举报
回复
mark!!!!!!
自然框架 2007-02-27
  • 打赏
  • 举报
回复
为什么“我这里是输入一个sql作为参数”,拆开不好吗?
加载更多回复(118)

62,269

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术交流专区
javascript云原生 企业社区
社区管理员
  • ASP.NET
  • .Net开发者社区
  • R小R
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

.NET 社区是一个围绕开源 .NET 的开放、热情、创新、包容的技术社区。社区致力于为广大 .NET 爱好者提供一个良好的知识共享、协同互助的 .NET 技术交流环境。我们尊重不同意见,支持健康理性的辩论和互动,反对歧视和攻击。

希望和大家一起共同营造一个活跃、友好的社区氛围。

试试用AI创作助手写篇文章吧