How to set different ways to delete in oracle, with and without activating a trigger - oracle

I'm using oracle 11g the schema follows as:
TRANSFER(id_transfer, origin_account, destine_account, amount, date, id_replica)
DELETES(id, table, id_replica, date)
Other tables, each have an id_replica as identifier.
I also have a trigger per table, when delete it copies the id_replica deleted in the table DELETES. So if I delete a transfer it first save into DELETES ( id, transfer, id_replica, sysdate), this is for having a record on what I deleted.
The problem is that now I want to make a delete without activating this trigger, I need to have 2 ways of deleting a row, activating the trigger and save what I delete into the table DELETES, and deleting the row without activating this trigger.
Some solutions that came to my mind:
making 2 different procedures/functions to delete a row en each table, (or just one with the trigger the other one can be done as normal).
enabling or disabling the trigger before each delete, this is kind of complicated because its done a lot of times
How do I make a procedure/function to delete a row? Is there other way to do this?

Related

What's a way I can save a trigger "template" in oracle?

Let's say I created a table test_table in development just to test a trigger, this trigger would then be reused in many other tables (future and existing).
So I code the trigger, test it, all good! But at the moment, if I want to replicate it, I will have to copy it from test_table's triggers and edit it.
So if someone deletes the table accidentally, the trigger is gone, and I don't have it saved nowhere else. Or if I just want to delete random test tables in our database, I can't.
What's a recommended way to save a trigger as a "template" in oracle? So I can reuse it in other tables and have it not be dependant of a random test table, or any table.
There are a lot of ways you can keep a copy of your TRIGGER SQLText.
Here's a few examples.
In Version Control:
You can use any of the many version control tools to maintain a versioned history for any code you like, including SQL, PL/SQL, etc. You can rewind time, view differences over time, track changes to the template, even allow concurrent development.
As a Function:
If you want the template to live in the database, you can create a FUNCTION (or PACKAGE)that takes as parameters the target USER and TABLE, and it replaces the USER and TABLE values in its template to generate the SQLTEXT required to create or replace the template TRIGGER on the target TABLE. You can make it EDITIONABLE as needed.
In a Table:
You can always just create a TABLE that holds template TRIGGER SQLText as a CLOB or VARCHAR2. It would need to be somewhere where it isn't likele to be "randomly" deleted, though. You can AUDIT changes to the TABLE's data, to see the template change over time. Oracle has tons of auditing options.
In the logs:
You can just log (all) DDL out. If you ENABLE_DDL_LOGGING, the log xml will have a copy of every DDL statement, categorized, along with when and where it came from.

Creating a Triggers

How do I start a trigger so that this allows nobody to be able to rent a movie if their unpaid balance exceeds 50 dollars?
What you have here is a cross-row table constraint - i.e. you can't just put a single Oracle CONSTRAINT on a column, as these can only look at data within a single row at a time.
Oracle has support for only two cross-row constraint types - uniqueness (e.g. primary keys and unique constraints) and referential integrity (foreign keys).
In your case, you'll have to hand-code the constraint yourself - and with that comes the responsibility to ensure that the constraint is not violated in the presence of multiple sessions, each of which cannot see data inserted/updated by other concurrent sessions (at least, until they commit).
A simplistic approach is to add a trigger that issues a query to count how many records conflict with the new record; but this won't work because the trigger cannot see rows that have been inserted/updated by other sessions but not committed yet; so the trigger will sometimes allow members to rent 6 videos, as long as (for example) they get two cashiers to enter the data in separate terminals.
One way to get around this problem is to put some element of serialization in - e.g. the trigger would first request a lock on the member record (e.g. with a SELECT FOR UPDATE) before it's allowed to check the rentals; that way, if a 2nd session tries to insert rentals, it will wait until the first session does a commit or rollback.
Another way around this problem is to use an aggregating Materialized View, which would be based on a query that is designed to find any rows that fail the test; the expectation is that the MV will be empty, and you put a table constraint on the MV such that if a row was ever to appear in the MV, the constraint would be violated. The effect of this is that any statement that tries to insert rows that violate the constraint will cause a constraint violation when the MV is refreshed.
Writing the query for this based on your design is left as an exercise for the reader :)
If you want to restrict something about your table data then you should have a look at Constraints and not Triggers.
Constraints are ensuring some conditions about your table data. Like your example.
Triggers are fired when some action (i.e. INSERT, UPDATE, DELETE) took place and you can do some work then as a reaction to this action.

