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;
Related
I write datasnap application. I should know id of last uploaded record on server and after that upload new records from local db using cliendataset component.
I don't realise meaning of this code, what are there Parameters[0].Value, Parameters[1].Value and how it change correctly in order to return result from server:
FSelectCountryCommand.Parameters[0].Value.SetInt32();
FSelectCountryCommand.ExecuteUpdate;
Result := FSelectCountryCommand.Parameters[1].Value.GetInt32;
if I write manually random integer value, for example
result:=10; //FSelectCountryCommand.Parameters[1].Value.GetInt32;
then it works correctly and returns this value in ClientFormUnit.
My final goal is return result value(maxcountry_id) from Server to buttonclick procedure of ClientForm unit
Query on server:
select max(country_id) as maxcountry_id from country
ClientForm unit:
procedure Tclient.buttonCountryClick(Sender: TObject);
var c:integer;
begin
c:=Clientmodule1.ServerMethods1Client.SelectCountry;
Clientmodule1.lqCountry.close;
Clientmodule1.lqCountry.Params[0].AsInteger:= c;
Clientmodule1.lqCountry.close;
end;
ClientClassesUnit:
function TServerMethods1Client.SelectCountry:Integer;
begin
if FSelectCountryCommand = nil then
begin
FSelectCountryCommand := FDBXConnection.CreateCommand;
FSelectCountryCommand.CommandType :=
TDBXCommandTypes.DSServerMethod;
FSelectCountryCommand.Text := 'TServerMethods1.SelectCountry';
FSelectCountryCommand.Prepare;
end;
FSelectCountryCommand.Parameters[0].Value.SetInt32();
FSelectCountryCommand.ExecuteUpdate;
Result := FSelectCountryCommand.Parameters[1].Value.GetInt32;
end;
ServerMethodUnit:
function TServerMethods1.SelectCountry:Integer;
begin
qCountry.close;
qCountry.Open;
Result:= qCountryMaxCountry_id.AsInteger;
end;
I tried use global variable but without success: it is seen only in project area, not in project group area.
SOLUTION
this code works fine:
FSelectCountryCommand.ExecuteUpdate;
Result := FSelectCountryCommand.Parameters[0].Value.GetInt32;
As I understand, as select query doesn't contain parameters therefore this operator:
FSelectCountryCommand.Parameters[0].Value.SetInt32();
is redundant.
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.
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
I'm using Delphi 7 and Firebird 1.5.
I have a query that I create at runtime where some of the values might be null. I can't work out how to get Firebird to accept explicit nulls for values that I need to leave as null. At this stage I'm building the SQL so that I don't include parameters that are null but this is tedious and error-prone.
var
Qry: TSQLQuery;
begin
SetConnection(Query); // sets the TSQLConnection property to a live database connection
Query.SQL.Text := 'INSERT INTO SomeTable (ThisColumn) VALUES (:ThisValue)';
Query.ParamByName('ThisValue').IsNull := true; // read only, true by default
Query.ParamByName('ThisValue').Clear; // does not fix the problem
Query.ParamByName('ThisValue').IsNull = true; // still true
Query.ParamByName('ThisValue').Bound := true; // does not fix the problem
Query.ExecSQL;
Currently an EDatabaseError "No value for parameter 'ThisValue'"' is raised in DB.pas so I suspect this is by design rather than a firebird problem.
Can I set parameters to NULL? If so, how?
(edit: sorry for not being explicit about trying .Clear before. I left it out in favour of mentioning IsNull. Have added declaration and more code)
Sorry, one more thing: there is no "NOT NULL" constraint on the table. I don't think it's getting that far, but thought I should say.
Complete console app that displays the problem at my end:
program InsertNull;
{$APPTYPE CONSOLE}
uses
DB,
SQLExpr,
Variants,
SysUtils;
var
SQLConnection1: TSQLConnection;
Query: TSQLQuery;
begin
SQLConnection1 := TSQLConnection.Create(nil);
with SQLConnection1 do
begin
Name := 'SQLConnection1';
DriverName := 'Interbase';
GetDriverFunc := 'getSQLDriverINTERBASE';
LibraryName := 'dbexpint.dll';
LoginPrompt := False;
Params.clear;
Params.Add('Database=D:\Database\ZMDDEV12\clinplus');
Params.Add('RoleName=RoleName');
//REDACTED Params.Add('User_Name=');
//REDACTED Params.Add('Password=');
Params.Add('ServerCharSet=');
Params.Add('SQLDialect=1');
Params.Add('BlobSize=-1');
Params.Add('CommitRetain=False');
Params.Add('WaitOnLocks=True');
Params.Add('ErrorResourceFile=');
Params.Add('LocaleCode=0000');
Params.Add('Interbase TransIsolation=ReadCommited');
Params.Add('Trim Char=False');
VendorLib := 'gds32.dll';
Connected := True;
end;
SQLConnection1.Connected;
Query := TSQLQuery.Create(nil);
Query.SQLConnection := SQLConnection1;
Query.Sql.Text := 'INSERT INTO crs_edocument (EDOC_ID, LINKAGE_TYPE) VALUES (999327, :ThisValue)';
//Query.ParamByName('ThisValue').IsNull := true; // read only, true by default
// Query.ParamByName('ThisValue').Value := NULL;
Query.ParamByName('ThisValue').clear; // does not fix the problem
Query.ParamByName('ThisValue').Bound := True; // does not fix the problem
// Query.ParamByName('ThisValue').IsNull; // still true
Query.ExecSQL;
end.
The reason of the error is 'dbx' does not know the data type of the parameter. Since it is never assigned a value, it's data type is ftUnknown in execute time, hence the error. Same for 'ParamType', but 'ptInput' is assumed by default, so no problem with that.
Query.ParamByName('ThisValue').DataType := ftString;
You definitely don't need to Clear the parameter because it is already NULL. How do we know it? IsNull is returning true...
From TParam.Clear Method:
Use Clear to assign a NULL value to a
parameter.
From TParam.IsNull Property:
Indicates whether the value assigned
to the parameter is NULL (blank).
You definitely don't need to Bound the parameter as it is completely irrelevant. When 'Bound' is false, the dataset will attempt to provide a default value from its datasource for the parameter. But your dataset is not even linked to a data source. From the documentation:
[...] Datasets that represent queries
and stored procedures use the value of
Bound to determine whether to assign a
default value for the parameter. If
Bound is false, datasets that
represent queries attempt to assign a
value from the dataset indicated by
their DataSource property. [...]
If the documentation is not enough, refer to the code in TCustomSQLDataSet.SetParamsFromCursor in 'sqlexpr.pas'. It is the only place where the 'Bound' of a parameter is referred in dbx framework.
Use TParam.Clear
Query.ParamByName('ThisValue').Clear;
"Use Clear to assign a NULL value to a parameter." (from the Docs)
Sertac's answer is most correct, but I also found the choice of driver makes a difference.
For the benefit of others, here's an improved test program that demonstrates how you could insert nulls with a parameterised query with Firebird 1.5.
program InsertNull;
{$APPTYPE CONSOLE}
uses
DB,
SQLExpr,
Variants,
SysUtils;
var
SQLConnection1: TSQLConnection;
Query: TSQLQuery;
A, B, C: variant;
begin
SQLConnection1 := TSQLConnection.Create(nil);
Query := TSQLQuery.Create(nil);
try
try
with SQLConnection1 do
begin
Name := 'SQLConnection1';
DriverName := 'InterXpress for Firebird';
LibraryName := 'dbxup_fb.dll';
VendorLib := 'fbclient.dll';
GetDriverFunc := 'getSQLDriverFB';
//DriverName := 'Interbase';
//GetDriverFunc := 'getSQLDriverINTERBASE';
//LibraryName := 'dbexpint.dll';
LoginPrompt := False;
Params.clear;
Params.Add('Database=127.0.0.1:D:\Database\testdb');
Params.Add('RoleName=RoleName');
Params.Add('User_Name=SYSDBA');
Params.Add('Password=XXXXXXXXXXXX');
Params.Add('ServerCharSet=');
Params.Add('SQLDialect=1');
Params.Add('BlobSize=-1');
Params.Add('CommitRetain=False');
Params.Add('WaitOnLocks=True');
Params.Add('ErrorResourceFile=');
Params.Add('LocaleCode=0000');
Params.Add('Interbase TransIsolation=ReadCommited');
Params.Add('Trim Char=False');
//VendorLib := 'gds32.dll';
Connected := True;
end;
Query.SQLConnection := SQLConnection1;
Query.SQL.Clear;
Query.Params.Clear;
// FYI
// A is Firebird Varchar
// B is Firebird Integer
// C is Firebird Date
Query.Sql.Add('INSERT INTO tableX (A, B, C) VALUES (:A, :B, :C)');
Query.ParamByName('A').DataType := ftString;
Query.ParamByName('B').DataType := ftInteger;
Query.ParamByName('C').DataType := ftDateTime;
A := Null;
B := Null;
C := Null;
Query.ParamByName('A').AsString := A;
Query.ParamByName('B').AsInteger := B;
Query.ParamByName('C').AsDateTime := C;
Query.ExecSQL;
writeln('done');
readln;
except
on E: Exception do
begin
writeln(E.Message);
readln;
end;
end;
finally
Query.Free;
SQLConnection1.Free;
end;
end.
Have some property on TConnection Options named HandlingStringType/Convert empty strings to null. Keep it true and assume Query.ParamByName('ThisValue').AsString:='';
You can access it in
TConnection.FetchOptions.FormatOptions.StrsEmpty2Null:=True
Are you sure the params have been created by just setting the text of the SQL?
try
if Query.Params.count <> 0 then
// set params
.
.
Anyway why not make the SQL text:
'INSERT INTO crs_edocument (EDOC_ID, LINKAGE_TYPE) VALUES (999327, NULL)';
if you know the value is going to be null...
and I am tearing my hair out!!
Even something simple like this work:
procedure MyAdoQueryTest();
const MYSQL_CONNECT_STRING='Driver={MySQL ODBC 5.1 Driver};Server=%s;Port=3306;Database=%s;User=%s;Password=%s;Option=3;';
var AdoConnection : TADOConnection;
ADOQuery : TADOQuery;
Param : TParameter;
begin
AdoConnection := TADOConnection.Create(Nil);
AdoConnection.ConnectionString := Format(MYSQL_CONNECT_STRING,['localhost',
'mysql',
'root',
'']);
AdoConnection.LoginPrompt := False;
AdoConnection.Connected := True;
ADOQuery := TADOQuery.Create(Nil);
ADOQuery.Connection := AdoConnection;
ADOQuery.Sql.Clear();
ADOQuery.SQl.Add('SHOW :what_to_show');
Param := ADOQuery.Parameters.ParamByName('what_to_show');
Param.DataType := ftString;
Param.Value := 'databases';
ADOQuery.Prepared := true;
ADOQuery.Active := True;
end;
(btw, do I really need to use the 'Param' variable and 3 statements, or can I just ` ADOQuery.Parameters.ParamByName('what_to_show').Value := 'databases';?)
Anyway, when I run it, I get an exception at ADOQuery.SQl.Add('SHOW :what_to_show'); which says "Arguments are of the wrong type, are out of the acceptable range or are in conflict with one another".
What I am trying to do is to make 2 central functions: one which will accept and execute any SQL statement which will not return any data (such as INSERT INTO) and oen which will (such as SELECT).
I currently have these working with AdoConnection only, but am now trying to use AdoQuery because I want to parametrize my SQL statements to handle strings with quotes in them.
I can has halpz?
The error is here:
ADOQuery.SQl.Add('SHOW :what_to_show');
The :Param can only be used for values, not for dynamic column/keyword/table/database names.
This is because if it worked like that you'd have an SQL-injection risk depending on the contents of your parameter.
In order to fix that you'll have to inject your what_to_show thingy into the SQL-string.
Like so:
var
what_to_show: string;
begin
....
what_to_show:= 'tables';
ADOQuery.SQL.Text:= ('SHOW '+what_to_show);
....
Now it will work.
Warning
Make sure test everything you inject into the SQL to prevent users from being able inject their SQL-code into your queries.
Parameters prevent SQL injection, but because you cannot use them here you need to check them against a list of pre-approved values. e.g. a stringlist holding all the allowed what_to_shows.
Escaping or use of special chars is useless.
Safe injection example code
var
what_to_show: string;
i: integer;
inputapproved: boolean;
begin
....
what_to_show:= lower(trim(someinput));
i:= 0;
inputapproved:= false;
while (i < WhiteList.count) and not(inputapproved) do begin
inputapproved:= ( what_to_show = lower(Whitelist[i]) );
Inc(i);
end; {while}
if inputapproved then ADOQuery.SQL.Text:= ('SHOW '+what_to_show);
....