running Procedure by Trigger on new data in OracleDB - stored-procedures

I wrote a procedure to update a table by deriving its data from four other tables. The procedure itself works fine, but I need the table to be up to date all the time, so I call the procedure from triggers which fire, whenever one of the four tables ist altered.
This too works fine except for one detail. The procedure derives the data from the tables BEFORE the changes, so the update/delete/insert, which fired the trigger.
The trigger is rowlevel after u/d/i but even changing it to after statement did not help.
Not using a trigger is not really an option, because of the need being in sync. Changing it to a materialized view seems to be a good idea, but neede calculations are disallowed in those and nonmaterialized views are too slow, which is why I want to change it from a nonmaterialized view to a table.
Handing all information about what was changed in which way over to the procedure would be possible, but it would make the procedure so much more complicated, that I refrain from writing this code.
Is there any way to derive my table based on the new values directly triggered by a change in the four tables?
Regards
Ramin

Related

How to refence a DBGrid from another form

I have a mainForm with a DBGrid and I have a second form with a CheckListBox that shows all of the DBGrid columns for the user to choose. I need to reference in Form2 the DBGrid that I have in MainForm.
I would like this second form to handle all of the procedures connected to the dbdgrid columns , so that I can reuse it easily.
That was the idea, but I dod'nt find the way to pass the DBGrid reference.
Is it possible ?
Answering the question you asked, on your Form2, define a property
TForm2
[...]
private
FGrid : TDBGrid
public
property Grid : TDBGrid read FGrid write FGrid;
Then, after you've created an instance of TForm2, just do
Form2.Grid := MainForm.DBGrid1;
Then, on Form2, you can do anything valid you like to change Grid and the changes will be made to MainForm.DBGrid1.
Is it possible?
The question should rather be Is there a better way to achieve what I want?
Would it be maintainable if Form2 worked basically with a control from a different form? What if other forms would also need to hold references to components on other forms?
How hard would it be in a year to find a bug if controls are used over different forms?
Would such a solution match to the SOLID principles?
Answering these questions should help you to look for a different approach.
You should consider to separate UI and business logic. A TDBGrid seems to be a convenient way to get data from a database into your application but it violates the Single Responsibility Principle since it loads and displays data at the same time. Don't use it as a basic data provider inside your application. Perform the SQL queries from a deeper UI independant layer of your software. Store the results in containers and display them in all the ways you want in your different forms.

'cursor is in BOF position' error

On before delete of the table I have :
procedure TData_Module.MyTableBeforeDelete(DataSet: TDataSet);
begin
if MessageDlg('Are you sure you want to delete the record written by '+
QuotedStr(MyTable.FieldByName('written_by_who').AsString),mtConfirmation,[mbYes,mbNo],0) = mrYes then
MyTable.Delete;
end;
However, on trying I get the error :
...cursor is in BOF position
What goes ? Database is Accuracer.
edit : The entire code above :)
As I said in comments, it sounded from your q as if you are calling Delete within the dataset's BeforeDelete event. Don't do that, because the BeforeDelete event occurs while the dataset is already in the process of deleting the record. So you deleting it is pulling the rug out from under the dataset's built-in code.
So, if you want the user's confirmation, get it before calling Delete, not inside the BeforeDelete event. Btw, the standard TDBNavigator has a ConfirmDelete property associated with its integrated DeleteRecord button which will do exactly that, i.e. pop up a confirmation prompt to the user.
More generally, people frequently create problems for themselves by trying to perform actions within a dataset event which are inappropriate to the state the dataset is in when the event code is called. The TDataSet has a very carefully designed flow of logic to its operations, and t is rather too easy for the inexperienced on unwary programmer to subvert its logic by doing something in a TDataSet event that shouldn't be there: One example is calling Delete inside an event. Another, frequently encountered ,one is executing code which moves the dataset's cursor inside an event that's called as result of moving the cursor, like the AfterScroll event, f.i.
There's no easy rule to say what you should and shouldn't do inside a dataset event, but generally speaking, any action you take which attempts to change the dataset's State property (see TDataSetState in the OLH) should be your prime suspect when you find that something unexpected is happening.
I suppose another general rule, with exceptions that are usually clear from the descriptions of events in the OLH, is that dataset events should generally be used for reacting to the event by doing something to some other object, like updating a status panel, rather than doing something to the dataset. F.i. the AfterScroll event is very useful for reacting to changes when the dataset's cursor is moved. The exceptions are events like BeforePost which are intended to allow you the opportunity to do things (like validate changes to the dataset's fields).
Btw, you can call Abort from inside the BeforeDelete event and it will prevent the deletion. However, imo, it's cleaner and tidier to check whether a deletion should go ahead and plan and code for its consequences before going ahead rather than have to back out part way through. So, with respect, I disagree with the other answer. The time to decide whether to cross a bridge is before you start, not when you're already part way across it. Ymmv, of course.
The question is in the right place. To ask before the delete is wrong because it forces you to ask the question every time you call Delete. A more correct answer is to abort the delete here, in the OnBeforeDelete, if the user doesn't want to delete.

Dbgrid - automatic post to database

