I have an issue with my database.
Table structure is:
Table name: sales
sale_id (autoincrement)
date (datetime)
total (decimal)
etc.
I have 2 computers, one is "the server" and the other is "the client", when I Insert in "sales" sometimes the database saves more than 1 record, it's an issue kind of random because one day could be normal just save 1 record as is but other day could save 2 or more duplicates.
My code is:
qry1.SQL.Text := 'SELECT * FROM sales '
+ 'WHERE sale_id = 1';
qry1.Open;
qry1.Insert;
qry1.FieldByName('date').AsDateTime := Date;
qry1.FieldByName('total').AsFloat := total;
qry1.Post;
saleId := qry1.FieldByName('sale_id').AsInteger;
qry1.Close;
// Code to save sale details using saleId.
I'm using Delphi 10.3 + ZeosLib 7.2.6-stable + MySQL 8.0
I opened the ports in the server so I have a direct connection to MySQL, I don't know what could be happening
Hope you can help me
Update----
Thanks for your kind answers,
#nbk Yes, I did it already.
#A Lombardo I used "where" to get just 1 record and then I use the query to insert the new one similar to use TTable but instead of load the hole table I just get one record and I can insert (qry.Insert),
#TheSatinKnight not only I get two records, sometimes I get 3 or more, but makes sense probably the keayboard is not working well and could send "enter" key more than once.
#fpiette, I will do ti right now.
I will keep you posted.
There are better ways to accomplish an insert than to open a TZTable and inserting on that open table.
As another approach, drop 2 TZQuery (NOT TZTable) on your form (which I'll assume is TForm1 - change as appropriate).
Assuming the name is ZQuery1 and ZQuery2.
Set its connection property the same as your TZTable, so it uses the same connector.
Set ZQuery1.SQL property to 'Insert into sales (date, total) values (:pdate, :ptotal)' //(w/o quotes)
Set ZQuery2.SQL property to 'select last_insert_id() as iddb'
now add the Function below to your form's Private delcaration
TForm1 = class(TForm)
ZQuery1: TZQuery; //added when dropped on form
ZQuery2: TZQuery;
private
{ Private declarations }
function AddNewSale(SaleDate: TDateTime; Total: Double): Integer; //add this line
public
{ Public declarations }
end;
and then add the following code to your form's methods
var
Form1: TForm1;
implementation
{$R *.dfm}
function TForm1.AddNewSale(SaleDate: TDateTime; Total: Double): Integer;
begin
ZQuery1.ParamByName('pdate').AsDateTime := SaleDate;
ZQuery1.ParamByName('ptotal').AsFloat := Total;
ZQuery1.ExecSQL; //*Execute* the Insert - Only "open" SQL that returns a result set
//now the record has been added to your DB
if ZQuery1.RowsAffected = 1 then //check to ensure it was inserted
begin
ZQuery2.Open;
try
Result := ZQuery2.FieldByName('iddb').AsInteger;
finally
ZQuery2.Close;
end;
end
else
result := -1;//indicate error by returning negative value
end;
now in the place you want to insert the record, simply call this function:
var
ReturnValue: Integer;
begin
ReturnValue := AddNewSale(Date, total);
if ReturnValue < 0 then
//error happened
else
begin
//Everything worked
end;
end;
Thanks again for all your kind answers.
At the end the problem was keyboard, It had a problem with "Enter" key, so when you pressed it, it send more than one pulsation so #TheSatinKnigh your approach was correct
#fpiette I created the log file and I figured out as you said the request had been executed twice or more.
I know maybe it is a silly thing for a programmer because I was disabling the button to late, sorry for that
#A Lombardo thanks for you code I like it better than mine I will use it
Related
I'll say how I reproduce the problem on lazarus.
I have a form and a datamodule using zeos to enstablish a connection with a local oracle db.
The problem born when I put some code to interlocute with the db.
Here is an example:
OracleMng.ZQuery1.SQL.Clear;
That is exactly the line going in error.
Here is the full code of the form:
unit form1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, DBGrids, StdCtrls,
datamodule2;
type
{ TLogin }
TLogin = class(TForm)
Button1: TButton;
DBGrid1: TDBGrid;
procedure Button1Click(Sender: TObject);
private
public
end;
var
Login: TLogin;
implementation
{$R *.lfm}
{ TLogin }
procedure TLogin.Button1Click(Sender: TObject);
begin
OracleMng.ZQuery1.SQL.Clear;
end;
end.
Here is the code of the datamodule:
unit datamodule2;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, DB, ZConnection, ZDataset, ZSqlMonitor;
type
{ TOracleMng }
TOracleMng = class(TDataModule)
DataSource1: TDataSource;
ZConnection1: TZConnection;
ZQuery1: TZQuery;
private
public
end;
var
OracleMng: TOracleMng;
implementation
{$R *.lfm}
{ TOracleMng }
end.
I'm trying
if (OracleMng <> Nil) and (OracleMng.Zquery1 <> Nil) then OracleMng.ZQuery1.SQL.add('select * from help');
if (OracleMng <> Nil) and (OracleMng.Zquery1 <> Nil) then OracleMng.ZQuery1.ExecSQL;
dbgrid1.refresh;
I have no more errors but the DBGrid1 is not filled.
This is my project lpr file:
program project1;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Interfaces, // this includes the LCL widgetset
Forms, zcomponent, datamodule2, form1
{ you can add units after this };
{$R *.res}
begin
RequireDerivedFormResource:=True;
Application.Scaled:=True;
Application.Initialize;
Application.CreateForm(TLogin, Login);
Application.Run;
end.
The fact that the change I suggested in my comment, namely
if (OracleMng <> Nil) and (OracleMng.Zquery1 <> Nil) then
OracleMng.ZQuery1.SQL.Clear
evidently stopped you getting the SIGSEGV error suggests that your DataModule and
form are being created in the wrong order, i.e. form first. Check this out by going to
Project | View Source in the IDE. If you see something like
program MyProgram;
[...]
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.CreateForm(TDataModule1, DataModule1);
Application.Run;
end.
they are in the wrong order, so swap the two CreateForm lines
Application.CreateForm(TDataModule1, DataModule1);
Application.CreateForm(TForm1, Form1);
With that change, you should no longer need the
if (OracleMng <> Nil) and (OracleMng.Zquery1 <> Nil) then`
Next thing: You seem to be confused about when to use
ZQuery1.ExecSQL
and
ZQuery1.Open
Open is intended for when the SQL statement you are using produces a result set, that is
a collection of records which can be viewed in a TDBGrid. The most usual way to do this
is to use a SELECT statement as in
ZQuery1.SQL.Text := 'select * from MyTable';
ZQuery1.Open;
ExecQuery is intended for use where your SQL statement performs some operation on the database
which does not involve SELECTing records. The most common SQL statements which need ExecSQL are
UPDATE
INSERT
DELETE
though there are others, for example statements which execute stored procedures on the SQL Server
(note that some stored procedures return result sets and so need Open, rather than ExecSQL).
Note that ExecSQL will clear out any records which are in the dataset (ZQuery1) so after
you need to do Open again using a suitable SQL statement
var
S : String;
begin
S := 'update MyTable set number = number +1 where id = 5';
ZQuery.SQL.Text := S;
ZQuery1.ExecSQL; // no records shown in DBGrid1 from here
S := 'select * from MyTable';
ZQuery.SQL.Text := S;
ZQuery1.Open; // records shown in DBGrid1 again
end;
Note that I do
S := 'select * from MyTable';
ZQuery.SQL.Text := S;
instead of
ZQuery1.SQL.Clear;
ZQuery1.SQL.Add('select * from myTable');
The reason for this is that it's much easier to see the whole SQL statement in the debugger by
inspecting the variable S than inspecting the ZQuery1.SQL.Text property and much easier to
see any syntax errors.
You should always Close a dataset that you've Opened once you have finished working with it as it ensures what the data on disk is up to date. if the last SQL operation was ExecSQL, you don't need to close the dataset.
If you set the query's Text property the way I do, with ZQuery1.SQL.Text, you don't need to uses Clear. In any case, it is only equivalent to doing ZQuery1.SQL.Text := '' and it does not affect the state of the dataset - it only does anything when you call ExecSQL or Open.
I am having a problem using the TFDDataSet component in my application.
I have a function that fetch many times if a customer has new orders. If it returns empty the function ends.
...
fdm_XMLREsumo.Close;
fdm_XMLREsumo.Active := false;
_DataSetJSON := SM.GetXMLResumoChave( pEnt_ID, pChave); //See Edit 1
_DataSet.Close;
_DataSet := TFDJSONDataSetsReader.GetListValueByName( _DataSetJSON, sXMLResumo );
_DataSet.Open; <-- here's the problem
if not _DataSet.IsEmpty then begin
exit;
end;
fdm_XMLREsumo.AppendData( _DataSet );
...
The problem is every time it executes _DataSet.Open; it creates a new connection in my DB.Because of that I'm having a too many connections exception.I've checked in my Server Properties and it is like this:
I have tried Connection.Close,_DataSet.Close, _DataSet.Free and _DataSet.Destroy but nothing worked.I read this, and it explains that even if you do _DataSet.Close the connection still exists, because DataSets work in memory.There is also this guy having a similar issue, but using Query. Does anyone know how can I manage to solve this?I am using MySQL
EDIT 1
As #CraigYoung helped me saying my example needs MCVE
SM.GetXMLResumoChave method:
Here it uses a connection to the database that is closed at the end of the function. Already debugged, and here it does not leave an open connection in MySQL Proccess List
function TDAOXMLResumo.GetXMLResumoChave(xEnt_id: Integer; xChave: String): TFDJSONDataSets;
begin
if not oSM.FDConn.Connected then
oSM.FDConn.Connected := true;
QueryPesquisa.SQL.Clear;
QueryPesquisa.SQL.Text :=
' select * from table' +
' where ent_id = :ent_id ' +
' and xre_chNFe = :xre_chNFe ';
QueryPesquisa.ParamByName('ent_id').Asinteger := xEnt_id;
QueryPesquisa.ParamByName('xre_chNFe').Asstring := xChave;
Result := TFDJSONDataSets.Create;
//TFDJSONDataSetsWriter.ListAdd Opens the Query that is passed as parameter and store the data as JSON in the TFDJSONDataSets (Result) object
TFDJSONDataSetsWriter.ListAdd(Result, sXMLResumo, QueryPesquisa);
//Closing the Query
QueryPesquisa.Close;
//Closing the Connection
oSM.FDConn.Close;
end;`
Basically, the _DataSet is only receiving a JSON List here: _DataSet := TFDJSONDataSetsReader.GetListValueByName( _DataSetJSON, sXMLResumo );, and then open it to access the data in it.
I'm new with PL/SQL I have a assignment where I need to make a function. The assignment is as follows:
> "Create a function 'afdeling_van:'
> - this function accepts a medewerkernummer(employee number) as argument
> - give the afdelingnummer(department number) from the medewerkernummer(employee number) back"
So I need to create a function with a parameter that returns a number. After that I probably need to add some code to make it return a medewerker(employee) number back.
I got pretty stuck with this one as I am really new to PL/SQL. What I do have at the moment is this:
declare
procedure afdeling_van(p_persoon in medewerkers.mnr%type)--table name with column name
is
begin
select med.mnr
from medewerkers med;
where mnr = p_persoon;
end afdeling_van;
begin
afdeling_van(10);
end;
It's not working for me. I have tried different solutions. But as I lack experience and I cannot find the solution or information that I need on the web. I am trying it to ask here
one other thing. I think it's similair to my problem. In the previous assignment I made a procedure instead of a function. The procedure is as follows:
declare
v_medewerker varchar2(50) := ontsla_med();
procedure ontsla_med(p_medewerkers in medewerkers.naam%type)
is
begin
delete from medewerkers
where naam = p_medewerkers;
end ontsla_med;
begin
ontsla_med('');
dbms_output.put_line('Medewerker: ' || v_medewerker || 'verwijdert uit medewerker, inschrijven en uitvoeringen bestand.' );
exception
when no_data_found then
dbms_output.put_line('Medewerker bestaat niet/ is al verwijderd.');
end;
/
this works except for the last dbms_output.put_line. If I remove the output line, then it will work and with the output line, it won't.
I hope my question is not too vague.
Thanks in advance.
You need to create a function instead of a procedure, and you've got a semi-colon in the wrong place. Try something like:
declare
nReturned_value MEDEWERKERS.AFDELINGNUMMER%TYPE;
FUNCTION afdeling_van(p_persoon in medewerkers.mnr%type) --table name with column name
RETURN MEDEWERKERS.AFDELINGNUMMER%TYPE
is
nAFDELINGNUMMER MEDEWERKERS.AFDELINGNUMMER%TYPE;
begin
select med.AFDELINGNUMMER
INTO nAFDELINGNUMMER
from medewerkers med
where mnr = p_persoon;
RETURN nAFDELINGNUMMER ;
end afdeling_van;
begin
nReturned_value := afdeling_van(10);
DBMS_OUTPUT.PUT_LINE('nReturned_value = ' || nReturned_value);
end;
Edit
In your second example, I don't believe that the line v_medewerker varchar2(50) := ontsla_med(); will compile. ontsla_med is a procedure rather than a function, and because procedures don't return anything they can't be used in an assignment statement.
However, v_medewerker is only used in the DBMS_OUTPUT line which you say causes a problem - thus, it may be that the compiler is eliminating the variable because it's not used if the DBMS_OUTPUT line is removed, thus eliminating the problem. Try changing the declaration to v_medwerker varchar2(50) := 'Hello'; and see if that helps.
I am making a MySQLand getting an error I dont know how to fix it. What the function should be doing is asking the user for input, this input should be stored in the corresponding variables and return a variable. But when i try run the function I am getting errors on the lines that store the variables. I am quite new to mySQL so i might be doing something stupid. Any help will be great thank you
This is is the mysql function
create or replace FUNCTION AD_AGENCY_INFO(agency_id in agency_id%type)
RETURN agency_id
DECLARE
v_no_of_runs AD_AGENCY.NO_OF_AD_RUNS%TYPE = '&enter_number_of_Runs';
v_credit_worthy AD_AGENCY.CREDIT_WORTHY%TYPE = '&enter_credit_worthy';
v_available_slots AD_AGENCY.AVAILABLE_SLOTS%TYPE = '&enter_available_slots';
v_status AD_AGENCY.STATUS%TYPE = '&enter_status';
BEGIN
insert into ad_agency values (agency_id, v_no_of_runs, v_credit_worthy,v_available_slots, v_status);
insert into ad (agency_id) values (agency_id);
commit;
RETURN (agency_id));
END AD_AGENCY_INFO;
The error that i am getting is the following and is the same for lines 7,8,9
Error(6,45): PLS-00103: Encountered the symbol "=" when expecting one of the following: := ( ; not null range default character The symbol ":= was inserted before "=" to continue.
Try changing your declare statement(s) to be
v_no_of_runs AD_AGENCY.NO_OF_AD_RUNS%TYPE := '&enter_number_of_Runs'
Is there a case insensitive Replace for MySQL?
I'm trying to replace a user's old username with their new one within a paragraph text.
$targetuserold = "#".$mynewusername;
$targetusernew = "#".$newusername;
$sql = "
UPDATE timeline
SET message = Replace(message,'".$targetuserold."', '".$targetusernew."')
";
$result = mysql_query($sql);
This is missing the instances where the old username is a different case. Example: replacing "Hank" with "Jack" in all the rows in my database will leave behind instances of "hank".
An easier way that works without any stored function:
SELECT message,
substring(comments,position(lower('".$targetuserold."') in message) ) AS oldval
FROM timeline
WHERE message LIKE '%".$targetuserold."%'
gives you the exact, case sensitive spellings of the username in all messages. As you seem to run that from a PHP script, you could use that to collect the spellings together with the corresponding IDs, and then run a simple REPLACE(message,'".$oldval.",'".$targetusernew."') on that. Or use the above as sub-select:
UPDATE timeline
SET message = REPLACE(
message,
(SELECT substring(comments,position(lower('".$targetuserold."') in message))),
'".$targetusernew."'
)
Works like a charm here.
Credits given to this article, where I got the idea from.
Here it is:
DELIMITER $$
DROP FUNCTION IF EXISTS `replace_ci`$$
CREATE FUNCTION `replace_ci` ( str TEXT,needle CHAR(255),str_rep CHAR(255))
RETURNS TEXT
DETERMINISTIC
BEGIN
DECLARE return_str TEXT DEFAULT '';
DECLARE lower_str TEXT;
DECLARE lower_needle TEXT;
DECLARE pos INT DEFAULT 1;
DECLARE old_pos INT DEFAULT 1;
SELECT lower(str) INTO lower_str;
SELECT lower(needle) INTO lower_needle;
SELECT locate(lower_needle, lower_str, pos) INTO pos;
WHILE pos > 0 DO
SELECT concat(return_str, substr(str, old_pos, pos-old_pos), str_rep) INTO return_str;
SELECT pos + char_length(needle) INTO pos;
SELECT pos INTO old_pos;
SELECT locate(lower_needle, lower_str, pos) INTO pos;
END WHILE;
SELECT concat(return_str, substr(str, old_pos, char_length(str))) INTO return_str;
RETURN return_str;
END$$
DELIMITER ;
Usage:
$sql = "
UPDATE timeline
SET message = replace_ci(message,'".$targetuserold."', '".$targetusernew."')
";
My solution ultimately was that I cannot do a case insensitive Replace.
However, I did find a workaround.
I was trying to have a feature where a user can change their username. The system would then need to update wherever #oldusername was found in all the messages in the database.
The problem was... people wouldn't type other people's usernames in the correct case that it is found in the members table. So when the user would change their username, it wouldn't catch those instances of #oldSeRNAmE because of it not matching the case of the real format of the oldusername.
I don't have permission with my GoDaddy shared server to do this with a customized SQL function, so I had to find a different way.
My solution: Upon inserting new messages into the database, whenever a username is found in the new message, I have an UPDATE statement at that point to replace the username they typed with the correct formatted case that is found in the members table. That way, if that person ever wants to change their username in the future, all the instances of that username in the database will all be the same exact formatted case. Problem solved.