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

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;

Related

how i can filter the special character "ñ" in data set from delphi 7?

I have an application in delphi 7 that use a database query and bring information to client dataset. Then i need use the filter that provide the dataset to filter the info when the user press the keys. I have an issue with the character special "ñ" (aplication in spanish). The database connection is with PostgreSQL and works correctly.
SELECT * FROM public.users WHERE lastname ILIKE '%ñ%'
The function to filter the data is the following (simplified for this case):
procedure TfmForm.gdPersonalKeyPress(Sender: TObject;
var Key: Char);
begin
ClientDataSetUsers.Filtered := False;
ClientDataSetUsers.Filter := ' UPPER(lastname) LIKE ' + #39 + '%' + UpperCase(lbSearch.Caption) + '%' + #39;
ClientDataSetUsers.Filtered := True;
end;
This functionality also works well but not when I insert the character "Ñ" (it does not bring anything). Is there a way to filter the dataset using this character?
AnsiUpperCase('ñ')
was the answer that solved the problem

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]);

How to fix 'Unable to find record. No key specified'?

I'am using a firebird 2.5 server to write in a Database file(BD.fbd). My delphi XE8 project has a Data module(DMDados) with:
SQLConnection (conexao)
TSQLQUery1 (QueryBDPortico_Inicial) + TDataSetProvider1 (DSP_BDPortico_Inicial) + TClientDataSet1 (cdsBDPortico_Inicial)
TSQLQUery2 (QueryConsulta) (just for use SQL strings)
My database file has this table:
PORTICO_INICIAL
The table has these fields (all integer):
NPORTICO
ELEMENTO
ID
None of those fields are primary keys because I will have repeated values in some cases. The connection with the file is ok. The client data set is open when I run the code. The TSQLQUery2 (QueryConsulta) is open when needed.
My code, when triggered by a button, has to delete all tables' records (if exist) then full the table with integer numbers created by a LOOP.
In the first try the code just work fine, but when I press the button the second time i get the error 'Unable to find record. No key specified' then when I check the records the table is empty.
I tried to change the ProviderFlags of my query but this make no difference. I checked the field names, the table name or some SQL text error but find nothing.
My suspect is that when my code delete the records the old values stay in memory then when try apply updates with the new values the database use the old values to find the new record's place therefore causing this error.
procedure monta_portico ();
var
I,K,L,M, : integer;
begin
with DMDados do
begin
QUeryCOnsulta.SQL.Text := 'DELETE FROM PORTICO_INICIAL;';
QueryConsulta.ExecSQL();
K := 1;
for I := 1 to 10 do
begin
L := I*100;
for M := 1 to 3 do
begin
cdsBDPortico_Inicial.Insert;
cdsBDPortico_Inicial.FieldbyName('NPORTICO').AsInteger :=
M+L;
cdsBDPortico_Inicial.FieldbyName('ELEMENTO').AsInteger := M;
cdsBDPortico_Inicial.ApplyUpdates(0);
K := K +1;
end;
end;
end;
end;
I want that every time I use the code above it first delete all records in the table then fill it again with the loop.
When I use the code for the first time it do what I want but in the second time it just delete the records and can not fill the table with the values.
Update I've added some example code below. Also, when I wrote the original version of this answer, I'd forgotten that one of the TDataSetProvider Options is
poAllowMultiRecordUpdates, but I'm not sure that's involved in your problem.
The error message Unable to find record. No key specified is generated by the DataSetProvider, so isn't directly connected to your
QUeryCOnsulta.SQL.Text := 'DELETE FROM PORTICO_INICIAL;'
because that bypasses the DataSetProvider. The error is coming from an failed attempt to ApplyUpdates on the CDS. Try changing your call to it to
Assert(cdsBDPortico_Inicial.ApplyUpdates(0) = 0);
That will show you when the error occurs because the return result of ApplyUpdates gives the number of errors that occurred when calling it.
You say
will have repeated values in some cases
If that's true when the problem occurs, it's because you are hitting a fundamental limitation in the way a DataSetProvider works. To apply the updates on the source dataset, it has to generate SQL to send back to the source dataset (TSqlQuery1) which uniquely identifies the row to update in the source data, which is impossible if the source dataset contains duplicated rows.
Basically, you need to re-think your code so that the source dataset rows are all unique. Once you've done that, setting the DSP's UpdateMode to upWhereAll should avoid the problem. It would be best for the source dataset to have a primary key, of course.
A quick work-around would be to use CDS.Locate in the loop where you insert the records, to see if it can locate an already-existing record with the values you're about to add.
Btw, sorry for raising the point about the ProviderFlags. It's irrelevant if there are duplicated rows, because whatever they are set to, the DSP will still fail to update a single record.
In case it helps, here is some code which might help populating your table
in a way which avoids getting duplicates. It only populates the first two
columns, as in the code you show in your q.
function RowExists(ADataset : TDataSet; FieldNames : String; Values : Variant) : Boolean;
begin
Result := ADataSet.Locate(FieldNames, Values, []);
end;
procedure TForm1.PopulateTable;
var
Int1,
Int2,
Int3 : Integer;
i : Integer;
RowData : Variant;
begin
CDS1.IndexFieldNames := 'Int1;Int2';
for i := 1 to 100 do begin
Int1 := Round(Random(100));
Int2 := Round(Random(100));
RowData := VarArrayOf([Int1, Int2]);
if not RowExists(CDS1, 'Int1;Int2', RowData) then
CDS1.InsertRecord([Int1, Int2]);
end;
CDS1.First;
Assert(CDS1.ApplyUpdates(0) = 0);
end;
Splite the problem into small parties using functions and procedures
create an instance of TSqlQuery Execute the SQL statment's and destroy the instance when you finish with it...
procedure DeleteAll;
var
Qry: TSqlQuery;
begin
Qry := TSqlQuery.Create(nil);
try
Qry.SqlConnection := DMDados.conexao;
Qry.Sql.Text := 'DELETE FROM PORTICO_INICIAL;';
Qry.ExecSql;
finally
Qry.Free;
end;
end;
your can even execute directly from TSQlConnection with one line...
DMDados.conexao.ExecuteDirect('DELETE FROM PORTICO_INICIAL;')
procedure monta_portico ();
var
I,K,L,M, : integer;
begin
with DMDados do
begin
DeleteAll;
K := 1;
for I := 1 to 10 do
begin
L := I*100;
for M := 1 to 3 do
begin
cdsBDPortico_Inicial.Insert;
cdsBDPortico_Inicial.FieldbyName('NPORTICO').AsInteger :=
M+L;
cdsBDPortico_Inicial.FieldbyName('ELEMENTO').AsInteger := M;
cdsBDPortico_Inicial.ApplyUpdates(0);
K := K +1;
end;
end;
end;
end;
Just few obvervations, cause the primary answers were given, but they not deal with the secondary problems.
cdsBDPortico_Inicial.FieldbyName('NPORTICO').AsInteger :=
FieldByName is slow function - it is linear search over objects array with uppercased string comparison over each one. You better only call it once for every field, not do it again in again in the loop.
cdsBDPortico_Inicial.ApplyUpdates(0);
Again, applying updates is relatively slow - it requires roundtrip to the server all through internal guts of DataSnap library, why so often?
BTW, you delete rows from SQL table - but where do you delete rows from cdsBDPortico_Inicial ??? I do not see that code.
Was I in your shows I would write something like that (granted I am not big fan of Datasnap and CDS):
procedure monta_portico ();
var
Qry: TSqlQuery;
_p_EL, _p_NP: TParam;
Tra: TDBXTransaction;
var
I,K,L,M, : integer;
begin
Tra := nil;
Qry := TSqlQuery.Create(DMDados.conexao); // this way the query would have owner
try // thus even if I screw and forget to free it - someone eventually would
Qry.SqlConnection := DMDados.conexao;
Tra := Qry.SqlConnection.BeginTransaction;
// think about making a special function that would create query
// and set some its properties - like connection, transaction, preparation, etc
// so you would not repeat yourself again and again, risking mistyping
Qry.Sql.Text := 'DELETE FROM PORTICO_INICIAL'; // you do not need ';' for one statement, it is not script, not a PSQL block here
Qry.ExecSql;
Qry.Sql.Text := 'INSERT INTO PORTICO_INICIAL(NPORTICO,ELEMENTO) '
+ 'VALUES (:NP,:EL)';
Qry.Prepared := True;
_p_EL := Qry.ParamByName('EL'); // cache objects, do not repeat linear searches
_p_NP := Qry.ParamByName('NP'); // for simple queries you can even do ... := Qry.Params[0]
K := 1;
for I := 1 to 10 do
begin
L := I*100;
for M := 1 to 3 do
begin
_p_NP.AsInteger := M+L;
_p_EL.AsInteger := M;
Qry.ExecSQL;
Inc(K); // why? you seem to never use it
end;
end;
Qry.SqlConnection.CommitFreeAndNil(tra);
finally
if nil <> tra then Qry.SqlConnection.RollbackFreeAndNil(tra);
Qry.Destroy;
end;
end;
This procedure does not populate cdsBDPortico_Inicial - but do you really need it?
If you do - maybe you can re-read it from the database: there could be other programs that added rows into the table too.
Or you can insert many rows and then apply them all in one command, before committing the transaction (often abreviated tx), but even then, do not call FieldByName more than once.
Also, think about logical blocks of your program work in advance, those very transactions, temporary TSQLQuery objects etc.
However boring and tedious it is now, you would bring yourself many more spaghetti trouble if you don't. Adding this logic retroactively after you have many small functions calling one another in unpredictable order is very hard.
Also, if you make Firebird server auto-assigning the ID field (and your program does not need any special values in ID and will be ok with Firebird-made values) then the following command might server yet better for you: INSERT INTO PORTICO_INICIAL(NPORTICO,ELEMENTO) VALUES (:NP,:EL) RETURNING ID

