SQL Query Parameters - delphi

This works just fine:
Query = 'SELECT * from table_1 where code = :value; ';
I'm however trying to use the LIKE statement and It says that It couldn't find the parameter VALUE In this case:
Query = 'SELECT * from table_1 where code LIKE ''%:value;%''';
Param := ADOQuery1.Parameters.ParamByName('value');
Param.DataType := ftString;
Param.Value := 'bob';
I wanted to use a backslash to ignore the quotes, because It works in most languages but It looks like It doesn't work in Delphi.

Parameters automatically put the quotes around strings for you. Therefore, you cannot include such quotes in the SQL query. Because of that, you also cannot pass the % syntax with the query either.
Instead, keep your statement as the original (replacing = with LIKE), and pass the % around the actual value instead.
Query = 'SELECT * from table_1 where code LIKE :value';
Param := ADOQuery1.Parameters.ParamByName('value');
Param.DataType := ftString;
Param.Value := '%bob%';
On a side note, this code's a bit more elegant...
ADOQuery1.Parameters.ParamValues['value']:= '%bob%';
No need to tell it it's a string. It will detect that automagically.

"No need to tell it it's a string (the Param.DataType) . It will detect that automagically."
In general it is true.
Actually it depends of some TConnection properties, such as .ParamCreate,.ResourceOptions.AssignedValue.rvParamCreate, .ResourceOptions.AssignedValue.rvDefaultParamType=false,and defaultParamDataType . If those properties were not changed then
It will detect that automagically
So in some cases that would be explicit.

Related

Delphi FDQuery.SQL string starting with '!' dont work

I have a query that is dynamically set up with a parameter.
I query for lines with a varchar field that contains values that can begin with a '!'.
But I get no match of those.
I use SQLServer as the database server.
If I take the sqlcode and run it directly in the database manager it works but not with TFDQuery.
Se the code example below:
myParameter := '!Tommy';
with qryExec do
begin
SQL.Clear ;
SQL.Add('SELECT * FROM myTable T WHERE T.Name='+quotedStr(myParameter));
active := true ;
first;
if Not Eof then
begin
Result := True;
end;
end; //with
I have no idea what's wrong here, so I would be happy if anyone could come with an explanation.
I would suggest using actual parameters which also avoids the possibility of SQL injection. There are also overloaded versions of Open that reduce the housekeeping lines.
FDQuery1.Open('SELECT * FROM myTable T WHERE T.Name= :NAME',['!Tommy'],[ftWideString]);

ADOQuery1 parameter 'card' not found

