I have two tables TEST_DEPT and TEST_DEPT$AUDIT in Oracle 11g.
I have made an insert update delete after trigger. However, the last column in the Audit table is called REASON. The idea is after making the update for example, Oracle to prompt the user to fill in the REASON column.
This is what the after trigger looks like:
CREATE OR REPLACE TRIGGER auditDEPTAR AFTER
INSERT OR UPDATE OR DELETE ON TEST_DEPT FOR EACH ROW
DECLARE
test TEST_DEPT$audit%ROWTYPE;
BEGIN
IF inserting THEN test.change_type := 'INSERT';
ELSIF updating THEN test.change_type := 'UPDATE';
ELSE test.change_type := 'DELETE';
END IF;
test.changed_by := user;
test.changed_time := sysdate;
CASE test.change_type
WHEN 'INSERT' THEN
test.DEPTNO := :new.DEPTNO;
test.DNAME := :new.DNAME;
test.LOC := :new.LOC;
ELSE
test.DEPTNO := :old.DEPTNO;
test.DNAME := :old.DNAME;
test.LOC := :old.LOC;
END CASE;
INSERT INTO TEST_DEPT$audit VALUES test;
END;
Can it be done or should I use function or something else for workaround?
Please, help!
Related
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;
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.
The TListBox called lboMtrlList is populated with records from the database. The data displays properly when I run the application. When I click on any item in the list, shows the error:
Index out of range (-1)
despite the list not being empty.
Here's the code for populating the lboMtrlList:
procedure TfrmMakeQuote.FormCreate(Sender: TObject);
begin
con := TFDConnection.Create(nil);
query := TFDQuery.Create(con);
con.LoginPrompt := false;
con.Open('DriverID=SQLite;Database=C:\Users\katiee\Documents\Embarcadero\Studio\Projects\ProgramDatabase;');
query.Connection := con;
performQuery;
query.SQL.Text :=
'SELECT [Material Description] FROM MtrlDatabase ORDER BY MtrlID';
try
query.Open;
lboMtrlList.Items.Clear;
while not query.EOF do
begin
lboMtrlList.Items.Add(query.Fields[0].AsString);
query.Next;
end;
finally
query.Close;
end;
//ledtDesc.Height := 81;
//ledtNotes.Height := 51;
end;
I want to be able to double click on an item in the lboMtrlList and move it to another TListBox called lboSelectedMtrl. Here's the code:
procedure TfrmMakeQuote.lboMtrlListDblClick(Sender: TObject);
begin
lboMtrlList.Items.Add(lboSelectedMtrl.Items.Strings[lboSelectedMtrl.ItemIndex]);
end;
I want to be able to double click on an item in the lboMtrlList and move it to another TListBox called lboSelectedMtrl.
Your code is doing the opposite of that. It is trying to move an item from lboSelectedMtrl to lboMtrlList. You are getting the bounds error because there is no item selected in lboSelectedMtrl (lboSelectedMtrl.ItemIndex is -1).
Swap the ListBox variables, and add some error checking:
procedure TfrmMakeQuote.lboMtrlListDblClick(Sender: TObject);
var
Idx: Integer;
begin
Idx := lboMtrlList.ItemIndex;
if Idx <> -1 then
lboSelectedMtrl.Items.Add(lboMtrlList.Items.Strings[Idx]);
end;
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;
I have a small database I'm using dbgo, I have a DBgrid displaying my records, I need to know how to delete a record and refresh the database where the index arrow stays in the same position or at least go to the next? but currently my index arrow jump to start form the beginning each time I refresh !
Just keep and reset Recno
var
I:Integer;
.......
I := Ads.Recno;
Ads.Delete;
Ads.Recno := I;
an example implementation for usage with DBNavigator could be
Procedure DeleteAndKeepRecno(Ads: TCustomAdoDataset);
var
rn: Integer;
begin
rn := Ads.RecNo;
Ads.Delete;
Ads.RecNo := rn;
end;
procedure TForm4.DBNavigator1Click(Sender: TObject; Button: TNavigateBtn);
begin
if Button = nbDelete then
begin
DeleteAndKeepRecno (TCustomAdoDataset(TDBNavigator(Sender).DataSource.DataSet));
Abort;
end;
end;