TADODataset Field AsString performance - sql-server

I have been experimenting with retrieving data from an MSSQL server from a Delphi program (using Delphi 2007).
The program has so far been using TADODataset to retrieve data. During tests we found that TQuery with an ODBC provider fetched a large select of data much quicker.
But on the other hand TADODataset preformed much better with parametrized+prepared selects.
Both TADODataset and TQuery connect to the same MSSQL database table. For this experiment I used MSSQL Native Client as provider for both.
TADODataset is set up with a connection string to the database, TQuery uses TDatabase with ODBC connection.
Here is some code from the experiment:
procedure TForm2.Button1Click(Sender: TObject);
begin
ADODataSet1.CommandText := 'select top 100000 '+FDataFields+' from transactions';
GetData(ADODataSet1);
ReadData(ADODataset1);
ADODataSet1.Close;
end;
procedure TForm2.Button2Click(Sender: TObject);
var
I: Integer;
begin
ADODataSet1.CommandText := 'select '+FDataFields+' from transactions where originaltransno = :FieldValue order by Key2';
ADODataSet1.Parameters[0].Value := '000';
ADODataSet1.Prepared := True;
GetData(ADODataSet1);
for I := 0 to 2000 do
begin
ADODataSet1.Parameters[0].Value := '123';
//no match in DB, just a check if it is there
ADODataSet1.Requery;
ReadData(ADODataset1);
end;
ADODataSet1.Close;
ADODataSet1.Prepared := False;
end;
procedure TForm2.Button3Click(Sender: TObject);
begin
Query1.SQL.Clear;
Query1.SQL.Add('select top 100000 '+FDataFields+' from transactions');
GetData(Query1);
ReadData(Query1);
Query1.Close;
end;
procedure TForm2.Button4Click(Sender: TObject);
var
I: Integer;
begin
Query1.SQL.Clear;
Query1.SQL.Add('select '+FDataFields+' from transactions where originaltransno = :FieldValue order by Key2');
Query1.Params[0].Value := '000';
Query1.Prepare;
GetData(Query1);
for I := 0 to 2000 do
begin
Query1.Params[0].Value := '123';
Query1.Close;
Query1.Open;
ReadData(Query1);
end;
Query1.Close;
Query1.UnPrepare;
end;
procedure TForm2.GetData(ADataSet: TDataSet);
begin
ADataSet.DisableControls;
ADataSet.Open;
end;
procedure TForm2.ReadData(ADataSet: TDataSet);
begin
while not ADataSet.EOF do
begin
ReadLine(ADataSet);
ADataSet.Next;
end;
end;
function TForm2.ReadLine(ADataSet: TDataSet): string;
var
I: Integer;
begin
for I := 0 to ADataSet.Fields.Count - 1 do
begin
if ADataSet.FieldDefs[I].DataType = ftString then
Result := Result + ADataSet.Fields[I].AsString;
end;
end;
A rough timing (using CPU Time in task manager)
Button1 - TADODataset large select: 13 seconds
Button2 - TADODataset small selects: 7 seconds
Button3 - TQuery large select: 4 seconds
Button4 - TQuery small selects: 24 seconds
Futher testing indicated the slow time with large TADODataset select is from reading the fields AsString
I tried replacing ReadLine:
function TForm2.ReadLine(ADataSet: TDataSet): int64;
var
I: Integer;
begin
for I := 0 to ADataSet.Fields.Count - 1 do
begin
if ADataSet.FieldDefs[I].DataType = ftInteger then
Result := Result + ADataSet.Fields[I].AsInteger;
end;
end;
With integer reading I got these timings:
Button1 - TADODataset large select: 3 seconds
Button3 - TQuery large
select: 3 seconds
I tried using Value instead, but "adding" a Variant to string was much slower
Can I do anything in reading the strings that speed up TADODataset reading?

Related

Storing TTreeView inside SQL Server database table