TAdoquery date format

I am Java developer. I have some old program in Delphi. In old version they work with mdb. I fixed it for connection with SQL Server. All SQL queries are implemented with TAdoQuery.
qryTemp.SQL.Text:='select sum(iif(ComeSumm>0,comesumm,0)),sum(iif(lostSumm>0,lostsumm,0)) from cash '+
'where (IdCashClause is null or idcashclause<>8) '+
' and cashNum='+IntToStr(i)+
' and CashType=0'+
' and format(PayDate,"dd/mm/yyyy")=format('''+DateToStr(Date)+''',"dd/mm/yyyy") ';
The program throws an exception:
Invalid column name 'dd/mm/yyyy'.
I have fixed other query for comparison:
qryTemp.SQL.Text:=' select top 1 iif(ComeSumm>0,comesumm,0) from cash '
+' where idCashReason=1 and idCashClause=8 and cashNum='+IntToStr(i)
+' and PayDate<:D'
+' order by payDate desc';
qryTemp.Parameters.ParamByName('D').Value:=DateTimeToStr(Date);
Can I quickly fix all queries for work with SQL Server without rewriting the whole project?
Assuming PayDate is defined as date/datetime in MSSQL you could use parameters as follow:
qryTemp.SQL.Text:=' select top 1 iif(ComeSumm>0,comesumm,0) from cash '
+' where idCashReason=1 and idCashClause=8 and cashNum='+IntToStr(i)
+' and PayDate<:D'
+' order by payDate desc';
qryTemp.Parameters.ParamByName('D').Value := Date;
qryTemp.Parameters.ParamByName('D').DataType := ftDateTime;
I'd also change cashNum to parameter i.e.:
...
+' where idCashReason=1 and idCashClause=8 and cashNum=:cashNum'+
...
qryTemp.Parameters.ParamByName('cashNum').Value := i;
Always prefer to use compatible data types with your parameters, rather than formatting and using strings. SQL does not need to guess your data types if you can explicitly define them.
Note: IIF was introduced in SQL Server 2012. for older version use CASE expression.
In older Non-Unicode Delphi versions, Parameters have issue with Unicode.
So, If you don't use Parameters you could use the following:
function DateTimeToSqlDateTime(const DT: TDateTime): WideString;
begin
Result := FormatDateTime('yyyy-MM-dd', DT) + ' ' + FormatDateTime('hh:mm:ss', DT);
end;
function SqlDateTimeStr(const DT: TDateTime; const Is_MSSQL: Boolean): WideString;
var
S: WideString;
begin
S := DateTimeToSqlDateTime(DT);
if Is_MSSQL then
Result := Format('CONVERT(DATETIME, ''%s'', 102)', [S])
else
Result := Format('#%s#', [S]); // MS-ACCESS
end;
And your query will look as follow:
...
+' and PayDate<' + SqlDateTimeStr(Date, True)
...
It´s very likely that the PayDate column is declared as DATE in the cash table. Considering that, your parameter should be a TDateTime and not a string, like this:
qryTemp.SQL.Text:=' select top 1 iif(ComeSumm>0,comesumm,0) from cash '
+' where idCashReason=:cashReason and idCashClause=8 and cashNum='+IntToStr(i)
+' and PayDate<:D'
+' order by payDate desc';
qryTemp.Parameters.ParamByName('D').Value := Date;
I replaced only one of the parameters, but you should consider replacing all of them, since this will improve the server performance by enabling its sentence cache.
Getting back to your original question, I guess the only way to refactor all the application would be to have a refactoring program that could parse your code, find those situations and follow a pattern to replace one piece of code by another.
I do not know any tool that can do that nowaways.
Maybe using find/replace that supports regular expressions can help you, but it certainly wont´t fix the cases in one single pass. You would have to run a series of replace phases in order to transform your code from what it is to what you want it to be.
DateToStr uses localization information contained in global variables to format the date string. Maybe this is the problem.
You can try FormatDateTime:
qryTemp.SQL.Text:='select sum(iif(ComeSumm>0,comesumm,0)),sum(iif(lostSumm>0,lostsumm,0)) from cash '+
'where (IdCashClause is null or idcashclause<>8) '+
' and cashNum='+IntToStr(i)+
' and CashType=0'+
' and format(PayDate,"dd/MM/yyyy")='''+FormatDateTime('dd/mm/yyyy',Date)+'''';

Using LIKE and '%' in an ADO dataset filter

I have a problem when trying to apply a filter to a ADO dataset in Delphi XE2 ...
Filter := ' [Name] like ''%john'' ';
It raises an exception:
Project Test.exe raised exception class EOleException with message
'Arguments are of the wrong type, are out of acceptable range,
or are in conflict with one another'. Process stopped.
but when using:
Filter := ' [Name] like ''john%'' '
it works fine!
why?
The Operator can only be one of the following:
= < > <= >= <> LIKE
If you use the LIKE operator, you can also use the * or % wildcards as the last character in the string or as the first and last character in the string.
http://www.devguru.com/technologies/ado/quickref/recordset_filter.html
You can catch the filter on the event OnFilterRecord:
procedure TForm1.ADODataSet1FilterRecord(DataSet: TDataSet; var Accept: Boolean);
var
iPos: Integer;
begin
iPos:= pos('john',ADODataSet1name.AsString);
if (iPos>0) and
(iPos = length(ADODataSet1name.AsString)-3) then
begin
Accept:= True;
end
else
begin
Accept:= False;
end;
end;
or
function TForm1.IsLastCriteria(AText: String): Boolean;
var
iPos: Integer;
begin
iPos:= pos(AText,ADODataSet1name.AsString);
Result:= (iPos>0) and
(iPos = length(ADODataSet1name.AsString)-length(AText)-1);
end;
procedure TForm1.ADODataSet1FilterRecord(DataSet: TDataSet; var Accept: Boolean);
begin
Accept:= IsLastCriteria('john');
end;
AS. I still ask you to read http://www.catb.org/esr/faqs/smart-questions.html#beprecise and describe your environment accordingly.
What is database server ?
What version are MDAC/ADO components?
What the query is ?
What is Name column type in SQL ?
You are "upping" the comments, that si nice. But you don't answer the questions. And that is not nice. We are not ESPers, we canot read you mind.
I put you few suggestions in comments above. Did you tried them ? Did they worked ? To quote them:
Maybe you can update ADO/MDAC to 2.8sp1 version. Referenced MSDN KB articles are told to apply to MDAC up to 2.7 version. Maybe 2.8sp1 no more has that limitation.
Maybe you can use some data-server specific tricks like copying last 4 letters of Name into a separate column.
Maybe you can move that condition into SQL SELECT WHERE clause and re-open the query.
Maybe there is event-handler like TBDEDataSet.OnFilterRecord
There are 4 workarounds and you either did not tried them or did not reported the results. Not nice.
Idea #1 is self-explanatory
Idea #3 was detailed by #SertacAkyuz as far as he could do it, giving lack of information about your program.
Idea #4 was detailed by #Ravaut123
Idea #2 is outlined below
Assuming your "ADO database" is backed by Microsoft Access, and according to http://www.databasedev.co.uk/access-sql-string-functions.html ...
Or using MS SQL and according to http://msdn.microsoft.com/en-us/library/ms177532.aspx ...
Or...
Query.SQL.Text := 'select Right(Name, 4) as name_tail, * from table where ...'
See that extra column added to query! 4 is length for "john" there.
With query like that the following filtering conditions are to be equivalent:
Filter := ' [Name] like ''%john'' ';
Filter := ' [name_tail] = ''john'' ';
However, if on some rows Name column is shorter than 4 letters, i don't know what RIGHT function would do. Maybe it will truncate the result, or maybe throw an error and abort the query. The latter maybe - depending on the real data - maybe can be alleviated by padding with spaces to the 4 length like
LTrim(Right(' ' || Name, 4)) as name_tail
That is for you to test, since only you know the details of your environment.
The simplest answer to this question was mentioned in comments.
If you used asterisk instead of percent symbol, everything would work great
Filter := ' [Name] like ''*john'' ';

Resources