i've used the parameter method but now i have a problem. i want to insert all my data inside the table. i need to insert 2 table at once. so heres my full coding. need help. why it says like that?
ADOQuery1.Close();
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('INSERT INTO STUDENT (CARD_ID,NAMA,MATRIC_ID,SUBJEK,KURSUS,FAKULTI,Seksyen,TAHUN) VALUES ');
ADOQuery1.SQL.Add('(card,nama,matric,subjek,kursus,fakulti,seksyen,tahun)');
ADOQuery1.SQL.Add('INSERT INTO subjek2 (CARD_ID, MATRIC_ID,NAMA,SUBJEK) VALUES');
ADOQuery1.SQL.Add('(card,matric,nama,subjek)');
ADOQuery1.Parameters.ParamByName('card').Value:= card1.Text;
ADOQuery1.Parameters.ParamByName('nama').Value:= Edit1.Text;
ADOQuery1.Parameters.ParamByName('matric').Value:= Edit2.Text;
ADOQuery1.Parameters.ParamByName('kursus').Value:= Edit3.Text;
ADOQuery1.Parameters.ParamByName('fakulti').Value:= Edit4.Text;
ADOQuery1.Parameters.ParamByName('seksyen').Value:= ComboBox1.Text;
ADOQuery1.Parameters.ParamByName('tahun').Value:= Edit5.Text;
ADOQuery1.Open();
There are some issues in your code.
SQL-Statement
If you want to execute more than one statement, then you have to use the statement delimiter (most times ;). You missed that in your statements.
INSERT INTO STUDENT (CARD_ID,NAMA,MATRIC_ID,SUBJEK,KURSUS,FAKULTI,Seksyen,TAHUN) VALUES
(card,nama,matric,subjek,kursus,fakulti,seksyen,tahun); -- missed ;
INSERT INTO subjek2 (CARD_ID, MATRIC_ID,NAMA,SUBJEK) VALUES
(card,matric,nama,subjek); -- optional on last statement
Parameters
Parameters in SQL-Statements must start with : otherwise they were treated as normal fields
INSERT INTO STUDENT (CARD_ID,NAMA,MATRIC_ID,SUBJEK,KURSUS,FAKULTI,Seksyen,TAHUN) VALUES
(:card,:nama,:matric,:subjek,:kursus,:fakulti,:seksyen,:tahun);
INSERT INTO subjek2 (CARD_ID, MATRIC_ID,NAMA,SUBJEK) VALUES
(:card,:matric,:nama,:subjek);
BTW: You did not provide any data to the parameter subjek in your code.
Executing Statement
Some statements return a cursor to data (SELECT) others do not (INSERT,DELETE,...).
If you are executing a statement, that did not return a cursor then you must not use Open. Instead you have to ExecSQL.
Mutiple Statements / Access / TADOQuery
You simply can not execute multiple statements using TADOQuery and Access. You have to execute the statements separately.
If you want to achieve, that all data is written or if any error occurs no data is written, then you have to start a transaction before the statements and you can commit or rollback.
Following all the advices you come to the following code (without transaction)
ADOQuery1.Close();
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('INSERT INTO STUDENT (CARD_ID,NAMA,MATRIC_ID,SUBJEK,KURSUS,FAKULTI,Seksyen,TAHUN) VALUES ');
ADOQuery1.SQL.Add('(:card,:nama,:matric,:subjek,:kursus,:fakulti,:seksyen,:tahun)');
ADOQuery1.Parameters.ParamByName('card').Value:= card1.Text;
ADOQuery1.Parameters.ParamByName('nama').Value:= Edit1.Text;
ADOQuery1.Parameters.ParamByName('matric').Value:= Edit2.Text;
ADOQuery1.Parameters.ParamByName('subjek').Value:= '????'; // I don't know what
ADOQuery1.Parameters.ParamByName('kursus').Value:= Edit3.Text;
ADOQuery1.Parameters.ParamByName('fakulti').Value:= Edit4.Text;
ADOQuery1.Parameters.ParamByName('seksyen').Value:= ComboBox1.Text;
ADOQuery1.Parameters.ParamByName('tahun').Value:= Edit5.Text;
ADOQuery1.ExecSQL;
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('INSERT INTO subjek2 (CARD_ID, MATRIC_ID,NAMA,SUBJEK) VALUES');
ADOQuery1.SQL.Add('(:card,:matric,:nama,:subjek)');
ADOQuery1.Parameters.ParamByName('card').Value:= card1.Text;
ADOQuery1.Parameters.ParamByName('nama').Value:= Edit1.Text;
ADOQuery1.Parameters.ParamByName('matric').Value:= Edit2.Text;
ADOQuery1.Parameters.ParamByName('subjek').Value:= '????'; // I don't know what
ADOQuery1.ExecSQL;
You've not provided the parameters correctly (as I showed you in my previous answer). Note the colon (:) before the name of each parameter:
ADOQuery1.SQL.Add('(:card, :matric, :nama, :subjek)');
Also note (from that same previous answer) that you do not use the colon when assigning values to the paramters:
ADOQuery1.Parameters.ParamByName('card').Value := card1.Text;
And, once again, use white space in your SQL!! You save one keystroke by not putting spaces in between the comma-separated things, and make it much more difficult to read and maintain later without them. It's much easier to read (:card, :matric, :nama, :subjek) than it is to read (:card,:matric,:nama,:subjek). Start learning to do things properly now and save yourself (and others) headaches later.

How Can I Create Insert and Update SQL's by pure code

