一个多对多关系表的数据分页显示问题-sp_cursoropen 比较 临时表方式

本文探讨了在MSSQL2000环境下,针对特定数据结构的分页技术实现方案,包括使用服务器游标和插入临时表的方法,并通过实验对比了这两种方法在不同场景下的性能差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 '=============================

各种分页存储过程谁好谁坏,其实没有绝对,而各类存储过程快慢表现的差别,其实质是查询过程中能否有效果利用索引的差别,尤其是聚集索引的利用

'=============================

 一个新闻模块,需要专题功能, 文章与专题是多对多关系,表数据结构如果下:
文章表:Article(Id,ClassId,Title....)
专题表:Special(SpecialId,Title,Url...)
文章专题表:ArticleSpecial(ArticleId,SpecialId,SectionId,OrderId) -- SectionId版块,

管理后台需要如下分页列表:
文章编号 文章标题 专题名称 版块号 排序号
对应:R(Article.Id,Article.Title,Special.Title,ArticleSpecial.SectionId,ArticleSpecial.OrderId)
另外在Asp.net 中采用ObjectDataSource + GridView , GridView中需要支持ArticleSpecial表的OrderId,SectionId,ArticleId等字段排序显示(每次只允许一个).

=============================================
目前在MSSQL2000下使用的分页存储过程基本就那么几个, 由于这里设计关系表(ArticleSpecial)是使用复合主键,应次Select Max(key) 方式, Set Rowcount  等方式不能用, 只能使用插入临时表方式跟服务器端游标(使用sp_cursoropen 等)方式,---注意这里不考虑网ArticleSpecial表中加入额外主键,加入额外主键后,分页管理到是可以方便解决,但是考虑上面三个表inner join 操作的性能问题故把主键(默认设置为聚集索引)分配给ArticleSpecial表的 SpecialD跟ArticleId列.
sp_cursoropen这些游标操作API,虽然在MS的帮助文档里找不到,但是如果使用asp + ADO 进行数据库操作时,其实可以看到(SQL跟踪)ADO调用的也就是这些游标操作API.

===============================
--//游标分页存储过程

ALTER        procedure QueryByCursor
@sqlstr nvarchar(4000), --查询字符串
@StartRows int, --第N页
@pageSize int, --每页行数
@Total int OUTPUT
As
Set nocount on
Declare @P1 int --P1是游标的id

If @StartRows<=0
  Begin
    Set @StartRows=1
  End
Else
  Begin
    Set @StartRows=@StartRows
  End

exec sp_cursoropen @P1 output,@sqlstr,@scrollopt=1,@ccopt=1,@Total=@Total output
exec sp_cursorfetch @P1,16,@startRows,@pagesize
exec sp_cursorclose @P1
Return @Total
Set NoCount off

 

//-- asp.net 中对应的操作

//1.帮助函数,对QueryByCursor进行封装,注意这里反回的是第二个Table,第一个为空
        public static DataSet Query(string connStr,int startRows,int pageSize,string sql,out int total)
        {
            SqlParameter sqlTotal = new SqlParameter("@total", 0);
            total = 0;
            sqlTotal.Direction = ParameterDirection.Output;
            SqlParameter[] parameters ={
                new SqlParameter("@startRows",startRows),
                new SqlParameter("@pageSize",pageSize),
                new SqlParameter("@sqlstr",sql),
                sqlTotal
            };
            DataSet ds = SqlHelper.ExecuteDataset(connStr, CommandType.StoredProcedure, "QueryByCursor", parameters);
            if (ds.Tables.Count >= 2)
            {
                ds.Tables.RemoveAt(0); //第一个为空
            }
            total = Convert.ToInt32(sqlTotal.Value);
            return ds;
        }

