pq: bind message supplies 1 parameters, but prepared statement "" requires 0 - database

I am trying to run a postgres if statement at my golang project, but i met this error, could you help me figure out?
the code is
newDate := "2022-06-22"
query := `
DO $$
DECLARE
new_date date:= $1;
BEGIN
IF EXISTS (SELECT * FROM systemtable WHERE date = new_date) THEN
UPDATE systemtable SET is_latest = TRUE WHERE date = new_date;
ELSE
INSERT INTO systemtable (date, is_latest) VALUES (new_date, TRUE);
END IF;
END$$;`
if _, err := txi.Exec(query, newDate); err != nil {
return err
}
Then the error returned is "pq: bind message supplies 1 parameters, but prepared statement "" requires 0"

Do your job with two separate statements within a transaction. That way you preserve consistency and don't have to perform any business logic on database side.

Related

Reading and writing .accdb file from DataModule in Delphi

I am making a school project which consists of creating a database and reading and writing into it. Within a DataModule I made the database in run time using a TAdoCommand which worked great and now I need to read and write into it. I placed some test data into the database using access but it is unable to read the database.
DataModule Here is a picture of the datamodule in design. I have created a connection, query, datasource and table which are all linked together. The TAdoCommand was used to make the database. The SQL command in the query is "SELECT Username,Password
FROM Users"
I then have a Login form in which I hope to use it to read the Users table with the database to check if the user exists in the database.
procedure TLoginFrm.LoginBtnClick(Sender: TObject);
var Username, Password : String;
i, NoOfRecords : Integer;
IsMatch : Boolean;
begin
NoOfRecords := modFile.adoDataSet.RecordCount;
if NoOfRecords = 0 then
begin
NewUserFrm.Show;
Application.Messagebox('There are currently no users. Please create new user.','Error');
UsernameBox.Text := '';
PasswordBox.Text := '';
end
else
begin
IsMatch := False;
modFile.adoDataSet.First;
Username := modFile.adoDataSet.FieldByName('Username').AsString;
Password := modFile.adoDataSet.FieldByName('Password').AsString;
for i := 1 to NoOfRecords do
begin
if (Username = UsernameBox.Text) and (Password = PasswordBox.Text) then
begin
LoginFrm.Hide;
CurrentUser := Username;
MainMenuFrm.Show;
IsMatch := True;
end
else
begin
modFile.adoDataSet.Next;
Username := modFile.adoDataSet.FieldByName('Username').AsString;
Password := modFile.adoDataSet.FieldByName('Password').AsString;
end;
end;//End of for loop
if not IsMatch then
begin
Application.MessageBox('Incorrect username or password. Try again.','Error');
UsernameBox.Text := '';
PasswordBox.Text := '';
LoginBtn.SetFocus;
end;
end;//End of parent Else
end;
When I put in test data using Access, it returns the message box "Incorrect username or password. Try again". So it recognises that there are more than 0 reccords in the table however it cannot read the actual data. Where did I go wrong?
You're For loop isn't working for you here. Instead you need to iterate your dataset this way:
else
begin
isMatch := false;
modFile.AdoDataset.first;
while not modFile.AdoDataset.eof do
begin
Username := modFile.AdoDataset.fieldbyname('Username').asstring;
Password := modFile.AdoDataset.fieldbyname('Password').asstring;
if (uppercase(Username) = uppercase(UsernameBox.text)) and (uppercase(Password) = uppercase(PasswordBox.text)) then
begin
IsMatch := True;
LoginFrm.Hide;
CurrentUser := Username;
MainForm.Show;
Exit; // no need to continue on once you have a match
end;
modFile.AdoDataset.next;
end;
end
else ...
You could also skip using a loop altogether and just use a locate
else
begin
isMatch := modFile.AdoDataset.Locate('Username;Password', VarArrayOf[UsernameBox.text, PasswordBox.text], [loCaseInsensitive]);// remove loCaseInsensitive if you prefer case sensitivity
if isMatch then
begin
CurrentUser := UsernameBox.text;
Loginfrm.Hide;
MainForm.Show;
end;
end;

Getting database field value not working