oracle and creating history

I am working on a system to track a project's history. There are 3 main tables: projects, tasks, and clients then 3 history tables for each. I have the following trigger on projects table.
CREATE OR REPLACE TRIGGER mySchema.trg_projectHistory
BEFORE UPDATE OR DELETE
ON mySchema.projects REFERENCING NEW AS New OLD AS Old
FOR EACH ROW
declare tmpVersion number;
BEGIN
select myPackage.GETPROJECTVERSION( :OLD.project_ID ) into tmpVersion from dual;
INSERT INTO mySchema.projectHistiry
( project_ID, ..., version )
VALUES
( :OLD.project_ID,
...
tmpVersion
);
EXCEPTION
WHEN OTHERS THEN
-- Consider logging the error and then re-raise
RAISE;
END ;
/
I got three triggers for each of my tables (projects, tasks, clients).
Here is the challenge: Not everything changes at the same time. For example, somebody could just update a certain tasks' cost. In this case, only one trigger fires and I got one insert. I'd like to insert one record into 3 history tables at once even if nothing changed in the projects and clients tables.
Also, what if somebody changes a project's end_date, the cost, and say the picks another client. Now, I have three triggers firing at the same time. Only in this case, I will have one record inserted into my three history tables. (which I want)
If i modify the triggers to do insert into 3 tables for the first example, then I will have 9 inserts when the second example happens.
Not quite sure how to tackle this. any help?
To me it sounds as if you want a transaction-level snapshot of the three tables created whenever you make a change to any of those tables.
Have a row level trigger on each of the three tables that calls a single packaged procedure with the project id and optionally client / task id.
The packaged procedure inserts into all three history tables the relevant project, client and tasks where there isn't already a history record for that key and transaction (ie you don't want duplicates). You got a couple of choices when it comes to the latter. You can use a unique constraint and either a BULK select and insert with FORALL/SAVE EXCEPTIONS, DML error logging (EXCEPTIONS INTO) or a INSERT...SELECT...WHERE NOT EXISTS...
You do need to keep track of your transactions. I'm guessing this is what you were doing with myPackage.GETPROJECTVERSION. The trick here is to only increment versions when you have a new transaction. If, when you get a new version number, you hold it in a pacakge level variable, you can easily tell whether your session has already got a version number or not.
If your session is going to run multiple transaction, you'll need to 'clear' out the session-level version number if it was part of a previous transaction. If you get DBMS_TRANSACTION.LOCAL_TRANSACTION_ID and store that at the package/session level as well, you can determine if you are in a new transaction, or part of the same transaction.
From your description, it looks like you would be capturing the effective and end date for each of the history rows once any of the original rows change.
Eg. Project_hist table would have eff_date and exp_date which has the start and end date for a given project. Project table would just have an effective date. (as it is the active project).
I don't see why you want to insert rows for all three history tables when only one of the table values is updated. You can pretty much get the details as you need (as of a given date) using your current logic. (inserting old row in the history table for the table that has been updated only.).
Alternative answer.
Have a look at Total Recall / Flashback Archive
You can set the retention to 10 years, and use a simple AS OF TIMESTAMP to get the data as of any particular timestamp.
Not sure on performance though. It may be easier to have a daily or weekly retention and then a separate scheduled job that picks out the older versions using the VERSIONS BETWEEN syntax and stores them in your history table.

ORACLE :Are grants removed when an object is dropped?

