Retrieve output parameter from FireDac stored procedure - delphi

I've this Stored Procedure defined within a Firebird Database:
create or alter procedure GET_MSG (
IDLNG smallint,
IDMSG integer)
returns (
MSG varchar(200) character set UTF8)
as
begin
IF (:IDMSG > 40000) THEN
BEGIN
IF (:IDLNG = 1) THEN
BEGIN
SELECT NOMBRE01 FROM XMSG2 WHERE ID_XMSG2 = :IDMSG INTO :MSG;
EXIT;
END
IF (:IDLNG = 2) THEN
BEGIN
SELECT NOMBRE02 FROM XMSG2 WHERE ID_XMSG2 = :IDMSG INTO :MSG;
EXIT;
END
END ELSE
BEGIN
IF (:IDLNG = 1) THEN
BEGIN
SELECT NOMBRE01 FROM XMSG WHERE ID_XMSG = :IDMSG INTO :MSG;
EXIT;
END
IF (:IDLNG = 2) THEN
BEGIN
SELECT NOMBRE02 FROM XMSG WHERE ID_XMSG = :IDMSG INTO :MSG;
EXIT;
END
END
end
and I use this code to call this Stored Procedure from Firedac :
SPGeneric.StoredProcName:= 'GET_MSG';
SPGeneric.FetchOptions.Items:= SPGeneric.FetchOptions.Items - [fiMeta];
SPGeneric.Prepare;
with SPGeneric.Params do begin
Clear;
with Add do begin
Name:= 'IDLNG';
ParamType:= ptInput;
DataType:= ftSmallint;
Value:= IdLan;
end;
with Add do begin
Name:= 'IDMSG';
ParamType:= ptInput;
DataType:= ftInteger;
Value:= Id;
end;
with Add do begin
Name:= 'MSG';
ParamType:= ptOutput;
DataType:= ftString;
Size:= 200;
end;
end;
SPGeneric.ExecProc;
result:= VarToStr(SPGeneric.Params[2].Value);
The problem is that when I call this code with correct parameters (checked within Firebird), the result is always null. Is there anything wrong with this code?. Thanks
This is the code that works ok:
SPGeneric.StoredProcName:= 'GET_MSG';
SPGeneric.FetchOptions.Items:= SPGeneric.FetchOptions.Items - [fiMeta];
SPGeneric.Params.Clear;
with SPGeneric.Params.Add do begin
Name:= 'IDLNG';
ParamType:= ptInput;
DataType:= ftSmallint;
end;
with SPGeneric.Params.Add do begin
Name:= 'IDMSG';
ParamType:= ptInput;
DataType:= ftInteger;
end;
with SPGeneric.Params.Add do begin
Name:= 'MSG';
ParamType:= ptOutput;
DataType:= ftWideString;
Size:= 200;
end;
SPGeneric.Prepare;
SPGeneric.Params[0].Value:= IdLan;
SPGeneric.Params[1].Value:= Id;
SPGeneric.ExecProc;
result:= VarToStr(SPGeneric.Params[2].Value);
call Prepare after filling the parameters.
assign the parameters values after call prepare.

From the documentation :
After Prepare is called, the application cannot change command parameter data types and sizes. Otherwise, during the next Execute / ExecSQL / ExecProc / Open call, an exception will be raised. It is recommended to setup parameters before the Prepare call.
Here you have elected to not autopopulate the parameter information with
SPGeneric.FetchOptions.Items:= SPGeneric.FetchOptions.Items - [fiMeta];
So, since you are manually defining the parameters you should do this before calling Prepare.

Related

Delphi TTreeNode recursively append child nodes to parent node

