Why is this FireDAC SQL Server query with parameters failing? - delphi

This is my first time doing a query with parameters. (Using Delphi Seattle and FireDAC in SQL Server)
(I'm planning on using DML once I get this working.)
Why am I getting this error:
[FireDAC][Phys][ODBC][Microsoft][SQL Server Native Client 11.0]
[SQL Server]Incorrect syntax near ':'.
with this query:
procedure TForm2.Button1Click(Sender: TObject);
var
FDParam: TFDParam;
begin
FDQuery1.SQL.Text := 'CREATE TABLE TestTable (Column1 Int)';
FDQuery1.ExecSQL;
FDQuery1.SQL.Text := 'INSERT INTO TestTable (Column1) VALUES (111)';
FDQuery1.ExecSQL; // works fine
FDParam := FDQuery1.Params.Add;
FDParam.Name := 'Column1';
FDParam.DataType := ftInteger;
FDParam.Paramtype := ptInput;
FDQuery1.SQL.Text := 'INSERT INTO TestTable (Column1) VALUES(:Column1)' ;
FDQuery1.ParamByName('Column1').AsInteger := 222;
FDQuery1.ExecSQL; // FAILS
end;

Ken and Johan: thanks for your comments.
Someone here had set the connection's ResourceOptions.ParamCreate and ParamExpand to false.
Overriding that in the FDQuery eliminated the problem.
Thank you again.

Related

Get the result from the Query

I write this code :
Var Q : TFDQuery;
begin
Q := TFDQuery.Create(Self);
Q.Connection := FDConnection1;
Q.Params.CreateParam(ftString,'N',ptOutput);// Try also ptResult
Q.Params.CreateParam(ftInteger,'ID',ptInput);
Q.SQL.Text := 'SELECT NOM FROM EMPLOYEE WHERE ID_EMP = :ID';
Q.Params.ParamByName('ID').Value := 1;
Q.Active := True;
ShowMessage( VarToStr(Q.Params.ParamByName('N').Value) );
The result should be the name of the employer.
I get an error :
'N' parameter not found
How can I get the result from the Query using the parameter?
If I can't , what is the the function of :
ptOutput
ptResult
Try this code:
procedure TForm1.ExecuteQuery;
var
SQL : String;
Q : TFDQuery;
begin
SQL := 'select ''Sami'' as NOM'; // Tested with MS Sql Server backend
try
Q := TFDQuery.Create(Self);
Q.Connection := FDConnection1;
Q.Params.CreateParam(ftString, 'Nom', ptOutput);// Try also ptResult
Q.SQL.Text := SQL;
Q.Open;
ShowMessage( IntToStr(Q.ParamCount));
Caption := Q.FieldByName('Nom').AsString;
finally
Q.Free; // otherwise you have a memory leak
end;
end;
You'll see that the created parameter no longer exists once the FDQuery is opened, because FireDAC "knows" that there is nothing it can do with it.
Then, replace Q.Open by Q.ExecSQL. When that executes you get an exception
with the message
Cannot execute command returning result set.
Hint: Use Open method for SELECT-like commands.
And that's your problem. If you use a SELECT statement, you get a result set whether
you like it or not, and the way to access its contents is to do something like
Nom := Q.FieldByName('Nom').AsString
You asked in a comment what is the point of ptOutput parameters. Suppose your database has a stored procedure defined like this
Create Procedure spReturnValue(#Value varchar(80) out)
as
select #Value = 'something'
Then, in your code you could do
SQL := 'exec spReturnValue :Value'; // note the absence of the `out` qualifier in the invocation of the SP
try
Q := TFDQuery.Create(Self);
Q.Connection := FDConnection1;
Q.Params.CreateParam(ftString, 'Value', ptOutput);// Try also ptResult
Q.SQL.Text := SQL;
Q.ExecSQL;
ShowMessage( IntToStr(Q.ParamCount));
Caption := Q.ParamByName('Value').AsString;
finally
Q.Free; // otherwise you have a memory leak
end;
which retrieves the output parameter of the Stored Proc into Q's Value parameter.
There is no need to manually create parameters. Data access components are smart enough to parse the SQL string and populate the parameters collection by themselves
Also to get the result you must read the query's fields. When you call Open on a Query component, the fields collection will be populated with the fields that you specified in the SELECT [fields] SQL statement
As a side note, I advice that you use the type-safe version to get the value from a TField or TParameter object: See more here
var
q : TFDQuery;
begin
q := TFDQuery.Create(Self);
q.Connection := FDConnection1;
q.SQL.Text := 'SELECT NOM FROM EMPLOYEE WHERE ID_EMP = :ID';
q.ParamByName('ID').AsInteger := 1;
q.Open;
ShowMessage(q.FieldByName('Nom').AsString);
end;

Delphi Stored Procedure can't recognize param

I'm using Firebird 2.5 and IBExpert.
I have the following stored procedure:
SET TERM ^ ;
CREATE OR ALTER PROCEDURE "ButtonGroupName_proc" ("ButtonGroupName_in" "SystemObjectName")
returns ("ButtonGroupName_out" "SystemObjectName")
as
begin
for
select "ButtonName"
from "ButtonGroupName_ButtonName"
where "ButtonGroupName_ButtonName"."ButtonGroupName" = :"ButtonGroupName_in"
into :"ButtonGroupName_out"
do
suspend;
end
^
SET TERM ; ^
At runtime I coded:
...
var
lStoredProc : tFDStoredProc;
...
lStoredProc := tFDStoredProc.Create (Application);
with lStoredProc do begin
Connection := dmSysData.SysData_Connection;
StoredProcName := DoubleQuotedStr ('ButtonGroupName_proc');
ParamByName ('ButtonGroupName_in').Value := 'ButtonGroup_System_Tasks';
Open;
...
end;
When running, I get the "parameter 'ButtonGroupName_in' not found" error, though it is declared as input paramter in the Stored Procedure, as can be verified from the script above.
The code above, was adapted from a very similar example from the Web, but it doesn't work with my code.
Although Delphi can infer parameters from a SELECT statement automatically by parsing the contents of the SQL property, it cannot do the same for the parameters of a stored procedure, so you need to define them explicitly with the Params array:
lStoredProc := tFDStoredProc.Create (Application);
with lStoredProc do begin
Connection := dmSysData.SysData_Connection;
StoredProcName := DoubleQuotedStr ('ButtonGroupName_proc');
Params.Clear;
Params [0] := tFDParam.Create (Params, ptInput);
Params [0].Name := 'ButtonGroupName';
ParamByName ('ButtonGroupName').Value := 'ButtonGroup_System_Tasks';
Open;
while not Eof do begin
lButtonName := Fields [0].Value;
Next;
end;
It retrieves what I need: that is, the names of the buttons that belong to a specific Group of buttons.

Update bindsource and listview

I am working on a Delphi XE5 Firemonkey Mobil app.
I use FireDac for connection.
Just trying to do a simple query insert into sQlite database and update the listview with the inserted info.
procedure TTabbedwithNavigationForm.Button4Click(Sender: TObject);
begin
DataModule1.qSelectCustomers.SQL.Text := 'insert into Invoice (Name) values(:newName)';
DataModule1.qSelectCustomers.ParamByName('newName').AsString := 'test';
DataModule1.qSelectCustomers.ExecSQL;
BindSourceDB1.DataSet.Refresh;
////LinkFillControlToField1.BindList.FillList;
end;
My problem is i am getting error.
error:= TFDQuery : Can not perform this operation on a closed dataset.
I have tried opening the dats set but no go.
Why will this not work ?
You can insert a record into a dataset with a select query like this:
DataModule1.qSelectCustomers.SQL.Text := 'SELECT * FROM Invoice';
DataModuel1.qSelectCustomers.Active := True;
DataModule1.qSelectCustomers.Append;
DataModule1.qSelectCustomers.FieldByName('Name').Value := 'test';
DataModule1.qSelectCustomers.Post;

Error: Object Was Open - Delphi

I have the following issue, When ever I run this code in the procedure, getting a SQL from my SQL COMPACT DATABASE(sdf), it gives me an error "Object Already Open". How can I fix this. Below is my code for the procedure
Function GetSQL(sName: String; Var App: TApplication): String;
Var
Q: TADOQuery;
Begin
Q := TADOQuery.Create(App);
Q.ConnectionString := GetConnectionStringFromRegistry;
Q.Close;
Q.SQL.Text := 'SELECT * FROM SQLs WHERE Name = :sname';
Q.Parameters.ParamByName('sname').Value := sName;
Try
Q.Open;
If Q.RecordCount >= 1 Then
Begin
Q.First;
Result := Q['Query'];
Q.Close;
End;
Finally
Q.Free;
End;
End;
[This is what the error looks like]
[This is what the code looks like when I press Break]
The only thing I see that could be a problem is that your code leaves the query open if there are no rows returned:
Q.Open;
Try
If Q.RecordCount >= 1 Then
Begin
Q.First;
Result := Q['Query'];
Q.Close; // If Q.RecordCount = 0 above, this line never executes
End;
Finally
Q.Free;
End;
Move the Q.Close inside your finally instead, so it will always be called:
Q.Open;
Try
If Q.RecordCount >= 1 Then
Begin
Q.First;
Result := Q['Query'];
End;
Finally
Q.Close; // This will always run, even if no rows are returned
Q.Free; // or if an exception occurs.
End;
As an aside, you should use parameterized queries instead of concatenating the text, especially if you're running the same query multiple times with the only change being the value of sName. The server is smart enough to cache the compiled query and only replace the parameter value, which means your code executes faster and with less load on the server.
Function GetSQL(sName: String; Var App: TApplication): String;
Var
Q: TADOQuery;
Begin
Q := TADOQuery.Create(App);
Q.ConnectionString := GetConnectionStringFromRegistry;
// I've even tried closing it first
Q.Close;
Q.SQL.Text := 'SELECT Query FROM SQLs WHERE Name = :sname';
Q.ParamByName('sname').AsString := sName;
Try
// Error occurs here
Q.Open;
//Q.Active := True;
If Q.RecordCount >= 1 Then
Begin
Q.First;
Result := Q['Query'];
End;
Finally
Q.Close;
Q.Free;
End;
End;
Thanks to #user582118 for reminding this one...
This is actually a bug in the OleDB provider for SQL CE. If you have nvarchar fields greater than 127 characters in a table and you do a select query on that table, you will receive the DB_E_OBJECTOPEN error.
Original thread : https://stackoverflow.com/a/14222561/800214

How to return Oracle Cursor from stored proc as Client Dataset using Delphi and DBExpress

1st off I am Still a little green to Delphi so this might be a "mundane detail" that's being over looked. [sorry in advance]
I have a need to create a TSQLDataset or TClientDataSet from an Oracle 11g cursor contained in a package. I am using Delphi XE2 and DBExpress to connect to the DB and DataSnap to send the data back to the client.
I'm having problems executing the stored procedure from the Delphi code.
Package Head:
create or replace
PACKAGE KP_DATASNAPTEST AS
procedure GetFaxData(abbr varchar2, Res out SYS_REFCURSOR);
END KP_DATASNAPTEST;
Package Body:
create or replace
PACKAGE body KP_DATASNAPTEST AS
procedure GetFaxData(abbr varchar2, Res out SYS_REFCURSOR)is
Begin
open Res for
SELECT Name,
Address1,
City,
fax_nbr
FROM name
JOIN phone on name.Abrv = phone.abrv
WHERE phone.fax_nbr is not null and name.abrv = abbr;
end;
END KP_DATASNAPTEST;
I have no problem executing this procedure in SQL Developer the problem resides in this code on the DataSnap server:
function TKPSnapMethods.getCDS_Data2(): OleVariant;
var
cds: TClientDataSet;
dsp: TDataSetProvider;
strProc: TSQLStoredProc;
begin
strProc := TSQLStoredProc.Create(self);
try
strProc.MaxBlobSize := -1;
strProc.SQLConnection:= SQLCon;//TSQLConnection
dsp := TDataSetProvider.Create(self);
try
dsp.ResolveToDataSet := True;
dsp.Exported := False;
dsp.DataSet := strProc;
cds := TClientDataSet.Create(self);
try
cds.DisableStringTrim := True;
cds.ReadOnly := True;
cds.SetProvider(dsp);
strProc.Close;
strProc.StoredProcName:= 'KP_DATASNAPTEST.GetFaxData';
strProc.ParamCheck:= true;
strProc.ParamByName('abbr').AsString:= 'ZZZTOP';
strProc.Open; //<--Error: Parameter 'Abbr' not found.
cds.Open;
Result := cds.Data;
finally
FreeAndNil(cds);
end;
finally
FreeAndNil(dsp);
end;
finally
FreeAndNil(strProc);
self.SQLCon.Close;
end;
end;
I have also tried assigning the param value through the ClientDataSet without any luck.
I would not be apposed to returning a TDataSet from the function if its easier or produces results. The data is used to populate custom object attributes.
As paulsm4 mentioned in this answer, Delphi doesn't care about getting stored procedure parameter descriptors, and so that you have to it by yourself. To get params of the Oracle stored procedure from a package, you can try to use the GetProcedureParams method to fill the list with parameter descriptors and with the LoadParamListItems procedure fill with that list Params collection. In code it might look like follows.
Please note, that following code was written just in browser according to documentation, so it's untested. And yes, about freeing ProcParams variable, this is done by the FreeProcParams procedure:
var
ProcParams: TList;
StoredProc: TSQLStoredProc;
...
begin
...
StoredProc.PackageName := 'KP_DATASNAPTEST';
StoredProc.StoredProcName := 'GetFaxData';
ProcParams := TList.Create;
try
GetProcedureParams('GetFaxData', 'KP_DATASNAPTEST', ProcParams);
LoadParamListItems(StoredProc.Params, ProcParams);
StoredProc.ParamByName('abbr').AsString := 'ZZZTOP';
StoredProc.Open;
finally
FreeProcParams(ProcParams);
end;
...
end;
I don't think Delphi will automagically recognize Oracle parameter names and fill them in for you. I think you need to add the parameters. For example:
with strProc.Params.Add do
begin
Name := 'abbr';
ParamType := ptInput;
Value := ZZZTOP';
...
end;

Resources