Am trying to store my TTreeView inside SQL Server database table by using the next procedure:
procedure Save;
var
BlobField :TBlobField;
Query:TADOQuery;
Stream:TStream;
begin
Stream := TMemoryStream.Create;
Query := TADOQuery.Create(Self);
Query.SQL.Add('Select * From MyTable') ;
Query.Active := True;
Query.First;
Query.Edit;
BlobField := Query.FieldByName('MyTableField') as TBlobField;
Stream := Query.CreateBlobStream(BlobField, bmWrite);
TreeView1.SaveToStream(Stream);
Query.Refresh;
Query.Free;
Stream.Free;
end;
But every time I am getting the error: DataSet is not in edit or insert mode.
I'm using Delphi 10.1, Win 10, SQL server 2019.
Change Query.Refresh; to Query.Post;
Also, you need to Free the blob stream to finalize writing to the blob field before you then Post to commit the new data into the DB.
Also, you are leaking an unused TMemoryStream object.
Try this:
procedure Save;
var
BlobField: TField;
Query: TADOQuery;
Stream: TStream;
begin
Query := TADOQuery.Create(nil);
try
Query.SQL.Text := 'Select TOP(1) * From MyTable';
Query.Open;
try
Query.First;
Query.Edit;
try
BlobField := Query.FieldByName('MyTableField');
Stream := Query.CreateBlobStream(BlobField, bmWrite);
try
TreeView1.SaveToStream(Stream);
finally
Stream.Free;
end;
Query.Post;
except
Query.Cancel;
raise;
end;
finally
Query.Close;
end;
finally
Query.Free;
end;
end;

Delphi TFDStoredProc parameter default values missing (MS SQL)

I am migrating from ADO to FireDAC. After replacing TADOStoredProc to TFDStoredProc I have the following issue. My _OpenStp procedure opens a stored procedure having default values in its parameter list, and I don't want to pass all those parameters. E.g.
CREATE PROCEDURE [dbo].[usp_SearchDocument]
#User_Id INT
, #Window_Id INT = 10
, #Page INT = 1
...
The core of my procedure:
procedure _OpenStp(
const AConnection: TFDConnection;
var AStp: TFDStoredProc;
const AStpName: string;
const AParamNameA: array of string;
const AParamValueA: array of Variant);
var
i: Integer;
begin
if AStp <> nil then
begin
if AStp.Active then
AStp.Close;
end
else
AStp := TFDStoredProc.Create(nil);
AStp.Connection := AConnection;
AStp.StoredProcName := AStpName;
AStp.Prepare;
for i := Low(AParamNameA) to High(AParamNameA) do
AStp.Params.ParamByName(AParamNameA[i]).Value := AParamValueA[i];
AStp.Open;
end;
The Delphi code of the call:
_OpenStp(SomeConnection, SomeStp, 'usp_SearchDocument',
['User_Id'], [150]);
According to SQL Server Profiler the call was:
exec [dbo].[usp_SearchDocument]
#User_Id=150,
#Window_Id=NULL,
#Page=NULL
TFDStoredProc.Prepare doesn't seem to query the default values of the sp parameters. When I was using the ADO counterpart of my _OpenStp procedure, the TADOStoredProc.Parameters.Refresh method did that job:
procedure _OpenStp(
const AConnection: TADOConnection;
var AStp: TADOStoredProc;
const AStpName: string;
const AParamNameA: array of string;
const AParamValueA: array of Variant);
begin
if AStp <> nil then
begin
if AStp.Active then
AStp.Close;
end
else
AStp := TADOStoredProc.Create(nil);
AStp.Connection := AConnection;
AStp.ProcedureName := AStpName;
AStp.Parameters.Refresh;
for i := 0 to Length(AParamNameA) - 1 do
AStp.Parameters.ParamByName(AParamNameA[i]).Value := AParamValueA[i];
AStp.Open;
end;
SQL Server Profiler:
exec usp_SearchDocument 150,default,default
Unfortunately it isn't an option to rewrite the code to pass all of the parameters, I have to rely on sp parameter default values. Is there a way to modify the FireDAC version of my _OpenStp procedure to achieve this goal?
Edit: I don't even have information about the type of the parameters (see the _OpenStp procedure), I only know their names and the values to be set, so I can't create the TFDParams programmatically.
Edit#2: An EArgumentOutOfRangeException was thrown after deleting the unnecessary parameters:
for i := AStp.ParamCount - 1 downto 0 do
if AStp.Params[i].Name <> '#RETURN_VALUE' then
begin
ExistsInArray := False;
for j := Low(AParamNameA) to High(AParamNameA) do
if Char.ToLower(AStp.Params[i].Name) = Char.ToLower(Format('#%s', [AParamNameA[j]])) then
begin
ExistsInArray := True;
Break;
end;
if not ExistsInArray then
AStp.Params.Delete(i);
end;