I have an assignment in "project management". I have to assign modules which can also be sub-modules, so I want to append recursively sub-modules to modules.
Example:
P(project) Modules(M1,M2,M3,M4). Under M1 Module there will be sub-modules(M1S1,M1S2,M1S3), and under sub-module1 (M1S1) there can be many sub-modules (M1S1S1, M1S1S2, M1S1S3) and so on.
I have done this code using Recursion and TTreeNode but i feel the problem is with condition statement.
procedure TForm2.BitBtn1Click(Sender: TObject);
begin
lGlblProjID := 1;
lGlblProjName := 'Project';
ADOConnectionListner.Connected := true;
try
if ADOConnectionListner.Connected then
begin
RootNode := TreeView2.Items.Add(nil, lGlblProjName);
getSubChild(lGlblProjID, RootNode);
end;
except
on E: Exception do
begin
ShowMessage('Exception Class = ' + E.ClassName);
end;
end;
end;
procedure TForm2.getSubChild(var Pid: Integer; var SubRoot: TTreeNode);
var
lcount, I, lcurrentID: Integer;
lcurrentName: String;
lModuleNode: TTreeNode;
begin
// ShowMessage(IntToStr(Pid)+ ' '+SubRoot.Text);
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('SELECT * FROM treetab Where parent_id =:value1');
ADOQuery1.Parameters.ParamByName('value1').Value := Pid;
ADOQuery1.Active := true;
lcount := ADOQuery1.RecordCount;
for I := 0 to lcount - 1 do
begin
lcurrentID := ADOQuery1.FieldByName('id').AsInteger;
lcurrentName := ADOQuery1.FieldByName('name').AsString;
ShowMessage(' id ' + IntToStr(lcurrentID) + ' dd ' + lcurrentName); // print valu of i
if ((lcurrentID <> 0)and (SubRoot.Text <> '') ) then //or
begin
lModuleNode := TreeView1.Items.AddChild(SubRoot, lcurrentName);
getSubChild(lcurrentID, lModuleNode);
end else // if
// lcurrentID = 0
ShowMessage('end reached');
// TreeView1.Items.AddChild(SubRoot, ADOQuery1.FieldByName('name').AsString);
ADOQuery1.Next;
//*********
end;
end;
I want to retrieve all the sub-modules for a particular project like in this case project with id=1 only.
Your problem seems to be the non-local ADOQuery1 which gets cleared at entry on each recursive call. Therefore you loose all remaining records from a previous query. You should arrange a local storage for the query results.
Something like (untested):
procedure GetSubChild()
type
TTempRecord = record
id: integer;
name: string;
end;
TTempArray = array of TTempRecord;
var
lcount, I, lcurrentID: Integer;
lcurrentName: String;
lModuleNode: TTreeNode;
recs: TTempArray
begin
// ...
// query the db
// ...
lcount := ADOQuery1.RecordCount;
SetLength(recs, lcount);
for i := 0 to lcount-1 do
begin
recs[i].id := ADOQuery1.FieldByName('id').AsInteger;
recs[i].name := ADOQuery1.FieldByName('name').AsString;
ADOQuery1.Next;
end;
for i := 0 to lcount-1 do
begin
lcurrentID := recs[i].id;
lcurrentname := recs[i].name;
// ...
// add to treeview
// call recursively GetSubChild()
// ...
end;
end;

FireDAC BatchMove from MemoryTable

