Using parameterized SQL queries.
In my Using MySQL databases with Lazarus/Free Pascal tutorial I showed how TSQLQuery.SQL.Text may be used to define the SQL statement to be executed: Just assign the SQL command as a text string to this property. Assigning the string as such causes two issues:
- You have to take care of the quotes that are part of the SQL statement (quotes enclosing constant values).
- This way to proceed is insecure, making it possible to hack the database via SQL injection.
In parameterized queries, constant values are replaced by special parameter variables. You can name these parameters as you like,
but you have to prefix them with a colon (:). To set the parameters' values, use the TSQLQuery.Params.ParamByName
property.
Here's the code, constructing the SQL statement in my MySQL tutorial (SQL to read all cities for a given country – country that may be entered by its 3-letter code or its name – that have a population of over 1 million:
Query := 'SELECT country.Name AS CountryName, city.Name AS CityName, District, city.Population AS CityPopulation FROM country, city ';
Query += 'WHERE Code = CountryCode ';
if Length(edCountry.Text) = 3 then begin
// Considering 3 letters as country code
Query += 'AND CountryCode = "' + edCountry.Text + '" ';
end
else begin
// Considering other entries as country name
Query += 'AND country.Name = "' + edCountry.Text + '" ';
end;
Query += 'AND city.Population > 1000000 ';
Query += 'ORDER BY city.Population DESC';
// Query the database
dbSQLQuery.SQL.Text := Query;
And the same with a parameterized query:
Query := 'SELECT country.Name AS CountryName, city.Name AS CityName, District, city.Population AS CityPopulation FROM country, city ';
Query += 'WHERE Code = CountryCode ';
if Length(edCountry.Text) = 3 then begin
// Considering 3 letters as country code
Query += 'AND CountryCode = :CountryCode ';
end
else begin
// Considering other entries as country name
Query += 'AND country.Name = :CountryName ';
end;
Query += 'AND city.Population > :Population ';
Query += 'ORDER BY city.Population DESC';
dbSQLQuery.SQL.Text := Query; Count := 0;
if Length(edCountry.Text) = 3 then
dbSQLQuery.Params.ParamByName('CountryCode').AsString := edCountry.Text
else
dbSQLQuery.Params.ParamByName('CountryName').AsString := edCountry.Text;
dbSQLQuery.Params.ParamByName('Population').AsInteger := 1000000;
Note that the parameter name, used in the TSQLQuery.Params.ParamByName assignment must be stated in the value of TSQLQuery.SQL.Text. If, in the code above, you omit the "if/else" clause for the parameter assignment and query the database for the USA cities, you'll get the following error message:
With UPDATE and DELETE statements, parameterized queries may have another benefit: If you change several records, instead of running a new query for each
update or deletion, you can run one single query, only with different parameters. This is lots more efficient, the query
being in most cases much faster. With the parameterized TSQLQuery.SQL.Text assigned, the query is prepared, by calling the TSQLQuery.Prepare
method, then the actual values are assigned to TSQLQuery.Params.ParamByName and the query is run, by calling the TSQLQuery.ExecSQL method.
Here's the code that I use in my MySQL tutorial to update the population of a given city (identified by its ID or its name):
procedure MySQLUpdate(Transaction: TSQLTransaction; Query: TSQLQuery; CountryCode, CityID, CityName: string; NewPopulation: Integer; out MySQLError: string);
var
Sql: string;
begin
Sql := 'UPDATE city ';
Sql += 'SET Population = ' + IntToStr(NewPopulation) + ' ';
Sql += 'WHERE CountryCode = "' + CountryCode + '" ';
if CityID <> '' then
Sql += 'AND ID = ' + CityID + ' '
else
Sql += 'AND city.Name = "' + CityName + '"';
try
Query.SQL.Text := Sql;
Query.ExecSQL;
Transaction.Commit;
except
on E: ESQLDatabaseError do
MySQLError := E.Message;
end;
end;
And the same, using a parameterized query:
procedure MySQLUpdate(Transaction: TSQLTransaction; Query: TSQLQuery; CountryCode, CityID, CityName: string; NewPopulation: Integer; out MySQLError: string);
var
Sql: string;
begin
Sql := 'UPDATE city ';
Sql += 'SET Population = :Population ';
Sql += 'WHERE CountryCode = :CountryCode ';
if CityID <> '' then
Sql += 'AND ID = :CityID '
else
Sql += 'AND city.Name = :CityName ';
try
Query.SQL.Text := Sql;
Query.Prepare;
Query.Params.ParamByName('CountryCode').AsString := CountryCode;
if CityID <> '' then
Query.Params.ParamByName('CityID').AsString := CityID
else
Query.Params.ParamByName('CityName').AsString := CityName;
Query.Params.ParamByName('Population').AsInteger := NewPopulation;
Query.ExecSQL;
Transaction.Commit;
except
on E: ESQLDatabaseError do
MySQLError := E.Message;
end;
end;
In the case of a multiple update (the table values being passed to the procedure as arrays), the code would be something like this:
...
try
Query.SQL.Text := Sql;
Query.Prepare;
for I := 0 to Length(CityIDs) - 1 do begin
...
Query.ExecSQL;
Transaction.Commit;
end;
except
...
end;
end;
If you find this text helpful, please, support me and this website by signing my guestbook.