I am using the ado connection a adoquery and dsr and adotable
I have a database with bookingnumbers as a field in the table client.
I would like to get the last bookingnumber in the field and store it in a variable.
The booking number is saved as text on access.
So far I have:
Var
sNum : string;
....
sNum := Datamodule1.tblClient['BookingNumber'].Last;
but it is not working.
please help?
It is not a good idea to try and find the maximum value of a field by navigating the dataset, especially if the dataset isn't necessarily ordered by the field in question. Try something like this instead:
function TForm1.GetMaxBookingNumber : Integer;
var
Q : TAdoQuery;
begin
Q := TAdoQuery.Create(Nil);
Q.Connection := DataModule1.AdoConnection1; // or whatever the name of your connection is
try
Q.SQL.Text := 'SELECT MAX(BookingNumber) FROM CLIENT';
Q.Open;
// the `not IsNull` in the following allows for the table being empty
if not Q.Fields[0].IsNull then
Result := Q.Fields[0].AsInteger
else
Result := -1;
finally
Q.Free;
end;
end;
So here is what i wanted :
var
sNum : string;
procedure button click
begin
with Datamodule1 do
begin
qryClient.SQL.Add('SELECT BookingNumber FROM Client');
qryClient.Open;
qryClient.last;
sNum := qryClient['BookingNumber'];
end;
end;

Oracle Issue with retrieving single row when multiple rows returned

