使用原生ADO+DataSet内存表进行分页显示
ADO分页显示,通常有2种方式:
一种是利用原生ADO的页次定位功能,指定PageSize后,用AbsolutePage切换到Page的位置,而后再读取对应页的数据。但这种方式,必须使用本地游标(adUseClient)模式,会将服务上的数据全部下到本地,存在打开速度慢、服务器压力大的缺陷。 另一种构建复杂的SQL语句(或存储过程),采用SQL的TOP子句获取指定量的数据,效率很高。这种方式的SQL语句是特别改写的,无法做到原汁原味,有些特别复杂的SQL语句难以改写。
能否有第一种方式不用改写SQL语句而又高效便捷的ADO分页方式呢?
事实上,ADO 的服务游标下单向数据可以有很高的数据查询效率。
笔者采用原生ADO的服务游标下单向数据模式进行了分页显示尝试,取得了很好的效果。
实例:在一个15万的邮编库中取页大小为200的任一页数据,并在DBGRID中显示出来,耗时仅百毫秒。
Dcopyboy Email:dcopyboy@tom.com QQ:445235526
{********** 单元名称:使用原生ADO+DataSet内存表进行分页显示 创建日期:2010-07-18 创建者 卢良红 Dcopyboy 功能: 使用原生ADO+DataSet内存表t进行分页显示 当前版本: Email:dcopyboy@tom.com QQ:445235526 **********} unit Unit1;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, DB, ADODB, Grids, DBGrids, StdCtrls, ExtCtrls, ComObj, OleDB, ADOInt;
type TForm1 = class(TForm) Panel1:TPanel; Button1:TButton; DBGrid1:TDBGrid; DataSource1:TDataSource; DadoQ:TADODataSet; Button2:TButton; Button3:TButton; Button4:TButton; Label1:TLabel; procedure Button1Click(Sender:TObject); procedure Button4Click(Sender:TObject); procedure Button2Click(Sender:TObject); procedure Button3Click(Sender:TObject); private { Private declarations } aPage:Integer; public { Public declarations } end;
var Form1:TForm1;
implementation
{$R *.dfm}
//实现分页处理的核心过程
function SavePageToQuery(SQLText:string; Dadoq:TADODataSet; PageSize, Page:integer):integer;
function ADOTypeToFieldType(const ADOType:DataTypeEnum; EnableBCD:Boolean = True):TFieldType; begin case ADOType of adEmpty:Result := ftUnknown; adTinyInt, adSmallInt:Result := ftSmallint; adError, adInteger, adUnsignedInt:Result := ftInteger; adBigInt, adUnsignedBigInt:Result := ftLargeInt; adUnsignedTinyInt, adUnsignedSmallInt:Result := ftWord; adSingle, adDouble:Result := ftFloat; adCurrency:Result := ftBCD; adBoolean:Result := ftBoolean; adDBDate:Result := ftDate; adDBTime:Result := ftTime; adDate, adDBTimeStamp, adFileTime, adDBFileTime:Result := ftDateTime; adChar:Result := ftFixedChar; adVarChar:Result := ftString; adBSTR, adWChar, adVarWChar:Result := ftWideString; adLongVarChar, adLongVarWChar:Result := ftMemo; adLongVarBinary:Result := ftBlob; adBinary:Result := ftBytes; adVarBinary:Result := ftVarBytes; adChapter:Result := ftDataSet; adPropVariant, adVariant:Result := ftVariant; adIUnknown:Result := ftInterface; adIDispatch:Result := ftIDispatch; adGUID:Result := ftGUID; adDecimal, adNumeric, adVarNumeric: if EnableBCD then Result := ftBCD else Result := ftFloat; else Result := ftUnknown; end; end;
procedure AddFieldDef(F:variant; FieldDefs:TFieldDefs); var FieldType:TFieldType; FieldDef:TFieldDef; I:Integer; FName:string; FSize:Integer; FPrecision:Integer; begin FieldType := ADOTypeToFieldType(F.Type, true); if FieldType <> ftUnknown then begin FSize := 0; FPrecision := 0; FieldDef := FieldDefs.AddFieldDef; with FieldDef do begin FieldNo := FieldDefs.Count; I := 0; FName := F.Name; while (FName = '') or (FieldDefs.IndexOf(FName) >= 0) do begin Inc(I); if F.Name = '' then FName := Format('COLUMN%d', [I]) else { Do not localize } FName := Format('%s_%d', [F.Name, I]); end; Name := FName; if (F.Type = adNumeric) and (F.NumericScale = 0) and (F.Precision < 10) then FieldType := ftInteger; case FieldType of ftString, ftWideString, ftBytes, ftVarBytes, ftFixedChar: FSize := F.DefinedSize; ftBCD: begin FPrecision := F.Precision; FSize := ShortInt(F.NumericScale); if FSize < 0 then FSize := 4; end; ftInteger:FSize := 4; ftGuid:FSize := 38; end; if ((adFldRowID and F.Attributes) <> 0) then Attributes := Attributes + [faHiddenCol]; if ((adFldFixed and F.Attributes) <> 0) then Attributes := Attributes + [faFixed]; if (((adFldUpdatable + adFldUnknownUpdatable) and F.Attributes) = 0) or (FieldType = ftAutoInc) then Attributes := Attributes + [faReadOnly]; DataType := FieldType; Size := FSize; Precision := FPrecision; end; end; end;
var x, i:word; a, RecordCount:integer; AConnection, SQuery:variant; begin //有人说,ADO在打开时就会读入全部数据,这似乎不可能哦! //笔者采用15万的邮编库,保存到文件中的流大小约5.8M,正常打开 //用时数毫秒,移动数据指针用时百毫秒,这就是单向数据快的地方 //为了保证能双向翻页,不的不每次重新打开数据 AConnection := CreateOleObject('ADODB.Connection'); AConnection.Open('Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Initial Catalog=AYZD;Data Source=.'); a := GetTickCount; SQuery := CreateOleObject('ADODB.RecordSet'); // SQuery.CursorLocation:=adUseClient; //不能采用本地游标,否则超慢!! //下面这几句是为了取得记录数的,不用每次均查吧!! {SQuery.open('select count(*) as RecordCount from (' + SQLText + ') as bb', AConnection, adOpenForwardOnly, adLockReadOnly, adCmdText); RecordCount := SQuery.Fields['RecordCount'].Value; SQuery.close;} RecordCount:=153726; SQuery.open(SQLText, AConnection, adOpenForwardOnly, adLockReadOnly, adCmdText); if page <= 0 then page := 1; if (page - 1) * pagesize > RecordCount then begin page := RecordCount div PageSize; if page * pagesize < RecordCount then inc(page); end; Result := Page; //服务器端游标时下列2句不能用 {SQuery.PageSize := PageSize; SQuery.AbsolutePage := Page;} SQuery.move((page - 1) * PageSize);
//下边这段是要讲数据转换到 ADODataSet,以便在DBGRID中直接显示 //如采用StringGrid则要自己重写 Dadoq.DisableControls; Dadoq.Close; Dadoq.FieldDefs.Clear; for i := 0 to SQuery.Fields.count - 1 do AddFieldDef(SQuery.Fields[I], Dadoq.FieldDefs); Dadoq.CreateDataSet; Dadoq.Open; for x := 0 to PageSize - 1 do begin Dadoq.Append; for i := 0 to SQuery.fields.count - 1 do begin try Dadoq.Fields[i].Value := SQuery.Fields[i].Value; except end; end; Dadoq.post; SQuery.MoveNext; if SQuery.Eof then break; end; Dadoq.EnableControls; Form1.Label1.Caption := '用时:' + inttostr(GetTickCount - a); SQuery.close; AConnection.close; end;
procedure TForm1.Button1Click(Sender:TObject); begin aPage := SavePageToQuery('select * from 邮编', DadoQ, 200, 1); end;
procedure TForm1.Button4Click(Sender:TObject); begin aPage := SavePageToQuery('select * from 邮编', DadoQ, 200, 999999); end;
procedure TForm1.Button2Click(Sender:TObject); begin aPage := SavePageToQuery('select * from 邮编', DadoQ, 200, aPage - 1); end;
procedure TForm1.Button3Click(Sender:TObject); begin aPage := SavePageToQuery('select * from 邮编', DadoQ, 200, aPage + 1); end;
end. |