Can I make a quickly SQL call in the MainThread? Delphi - mysql

I have several SQL calls in my Delphi VCL application which uses FireDac to call a MySQL Server to get some data.
However, I am doing all this calls inside the UIThread because they take only a couple of milliseconds. Like the example below:
This is my UI Form
type
TfrmCadPlacas = class(TForm)
lblNumeroDaPlaca: TLabel;
...
...
...
procedure TfrmCadPlacas.btnSalvarClick(Sender: TObject);
begin
// ------------- SQL -------------
if not EstaEditando then
begin
InserirPlaca(Placa, Marca, Modelo, Cor, IDLote, IDHistorico,
dmDados.IDUsuario, dmDados.IDLoteamento);
ShowMessage(SMsgPlacaInseridaSucesso);
end;
end;
InserirPlaca procedure
procedure InserirPlaca(Placa, Marca, Modelo, Cor:string; IDLote, IDHistorico, IDUsuario, IDLoteamento: Integer);
var
qryInserir: TFDQuery;
begin
qryInserir := TFDQuery.Create(nil);
with qryInserir do
begin
Connection := dmDados.FDConnection;
Active := False;
SQL.Clear;
SQL.Add('INSERT INTO ' + T_PLACAS);
SQL.Add('(' + C_PLACA + ', ');
SQL.Add(C_MARCA + ', ');
SQL.Add(C_MODELO + ', ');
SQL.Add(C_COR + ', ');
SQL.Add(C_ID_LOTE + ', ');
SQL.Add(C_ID_HISTORICO + ') ');
SQL.Add('VALUES (:' + C_PLACA + ', ');
SQL.Add(':' + C_MARCA + ', ');
SQL.Add(':' + C_MODELO + ', ');
SQL.Add(':' + C_COR + ', ');
SQL.Add(':' + C_ID_LOTE + ', ');
SQL.Add(':' + C_ID_HISTORICO + ') ');
Params[0].AsString := Placa;
Params[1].AsString := Marca;
Params[2].AsString := Modelo;
Params[3].AsString := Cor;
Params[4].AsInteger := IDLote;
Params[5].AsInteger := PesquisarHistorico(IDLote);
ExecSQL;
end;
end;
As you can see, I am performing a MySQL Server request in the MainThread.
After watching some classes I noticed it is wrong to perform heavy operation in the UIThread as it should be only for GUI. In my case, the operation is pretty fast so it doesn't disturb the UI, if the internet connection is good.
Should I put this MySQL Server request in a background Thread?

Related

Showing first and last name in statusbar

I am a beginner in Delphi, and I would like to show first and last name in the StatusBar instead of the username.
Here is my code:
procedure TForm1.Button1Click(Sender: TObject);
begin
with Login do
begin
active := false;
sql.Clear;
sql.Text := 'SELECT korisnicko, lozinka from operateri where korisnicko = ' + quotedstr(edtkorisnicko.Text) + ' and lozinka = ' + quotedstr(edtlozinka.Text);
active := true;
if Login.RecordCount <> 0 then
begin
form1.Hide();
form2.statusbar1.panels[0].text:= 'Korisnik: ' + edtKorisnicko.Text;
form2.showmodal();
end
else
begin
ShowMessage('Korisničko ime ili lozinka nisu validni!');
end;
end;
"Korisnicko or edtKorisnicko" standing for "username".
Given that your own answer shows the desired name is stored in the ime_prezime field of the database record you are searching for, you can simply retrieve that field in the very same SELECT query that is searching for the username/password, there is no need to run a 2nd query to get that field value separately, eg:
procedure TForm1.Button1Click(Sender: TObject);
begin
Login.Close;
Login.SQL.Text := 'SELECT ime_prezime from operateri where korisnicko = ' + QuotedStr(edtkorisnicko.Text) + ' and lozinka = ' + QuotedStr(edtlozinka.Text);
Login.Open;
Login.First;
if not Login.Eof then
begin
Form1.Hide();
Form2.StatusBar1.Panels[0].Text := 'Korisnik: ' + Login.FieldByName('ime_prezime').AsString;
Form2.ShowModal();
end
else
begin
ShowMessage('Korisničko ime ili lozinka nisu validni!');
end;
end;
There is answer. :)
procedure TForm1.Button1Click(Sender: TObject);
begin
with Login do
begin
active := false;
sql.Clear;
sql.Text := 'SELECT korisnicko, lozinka from operateri where korisnicko = ' + quotedstr(edtkorisnicko.Text) + ' and lozinka = ' + quotedstr(edtlozinka.Text);
active := true;
if Login.RecordCount <> 0 then
begin
active := false;
sql.Clear;
sql.Text := 'SELECT ime_prezime FROM operateri WHERE korisnicko = ' + quotedstr(edtkorisnicko.Text);
active := true;
form1.Hide();
form2.statusbar1.panels[0].text := 'Korisnik: ' + Login.FieldByName('ime_prezime').Text;
form2.showmodal();
end
else
begin
ShowMessage('Korisničko ime ili lozinka nisu validni!');
end;
end;