I have a form with a query, a dataset, an editable dbgrid and an updatesql component. When I need to save the changes made in the dbgrid, I call this procedure:
procedure TEditCardDetailForm.SaveChanges;
begin
Database1.StartTransaction;
try
Query2.ApplyUpdates;
Database1.Commit;
except
Database1.Rollback;
raise;
end;
Query2.CommitUpdates;
end;
However I want the changes to be applied automatically to the database when I press Enter or go to another row after editing a cell in the dbgrid - the way it is done when I use a TTable component. Is there a way to do it?
If I understand it right (please correct me if not) you have a TQuery with CachedUpdates set to true, but want it to behave as if it would not be using cached but immediate updates. If that is the case the way you have set up your TQuery contradicts your desired behaviour. Cached updates are to be "held" on the client side until you decide to manually post them to the database by using ApplyUpdates.
In case you can set CachedUpdates to false, you only need to do following:
Link the TUpdateSQL to the TQuery via its UpdateObject property.
Write the insert, update and delete statements and assign them to the InsertSQL, ModifySQL and DeleteSQL properties of the TUpdateSQL.
I guess you already have done these two things, so putting CachedUpdates to false should do it.
You can find more information on Cached Updates for Delphi 5 here.
HTH
You have two scenarios to handle here:
save changes when changing the grid row
save changes when changing the grid column
The first one is easy to implement by calling your SaveChanges procedure in the AfterPost event of the underlying dataset (query2, a TClientDataSet?).
For the second one you only have to call query2.Post after the column has changed. This can be done in the OnDataChange event of the datasource. Make sure to check for Field <> nil and the dataset being in insert or edit mode before calling post.

Subclass a table and a big trouble in Delphi

alt text http://img29.imageshack.us/img29/825/simplemodel.jpg
How you can see above, it's a kind of subclass a table in a RDBMS (in this case, my favorite: MySQL) so I handle it with Visual Subclassing a base form for tb_order_base with validating field data, etc.
This way, I'm free of repeating code and some other bothering problems, well, it seems a true OO aproach. But ...
Now I got a Big problem with subclassed form i.e. tb_order_service with a master/detail approach, when I post the tb_order_base dataset, instead of Delphi first post it and get the PK ID from RDBMS and then post the TB_ORDER_PRODUCT with id filled, it does the opposed, posting the detail tb_order_product dataset first then master tb_order_base, so I get a big foreing key constraint error.
Does anyone has any idea to how to by-pass this amazing problem?
I have asked it before, but with few details in The Master/Detail Behavior
One thing I have done in such a condition was to break away from doing DBMS direct calls, and to instead use a memory dataset (or client dataset) for the form. When the user presses the save button, I would validate my edits and return any errors. If no errors then I would begin a database transaction, commit the master record (and then read the master record key back if its an insert), then run thru each table commiting the child records, followed by a commit of the transaction (or rollback if there were any problems saving child data).
I also stay away from using data-aware components. They work great for simple utility type programs, but when you start creating a complex system using them you will find little gotchas along the way that are easily solved by using a standard edit and a function to push/pull data to/from the database. The only exception I generally make would be for grids, but I only use the grid for selection...the actual editing is done using non data-aware components.

How long does a TDataset bookmark remain valid?

I have code like below in a project I'm working.
procedure TForm.EditBtnClick(Sender:TObject);
begin
// Mark is form variable. It's private
Mark = cdsMain.GetBookmark;
// blabalbal
.
.
.
end;
procedure TForm.OkBtnClick(Sender:TObject);
var
mistakes: Integer;
begin
//Validation stuff and transaction control
//removed to not clutter the code
If cdsMain.ChangeCount <> 0 then
mistakes := cdsMain.AppyUpdates(-1);
cdsMain.Refresh;
try
cdsMain.GotoBookmark(Mark);
// Yes, I know I would have to call FreeBookmark
// but I'm just reproducing
except
cdsMain.First;
end;
end;
Personally, I do not use bookmarks much — except to reposition a dataset where I only moved the cursor position (to create a listing, fill a string list, etc). If I Refresh, update (especially when a filter can make the record invisible), refetch (Close/Open) or any operation that modifies the data in the dataset, I don't use bookmarks. I prefer to Locate on the primary key (using a TClientDataset, of course) or requery modifying the parameters.
Until when is a bookmark valid? Until a Refresh? Until a Close/Open is done to refetch data? Where does the safe zone end?
Consider in the answer I'm using TClientDataset with a TSQLQuery (DbExpress).
Like both c0rwin and skamradt already mention: the bookmark behaviour depends on the TDataSet descendant you use.
In general, bookmarks become invalid during:
close/open
refresh (on datasets that support it)
data changes (sometimes only deletions)
I know 1. and 2. can invalidate your bookmarks in TClientDataSets. I am almost sure that for TClientDataSets it does not matter which underlying provider is used (TSQLQuery, TIBQuery, etc).
The only way to make sure what works and what not is testing it.
Which means you are totally right in not using them: bookmarks have an intrinsic chance of being unreliable.
To be on the safe side, always call BookmarkValid before going to a bookmark.
Personally I rarely ever use bookmarks. I instead use the id of the record I am viewing and perform a locate on it once the refresh is complete. If I need to iterate over all of the records in the set, I do that using a clone of the tClientDataset (which gets its own cursor).
It is my understanding is that the implementation of the bookmark is up to the vendor of the tDataset descendant and can vary between implementations. In my very simple dataset (tBinData), I implemented bookmarks as the physical record number so it would persist between refreshes as long as the record was not deleted. I can not speak this true for all implementations.
TDataSet implements virtual bookmark methods. While these methods ensure that any dataset object derived from TDataSet returns a value if a bookmark method is called, the return values are merely defaults that do not keep track of the current location. Descendants of TDataSet, such as TBDEDataSet, reimplement the bookmark methods to return meaningful values as described in the following list:
BookmarkValid, for determining if a specified bookmark is in use.
CompareBookmarks, to test two bookmarks to see if they are the same.
GetBookmark, to allocate a bookmark for your current position in the dataset.
GotoBookmark, to return to a bookmark previously created by GetBookmark
FreeBookmark, to free a bookmark previously allocated by GetBookmark.
Get it from here

Resources