Reuse TSQLQuery Missing Params - delphi

I'm using Delphi XE2 and a TSQLQuery object. It works the first time that I use it. If I immediately reuse it, then it doesn't parse the new SQL for its parameters and rebuild the qry.Params list:
var
qry: TSQLQuery;
begin
qry := TSQLQuery.Create(nil);
try
qry.MaxBlobSize := -1;
qry.SQLConnectin := AnExistingConnection;
qry.CommandText := 'select field1 from table1 where fieldX = #valueX';
qry.ParamByName('valueX').Value := 1;
qry.Open;
// ... use data ...
qry.Close;
qry.Params.Clear; // <- works the same with or without this
qry.CommandText := 'select field2 from table2 where fieldY = #valueY';
qry.ParamByName('valueY').Value := 2; // <- Error: 'valueY' Param not found
qry.Open;
finally
FreeAndNil(qry);
end;
end;
It doesn't matter what I do, it doesn't parse the 2nd SQL statement for its parameters so I can't bind the 'valueY' parameter by name.
I can think of two workarounds:
Manually build the qry.Params list myself.
Destroy and recreate the qry object in between the two commands.
I shouldn't have to do either of these. Perhaps there is a property or something on the qry object that will cause it to reparse parameters each time a new SQL statement is assigned to its CommandText property?

Turned out to be a syntax issue. Params must be prefaced with a : not a #. I had local SQL variables throughout the real first query, so there was a mixture of #param and :param variables throughout the SQL. By using the :param syntax for all bound parameters, the TSQLQuery does properly parse the parameters each time, like it is supposed to do.
var
qry: TSQLQuery;
begin
qry := TSQLQuery.Create(nil);
try
qry.MaxBlobSize := -1;
qry.SQLConnectin := AnExistingConnection;
qry.CommandText := 'select field1 from table1 where fieldX = :valueX';
qry.ParamByName('valueX').Value := 1;
qry.Open;
// ... use data ...
qry.Close;
qry.CommandText := 'select field2 from table2 where fieldY = :valueY';
qry.ParamByName('valueY').Value := 2;
qry.Open;
finally
FreeAndNil(qry);
end;
end;

Use the TSQLQuery.SQL property instead of the TSQLQuery.CommandText property:
qry.SQL.Text := 'select field1 from table1 where fieldX = #valueX';
...
qry.SQL.Text := 'select field2 from table2 where fieldY = #valueY';
No need to call Params.Clear in between, the SQL property will handle that for you.

Related

ADOQuery Get Result (Delphi)

I Tried to get result from ADOQuery in Delphi. I wrote this function for Get a Name from table according custom ID.
function GetNameByID(Id : Integer) : string;
var query : string;
Begin
ShowMessage(GetDBGridViewIndex().ToString);
query := 'SELECT Name FROM Table1 WHERE ID=' + IntToStr(Id);
With ADOQuery do
Begin
try
SQL.Clear;
SQL.Add(query);
Open;
First;
Result:= // Need Get Result;
finally
Close;
end;
End;
ShowMessage(result);
End;
But I don't know how can return Result from ADOQuery.
TADOQuery is a descendant of TDataset.
You can iterate through the result records with the First, Next, Prior, and Last methods and find out if you've reached the end with Eof.
Within each result record you can access the fields with:
Fields[Index].AsString
Fields[Index].AsInteger
...
or
FieldByName(FieldName).AsString
FieldByName(FieldName).AsInteger
...
In this case you can access to the result using:
Result := Fields[0].AsString;
Result := FieldByName('Name').AsString;
After you Open the query, the cursor is pointing the First record. You don't need to call the First method after Open.

Delphi query multi Parameters

