SQL trigger for replicating actions of an ON DELETE CASCADE - sql

I am working on a project that requires creating a trigger that basically replicates the actions of an ON DELETE CASCADE clause of a FOREIGN KEY CONSTRAINT. The parameter of interest is "account_number", referencing relationship is "depositor" and referenced relationship is "account". Basically, when an "account_number" is deleted from "account" table, the trigger is to delete all occurences of that "account_number" in the depositor table ("all" specified since there could be joint accounts with multiple customer_ID's listed in the depositor table with that "account_number".
Seems simple enough and I tried a few approaches that I thought should all work; yet none of them are working - that is, when I delete an "account_number" from the "account" table, the corresponding "account_number"s in the depositor table are not getting deleted (despite not getting any error messages along the way). I tried the trigger funciton a couple ways, with the commented out sections below used in one case and commented out in another case - neither worked. What am I missing?
CREATE OR REPLACE FUNCTION Demirci_07_bankTriggerFunction()
RETURNS TRIGGER
LANGUAGE plpgsql
AS
$$
/*
DECLARE
acct_no_deleted INTEGER;
*/
BEGIN
/*
PERFORM account_number
FROM account
WHERE account_number = OLD.account_number;
DELETE FROM depositor
WHERE account_number = acct_no_deleted;
*/
DELETE FROM depositor
WHERE account_number = OLD.account_number;
RETURN OLD;
END;
$$;
CREATE TRIGGER Demirci_07_bankTrigger
AFTER DELETE ON account
FOR EACH ROW
EXECUTE PROCEDURE Demirci_07_bankTriggerFunction();

The problem is that it is an AFTER DELETE trigger. You have to remove the dependent rows before you delete the referenced row.

Related

Dropping a Table in a PostgreSQL Delete Trigger

I am working on a web application where users create copies of a table.
They choose the name of the original table in a dropdown and the application creates a copy with a random name in the schema copy_tables.
The name of the copy table is inserted into the table config.copy_tables into the column copy_table_name.
There is no way for users to delete the copies. However an admin might manually delete an entry from config.copy_tables.
When that happens I would like to also drop the corresponding table in the schema copy_tables.
You find my attempt below. The copy_tables.OLD.copy_table_name part causes issues and I am not sure how to fix that. Basically I would like to:
drop the table in the schema copy_tables
whose name appeared in the column copy_table_name (config.copy_tables) in the row that was just deleted
CREATE OR REPLACE FUNCTION drop_copy_table()
RETURNS TRIGGER AS
$$
BEGIN
DROP TABLE copy_tables.OLD.copy_table_name;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trigger_delete_copy_table ON config.copy_tables;
CREATE TRIGGER trigger_delete_copy_table
AFTER DELETE ON config.copy_tables
FOR EACH ROW
EXECUTE PROCEDURE drop_copy_table();

This Oracle trigger has problems

create or replace NONEDITIONABLE TRIGGER SumUpdate AFTER INSERT ON stavkaotpremnice FOR EACH ROW
declare pragma autonomous_transaction;
begin
UPDATE otpremnica a
set a.ukupno=
(SELECT SUM(ukupno)
FROM stavkaotpremnice
WHERE brojotpremnice =: new.brojotpremnice)
WHERE a.brojotpremnice = :new.brojotpremnice;
commit;
end;
This trigger is to sum values of a column called "ukupno" in table stavakaotpremnice then store it in another table otpremnica in a column also called "ukupno".
The trigger check if the id(brojotpremnice) is the same and make the sum.
Brojotpremnice is a foreign key from table otpremnica.
Does anyone know why it is totally ignoring the first entry?
If i put rows in stavkaotpremnica i just count the first entry.
This type of problem can be solved via incremental addition as follows:
UPDATE otpremnica a
set a.ukupno = a.ukupno + :new.ukupno
WHERE a.brojotpremnice = :new.brojotpremnice;
Also, please read about pragma autonomous_transaction. Why it is used? Intentional? If you have no idea, read it. (It separates the transaction)

Conditional CASCADE operation for foreign key constraint?

I have parent and child table where child has a FK pointing to the PK of parent table. When I delete something in parent table I can have child records deleted as well by having ON DELETE CASCADE.
However, in my parent table I don't delete records at all. Instead I set the column state = "passive". I want to delete related entries in the child table.
Do we have something like a "conditional CASCADE" in Postgres? Or is the solution to manually delete entries in the child table?
You would have to do this in a trigger that takes action ON UPDATE. Where the NEW.state = "passive", delete the child rows.
There is nothing like "conditional CASCADE". The closest thing that comes to mind would be to disable triggers. But that's not helpful in your case.
Assumptions:
- state is defined NOT NULL.
- parent_id never changes. If it does you'll want to cascade that UPDATE as well.
The condition to fire the trigger ON UPDATE should be:
NEW.state = "passive"
AND OLD.state <> "passive"
.. since you do not want to trigger it over and over again, only once when parent is set to "passive".
CREATE OR REPLACE FUNCTION trg_upbef()
RETURNS TRIGGER AS
$func$
BEGIN
DELETE FROM child
WHERE parent_id = OLD.parent_id; -- OLD works for UPDATE & DELETE
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
Instead of checking the condition in the trigger function, you can do that in the trigger directly since Postgres 9.0, thereby saving a bit of overhead:
CREATE TRIGGER upd_cascade_del
BEFORE UPDATE ON parent
FOR EACH ROW
WHEN (NEW.state = "passive" AND
OLD.state <> "passive") -- parenthesis required
EXECUTE PROCEDURE trg_upbef();
Don't forget to add a trigger for ON DELETE as well. You don't normally DELETE, but if you do, you want to either raise an exception or cascade the operation.
Trigger function has to RETURN OLD, since NEW is not defined in this case. Otherwise the same:
CREATE OR REPLACE FUNCTION trg_delbef()
...
RETURN OLD;
...
$func$ LANGUAGE plpgsql;
CREATE TRIGGER del_cascade_del
BEFORE DELETE ON parent
FOR EACH ROW
WHEN (OLD.state <> "passive") -- only if not already passive
EXECUTE PROCEDURE trg_delbef();

Creating a Trigger To Delete Records

I have a table which we can call decks. Decks has an id used as its primary key along with some other attributes. The cards table contains a foreign key reference to the deck id and has a primary key of cardid as well. Another table exists called answers where its foreign key is the cardid.
So in order to delete from decks, the database requires I delete from answers first, then cards, and then finally from decks.
I would like to create a trigger which takes care of the first and second delete so that I only have to specify a delete statement from the decks table to completely destroy a deck.
Below is an example PostgreSQL trigger I've found, but I am not sure if its even possible to do what I am asking as I can find no examples online of anyone creating a trigger this way.
CREATE OR REPLACE FUNCTION autoCalculate() RETURNS TRIGGER AS $$
BEGIN
IF NEW.wins < 0 THEN
RAISE EXCEPTION 'Wins cannot be negative';
END IF;
IF(OLD.wins <> NEW.wins_ OR (OLD.losses <> NEW.losses) THEN
NEW.Winning_Percentage := calc_winning_percentage(NEW.Wins, NEW.Losses);
END IF
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER update_winning_percentage ON standings;
CREATE TRIGGER update_winning_percentage BEFORE INSERT OR UPDATE ON standings
FOR EACH ROW EXECUTE PROCEDURE autoCalculate();
If anyone has knowledge to do this, if they could nudge me in the right direction or provide an example of how to do this I'd be grateful!
On your foreign key definitions use ON DELETE CASCADE and it will take care of this for you
In Postgres 8.2 you can specify On Delete Casacde when you define your foreign key relationships.
http://www.postgresql.org/docs/8.2/static/ddl-constraints.html

Oracle trigger- instead of delete, update the row

How do I write an Oracle trigger, than when a user deletes a certain record, the delete doesnt actually happen, but instead performs an update on those rows and sets the status of the record to 'D'?
I tried:
create or replace
trigger DELFOUR.T4M_ITEM_ONDELETE
before delete on M_ITEM_H
FOR EACH ROW
BEGIN
UPDATE
M_ITEM_H
SET
ITEM_STAT = 'D'
WHERE
CUST_CODE = 'TEST'
AND ITEM_CODE = 'GDAY'
;
raise_application_error(-20000,'Cannot delete item');
END;
But I am getting mutating table errors. Is this possible?
If you really need a trigger, the more logical approach would be to create a view, create an INSEAD OF DELETE trigger on the view, and to force the applications to issue their deletes against the view rather than against the base table.
CREATE VIEW vw_m_item_h
AS
SELECT *
FROM m_item_h;
CREATE OR REPLACE TRIGGER t4m_item_ondelete
INSTEAD OF DELETE ON vw_m_item_h
FOR EACH ROW
AS
BEGIN
UPDATE m_item_h
SET item_stat = 'D'
WHERE <<primary key>> = :old.<<primary key>>;
END;
Better yet, you would dispense with the trigger, create a delete_item procedure that your application would call rather than issuing a DELETE and that procedure would simply update the row to set the item_stat column rather than deleting the row.
If you really, really, really want a solution that involves a trigger on the table itself, you could
Create a package with a member that is a collection of records that map to the data in the m_item_h table
Create a before delete statement-level trigger that empties this collection
Create a before delete row-level trigger that inserts the :old.<<primary key>> and all the other :old values into the collection
Create an after delete statement-level trigger that iterates through the collection, re-inserts the rows into the table, and sets the item_stat column.
This would involve more work than an instead of trigger since you'd have to delete and then re-insert the row and it would involve way more moving pieces so it would be much less elegant. But it would work.
First of all the trigger you wrote would throw a mutating table error. Technically what you are asking is not possible i.e. delete wouldn't delete but rather update, unless you raise an exception in the middle which could be an ugly way of doing it. I would think users using some sort of application front end which lets them delete data using a delete button, so you may use an update statement there instead of a delete statement.
Another option would be to create a log table, where you could insert the record before deleting it from the actual table and then join the log table with the actual table to retrieve deleted records. Something like-
CRETAE TABLE M_ITEM_H_DEL_LOG as SELECT * FROM M_ITEM_H WHERE 1=2;
And then
create or replace
trigger DELFOUR.T4M_ITEM_ONDELETE
before delete on M_ITEM_H
FOR EACH ROW
BEGIN
INSERT INTO
M_ITEM_H_DEL_LOG
VALUES (:old.col1, :old.col2,.....) --col1, col2...are columns in M_ITEM_H
;
END;