使用原生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.
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.