I'm trying to make a query on dataBase Table with ADOQuery by this code
begin
adoquery1.close;
adoquery1.sql.clear;
adoquery1.sql.add(SELECT * FROM Table WHERE name=:name and tel=:tel);
adoquery1.Parameters.ParamByName('name').Value:= edtName.text;
adoquery1.Parameters.ParamByName('tel').Value:= edtTel.text;
adoquery1.open;
end;
Now, if I set edtName and edtTel the query is return a result
But what if edtName or edtTel is empty
the query will return null
what should I do, to make the query returning the result if one of them or both has a value??
Thanks.
You should check if textboxes are empty;
begin
adoquery1.close;
adoquery1.sql.clear;
adoquery1.sql.add(SELECT * FROM Table WHERE 1=1 );
if edtName.text <> '' then
Begin
adoquery1.sql.add(' And name=:name ');
adoquery1.Parameters.ParamByName('name').Value:= edtName.text;
End;
if edtTel.text <> '' then
Begin
adoquery1.sql.add(' And tel=:tel ');
adoquery1.Parameters.ParamByName('tel').Value:= edtTel.text;
End;
adoquery1.open;
end;
note: if both of them are empty result will show all records.
Build the SQL dynamically based on whether the Edit boxes are empty or not, eg:
var
hasName, hasTel: Boolean;
whereClause: string;
begin
ADOQuery1.Close;
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('SELECT * FROM Table');
hasName := edtName.GetTextLen > 0;
hasTel := edtTel.GetTextLen > 0;
if hasName or hasTel then
begin
whereClause := 'WHERE ';
if hasName then
whereClause := whereClause + 'name=:name';
if hasTel then
begin
if hasName then
whereClause := whereClause + ' and ';
whereClause := whereClause + 'tel=:tel';
end;
ADOQuery1.SQL.Add(whereClause);
if hasName then
ADOQuery1.Parameters.ParamByName('name').Value := edtName.Text;
if hasTel then
ADOQuery1.Parameters.ParamByName('tel').Value := edtTel.Text;
end;
ADOQuery1.Open;
end;
Or, more generically:
var
params: TStringList;
I: Integer;
begin
ADOQuery1.Close;
ADOQuery1.SQL.Clear;
params := TStringList.Create;
try
if edtName.GetTextLen > 0 then
params.Add('name=' + edtName.Text);
if edtTel.GetTextLen > 0 then
params.Add('tel=' + edtTel.Text);
// other parameters as needed ...
ADOQuery1.SQL.Add('SELECT * FROM Table');
if params.Count > 0 then
begin
ADOQuery1.SQL.Add('WHERE ' + params.Names[0] + '=:' + params.Names[0]);
for I := 1 to params.Count-1 do
ADOQuery1.SQL.Add('AND ' + params.Names[I] + '=:' + params.Names[I]);
for I := 0 to params.Count-1 do
ADOQuery1.Parameters.ParamByName(params.Names[I]).Value := params.ValueFromIndex[I];
end;
finally
params.Free;
end;
ADOQuery1.Open;
end;
You could do what sddk was suggesting (building your SQL in response of the TEdits value) or
you could assign NULL value to your parameters like that
begin
adoquery1.close;
adoquery1.sql.clear;
adoquery1.sql.add('SELECT * FROM Table WHERE name=:name and tel=:tel');
if edtName.text <> '' then begin
adoquery1.Parameters.ParamByName('name').Value:= edtName.text;
end else begin
adoquery1.Parameters.ParamByName('name').Value:= Null;
end;
if edtTel.text <> '' then begin
adoquery1.Parameters.ParamByName('tel').Value:= edtTel.text;
end else begin
adoquery1.Parameters.ParamByName('tel').Value:= Null;
end;
adoquery1.open;
end;
Don't forget to add System.Variants in your uses list (for the Null use)
You need to beware of testing for Null, as others have warned, but there is a bit more to it
than merely setting the parameter value to Null.
Suppose you have a Sql Server table with a name column and a number of rows have null
entries for that column. Then consider this code:
procedure TForm1.Button1Click(Sender: TObject);
begin
if cbUseNullParam.Checked then begin
AdoQuery1.SQL.Text := 'select * from MATable1 where name = :name';
AdoQuery1.Parameters.ParamByName('name').Value := Null;
end
else
AdoQuery1.SQL.Text := 'select * from MATable1 where name is Null';
AdoQuery1.Open;
end;
In other words, if the cbUseNullParam checkbox is checked, set the name param
to Nll, otherwise use SQL which explicitly specifies that the name column is Null.
Open Sql Server Management Studio's Profiler and observe what happens, both in what is sent to the server, and what a DBGrid connected to AdoQuery1 displays, which is this.
When cbUseNullParam is checked, the query fails to return the rows having
a Null name. When it is not checked, the correct rows are returned.
In other words, using ADO against MS Sql Server at any rate, if you want to find rows that have a Null state for a given column, you need to use Sql that explicitly states that the column is Null, rather than relying of setting an AdoQuery parameter for that column to Null. So, if your case, you actually need four versions of your SQL (one each for the two columns being Null, one for them both being Null and one for neither being Null).