I currently have 2 schemas, A and B.
B has a table, and A executes selects inserts and updates on it.
In our sql scripts, we have granted permissions to A so it can complete its tasks.
grant select on B.thetable to A
etc,etc
Now, table 'thetable' is dropped and another table is renamed to B at least once a day.
rename someothertable to thetable
After doing this, we get an error when A executes a select on B.thetable.
ORA-00942: table or view does not exist
Is it possible that after executing the drop + rename operations, grants are lost as well?
Do we have to assign permissions once again ?
update
someothertable has no grants.
update2
The daily process that inserts data into 'thetable' executes a commit every N insertions, so were not able to execute any rollback. That's why we use 2 tables.
Thanks in advance
Yes, once you drop the table, the grant is also dropped.
You could try to create a VIEW selecting from thetable and granting SELECT on that.
Your strategy of dropping a table regularly does not sound quite right to me though. Why do you have to do this?
EDIT
There are better ways than dropping the table every day.
Add another column to thetable that states if the row is valid.
Put an index on that column (or extend your existing index that you use to select from that table).
Add another condition to your queries to only consider "valid" rows or create a view to handle that.
When importing data, set the new rows to "new". Once the import is done, you can delete all "valid" rows and set the "new" rows to "valid" in a single transaction.
If the import fails, you can just rollback your transaction.
Perhaps the process that renames the table should also execute a procedure that does your grants for you? You could even get fancy and query the dictionary for existing grants and apply those to the renamed table.
No :
"Oracle Database automatically transfers integrity constraints, indexes, and grants on the old object to the new object."
http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/statements_9019.htm#SQLRF01608
You must have another problem
Another approach would be to use a temporary table for the work you're doing. After all, it sounds like it is just the data is transitory, at least in that table, and you wouldn't keep having to reapply the grants each time you had a new set of data/create the new table

Oracle checking existence before deletion in a trigger

I have analyzed a hibernate generated oracle database and discovered that a delete of a row from a single table will spawn the firing of 1200+ triggers in order to delete the related rows in child tables. The triggers are all auto-generated the same - an automatic delete of a child row without checking for existence first. As it is impossible to predict which child tables will actually have related rows, I think a viable solution to preventing the firing of the cascaded delete down a deeply branched completely empty limb, would be to check for the existence of a related row before attempting to delete. In other dbms', I could simply state " if exists....." before deleting. Is there a comparable way to do this in oracle?
"delete of a row from a single table will spawn the firing of 1200+ triggers"
Are these statement or row level triggers ?
If the latter, they'll only fire if a row is deleted. Say you have a BEFORE DELETE trigger on customers to delete the customers orders, and a BEFORE DELETE trigger on orders to delete order items. If the customer has no orders, and the orders table trigger is a row level trigger, then it will not fire the delete from order items.
"check for the existence of a related row before attempting to delete"
Probably no benefit. In fact it would do more work having a SELECT followed by a DELETE.
Of course the Hibernate logic is broken. The deleting session will only see (and try to delete) committed transactions. If FRED has inserted an order for the customer, but it is not committed, JOHN's delete (through the trigger) won't see it or try to delete it. It will however still 'succeed' and try to delete the parent customer.
If you have actually got your foreign key constraints enabled in the database, Oracle will kick in. It will wait until FRED commits, then reject the delete as it has a child.
If the foreign key constraints aren't in place, you have an order for a non-existent customer. This is why you should have this sort of business logic enforced in the database.
If possible, modify and setup your DB tables appropriately. - Involve a DBA if you have one at your disposal.
You need to use Foreign Key constraints and cascade deletes.
This eliminates the need for triggers, etc...
You can query the special dba_objects table: 
DECLARE
X NUMBER;
BEGIN
SELECT COUNT(*) INTO X FROM DBA_OBJECTS WHERE OBJECT_TYPE = 'TRIGGER' AND OBJECT_NAME = 'YOUR_TRIGGER_NAME_HERE';
IF X = 0 THEN
--Trigger doesn't exist, OK to delete...
END IF;
END;
select * from Tab where Tname = "TABLENAME"
If this query returns any row then the table exists otherwise it doesn't.

Resources