I have a problem that i've spent about 3 days on.
I have a table(CDKEY) with 6 columns: CDKEYSEQ, Userseq,Banned, Communityseq, cdkey, Email.
Banned is always 0 (at this point), Userseq is NULL unless someone logged on/registered with the cdkey and email is NULL until the cdkey is registered.
Basically Userseq doesn't get filled in until a user logs in. So there will always be an email value before a user sequence value.
NOW The issue:
I'm trying to create a stored procedure that gets called when someone wants a cdkey (which they provide an email for).
The procedure first checks a table called community to make sure the Community exists.
Then if the Community exists The procedure is supposed to check the CDKEY Table for a key that has the correct community sequence AND AlSO has a NULL Value for both USERSEQ and EMAIL.
Obviously using just a select query doesnt work because there are multiple rows that are returned that match those conditions.
I tried using cursors, which got me a little further.
The problem with the cursors is that when I had two conditions after the WHERE clause, it didnt return anything.
Here is my current Procedure Code:
create or replace PROCEDURE KEYREGISTRATION(
PRODUCT_IN IN VARCHAR2 ,
in_CPUID IN LONG ,
in_MACID IN LONG ,
in_MACID2 IN LONG ,
in_HDID IN LONG ,
in_PCCores IN LONG ,
in_PCName IN VARCHAR2 ,
in_Email IN VARCHAR2 ,
out_cdkey OUT VARCHAR2 ,
returncode OUT NUMBER )
AS
CodeSuccess CONSTANT NUMBER := 0;
CoreError CONSTANT NUMBER := 2;
CodeAlreadyExists CONSTANT NUMBER := 3;
CodeBadProduct CONSTANT NUMBER := 4;
new_cdkey VARCHAR2(50);
old_cdkey VARCHAR2(50);
acommunitySeq NUMBER;
BEGIN
acommunitySeq := 0;
new_cdkey := '';
old_cdkey := '';
SELECT COMMUNITYSEQ INTO acommunityseq FROM COMMUNITY WHERE NAME = PRODUCT_IN;
returncode := CodeSuccess;
/*EXCEPTION
WHEN NO_DATA_FOUND THEN
returncode := CodeBadProduct; */
IF returncode = CodeSuccess THEN
BEGIN
SELECT CDKEY INTO old_cdkey FROM CDKEY WHERE EMAIL = in_email;
returncode := CodeBadProduct;
out_cdkey := old_cdkey;
RETURN;
EXCEPTION
WHEN NO_DATA_FOUND THEN
returncode := CodeSuccess;
END;
END IF;
IF returncode = CodeSuccess THEN
/*SELECT CDKEY into new_cdkey FROM CDKEY WHERE EMAIL = NULL AND COMMUNITYSEQ = acommunityseq; */
DECLARE
CURSOR c1
IS
SELECT CDKEY FROM CDKEY WHERE COMMUNITYSEQ = acommunityseq AND EMAIL = NULL;
BEGIN
OPEN c1;
FETCH c1 INTO new_cdkey;
IF ( c1%notfound ) THEN
returncode := CoreError;
END IF;
UPDATE cdkey SET EMAIL = in_email WHERE CDKEY = new_cdkey;
INSERT INTO user_hw VALUES( EMAIL = in_email, CPUID = in_cpuid,
MACID = in_macid, MACID2 = in_macid2, CPUCORES = in_pccores, PCNAME = in_pcname;
out_cdkey := new_cdkey;
returncode := CodeSuccess;
COMMIT;
END;
ELSE
returncode := CoreError;
ROLLBACK;
END IF;
END KEYREGISTRATION;
You think a query will not work - and your reasoning is "because a query will return too many rows." That is incorrect. Add a WHERE clause (or add to the filters you already have), with the condition ROWNUM = 1 - this will return the first row that meets all the other conditions, the processing will end, and you will get just this row and nothing else.

Multiple parameterized Delphi SQL updates within a transaction

I am trying to update two different SQL tables in the same loop using parameterized queries in Delphi XE8. I also want to wrap the whole thing in a transaction, so that if anything in the loop fails, neither table gets updated.
I don't really know what I'm doing, would appreciate some help.
The code below is a simplified version of what I'm trying to achieve, and my best guess as to how to go about it. But I'm not really sure of it at all, particularly the use of two datasets connected to the 'SQL connection' component.
SQL_transaction.TransactionID :=1;
SQL_transaction.IsolationLevel:=xilREADCOMMITTED;
SQL_connection.BeginTransaction;
Try
{ Create connections }
SQL_dataset1 :=TSQLDataSet.Create(nil);
SQL_dataset1.SQLConnection:=SQL_connection;
SQL_dataset2 :=TSQLDataSet.Create(nil);
SQL_dataset2.SQLConnection:=SQL_connection;
{ Create queries }
SQL_dataset1.CommandType:=ctQuery;
SQL_dataset1.CommandText:={ some parameterized query updating table A }
SQL_dataset2.CommandType:=ctQuery;
SQL_dataset2.CommandText:={ some parameterized query updating table B }
{ Populate parameters and execute }
For I:=0 to whatever do
begin
SQL_dataset1.ParamByName('Table A Field 1').AsString:='Value';
SQL_dataset1.ExecSQL;
SQL_dataset2.ParamByName('Table B Field 1').AsString:='Value';
SQL_dataset2.ExecSQL;
end;
SQL_connection.Commit(SQL_transaction);
except
SQL_connection.Rollback(SQL_transaction);
end;
I am using Delphi XE8, and the database can be either SQL server or SQLite.
The logic of your transaction handling is correct (except the missing exception re-raise mentioned by #whosrdaddy). What is wrong are missing try..finally blocks for your dataset instances. Except that you should stop using TSQLConnection deprecated methods that are using the TTransactinDesc record (always check the compiler warnings when you're building your app.). And you can also switch to TSQLQuery component. Try something like this instead:
var
I: Integer;
Query1: TSQLQuery;
Query2: TSQLQuery;
Connection: TSQLConnection;
Transaction: TDBXTransaction;
begin
...
Query1 := TSQLQuery.Create(nil);
try
Query1.SQLConnection := Connection;
Query1.SQL.Text := '...';
Query2 := TSQLQuery.Create(nil);
try
Query2.SQLConnection := Connection;
Query2.SQL.Text := '...';
Transaction := Connection.BeginTransaction;
try
// fill params here and execute the commands
for I := 0 to 42 to
begin
Query1.ExecSQL;
Query2.ExecSQL;
end;
// commit if everything went right
Connection.CommitFreeAndNil(Transaction);
except
// rollback at failure, and re-raise the exception
Connection.RollbackFreeAndNil(Transaction);
raise;
end;
finally
Query2.Free;
end;
finally
Query1.Free;
end;
end;
I prefer try finally over try except
here's how to make it work in a try finally block
var
a_Error: boolean;
begin
a_Error := True;//set in error state...
SQL_dataset1 := nil;
SQL_dataset2 := nil;
SQL_transaction.TransactionID :=1;
SQL_transaction.IsolationLevel:=xilREADCOMMITTED;
SQL_connection.BeginTransaction;
Try
{ Create connections }
SQL_dataset1 :=TSQLDataSet.Create(nil);
SQL_dataset1.SQLConnection:=SQL_connection;
SQL_dataset2 :=TSQLDataSet.Create(nil);
SQL_dataset2.SQLConnection:=SQL_connection;
{ Create queries }
SQL_dataset1.CommandType:=ctQuery;
SQL_dataset1.CommandText:={ some parameterized query updating table A }
SQL_dataset2.CommandType:=ctQuery;
SQL_dataset2.CommandText:={ some parameterized query updating table B }
{ Populate parameters and execute }
For I:=0 to whatever do
begin
SQL_dataset1.ParamByName('Table A Field 1').AsString:='Value';
SQL_dataset1.ExecSQL;
SQL_dataset2.ParamByName('Table B Field 1').AsString:='Value';
SQL_dataset2.ExecSQL;
end;
a_Error := False;//if you don't get here you had a problem
finally
if a_Error then
SQL_connection.Rollback(SQL_transaction)
else
SQL_connection.Commit(SQL_transaction);
SQL_dataset1.Free;
SQL_dataset2.Free;
end;
end;
I added some code on how Try Finally works with init objects to nil
TMyObject = class(TObject)
Name: string;
end;
procedure TForm11.Button1Click(Sender: TObject);
var
a_MyObject1, a_MyObject2: TMyObject;
begin
a_MyObject1 := nil;
a_MyObject2 := nil;
try
a_MyObject1 := TMyObject.Create;
a_MyObject1.Name := 'Object1';
if Sender = Button1 then
raise exception.Create('Object 2 not created');
ShowMessage('We will not see this');
a_MyObject2 := TMyObject.Create;
a_MyObject2.Name := 'Object2';
finally
a_MyObject2.Free;
ShowMessage('We will see this even though we called a_MyObject2.free on a nil object');
a_MyObject1.Free;
end;
end;

Login display error message

I need my program to log a user in from a database. This entails a diver number (like a username) and a password which is already in the database. Unfortunately, I don't know SQL right now and would rather use a technique similar to the one I've done here. I get an error message in run time that says: adotblDiversInfo: Cannot perform this operation on a closed dataset. Thank you so much for your help in advance (:
This is my code:
procedure TfrmHomeScreen.btnLogInClick(Sender: TObject);
var
iDiverNumber : Integer;
sPassword, sKnownPassword : String;
bFlagDiverNumber, bFlagPassword, Result : Boolean;
begin
iDiverNumber := StrToInt(ledDiverNumber.Text);
sPassword := ledPassword.Text;
with frmDM do
adotblDiversInfo.Filtered := False;
frmDM.adotblDiversInfo.Filter := 'Diver Number' + IntToStr(iDiverNumber);
frmDM.adotblDiversInfo.Filtered := True;
if frmDM.adotblDiversInfo.RecordCount = 0 then
ShowMessage(IntToStr(iDiverNumber) + ' cannot be found')
else
begin
sKnownPassword := frmDM.adotblDiversInfo['Password'];
if sKnownPassword = sPassword then
ShowMessage('Login successful')
else
ShowMessage('Incorrect password. Please try again');
end;
end;
The error you're getting is because you've forgotten to open the dataset before attempting to access it. Use frmDM.adoTblDiversInfo.Open; or frmDM.adoTblDiversInfo.Active := True; to do so before trying to use the table.
Your code could be much simpler (and faster) if you change it somewhat. Instead of filtering the entire dataset, simply see if you can Locate the proper record.
procedure TfrmHomeScreen.btnLogInClick(Sender: TObject);
var
iDiverNumber : Integer;
begin
if not frmDM.adoTblDiversInfo.Active then
frmDM.adoTblDiversInfo.Open;
iDiverNumber := StrToInt(ledDiverNumber.Text);
sPassword := ledPassword.Text;
if frmDM.adoTblDiversInfo.Locate('Diver Number', iDiverNumber, []) the
begin
if frmDM.adoTblDiversInfo['Password'] = ledPassword.Text then
ShowMessage('Login successful')
else
ShowMessage('Invalid password. Please try again.');
end
else
ShowMessage(ledDiverNumber.Text);
end;

Resources