Oracle checking existence before deletion in a trigger - oracle

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.

Related

Order of firing before insert triggers in Oracle [duplicate]

Below are my table structures :
Table -Customer
CustomerID Blacklisted Customer Name
101 Y ABC
102 Y DEF
Table -Blacklist
CustomerID BlacklistID Customer Name
101 1011 ABC
102 1012 DEF
Table -Reason
BlacklistID ReasonID Reason Code
1012 02 Rcode2
Main table "Customer" is to store customer information.There is a trigger after update on table "Customer" to insert record in table "Blacklist" if somebody updates the blacklisted as Y in customer table.
We consider the customer as blacklisted if ,
Blacklisted column in Customer table as value 'Y' and.
There are records present for customer in Blacklist and Reason table
Now my requirement is to blacklist the customer from backend.For this i am writing stored procedure with below queries:
Update customer set blacklisted ='Y' where customerid='102';
select BlacklistID into var_id from blacklist where customerid='102';
Insert into reason(BlacklistID,ReasonID,ReasonCode)values(var_ id,111,'RCODE1');
Now to insert entry in Reason table(step-3),i need BlacklistID which is a foreign key and i will get the value of BlacklistID once the trigger on customer table gets exceuted.So my confusion is, can i assume the trigger on update of 'Customer' table will always get excuted before the cntrl reaches my INSERT INTO reason(step-3) statement. Please suggest.
If you need to be certain about the order of trigger execution, you can specify this order when creating the trigger.
This is done with the FOLLOWS ... and PRECEEDS ... options of the create trigger statement:
More details in the manual: http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/create_trigger.htm#CJAEJAFB
FOLLOWS | PRECEDES
Specifies the relative firing of triggers that have the same timing point. It is especially useful when creating crossedition triggers, which must fire in a specific order to achieve their purpose.
Use FOLLOWS to indicate that the trigger being created must fire after the specified triggers. You can specify FOLLOWS for a conventional trigger or for a forward crossedition trigger.
Use PRECEDES to indicate that the trigger being created must fire before the specified triggers. You can specify PRECEDES only for a reverse crossedition trigger.
Yes. Triggers are part of the statement. Although you cannot be fully certain *) of the order in which multiple triggers in the same statement are executed, you can be certain that they al are done when the statement itself is done. So by the time of step 2, all update triggers of step one have fired.
*) Actually, the default order is:
Statement level before triggers
Row level before triggers
Row level after triggers
Statement level after triggers
But if you have, say, two row level before trigger, by default you cannot be certain in which order those two are executed. But I learned from the comments that in Oracle 11, you can actually specify the order to cover even those cases.
I don't see a need for all these tables, and therefore no need for a trigger.
Why do you not just use a blacklist reason code in the customer table? The purpose of the blacklist table is unclear as it seems to add no data and just repeats data from the customer table with an additional id column?
Or if you need multiple reason codes then just use a blacklist_reason table that references the customer id and a reason code -- I don't think you even need the blacklisted column in the customer table.

Delete records in an efficient way

I've two tables say STOCK and ITEM. We have a query to delete some records from ITEM table,
delete from ITEM where item_id not in(select itemId from STOCK)
And now I've more than 15,00,000 records to delete, the query was taking much time to do the operation.
When I searched, I found some efficient ways to do this action.
One way:
CREATE TABLE ITEM_TEMP AS
SELECT * FROM ITEM WHERE item_id in(select itemId from STOCK) ;
TRUNCATE TABLE ITEM;
INSERT /+ APPEND +/ INTO ITEM SELECT * FROM ITEM_TEMP;
DROP TABLE ITEM_TEMP;
Secondly instead of truncating just drop the ITEM and then rename the ITEM_TEMP to ITEM. But in this case I've to re create all the indexes.
Can anyone please suggest which one of the above is more efficient, as I could not check this in Production.
I think the correct approach depends on your environment, here.
If you have privileges on the table that must not be affected, or at least must be restored if you drop the table, then the INSERT /*+ APPEND */ may simply be more reliable. Triggers, similarly, or foreign keys, or any objects that will be automatically dropped when the base table is dropped (foreign keys complicate the truncate, of course).
I would usually go for the truncate and insert method based on that. don't worry about the presence on indexes on the table -- a direct path insert is very efficient at building them.
However, if you have a simple table without dependent objects then there's nothing wrong with the drop-and-rename approach.
I also would not rule out just running multiple deletes of a limited number of rows, especially if this is in a production environment.
Best way from used space (and high watermark) and performance is to drop table and then rename ITEM_TEMP table. But, as you mentioned, after that you need to recreate indexes (also grants, triggers, constraints). Also all depending objects will be invalidated.
Some times I try to delete by portions:
begin
loop
delete from ITEM where item_id not in(select itemId from STOCK) and rownum < 10000;
exit when SQL%ROWCOUNT = 0;
commit;
end loop;
end;
Since you have very high number of rows, it better use partition table , may be List partition on "itemId". Then you can easily drop a partition.
Also if your application could run faster. This need design change but it will give benefit in long run.

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

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?

Is it possible to compare other tables within a trigger?

I have a database with tables that are chained together with foreign keys, and the last one in the chain also has a foreign key to itself. I want to delete them with cascade on, exapt for the last one in the chain. That one should be set null, unless it's parent record has a certain value. I figured i would do that with a trigger: whenever the last table updated, if the foreign key to itself had been set to null, check the field in the parent record, and if it is the value "default", delete the record in the last table.
However, I haven't found any help online indicting that comparing a parent record in another table.
Is this possible?
In general, a row-level trigger on table A cannot query table A. Doing so would generally raise a mutating table exception (ORA-04091). So a trigger is generally not the right solution.
Presumably, you have some sort of API (i.e. a stored procedure) to delete records from the parent table. That API should query this last table before issuing the DELETE against the parent table. It should take care of updating the last table in the chain as well as deleting the data from the parent table.
If you really wanted a trigger-based solution, life would get substantially more complicated. You could work around the mutating table exception by
Creating a package with a collection of primary keys from the parent table
Creating a before statement trigger that initializes this collection
Creating a row-level trigger that populates the collection with the primary keys that were modified by the SQL statement
Creating an after statement trigger that iterates over the collection and issues whatever DML is necessary (unlike row-level triggers, statement-level triggers on table A can query or modify table A).
If you're using 11g, you can simplify this a bit with a compound trigger with before statement, after row, and after statement sections. But you've still got a number of moving pieces to try to coordinate.
AFAIK you won't be able to really delete the record in the last table (mutating table problem), but you could update a status field indicating the record has been logically deleted (untested):
create or replace trigger last_table_trig
before update on last_table
for each row
declare
l_parentField varchar2(100);
begin
if :new.self_ref_fk is null then
select p.parent_field into l_parentField from parent_table p
where p.pk = :new.parent_fk;
if l_parentField = 'default' then
:new.status := 'DELETED';
end if;
end if;
end;

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

Resources