My incoming data is loaded in a TFDMemTable, (the reader). The writer is a TFDQuery.
Incoming data should be inserted if not in the target, otherwise updated. Matches are based on the UUID field.
I am unable to properly define that the UUID field is the key.
Here is a code example - does not work. FBatchMove.Execute fails because cannot it find any key fields.
procedure TSubDB.FindDestRecord(ASender: TObject; var AFound: Boolean);
var
aSrc: TBytes;
begin
SetLength(aSrc, 16);
aSrc := FReader.DataSet.FieldByName('UUID').AsBytes;
AFound := FWriter.DataSet.Locate('UUID', aSrc, []);
end;
function TSubDB.LoadDB(const aFilename: string): boolean;
var
FQry: TFDQuery;
FBatchMove: TFDBatchMove;
FReader: TFDBatchMoveDataSetReader;
FWriter: TFDBatchMoveDataSetWriter;
FMemTable: TFDMemTable;
begin
FQry := TFDQuery.Create(nil);
FQry.Connection := dmFB.myDB;
FQry.FetchOptions.AssignedValues := [evItems];
FQry.FetchOptions.Items := [fiBlobs, fiDetails];
FBatchMove := TFDBatchMove.Create(nil);
FBatchMove.Analyze := [taDelimSep, taHeader, taFields];
FReader := TFDBatchMoveDataSetReader.Create(FBatchMove);
FWriter := TFDBatchMoveDataSetWriter.Create(FBatchMove);
FMemTable := TFDMemTable.Create(nil);
try
FMemTable.LoadFromFile(aFileName, sfBinary);
//Not sure how to make the BatchMove recognize that UUID is the key for OnFindDestRecord
FMemTable.IndexFieldNames := 'UUID';
with FMemTable.Indexes.Add do
begin
Name :='idxUUID';
Fields := 'UUID';
Active := true;
end;
FMemTable.IndexName := 'idxUUID';
FMemTable.IndexesActive := true;
FMemTable.FieldByName('UUID').ProviderFlags := FMemTable.FieldByName('UUID').ProviderFlags + [pfInKey];
FReader.DataSet := FMemTable;
FQry.SQL.Text := 'select * from test';
FWriter.DataSet := FQry;
FBatchMove.OnFindDestRecord := FindDestRecord;
FBatchMove.Mode := dmAppendUpdate;
//None of the above seems to keep the pfInKey in the UUID field's ProviderFlags
FBatchMove.Execute;
FQry.Open;
FQry.Close;
finally
FMemTable.Free;
FWriter.Free;
FReader.Free;
FBatchMove.Free;
FQry.Free;
end;
end;
I would really appreciate a working example of batch move (where the target has data, so the batch move mode is dmAppendUpdate).
The key here is that the writer needs to be a TFDBatchMoveSQLWriter with a TableName set. This way the destination had the primary key defined and it is then used to decide whether to insert or update.
function TSubDB.LoadDB(const aFilename: string): boolean;
var
FQry: TFDQuery;
FBatchMove: TFDBatchMove;
FReader: TFDBatchMoveDataSetReader;
FWriter: TFDBatchMoveSQLWriter;
FMemTable: TFDMemTable;
begin
FQry := TFDQuery.Create(nil);
FQry.Connection := dmFB.myDB;
FQry.FetchOptions.AssignedValues := [evItems];
FQry.FetchOptions.Items := [fiBlobs, fiDetails];
FBatchMove := TFDBatchMove.Create(nil);
FBatchMove.Analyze := [taDelimSep, taHeader, taFields];
FReader := TFDBatchMoveDataSetReader.Create(FBatchMove);
FWriter := TFDBatchMoveSQLWriter.Create(FBatchMove);
FMemTable := TFDMemTable.Create(nil);
try
FMemTable.LoadFromFile(aFileName, sfBinary);
FReader.DataSet := FMemTable;
FQry.SQL.Text := 'select * from test';
FWriter.Connection := dmFB.myDB;
FWriter.TableName := 'test';
FBatchMove.Mode := dmAppendUpdate;
FBatchMove.Execute;
FQry.Open;
FQry.Close;
finally
FMemTable.Free;
FWriter.Free;
FReader.Free;
FBatchMove.Free;
FQry.Free;
end;
end;

Paradox error on CreateTable

I was creating table during execution, however at the creation of the table, (see my code)
procedure CreateTempTable(pDataBaseName,pSessionName:String);
begin
//-------create "TempTable"
TempTable:=TTable.Create(application);
With TempTable Do
begin
//-------set false in "Active"
Active := False;
//-------name of Session
SessionName:=pSessionName;
//-------name of DataBase.
DatabaseName :=pDataBaseName;
Filtered := True;
//-------name of table
TableName := 'TempTabl.DB';
//-------paradox type
TableType := ttParadox;
//-------if the table is already exists
if TempTable.Exists then
//-------delete the table
TempTable.DeleteTable;
//-------create 2 fields "Field1" & "Field2"
with FieldDefs do
begin
Clear;
with AddFieldDef do
begin
DataType := ftFloat;
Name := 'Field1';
end;
with AddFieldDef do
begin
DataType := ftFloat;
Name:='Field2';
end;
end;
//-------Create table
CreateTable; // Here where the exception bursts off
end;
end;
an exception is raised, which is: "Table is open, Table does not exist".
so what is exactly the problem, is it open or doesnot exist?
This is the exception:
(inside With TempTable Do) There is no need to delete the TempTabl.DB explicit. Is overwritten by CreateTable anyway.
To test if the table is not being used by other components in the IDE, you can try to delete the file TempTabl.DB.
CreateTempTable is now a function CreateTempTable(pDataBaseName,pSessionName:String):Boolean;
So you can better handle errors.
Tested with delphi 5 and RAD Studio 2007.
function CreateTempTable(pDataBaseName,pSessionName:String):Boolean;
begin
result:=false;
// assume pDataBaseName=directory, or change it
if FileExists(pDataBaseName+'TempTabl.DB') then begin
if NOT DeleteFile(pDataBaseName+'TempTabl.DB') then begin
showMessage('Table opened by another part of the IDE');
exit;
end;
end;
TempTable:=TTable.Create(application);
With TempTable Do
begin
Active := False;
SessionName:=pSessionName;
DatabaseName :=pDataBaseName;
//Filtered := True;
TableName := 'TempTabl.DB';
TableType := ttParadox;
with FieldDefs do
begin
Clear;
with AddFieldDef do
begin
DataType := ftFloat;
Name := 'Field1';
end;
with AddFieldDef do
begin
DataType := ftFloat;
Name:='Field2';
end;
end;
CreateTable;
result:=true;
end;
end;