Delphi 2009, How can I detect if a MySQL transaction was rolled back?

Adapting the code from mysql transaction - roll back on any exception I am making two inserts into a table via a transaction that rolls back if either insert fails. I would like to use Delphi to detect if a rollback has happened or not.
I already use a try - except block to check for errors in executing the whole transaction but presumably a rollback is treated by Delphi as a correct execution.
Is the only way to do this to use a SELECT afterward to see if the data is there?
That could be tricky as the live data that is inserted could possibly be duplicated if two transactions happened for the same person, same amount and on the same day. (The uniqueness of each row is implemented via an autoincrement column (subs_paid_id) that doesn't appear in the inserts)
At the risk of being verbose I have below shown the DLL for the table and my delphi code for doing the inserts.
(ToSQL is just a class that converts various passed parameters into a format suitable for a SQL string. MyConnection1 is a Devart MyDAC TMyConnection conected to a remote database)
/*DDL Information*/
-------------------
CREATE TABLE subscriptions_paid
(
subscription_year varchar(4) NOT NULL DEFAULT '',
member_id int(11) NOT NULL DEFAULT '0',
individual_subs_due float DEFAULT NULL,
individual_subs_paid float DEFAULT NULL,
payment_date date DEFAULT NULL,
import_date date DEFAULT NULL,
user_comment varchar(100),
subs_paid_id int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (subscription_year,member_id,subs_paid_id),
KEY subs_paid_id (subs_paid_id)
)
ENGINE=InnoDB AUTO_INCREMENT=538 DEFAULT CHARSET=utf8
and the Delphi code, including generating the SQL is
procedure TFrm_EditSubsPaid.btnConfirmTransferToSelectedClick(Sender: TObject);
//make sql to add a new payment for FirstMemberID, FirstSubsDue, FirstSubsYear using
//dtpNewPaymentDate, edtNewPaymentComment, edtNewPayment and
//a new negative 'payment' for SecondMemberID, SecondSubsDue,
//SecondSubsYear, using edtAmountToTransferFrom , dtpTransferFrom and
//edtNewTransferFromComment
var overpaid : single;
begin
if TransferAmountValid then
begin
SQL := ''
+'DELIMITER $$ '
+'CREATE PROCEDURE transfer() '
+'BEGIN '
//next two lines allow for all the transaction to be rolled back if any insert fails
+' DECLARE `_rollback` BOOL DEFAULT 0; '
+' DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET `_rollback` = 1; '
+ 'START TRANSACTION '
+'INSERT INTO subscriptions_paid ' //the positive payment into account
+' ( '
+' subscription_year , '
+' member_id , '
+' individual_subs_due , '
+' individual_subs_paid, '
+' payment_date , '
+' import_date , '
+' user_comment '
+' ) '
+' VALUES '
+' ( '
+' ToSQL.Text(FirstSubsYear) + ', '
+ FirstMemberID + ', '
+ ToSQL.Float(StrToFloat(FirstSubsDue)) +', '
+ ToSQL.Float(StrToFloat(edtAmountToTransferFrom.Text)) +', '
+ ToSQL.Date(DateOf(dtpTransferFrom.date)) + ', '
+ 'NULL' + ', '
+ ToSQL.Text('(Tfr From ' + SecondMemberID +') ' + edtNewTransferFromComment.text) +' '
+ ');'
+'INSERT INTO subscriptions_paid ' //the negative payment out of account
+' ( '
+' subscription_year , '
+' member_id , '
+' individual_subs_due , '
+' individual_subs_paid, '
+' payment_date , '
+' import_date , '
+' user_comment '
+' ) '
+' VALUES '
+' ( '
+ ToSQL.Text(FirstSubsYear) + ', '
+ SecondMemberID + ', '
+ ToSQL.Float(StrToFloat(SecondSubsDue)) +', '
+ ToSQL.Float(StrToFloat('-' + edtAmountToTransferFrom.Text)) +', '
+ ToSQL.Date(DateOf(dtpTransferFrom.date)) + ', '
+ 'NULL' + ', '
+ ToSQL.Text('(Tfr To ' + FirstMemberID +') ' + edtNewTransferFromComment.text) +' '
+ ');'
//next five lines allow for all the transaction to be rolled back if any insert fails
+'IF `_rollback` THEN '
+' ROLLBACK; '
+'ELSE '
+' COMMIT; '
+'END IF; '
+'END$$'
+'DELIMITER ;' ;
try
begin
dMod.MyConnection1.ExecSQL(sql);
dMod.MyConnection1.ExecSQL('CALL transfer;');
// ???? now check if the inserts went OK ???
end;
except
on E : Exception do
begin
showmessage (
'Exception class name = '+E.ClassName+ slinebreak
+ 'Exception message = '+E.Message);
end //on E
end;//try
end; //if
end;

Populate ComboBox with Title Case Query Result

with TdmBCElections.Create(Self) do
begin
with dmBCElections, qryParties do
begin
SQL.Clear;
if rgpParty.ItemIndex = 0 then
SQL.Text := 'SELECT StrConv(P_Surname, 3), StrConv(P_Names, 3) ' +
'FROM Parties WHERE P_Type = "HEAD"'
else
SQL.Text := 'SELECT StrConv(P_Surname, 3), StrConv(P_Names, 3) ' +
'FROM Parties WHERE P_Type = "TEACHER"';
Open;
while not Eof do
begin
cmbDetails.Items.Add(qryParties['StrConv(P_Surname, 3)'] + ', ' +
qryParties['StrConv(P_Names, 3)']);
Next;
end;
end;
end;
The code above gives me the following error message:
How do I call the table fields when StrConv is applied to them?
You can assign an alias to the fields:
with TdmBCElections.Create(Self) do
begin
with dmBCElections, qryParties do
begin
if rgpParty.ItemIndex = 0 then
SQL.Text := 'SELECT StrConv(P_Surname, 3) as ConvertedSurname, StrConv(P_Names, 3) as ConvertedNames ' +
'FROM Parties WHERE P_Type = "HEAD"'
else
SQL.Text := 'SELECT StrConv(P_Surname, 3) as ConvertedSurname, StrConv(P_Names, 3) as ConvertedNames ' +
'FROM Parties WHERE P_Type = "TEACHER"';
Open;
while not Eof do
begin
cmbDetails.Items.Add(qryParties['ConvertedSurname'] + ', ' +
qryParties['ConvertedNames']);
Next;
end;
end;
end;
Otherwise, you can use field indexes instead of names:
with TdmBCElections.Create(Self) do
begin
with dmBCElections, qryParties do
begin
if rgpParty.ItemIndex = 0 then
SQL.Text := 'SELECT StrConv(P_Surname, 3), StrConv(P_Names, 3) ' +
'FROM Parties WHERE P_Type = "HEAD"'
else
SQL.Text := 'SELECT StrConv(P_Surname, 3), StrConv(P_Names, 3) ' +
'FROM Parties WHERE P_Type = "TEACHER"';
Open;
while not Eof do
begin
cmbDetails.Items.Add(qryParties.Fields[0].AsString + ', ' + qryParties.Fields[1].AsString);
Next;
end;
end;
end;
Either way, I suggest you consider using a parameterized query instead:
SQL.Text := 'SELECT ... FROM Parties WHERE P_Type = :PType';
if rgpParty.ItemIndex = 0 then
Parameters.ParamByName('PType').Value := 'HEAD'
else
Parameters.ParamByName('PType').Value := 'TEACHER';

MySQL function if elseif 1064

I'm trying to use MySQL but since 2 days I'm stuck on it.
I've got an 1064 error which mean syntax error. I tried a lot of things but none worked.
DELIMITER $$
CREATE FUNCTION set_libelle(civilite VARCHAR(4), prenom VARCHAR(30), nom VARCHAR(30))
RETURNS VARCHAR(20)
BEGIN
IF (LEN(civilite)+LEN(prenom)+LEN(nom)<19) THEN
RETURN civilite + ' ' + prenom + ' ' + nom;
ELSEIF (LEN(civilite)+LEN(nom)<17) THEN
RETURN civilite + ' ' + LEFT(prenom,1) + '. ' + nom;
ELSE
RETURN civilite + ' ' + LEFT(prenom,1) + '. ' + LEFT(nom, (17-(LEN(civilite)));
END IF;
END$$
DELIMITER ;
Have you got an idea why this doesn't work ?
Thank you
You are using + for string concatenation rather than concat().
The proper syntax is:
DELIMITER //
CREATE FUNCTION set_libelle(civilite VARCHAR(4), prenom VARCHAR(30), nom VARCHAR(30))
RETURNS VARCHAR(20)
BEGIN
IF (LEN(civilite) + LEN(prenom) + LEN(nom) < 19) THEN
RETURN CONCAT_WS(' ', civilite, prenom, ',', nom);
ELSEIF (LEN(civilite) + LEN(nom) < 17) THEN
RETURN CONCAT_WS(' ', civilite, LEFT(prenom, 1), '.', nom);
ELSE
RETURN CONCAT_WS(' ', civilite, LEFT(prenom, 1), '.', LEFT(nom, 17 - LEN(civilite)) );
END IF;
END; //
DELIMITER ;
Here is a SQL Fiddle.

Receiving data from an SQL table in Delphi

I have a form which needs to fetch and display data from a MYSQL database.
The SQL code which I have written works fine, as I have tried to execute it within MYSQL.
I have also used a similar format of code to the one in my form, within another form, to fetch and display SQL data. The code works perfectly in the other form.
However, my code for this form does not seem to be receiving/displaying the data from the SQL table.
Here's a snippet of the code written for the OnShow event of the form.
procedure TfrmEditBooking.FormShow(Sender: TObject);
var
CustName: string;
begin
if SelectedID > 0 then
begin
with frmBookingData.ADOQuery1 do
begin
Open;
SQL.Clear;
SQL.Add('SELECT Name, Surname ' +
'FROM customers_main ' +
'WHERE customers_main.idcustomers_main ' +
'IN ' +
'(SELECT bookings_main.customers_main_idcustomers_main ' +
'FROM bookings_main ' +
'WHERE bookings_main.idbookings_main = ' + IntToStr(SelectedID) + ')');
ExecSQL;
CustName := FieldByName('Surname').AsString + ' ' + FieldByName('Name').AsString;
Label1.Caption := CustName;
Close;
end;
end;
end;
One uses 'execsql' when one is not expecting to receive a cursor into a dataset (in other words, use 'execsql' with 'update', 'insert' and 'delete' statements but not with 'select').
You should replace the 'execsql' command with 'open' ... and remove the 'open' before 'sql.clear'.
Don't use a subquery when you should be using a join.
I think that your code ought to look like this
procedure TfrmEditBooking.FormShow(Sender: TObject);
begin
if SelectedID > 0 then
with frmBookingData.ADOQuery1 do
begin
SQL.Clear;
SQL.Add ('SELECT Name, Surname ');
sql.add ('FROM customers_main inner join bookings_main');
sql.add ('on customers_main.idcustomers_main = ');
sql.add ('bookings_main.customers_main_idcustomers_main');
sql.add ('where bookings_main.idbookings_main = :p1');
sql.params[0].asinteger:= SelectedID;
open;
Label1.Caption := fieldbyname ('name').asstring + ' ' +
fieldbyname ('surname').asstring;
Close;
end;
end;
To get this running you should change it to
procedure TfrmEditBooking.FormShow(Sender: TObject);
var
CustName: string;
begin
if SelectedID > 0 then
begin
with frmBookingData.ADOQuery1 do
begin
Close; // close first
SQL.Clear;
SQL.Add(
'SELECT Name, Surname '+
'FROM customers_main '+
'WHERE customers_main.idcustomers_main '+
'IN '+
'(SELECT bookings_main.customers_main_idcustomers_main '+
'FROM bookings_main '+
'WHERE bookings_main.idbookings_main = '+IntToStr(SelectedID)+')');
Open; // open the query
if not Eof then
CustName := FieldByName('Surname').AsString+' '+FieldByName('Name').AsString
else
CustName := 'not found';
Close; // close when finished
end;
Label1.Caption := CustName;
end;
end;
But you should get some (negative) side effects, if frmBookingData.ADOQuery1 is already in use for something different