I'm currently using AdoQuery's and append post commands. But for data security I want to change my code with insert into and update table name...
But I have a lot of forms and tables...
Because of that I think maybe someone has already developed code for generating insert statements.
Actually I have found a way but I'm stuck.
I have query1. it contains the fieldlist.
I'm creating a parameter list in another query from this fieldlist.
I'm updating the parameters field by field.
This is not very convenient
Can someone give me a easy ways to do this.
Note: I prefer coding this job with only standard components. I don't want to install additional components.
Maybe not the reply you want. I think you need to raise the abstraction level. You need to skip SQL. An ORM framework can do this for you. It maybe feels like a big step for you but I promise it is also a relief to just use code like:
Person.name := 'Bob';
Invoice.customer.address.street := 'Abbey road';
Edit1.text := Invoice.customer.name;
To actually update database you need to call an update method that differ depending on framework. For a list of frameworks see here. I am also aware of TMS Aurelius. I use Bold on daily use. Bold also have features like OCL, derived attributes and links in the model, some boldaware components (it updates whenever db changes). But it has one big disadvantage. It is only available for D2006/D2007. I am working for a solution on this because I think it is the best and most mature ORM framework for Delphi. See also my blog on Bold for Delphi. Ask if you have questions!
You take the fieldlist from your query.
Create a new query with parameters.
And fill in the values.
Something like this:
const
TableNameEscapeStart = '['; //SQL server, use '`' for MySQL
TableNameEscapeEnd = ']'; //SQL server, use '`' for MySQL
FieldNameEscapeStart = '[';
FieldNameEscapeEnd = ']';
function CreateInsertStatementFromTable1ToTable2(Table1, Table2: TTable): String;
var
i: integer;
comma: string;
begin
i:= 0;
Result:= 'INSERT INTO '+TableNameEscapeStart + Table2.TableName + TableNameEscapeEnd + ' (';
comma:= ' , '
while i < Table1.FieldCount do begin
if (i = Table1.FieldCount -1) then begin comma:= ' '; end;
Result:= Result + FieldNameEscapeStart + Table1.Fields.Field[i].Name + FieldNameEscapeEnd + comma;
end;
Result:= Result +' ) VALUES ( ';
i:= 0;
comma:= ' , '
while i < Table1.FieldCount do begin
if (i = Table1.FieldCount -1) then begin comma:= ' '; end;
Result:= Result +':' + IntToStr(i+1) + comma;
end; {while}
Result:= Result + ' ); ';
end;
There are three avenues for SQL injection here.
1. The field values
2. The table name
3. The field names
The first is covered by the use of parameters.
The second and third are covered, because you're using the table and field names of the table directly.
If you don't have a trusted source of table and fields names, then you need to compare these against the table and fieldnames obtained directly from the table.
See: Delphi - prevent against SQL injection
You insert the data using ParamByName (slowly) or more efficiently using Param[i] where i starts at 0.
In MySQL it's even easier:
If table1 and table2 have the same fields, the following SQL will insert all data in table2 into table1:
INSERT INTO table1 SELECT * FROM table2;

How to avoid "Type mismatch in expression" in ClientDataSet Filter

The erro msg "Type mismatch in expression" appear when I run this code:
CDSIndicados.Filtered := False;
CDSIndicados.Filter := 'EDICOES_ID like ' + QuotedStr(IntToStr(Integer(cxComboBox1.Properties.Items.Objects[cxComboBox1.ItemIndex])));
CDSIndicados.Filtered := True;
I know this message may appear when there is an error in the data type of the field. But I could not fix. Was that it?
I'm suspecting that your EDICOES_ID field is an integer value, in which case you don't need to quote it in your filter expression and the LIKE operator isn't supported AFAIK. If it is a string field, you do need the quotes and LIKE is supported, but you typically want a wildcard in the expression as well. (LIKE is only supported for character (string) type fields. For numerics or dates, you need to use the usual comparison operators >, <, >=, <=, = or BETWEEN.)
Do yourself a favor, too, and declare a local variable, and making sure that there's actually an item selected in the ComboBox before trying to access its Objects. I've added one both for the ItemIndex and for intermediate storage of the typecast Object you're retrieving, which makes it much easier to debug if you need to do so.
Here's a solution either way (whether it's an integer field, or a string that needs quoting).
var
Idx, Value: Integer;
begin
Idx := ComboBox1.ItemIndex;
if Idx > -1 then
begin
CDSIndicados.Filtered := False;
Value := Integer(cxComboBox1.Properties.Items.Objects[Idx]);
// If the field is an integer, you don't need a quoted value,
// and LIKE isn't supported in the filter.
CDSIndicados.Filter := 'EDICOES_ID = ' + IntToStr(Value);
// Not relevant here, but LIKE isn't supported for date values
// either. For those, use something like this
CDSIndicados.Filter := 'EDICOES_DATE = ' + QuotedStr(DateToStr(Value));
// or, if the field is string and you want LIKE, you need to
// quote the value and include a wildcard inside that quoted
// string.
CDSIndicados.Filter := 'EDICOES_ID LIKE ' + QuotedStr(IntToStr(Value) + '%');
CDSIndicados.Filtered := True;
end;
end;