Optional Anonymous Method

I would like to expose a function that can take an optional anonymous method :
type
TParamsProc = reference to procedure(Params: TSQLParams);
TFieldsProc = reference to procedure(Fields: TSQLResult);
TDbController = class
...
public
procedure Select(const SQL: sting; ParamsProc: TParamsProc; FieldsProc: TFieldsProc);
end;
implementation
procedure TDbController.Select(const SQL: sting; ParamsProc: TParamsProc; FieldsProc: TFieldsProc);
var
Q: TUIBQuery;
begin
Q := TUIBQuery.Create(nil);
try
Q.Database := FDatabase;
Q.Transaction := FTransaction;
Q.SQL.Text := SQL;
ParamsProc(Q.Params);
Q.Open;
while not Q.Eof do
begin
FieldsProc(Q.Result);
Q.Next;
end;
finally
Q.Free;
end;
end;
As sometimes I have no params to pass to a SQL Query, I would like to make the ParamsProc optional.
this code don't work :
if ParamsProc <> nil then ParamsProc(Q.Params);
nor this one :
if #ParamsProc <> nil then ParamsProc(Q.Params);
The first one don't compile, the second one compile but don't work because ParamsProc has always a non nil value.
Example of call :
FController.Select(
'select A, B, C from SOME_TABLE',
nil,
procedure(Fields: TSQLResult)
begin
FA := Fields.AsInteger[0];
FB := Fields.AsString[1];
FC := Fields.AsCurrency[2];
end
);
Edit
Seems that Assigned(ParamsProc) do the trick.
Following Jeroen Pluimers advice, I make my "Edit" an "Answer" :
Assigned(ParamsProc) do the trick :
procedure TDbController.Select(const SQL: sting; ParamsProc: TParamsProc; FieldsProc: TFieldsProc);
var
Q: TUIBQuery;
begin
Q := TUIBQuery.Create(nil);
try
Q.Database := FDatabase;
Q.Transaction := FTransaction;
Q.SQL.Text := SQL;
if Assigned(ParamsProc) then
ParamsProc(Q.Params);
Q.Open;
while not Q.Eof do
begin
FieldsProc(Q.Result);
Q.Next;
end;
finally
Q.Free;
end;
end;
Hope this helps !

How to auto fill in editlabel field with specific database row delphi

Hi there I have a problem
I need to auto fill in information from the database, but if i do it like this:
leemail.text := dm.atInlog['email'];
lenaam.text := dm.atInlog['naam'];
leAdres.text := dm.atInlog['adres'];
lePostcode.text := dm.atInlog['postcode'];
leTelefoonnummer.text := dm.atInlog['telefoon'];
leWoonplaats.Text := dm.atInlog['Woonplaats']
It just takes the first row. I want a specific row.
I can make it work with a button like this:
procedure TfmKlant.BTGegevensClick(Sender: TObject);
begin
//vraag gegevens van gebruiker op
dm.atInlog.Open;
while (not gevonden) and (not dm.atInlog.eof) do
begin
if dm.atInlog['email'] = fminloggen.inlognaam
then
begin
// plaats gegevens in de textboxen
gevonden := true;
leemail.text := dm.atInlog['email'];
lenaam.text := dm.atInlog['naam'];
leAdres.text := dm.atInlog['adres'];
lePostcode.text := dm.atInlog['postcode'];
leTelefoonnummer.text := dm.atInlog['telefoon'];
leWoonplaats.Text := dm.atInlog['Woonplaats']
end
else dm.atInlog.Next;
end;
But It does not do this in create form. How can I auto fill in the labeledit with the requested data?
thanks in advance
You could use TDataSet.Locate or Lookup:
type
TfmKlant = class(TForm)
// ... other declarations
private
procedure ShowData(p_Email: string);
end;
...
procedure TfmKlant.FormCreate(Sender: TObject);
begin
// assuming the data set is already open, and fminloggen.inlognaaem is already set
if dm.atInLog.Locate('email', fminloggen.inlognaam, []) then
begin
ShowData(fminloggen.inloognam);
end;
end;
procedure TfmKlant.ShowData(p_Email: string);
begin
gevonden := true;
leemail.text := dm.atInlog['email'];
lenaam.text := dm.atInlog['naam'];
leAdres.text := dm.atInlog['adres'];
lePostcode.text := dm.atInlog['postcode'];
leTelefoonnummer.text := dm.atInlog['telefoon'];
leWoonplaats.Text := dm.atInlog['Woonplaats']
end;

Resources