//过滤以及一些拼接操作,最后调用上面的帮助函数Query(...)

      public static DataSet SelectByCursor(int startRowIndex, int maximumRows, int specialId, string key, string sortExpress)
        {
            key = StringHelper.FilterTSQL(key);
            sortExpress = StringHelper.FilterTSQL(sortExpress);
            string sql = @" Select  a2s.*,a.Title,s.Title as SpecialName
  From  
   ArticleSpecial a2s
   inner join
   Article a on a.Id=a2s.ArticleId
   Inner join
   Special s On s.SpecialId=a2s.SpecialId ";
            string where = "Where 1=1 ";
            string order = "";
            if (specialId > 0)
            {
                where += " And a2s.SpecialId=" + specialId;
            }
            if (!string.IsNullOrEmpty(key))
            {
                where += " And  (a.Title like '%" + key + "%' Or a.Keys like '%" + key + "%')";
            }
            if (!string.IsNullOrEmpty(sortExpress))
            {
                order = " Order By a2s." + sortExpress;
            }
            sql += where + order;
           return DBH.Query(DBH.NewsDB, startRowIndex, maximumRows, sql, out _RecordCount);

        }

 

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

 使用插入临时表的方式:

//--存储过程入下:

 

---根据指定条件返回分页后的文章专题数据
---第二节的多个inner join 语句始终使用 t开始
ALTER          Proc ArticleSpecial_Query
   @starRows int =0,
   @pageSize int =20,
   @SpecialId int,
   @Key nvarchar(100),
   @OrderColumn nvarchar(50)
 As

Declare @SQL nvarchar(4000)
Declare @where nvarchar(200)
Declare @order nvarchar
Set @where=' 1=1 '
--专题不为0
If @specialId>0
  Begin
   Set @Where=@Where + ' And a2s.SpecialId=' + cast(@SpecialId as nvarchar(50))
  End
--关键字不为空
If @Key !=''
  Begin
   Set @where=@where + ' And (a.Title like ''%' +@key +'%'' Or a.Keys like ''%' + @key + '%'')'
  End

If @OrderColumn=''
  Begin
   Set @OrderColumn='ArticleId'
  End
 