Getting a Field List from a DBExpress TSQLQuery

I am having a problem getting a list of fields from a query defined at run time by the users of my program. I let my users enter a SQL query into a memo control and then I want to let them go through the fields that will return and do such things as format the output, sum column values and so forth. So, I have to get the column names so they have a place to enter the additional information.
I would do fine if there were no parameters, but I also have to let them define filter parameters for the query. So, if I want to set the parameters to null, I have to know what the parameter's datatype is.
I am using Delphi 2006. I connect to a Firebird 2.1 database using the DBExpress component TSQLConnection and TSQLQuery. Previously, I was successful using:
for i := 0 to Qry.Params.Count - 1 do Qry.Params[i].value := varNull;
I discovered I had a problem when I tried to use a date parameter. It was just a coincidence that all my parameters up until then had been integers (record IDs). It turns out that varNull is just an enumerated constant with a value of 1 so I was getting acceptable results (no records) was working okay.
I only need a list of the fields. Maybe I should just parse the SELECT clause of the SQL statement. I thought setting Qry.Prepared to True would get me a list of the fields but no such luck. It wants values for the parameters.
If you have an idea, I would sure like to hear it. Thanks for any help.
Replied again 'coz I'm interested. My methods works (with my queries) because they have been pre-defined with the params' datatypes preset to the correct type:)
I'm not sure how you are expecting the query to know or derive the datatype of the param given that you are not even selecting the field that it operates against.
So I think your query setup and user input method will need more attention. I've just looked up how I did this a while ago. I do not use a parameterised query - I just get the "parameter values" from the user and put them directly into the SQL. So your sql would then read:
SELECT s.hEmployee, e.sLastName
FROM PR_Paystub s
INNER JOIN PR_Employee e ON e.hKey = s.hEmployee
WHERE s.dtPaydate > '01/01/2008'
therefore no parameter type knowledge is necessary. Does not stop your users entering garbage but that goes back to input control :)
Although a slightly different dataset type this is what I use with TClientDataset simple and effective :)
for i := 0 to FilterDataSet.Params.Count -1 do
begin
Case FilterDataSet.Params.Items[i].Datatype of
ftString:
ftSmallint, ftInteger, ftWord:
ftFloat, ftCurrency, ftBCD:
ftDate:
ftTime:
ftDateTime:
.
.
.
end;
end;
can you not do something similar with the query?
You guys are making this way too hard:
for i := 0 to Qry.Params.Count - 1 do begin
Qry.Params[i].Clear;
Qry.Params[i].Bound := True;
end;
I'm not sure what version of Delphi you are using. In the Delphi 2006 help under Variant Types, it says:
Special conversion rules apply to the
Borland.Delphi.System.TDateTime type
declared in the System unit. When a
Borland.Delphi.System.TDateTime is
converted to any other type, it
treated as a normal Double. When an
integer, real, or Boolean is converted
to a Borland.Delphi.System.TDateTime,
it is first converted to a Double,
then read as a date-time value. When a
string is converted to a
Borland.Delphi.System.TDateTime, it is
interpreted as a date-time value using
the regional settings. When an
Unassigned value is converted to
Borland.Delphi.System.TDateTime, it is
treated like the real or integer value
0. Converting a Null value to Borland.Delphi.System.TDateTime raises
an exception.
The last sentence seems important to me. I would read that as varNull cannot be converted to a TDateTime to put into the field, and hence you get the exception that you're experiencing.
It also implies that this is the only special case.
Couldn't you do something like:
for i := 0 to Qry.Params.Count - 1 do
begin
if VarType(Qry.Params[i].value) and varTypeMask = varDate then
begin
Qry.Params[i].value := Now; //or whatever you choose as your default
end
else
begin
Qry.Params[i].value := varNull;
end;
end;
What I ended up doing was this:
sNull := 'NULL';
Qry.SQL.Add(sSQL);
for i := 0 to Qry.Params.Count - 1 do begin
sParamName := Qry.Params[i].Name;
sSQL := SearchAndReplace (sSQL, ':' + sParamName, sNull, DELIMITERS);
end;
I had to write SearchAndReplace but that was easy. Delimiters are just the characters that signal the end of a word.
TmpQuery.ParamByName('MyDateTimeParam').DataType := ftDate;
TmpQuery.ParamByName('MyDateTimeParam').Clear;
TmpQuery.ParamByName('MyDateTimeParam').Bound := True;

Resources