Inno Setup : Automatically check a checkbox after a specified time in seconds

Hello I want My Inno Setup Script to automatically check a CheckBox in one of my wizard pages after a specified time (e.g. 5 seconds).
Here's Why:
I created a checkbox which can change WizardForm's ClientWidth and ClientHeight when toggled.
If I don't click on it, the width and height of the WizardForm stays same. That's how it behaves.
The code I written to do that:
var
MinimizerCheckBox: TNewCheckBox;
procedure InitializeWizard();
begin
MinimizerCheckBox := TNewCheckBox.Create(WizardForm);
with MinimizerCheckBox do
begin
Name := 'MinimizerCheckBox';
Parent := WizardForm;
Left := ScaleX(560);
Top := ScaleY(315);
Width := ScaleX(90);
Height := ScaleY(14);
Alignment := taLeftJustify;
Caption := 'Compact Mode';
OnClick := #MinimizerCheckBoxClick;
TabOrder := 3;
end;
end;
procedure MinimizerCheckBoxClick(Sender: TObject);
begin
if MinimixerCheckBox.Checked then
begin
with WizardForm do
begin
WizardForm.ClientWidth:=420;
WizardForm.ClientHeight:=175;
end;
end else begin
with WizardForm do
begin
WizardForm.ClientWidth:=654;
WizardForm.ClientHeight:=407;
end;
end;
end;
I want to check that checkbox automatically after a specified time.
Any example code to do this?
Thanks in advance.
You can schedule a timer to check the checkbox like this:
[Code]
var
MinimizerCheckBox: TCheckBox;
...
function SetTimer(
hWnd: longword; nIDEvent, uElapse: longword; lpTimerFunc: longword): longword;
external 'SetTimer#user32.dll stdcall';
function KillTimer(hWnd: HWND; uIDEvent: UINT): BOOL;
external 'KillTimer#user32.dll stdcall';
var
CheckTimerID: Integer;
procedure StopCheckTimer;
begin
Log('Killing timer');
KillTimer(0, CheckTimerID);
CheckTimerID := 0;
end;
procedure CheckProc(h: LongWord; msg: LongWord; idevent: LongWord; dwTime: LongWord);
begin
Log('Timer elapsed');
StopCheckTimer;
MinimizerCheckBox.Checked := True;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpXXX then { your page id }
begin
Log('Starting 5s timer');
CheckTimerID := SetTimer(0, 0, 5000, CreateCallback(#CheckProc));
end
else
if CheckTimerID <> 0 then
begin
StopCheckTimer;
end;
end;
For CreateCallback function, you need Inno Setup 6. If you are stuck with Inno Setup 5, you can use WrapCallback function from InnoTools InnoCallback library.

Unable to insert Unicode into SQL Server 2008 using Delphi ZeosLib and Delphi 7

I'm having trouble inserting Unicode into a SQL Server database using Delphi ZeosLib and Delphi 7, and then reading the inserted value. I've created a simple test program that first inserts and then queries the inserted value.
The test table schema:
CREATE TABLE [dbo].[incominglog](
[message] [nvarchar](500) NULL
) ON [PRIMARY]
I've upload the simple test program source (ZeosLib source included) - click here to download. I've also included ntwdblib.dll but you can use your own.
The test program also requires TNT component which can be downloaded from here
Using the test program, the Unicode characters that I have inserted appear as question marks on retrieval - I'm not certain whether the problem lies with the insert code or the query code.
I've also tried encoding the data into utf-8 before inserting and then decoding the data after retrieving from utf-8 - please search "//inserted as utf8" in the test program source. I'm able to view the Unicode after it has been decoded, so this method works. However, for my actual application, I can't encode as UTF-8 as SQL Server doesn't support UTF-8 completely - some characters can't be stored. See my previous question here.
Will appreciate any pointers. :)
Meanwhile, here's the source for the Test program:
unit Unit1;
interface
uses
ZConnection, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ZAbstractRODataset, ZAbstractDataset, ZAbstractTable, ZDataset,
StdCtrls, TntStdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
TntMemo1: TTntMemo;
Button2: TButton;
TntEdit1: TTntEdit;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
FZConnection: TZConnection;
FZQuery: TZQuery;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
tntmemo1.Lines.Clear;
FZConnection := TZConnection.Create(Owner);
FZConnection.LoginPrompt := False;
FZQuery := TZQuery.Create(Owner);
FZQuery.Connection := FZConnection;
FZConnection.Protocol := 'mssql';
FZConnection.Database := 'replace-with-your-db';
FZConnection.HostName := 'localhost';
FZConnection.User := 'sa';
FZConnection.Password := 'replace-with-your-password';
FZConnection.Connect;
FZQuery.SQL.Text := 'SELECT * from incominglog';
FZQuery.ExecSQL;
FZQuery.Open;
FZQuery.First;
while not FZQuery.EOF do
begin
tntmemo1.Lines.add(FZQuery.FieldByName('message').AsString);
// tntmemo1.Lines.add(utf8decode(FZQuery.FieldByName('message').AsString)); //inserted as utf8
FZQuery.Next;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
sqlstring, data:widestring;
begin
FZConnection := TZConnection.Create(Owner);
FZConnection.LoginPrompt := False;
FZQuery := TZQuery.Create(Owner);
FZQuery.Connection := FZConnection;
FZConnection.Protocol := 'mssql';
FZConnection.Database := 'replace-with-your-db';
FZConnection.HostName := 'localhost';
FZConnection.User := 'sa';
FZConnection.Password := 'replace-with-your-password';
FZConnection.Connect;
data:= tntedit1.Text;
// data:= utf8encode(tntedit1.Text); //inserted as utf8
sqlstring:= 'INSERT INTO INCOMINGLOG ([MESSAGE]) VALUES(N''' + data + ''')';
FZQuery.SQL.Text := sqlstring;
FZQuery.ExecSQL;
end;
end.
I have not tested your example, but I'm able to save and retrieve from database Unicode characters without problem on SQL Server with Delphi 7 VCL/CLX and zeoslib.
I think in your case it will be enough changing your save procedure like this :
procedure TForm1.Button2Click(Sender: TObject);
var
sqlstring : widestring;
data : UTF8String;
begin
FZConnection := TZConnection.Create(Owner);
FZConnection.LoginPrompt := False;
FZQuery := TZQuery.Create(Owner);
FZQuery.Connection := FZConnection;
FZConnection.Protocol := 'mssql';
FZConnection.Database := 'replace-with-your-db';
FZConnection.HostName := 'localhost';
FZConnection.User := 'sa';
FZConnection.Password := 'replace-with-your-password';
FZConnection.Connect;
data:= UTF8String( utf8encode(tntedit1.Text) );
sqlstring:= 'INSERT INTO INCOMINGLOG ([MESSAGE]) VALUES(:DATA)';
FZQuery.SQL.Text := sqlstring;
FZQuery.ParamByName('DATA').AsString := data;
FZQuery.ExecSQL;
end;
The point is changing your data string variable in UTF8String type, and use parameters pass the data string to the query ...
To be honest I use it with a ZTable and Post command, but it should be the same with a ZQuery like yours ...

Calling SQL Server stored procs from Delphi with arguments?

I'm trying to call SP_SPACEUSED from Delphi 2010 using ADO. I can call it without arguments by using TSQLStoredProc, and setting the StoredProcName. This gives me the database size. I now need a specific table size, and SP_SPACEUSED takes a single arugument, #objname. How do I pass this as an argument. I have tried passing this as a parameter, but this doesn't work. Is it a parameter? Can I do this from Delphi?
Quick and dirty example (in D6 but it should work without any change in 2010):
var
I: Integer;
adStoredProc : TADOStoredProc;
begin
adStoredProc := TADOStoredProc.Create(nil);
try
adStoredProc.Connection := ADOConnection1;
adStoredProc.ProcedureName := 'SP_SPACEUSED';
adStoredProc.Parameters.Refresh;
for I := 0 to adStoredProc.Parameters.Count - 1 do // Iterate
begin
if Sametext(adStoredProc.Parameters[i].Name,'#objname') then
adStoredProc.Parameters[i].Value := 't_config';
end; // for
adStoredProc.Open;
for I := 0 to adStoredProc.FieldCount - 1 do // Iterate
begin
memo1.Lines.Append(format('%s : %s', [adStoredProc.Fields[i].Fieldname, adStoredProc.Fields[i].AsString]));
end; // for
finally
FreeAndNil(adStoredProc);
end;
end;

Resources