Set @SQL='
  Set Nocount ON
  Create table #t (nid bigint identity(1,1)  Primary Key,ArticleID int,SpecialID int)

  Insert Into #t(ArticleId,SpecialId)
  Select a2s.ArticleId,a2s.SpecialId
  From
  ArticleSpecial a2s inner join Article a on a.Id=a2s.ArticleId
  Where '+ @where +'
  Order By a2s.' + @OrderColumn +'

  Select  a2s.*,a.Title,s.Title as SpecialName
  From  
   ArticleSpecial a2s
   inner join
   Article a on a.Id=a2s.ArticleId
   Inner join
   Special s On s.SpecialId=a2s.SpecialId
   Inner join [#t] t 
   On a2s.ArticleID=t.ArticleID And t.SpecialID=a2s.SpecialID
   Where nid >' + cast(@starRows as nvarchar) +' and nid <= ' + cast( (@starRows +@pageSize) as nvarchar) +'

  Set NoCount Off
  Drop Table #t
    
'
Exec(@SQL

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

//测试代码: Article表中有10万条数据, Special表中有200表记录(200个专题),ArticleSpecial表中有1万2千条记录,
//这个数据级别基本符合公司5到10年的需求了

        private delegate DataSet TestFunction(int startIndex, int pageSize, int specialId, string key, string sortExpress);
        private void button1_Click(object sender, EventArgs e)
        {
            button1.Enabled = false;
            TestFunction fun1 = new TestFunction(ArticleSpecialDAL.Select);
            TestFunction fun2 = new TestFunction(ArticleSpecialDAL.SelectByCursor);
           
            Stopwatch sw1 = new Stopwatch();
            Stopwatch sw2 = new Stopwatch();

            #region 方式1
            sw1.Start();
            for (int i = 0; i < 12000; i = i + 500)
            {
                DataSet ds = DoFunction(fun1, i, 20, 0, "", "ArticleId");

            }
            sw1.Stop();
            #endregion
            #region 方式2
            sw2.Start();
            for (int i = 0; i < 12000; i = i + 500)
            {
                DataSet ds = DoFunction(fun2, i, 20, 0, "", "ArticleId");

            }
            sw2.Stop();
            #endregion

           
            label1.Text = "Select方式:" + sw1.ElapsedMilliseconds.ToString() +Environment.NewLine
                          + "SelectByCursor:" + sw2.ElapsedMilliseconds.ToString();
            button1.Enabled = true;
              
        }
        private DataSet DoFunction(TestFunction fun,int startIndex,int pageSize ,int specialId ,string key ,string sortExpress)
        {
            return fun(startIndex, pageSize, specialId, key, sortExpress);
        }

//============================
 测试结果:
使用服务器游标方式比使用插入临时表方式快了将近10倍,插入临时表方式完成上面操作一般需要2万毫秒,而使用游标方式只需要2千毫秒,当然网上经常可以看到的说法是游标方式比插入临时表方式要慢, 这个主要是大家面对的问题不同,数据量也不同,我上面的测试结果是在ArticleSpecial表有12000条记录下得出的,当数据量继续整加时是什么情况我没做测试.
另外发现当传入key关键字跟key保持为空时,插入临时表方式运行时间差不多,而使用游标方式者区别很到,另外两种方式的CUP站用率都比为空时高很多(我这达到50%,为空时10%),可见字符比较操作占用大量的cup操作时间

帮我转换成SQLSERVER 版本 CREATE OR REPLACE PROCEDURE IMES.SP_CHECK_ROUTE ( T_STATIONNAME IN VARCHAR2, T_SN IN VARCHAR2, TRES OUT VARCHAR2) AS V_COUNT NUMBER; V_CURRENT_STATION IMES.P_SN_STATUS.STATION_TYPE%TYPE; --当前站 V_NEXT_STATION IMES.P_SN_STATUS.NEXT_STATION_TYPE%TYPE; --下一站 V_WORK_ORDER IMES.P_SN_STATUS.WORK_ORDER%TYPE; --工单 V_ROUTE_NAME IMES.P_SN_STATUS.ROUTE_NAME%TYPE; --route名字 V_START_STATION IMES.P_SN_STATUS.STATION_TYPE%TYPE; --工单对应开始站 V_CURRENT_STATUS IMES.P_SN_STATUS.CURRENT_STATUS%TYPE; --当前状态 V_SNSTATUS VARCHAR2 (100); --当前状态 V_WORK_FLAG IMES.P_SN_STATUS.WORK_FLAG%TYPE; --SN 是否报废,1报废,0 OK V_INPUT_STATION IMES.P_SN_STATUS.STATION_TYPE%TYPE; --当前站点对应投入站 V_LINE IMES.M_STATION.LINE%TYPE; --线别名字 V_STAGE IMES.M_STATION.STAGE%TYPE; --区段名 V_NEXT_DESC IMES.M_STATION_TYPE.STATION_TYPE_DESC%TYPE; ----下一站描述 BEGIN ---注释面选择的station对应参数1.0.0.0 By Dulin 2020/12/5 --V1.0.0.1 By Penn 2021/4/21 增加翻译功能 TRES := 'OK'; IMES.SP_GET_PLACE (T_STATIONNAME, v_LINE, v_STAGE, V_INPUT_STATION); ----------------------------------------------------------------SN 当前状态检查--------------------------------------------- BEGIN ------------查询SN当前状态参数----------------- SELECT A.CURRENT_STATUS, IMES.FN_SNStatus_Result (CURRENT_STATUS), A.WORK_FLAG, A.STATION_TYPE, A.ROUTE_NAME, A.NEXT_STATION_TYPE, A.WORK_ORDER INTO V_CURRENT_STATUS, V_SNSTATUS, V_WORK_FLAG, V_CURRENT_STATION, V_ROUTE_NAME, V_NEXT_STATION, V_WORK_ORDER FROM IMES.P_SN_STATUS A WHERE SERIAL_NUMBER = T_SN AND ROWNUM = 1; EXCEPTION WHEN NO_DATA_FOUND THEN --------------SN 不存在------------------------ TRES := IMES.FN_GET_TRANSLATE_MSG ('A0001', --ERR_CODE,PARAM1,PARAM2..PARAM10 T_SN, --INPUT_VALUE V_LINE, --Line T_STATIONNAME, --Station 'SP_CHECK_ROUTE', --PROGRAM '', --IP '', --empNo ''); --Remark GOTO ENDP; WHEN OTHERS THEN TRES := IMES.FN_GET_TRANSLATE_MSG ( 'B0001,SP_CHECK_ROUTE,' || SQLERRM || '<' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE || '>', --ERR_CODE,PARAM1,PARAM2..PARAM10 T_SN, --INPUT_VALUE V_LINE, --Line T_STATIONNAME, --Station 'SP_CHECK_ROUTE', --PROGRAM '', --IP '', --empNo ''); --Remark GOTO ENDP; END; ---------------SN的各种状态------------------ IF V_WORK_FLAG = '1' THEN TRES := 'SCRAP'; ELSIF V_WORK_FLAG = '3' THEN TRES := 'IN LAB'; ELSIF ( V_CURRENT_STATUS = '1' OR V_CURRENT_STATUS = '2' OR V_CURRENT_STATUS = '3' OR V_CURRENT_STATUS = '4' OR V_CURRENT_STATUS = '7') THEN TRES := V_SNSTATUS; ELSE SELECT COUNT (*) INTO V_COUNT FROM IMES.P_QC_OFFLINE A WHERE A.SERIAL_NUMBER = T_SN AND (A.QC_IN_TIME IS NULL OR A.QC_OUT_TIME IS NULL); IF V_COUNT > 0 THEN TRES := ' IN OFFLINE QC!'; END IF; END IF; IF TRES <> 'OK' THEN TRES := IMES.FN_GET_TRANSLATE_MSG ('A0182,SCRAP', --ERR_CODE,PARAM1,PARAM2..PARAM10 T_SN, --INPUT_VALUE '', --Line V_INPUT_STATION, --Station 'SP_CHECK_ROUTE', --PROGRAM '', --IP '', --empNo ''); --Remark GOTO ENDP; END IF; -------------------------SN Hold 检查----------------------------- IMES.SP_CHECK_HOLD (T_SN, T_STATIONNAME, TRES); IF TRES <> 'OK' THEN GOTO ENDP; END IF; -------------------------------------------------------------------------SN 流程检查--------------------------------------------------------------- -----------没有强制指站------------- IF (V_NEXT_STATION IS NULL) OR (V_NEXT_STATION = '0') THEN -----------------SN生成之后未生产过--------------------- IF (V_CURRENT_STATION = '0') THEN BEGIN -----------查询SN对应工单的默认投入站别----------------------- SELECT START_STATION_TYPE INTO V_START_STATION FROM IMES.P_WO_BASE WHERE WORK_ORDER = V_WORK_ORDER AND ROWNUM = 1; EXCEPTION WHEN NO_DATA_FOUND THEN -----------工单不存在--------------------------- TRES := IMES.FN_GET_TRANSLATE_MSG ('A0004', --ERR_CODE,PARAM1,PARAM2..PARAM10 T_SN, --INPUT_VALUE V_LINE, --Line T_STATIONNAME, --Station 'SP_CHECK_ROUTE', --PROGRAM '', --IP '', --empNo ''); --Remark GOTO ENDP; WHEN OTHERS THEN TRES := IMES.FN_GET_TRANSLATE_MSG ( 'B0001,SP_CHECK_ROUTE,' || SQLERRM || '<' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE || '>', --ERR_CODE,PARAM1,PARAM2..PARAM10 T_SN, --INPUT_VALUE V_LINE, --Line T_STATIONNAME, --Station 'SP_CHECK_ROUTE', --PROGRAM '', --IP '', --empNo ''); --Remark GOTO ENDP; END; --如果工单对应站别等于当前所选站点对应的投入站别 IF (V_START_STATION = V_INPUT_STATION) THEN TRES := 'OK'; ELSE SELECT IMES.FN_GET_STATIONTYPE_DESC (V_START_STATION) INTO V_NEXT_DESC FROM DUAL; ------工单开始站不存在--------- IF V_NEXT_DESC = '' OR V_NEXT_DESC IS NULL THEN --TRES := 'WO Start Station Type NG'; TRES := IMES.FN_GET_TRANSLATE_MSG ('A0583', --ERR_CODE,PARAM1,PARAM2..PARAM10 T_SN, --INPUT_VALUE V_LINE, --Line T_STATIONNAME, --Station 'SP_CHECK_ROUTE', --PROGRAM '', --IP '', --empNo ''); --Remark ELSE --------------------------工单开始站点存在,报错SN 流程错误------------------- TRES := IMES.FN_GET_TRANSLATE_MSG ( 'A0416,' || T_SN || ',' || V_START_STATION || '(' || V_NEXT_DESC || ')', --ERR_CODE,PARAM1,PARAM2..PARAM10 T_SN, --INPUT_VALUE V_LINE, --Line T_STATIONNAME, --Station 'SP_CHECK_ROUTE', --PROGRAM '', --IP '', --empNo ''); --Remark END IF; END IF; ELSE ---------如果SN当前站点不为空,正常流程检查----------------- BEGIN --------------------根据当前站点及SN状态和下一站查询流程,能够查到说明流程正确-------------------------- SELECT NEXT_STATION_TYPE INTO V_NEXT_STATION FROM IMES.M_ROUTE_DETAIL WHERE RESULT = V_CURRENT_STATUS AND ROUTE_NAME = V_ROUTE_NAME AND STATION_TYPE = V_CURRENT_STATION AND NEXT_STATION_TYPE = V_INPUT_STATION AND ROWNUM = 1; TRES := 'OK'; EXCEPTION WHEN OTHERS THEN BEGIN --------------------流程错误,获取下一站站点名称并报错--------------------------- SELECT NEXT_STATION_TYPE INTO V_NEXT_STATION FROM IMES.M_ROUTE_DETAIL A WHERE A.ROUTE_NAME = V_ROUTE_NAME AND A.SEQ = (SELECT MAX (SEQ) FROM IMES.M_ROUTE_DETAIL WHERE STATION_TYPE = V_CURRENT_STATION AND ROUTE_NAME = V_ROUTE_NAME AND RESULT = V_CURRENT_STATUS); TRES := IMES.FN_GET_TRANSLATE_MSG ( 'A0416,' || T_SN || ',' || V_NEXT_STATION || '(' || IMES.FN_GET_STATIONTYPE_DESC ( V_NEXT_STATION) || ')', --ERR_CODE,PARAM1,PARAM2..PARAM10 T_SN, --INPUT_VALUE V_LINE, --Line T_STATIONNAME, --Station 'SP_CHECK_ROUTE', --PROGRAM '', --IP '', --empNo ''); --Remark EXCEPTION WHEN OTHERS THEN ---------------根据当前站在流程里面查不到下一站,如果是良品状态则ROUTE END,否则REPAIR NG---------- IF V_CURRENT_STATUS = '0' THEN TRES := 'ROUTE END'; ELSE TRES := 'REPAIR NG'; END IF; END; END; END IF; ELSE ----------------------------------有强制指站------------------------------- IF ((V_CURRENT_STATUS = '0') OR (V_CURRENT_STATUS = '9')) THEN IF V_NEXT_STATION = V_INPUT_STATION THEN TRES := 'OK'; ELSE TRES := IMES.FN_GET_TRANSLATE_MSG ( 'A0416,' || T_SN || ',' || V_NEXT_STATION || '(' || IMES.FN_GET_STATIONTYPE_DESC (V_NEXT_STATION) || ')', --ERR_CODE,PARAM1,PARAM2..PARAM10 T_SN, --INPUT_VALUE V_LINE, --Line T_STATIONNAME, --Station 'SP_CHECK_ROUTE', --PROGRAM '', --IP '', --empNo ''); --Remark END IF; ELSE TRES := 'REPAIR NG'; END IF; END IF; <<ENDP>> NULL; EXCEPTION WHEN OTHERS THEN TRES := IMES.FN_GET_TRANSLATE_MSG ( 'B0001,SP_CHECK_ROUTE,' || SQLERRM || '<' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE || '>', --ERR_CODE,PARAM1,PARAM2..PARAM10 T_SN, --INPUT_VALUE V_LINE, --Line T_STATIONNAME, --Station 'SP_CHECK_ROUTE', --PROGRAM '', --IP '', --empNo ''); END; /
最新发布
07-25
--数据追溯查询 CREATE PROCEDURE [dbo].[proc_RecordFive] --查询条件 @productid varchar(50),--产品ID @mindate datetime,--最小时间 @maxdate datetime,--最大时间 @IsOk int, --是否合格 为空不查询 @startIndex int, --当前分页起始行 @EndIndex int, --当前分页结束行 --临时变量 @ProcessID varchar(50), @ItemSql nvarchar(max) AS BEGIN if @EndIndex>5000 RAISERROR('要查询的生产记录超出了5000条!',16,1) else begin --1.查找所有工序 对应字段 select c.F_Name as TTechniqueName,b.F_ID ProcessID,d.F_SortCode,b.F_Name ProcessName,a.F_Name AS ColText,f_ColName into #table1 from TProcessIOSet a right join TProcess b on a.F_VersionID=b.F_EffectiveVersionId right join TTechnique c on b.F_TechniqueID=c.f_id right join TTechniqueLine d on d.F_CurProcessID=b.F_id and d.F_TVersionID=c.F_EffectiveVersionId where c.F_ProductID=F_ProductID and a.F_Type=0 order by d.F_SortCode,a.f_ColName --2.查找所有生产记录 select * into #table2 from (select ROW_NUMBER() OVER (ORDER BY F_CreatorTime ASC) rowid, F_ProductCode,F_CreatorTime,lastIsok from( SELECT a.F_ProductCode,min(a.F_CreatorTime) F_CreatorTime,max(case d.F_NProcessID when '-1' then a.F_ISOK else 0 end) as lastIsok FROM TAllIDCode a left join TProcess b on a.F_ProcessID=b.F_Id left join TTechnique c on b.F_TechniqueID=c.f_id left join TTechniqueLine d on d.F_CurProcessID=b.F_id and d.F_TVersionID=c.F_EffectiveVersionId where c.F_ProductID=F_ProductID and a.F_CreatorTime BETWEEN @mindate AND @maxdate group by a.F_ProductCode ) _T where @IsOk is null or lastIsok=@IsOk) t where rowid BETWEEN @startIndex AND @EndIndex --3.打印所有列 select * from #table1 --4.打印所有生产 select * from #table2 --5.打印所有工序生产记录 declare Process_cur cursor for select ProcessID from #table1 group by ProcessID,F_SortCode order by F_SortCode open Process_cur fetch next from Process_cur into @ProcessID while (@@fetch_status=0) begin if exists (select * from sysobjects where id = object_id(N'TProductionDetail_'+replace(@ProcessID,'-','_')) and OBJECTPROPERTY(id, N'IsUserTable') = 1) begin set @ItemSql='select a.rowid,c.F_ISOK,c.F_ProductionTime,' select @ItemSql+='c.'+f_ColName+',' from #table1 where ProcessID=@ProcessID order by F_SortCode,f_ColName set @ItemSql +=' 0 as fakecol from #table2 a left join TAllIDCode b on b.F_ProductCode=a.F_ProductCode left join TProductionDetail_'+replace(@ProcessID,'-','_')+' c on c.f_id=b.F_KeyId where b.F_ProcessID='''+@ProcessID+''' ;' print @ItemSql exec sp_executesql @ItemSql end else select null fetch next from Process_cur into @ProcessID end close Process_cur deallocate Process_cur end end
03-17
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值