How To Copy All Lines of Memo String to DBGrid Fields

I want to to copy all lines of string in memo to dbgrid fields (Fields FREKUENSI_TPOKOK). I have tried the following :
procedure Tfrm_csbo.cmd_copyClick(Sender: TObject);
var
tpokok: string;
i: integer;
Begin
tbl_LLOANP.First;
for i := 0 to Memo2.Lines.Count-1 do
tpokok := 'UPDATE LLOANP SET FREKUENSI_TPOKOK = ' + Memo2.Lines[i];
qr_LLOANP.Close;
qr_LLOANP.SQL.Clear;
qr_LLOANP.SQL.Add(tpokok);
qr_LLOANP.ExecSQL;
end
end;
but the result is not as i expected to be.
If we indent your code properly then it looks like this:
tbl_LLOANP.First;
for i := 0 to Memo2.Lines.Count-1 do
tpokok := 'UPDATE LLOANP SET FREKUENSI_TPOKOK = ' + Memo2.Lines[i];
qr_LLOANP.Close;
qr_LLOANP.SQL.Clear;
qr_LLOANP.SQL.Add(tpokok);
qr_LLOANP.ExecSQL;
So you execute a single SQL statement, rather than one per item. Perhaps you meant to write:
tbl_LLOANP.First;
for i := 0 to Memo2.Lines.Count-1 do
begin
tpokok := 'UPDATE LLOANP SET FREKUENSI_TPOKOK = ' + Memo2.Lines[i];
qr_LLOANP.Close;
qr_LLOANP.SQL.Clear;
qr_LLOANP.SQL.Add(tpokok);
qr_LLOANP.ExecSQL;
end;
Do note that you are opening yourself up to SQL injection here. You should always use SQL parameters rather than build your own queries from user supplied data.

What happens if I call ParamByName for a parameter that has multiple locations in a query? [duplicate]

I am using ADOQuery in Delphi 7 and Oracle. I am getting error while passing parameters to ADOQuery. I have used following line. Please help me to identify error.
ADOQuery.Sql.text:= 'select * from temp_table '+
'where column1 in (select column from table2 where id=:id) and id=:id';
ADOQuery.Parameters.ParamByValue('id').value= 'abc';
ADOQuery.open;
when I open the query i will get following error:
Parameter object is improperly defined. Inconsistent or incomplete information is provided.
We have the same problem, we ended "masking" the class TParameters like this:
Declaration:
TMyParameter = class(TParameter)
private
function GetAsValue: variant;
Procedure SetAsValue(const Value: variant);
public
property Value: variant read GetAsValue write SetAsValue;
end;
Implementation:
procedure TMyParameter.SetAsValue(const Value: variant);
var
iPar: Integer;
begin
for iPar:= 0 to Collection.Count - 1 do
if (Name = TParameter(Collection.Items[iPar]).Name) then
TParameter(Collection.Items[iPar]).Value:= Value;
end;
function TMyParameter.GetAsValue: variant;
begin
Result:= inherited Value;
end;
And how to use:
TMyParameter(ADOQuery.Parameters.ParamByName('id')).AsValue:= 'abc';
I hope it helps.
for i:=0 to ADOQuery.Parameters.Count-1 do
begin
if ADOQuery.Parameters.Items[i].Name = 'id' then
ADOQuery.Parameters.Items[i].Value := 'abc';
end;
You need to distinguish between the two id;s:
ADOQuery.Sql.text:= 'select * from temp_table a where column1 in (select column from table2 b where b.id=:id) and a.id=:id';
ADOQuery.Parameters.ParamByValue('id').value= 'abc';
ADOQuery.open;
In the SQL code declare a variable of the necessary type, assign to that variable the parameter; you will be able to use that variable as many times as necessary:
ADOQuery.Sql.text:= 'declare #param varchar(50); set #param = :id; '+
'select * from temp_table '+
'where column1 in (select column from table2 where id=#param) and id=#param';
ADOQuery.Parameters.ParamByValue('id').value= 'abc';
ADOQuery.open;
Regards

How can I pass a list of values as a parameter for a TOraQuery?

I have a TOraQuery with SQL defined something like this
SELECT ML.ID, Ml.detail1, Ml.detail2
FROM MY_LIST ML
WHERE
ML.ID in (:My_IDS)
If I was to build this query on the fly, I'd naturally end up with something like this:
SELECT ML.ID, Ml.detail1, Ml.detail2
FROM MY_LIST ML
WHERE
ML.ID in (14001,14002,14003)
However, I'd like to pass in 14001,14002,14003 as a parameter.
myListQuery.Active := False;
myListQuery.ParamByName('My_IDS').AsString := '14001,14002,14003';
myListQuery.Active := True;
But of course that generates an ORA-01722: invalid number. Do I have any other option other than building up the query on the fly.
AFAIK, it is not possible directly.
You'll have to convert the list into a SQL list in plain text.
For instance:
function ListToText(const Args: array of string): string; overload;
var
i: integer;
begin
result := '(';
for i := 0 to high(Args) do
result := result+QuotedStr(Args[i])+',';
result[length(result)] := ')';
end;
function ListToText(const Args: array of integer): string; overload;
var
i: integer;
begin
result := '(';
for i := 0 to high(Args) do
result := result+IntToStr(Args[i])+',';
result[length(result)] := ')';
end;
To be used as such:
SQL.Text := 'select * from myTable where intKey in '+ListToText([1,2,3]);
SQL.Text := 'select * from myTable where stringKey in '+ListToText(['a','b','c']);
Or in your case:
myListQuery.SQL.Text := 'SELECT ML.ID, Ml.detail1, Ml.detail);
myListQuery.SQL.Add('FROM MY_LIST ML ');
myListQuery.SQL.Add('WHERE ');
myListQuery.SQL.Add('ML.ID in ') + ListToText([14001,14002,14003]);
You can do it but it requires some additional setup. Hopefully this works with your version of Oracle.
Create a table type
Create a function that converts your string to your table type
Use CAST in the subquery. Pass your value to the bind variable using the same thing you have in your code (i.e. ParamByName('').AsString).
create or replace type myTableType as table of varchar2 (255);
create or replace function in_list( p_string in varchar2 ) return myTableType as
l_string long default p_string || ',';
l_data myTableType := myTableType();
n number;
begin
loop
exit when l_string is null;
n := instr( l_string, ',' );
l_data.extend;
l_data(l_data.count) :=
ltrim( rtrim( substr( l_string, 1, n-1 ) ) );
l_string := substr( l_string, n+1 );
end loop;
return l_data;
end;
select * from THE ( select cast( in_list(:MY_BIND_VARIABLE) as mytableType ) from dual ) a
If this works for you, credit for the answer and example code goes to Tom Kyte from Oracle who runs asktom.com. https://asktom.oracle.com/pls/asktom/f?p=100:11:0%3a%3a%3a%3aP11_QUESTION_ID:210612357425
You can use a "Macro"
Not quite what I was looking for, but it's a hair closer to a parameter than building the SQL on the fly.
Create the TOraQuery like this
SELECT ML.ID, Ml.detail1, Ml.detail2
FROM MY_LIST ML
WHERE
ML.ID in (&My_IDS)
Now I can pass in 14001,14002,14003 as a macro.
myListQuery.Active := False;
myListQuery.MacroByName('My_IDS').value := '14001,14002,14003';
myListQuery.Active := True;

Resources