Cascade trigger not working - postgresql

Hei guys. The point is simple. I have some tables called PERSONS, STUDENTS, EMPLOYEES. And IDPERSON = idstudent, idperson = idemployee. SO far so good.
Im trying to get make a delete cascade trigger that, whenever I delete a PERSON, it deletes the row in table PERSONS and it deletes depending if that person was putten in table STUDENTS or EMPLOYEES.
Because in table PERSONS i find only (Name, IDPERSON, Telephone, Email, etc) and in Student/Emplyee I find more technical details, such as: ScheduleEmployee, IdAccomodationStudent, etc. Hope u got it.
CREATE FUNCTION stergereStudenti () RETURNS trigger AS $$
BEGIN
DELETE FROM persoane WHERE idpersoana = OLD.idpersoana;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trgStergereStudenti BEFORE DELETE ON TABLE studenti
FOR EACH ROW EXECUTE stergereStudenti();
For some reason it doesnt work. I looked into old stackoverflow posts and I got the trigger above and addapted on my Database. But is not working.. Please help :(

Triggers look like the wrong tool for the job. Instead, you could use a foreign key with the on delete cascade option:
ALTER TABLE student
ADD CONSTRAINT student_fk
FOREIGN KEY(idpersoana)
REFERENCES person(idpersoana) ON DELETE CASCADE;

Related

Postgres: Ignore DELETE triggers for cascade deletes

I am trying to implement a relation of persons to email addresses where a person must have at least one email address at all times. The tables look like this:
CREATE TABLE persons (
id serial PRIMARY KEY,
name text NOT NULL
);
CREATE TABLE email_addresses (
id serial PRIMARY KEY,
person_id integer REFERENCES persons (id) ON DELETE CASCADE ON UPDATE CASCADE,
email_address text NOT NULL
);
In order to implement the constraint that a person must have at least one email address, I thought I'd use triggers. One of the triggers necessary to satisfy the constraint is a BEFORE DELETE trigger on the email_addresses table that raises an error if the DELETE would remove the last email address for a person:
CREATE FUNCTION email_addresses_delete_trigger() RETURNS trigger AS $$
DECLARE
num_email_addresses integer;
BEGIN
num_email_addresses := (SELECT count(*) FROM email_addresses WHERE person_id = OLD.person_id);
IF num_email_addresses < 2 THEN
RAISE EXCEPTION 'A person must have at least one email address';
END IF;
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER email_addresses_delete_trigger BEFORE DELETE ON email_addresses
FOR EACH ROW EXECUTE FUNCTION email_addresses_delete_trigger();
This trigger does what it is meant to do, however it prevents deletes of a person from the persons table. For example:
mydb=# DELETE FROM persons WHERE id = 1;
ERROR: A person must have at least one email address
CONTEXT: PL/pgSQL function email_addresses_delete_trigger() line 7 at RAISE
SQL statement "DELETE FROM ONLY "public"."email_addresses" WHERE $1 OPERATOR(pg_catalog.=) "person_id""
If I am deleting a person, then I want all their email addresses deleted too, and so I don't care if the constraint represented by the trigger is maintained during the process of the cascade delete. Is there a way to "ignore" this trigger when a person is deleted? Or is there some other way that I would need to delete a person?
My recommendation is to change the data model so that you have a not nullable foreign key constraint from persons to email_addresses that links to one of the addresses of the person. Then your requirement is automatically fulfilled, and you don't need a trigger.
That will make some things like deleting an e-mail address more complicated, but you don't have to rely on a trigger for integrity, which is always subject to race conditions.

Is it possible to refer a column in a view as foreign key (PostgreSQL 9.4)?

I know in older versions it was impossible, is it the same with version 9.4?
I'm trying to do something like this:
CREATE VIEW products AS
SELECT d1.id AS id, d1.price AS pr FROM dup.freshProducts AS d1
UNION
SELECT d2.id AS id, d2.price AS pr FROM dup.cannedProducts AS d2;
CREATE TABLE orderLines
(
line_id integer PRIMARY KEY,
product_no integer REFERENCES productView.id
);
I'm trying to implement an inheritance relationship where freshProducts and cannedProducts both inherit from products. I implemented it using two different tables and I created a view products that has only the common properties between freshProducts and cannedProducts. In addition, each row in orderLines has a relationship with a product, either a freshProduct or a cannedProduct. See image for clarification.
If referencing to a view is yet not possible, which solution do you think is best? I've thought of eihter a materialized view or implementing the restriction using triggers. Could you recommend any good example of such triggers to use as a basis?
Thank-you very much!
Referencing a (materialized) view wouldn't work and a trigger might look like this:
CREATE OR REPLACE FUNCTION reject_not_existing_id()
RETURNS "trigger" AS
$BODY$
BEGIN
IF NEW.product_no NOT IN (SELECT id FROM dup.freshProducts UNION SELECT id FROM dup.cannedProducts) THEN
RAISE EXCEPTION 'The product id % does not exist', NEW.product_no;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
CREATE TRIGGER tr_before_insert_or_update
BEFORE INSERT OR UPDATE OF product_no
ON orderLines
FOR EACH ROW
EXECUTE PROCEDURE reject_not_existing_id();
(See also http://www.tek-tips.com/viewthread.cfm?qid=1116256)
A materialized view might look like a good approach but fails for two reasons: Like a view you simply can't reference it, because it is no table (go ahead and try). Assuming you could, there would still be the problem of preventing two equal ids in freshProducts and cannedProducts. Yes you can define an UNIQUE INDEX on a materialized view, but how to make sure the same id isn't used both in fresh an canned in the first place?
That's something you still have to solve if using the trigger in orderLines.
That brings me to suggest to rethink your model. 'Fresh' and 'canned' might as well be values of an attribute of a single table products, hence making all the trouble superfluous. If fresh and canned product significantly differ in (the number of) their attributes (can't think of any other reason to create two different tables) then reference the product id in two other tables. Like
CREATE TABLE products
(
id ... PRIMARY KEY
, fresh_or_canned ...
, price ...
, another_common_attribute_1 ...
, ...
, another_common_attribute_n ...
);
CREATE TABLE canned_specific_data
(
canned_id ... REFERENCES products (id)
, type_of_can ...
, ...
, another_attribute_that_does_not_apply_to_fresh ...
);
CREATE TABLE fresh_specific_data
(
fresh_id ... REFERENCES products (id)
, date_of_harvest ...
, ...
, another_attribute_that_does_not_apply_to_canned ...
);
The simple answer to preventing ID duplication is to simply use the same sequence as the default value for IDs in both freshProducts and cannedProducts.
Now, there comes the question, why do you need a foreign key at all? Typically this is to prevent deletion of data that another table depends upon, however, you can write a trigger to prevent that. Alsowise, you have updating that value to something that doesn't exist in the keyed-to table, but you can write a trigger for that too.
So basically you can write triggers to implement all the desired functionality of a foreign key without actually needing a foreign key, with the added benefit that they WILL work with such a view.

postgresql trigger (strategy) to update table based on entries of another table

I am fairly new to PostgreSQL (spoilt by django ORM!), and I would like to create a trigger which updates a table based on entries of another table.
So, I have the following table on my schema:
collection_myblogs(id, col1,col2,title,col4,col5)
..where field id is autogenerated. Now, I have a new table created like so:
CREATE TABLE FullText(id SERIAL NOT NULL, content text NOT NULL);
ALTER TABLE ONLY FullText ADD CONSTRAINT fulltext_pkey PRIMARY KEY (id);
and I insert values from collection_myblogs like so:
INSERT INTO FullText(content) SELECT title FROM collection_myblogs;
All fine so far...I would now like a trigger on FullText such that FullText updates itself with new entries everytime collection_myblogs has a new entry. So, I attempted creating a trigger as following:
CREATE TRIGGER collection_ft_update BEFORE INSERT OR UPDATE ON collection_myblogs FOR EACH ROW EXECUTE PROCEDURE ft_update();
Now, I am not entirely sure what should go on ft_update() function, and at the moment, I have:
CREATE FUNCTION ft_update() RETURNS trigger AS '
BEGIN
INSERT INTO FullText(content) SELECT new.title;
return new;
END
' LANGUAGE plpgsql;
..which works fine for INSERTS but not UPDATES. i.e if I update the title of the orginal column collections_myblog(title) it appears as a new entry on FullText I am unsure how to deal with ids here.
I would like the ids i.e primary keys to be the same on each table. So, the idea for me is to have FullText(id, content) == collection_myblogs(id, title) - if this makes sense. So, the id and the content should be replicated from collection_myblogs table. How would one go about achieving this?
My understanding is that I can use a trigger before any insert or an update on my collection_myblogs and somehow maintain FullText(id, content) == collection_myblogs(id, title)
I would appreciate any guidance on this.
There are actually a large number of ways to handle this problem. Some examples:
Use table inheritance to create an "interface" to your data (no trigger needed, the abstract table ends up functioning like a view). This is complicated territory though.
Use the trigger approach like you do and then handle UPDATE and DELETE separately. The big issue here is that if you have two areas of text that are identical, your update trigger needs to be able to separate them.
There are many others but those should get you started.
Actually, it turned out to be quite simple. Just had to follow this

Cascade new IDs to a second, related table

I have two tables, Contacts and Contacts_Detail. I am importing records into the Contacts table and need to run a SP to create a record in the Contacts_Detail table for each new record in the Contacts. There is an ID in the Contacts table and a matching ID_D in the Contacts_Detail table.
I'm using this to insert the record into Contacts_Detail but get the 'Subquery returned more than 1 value.' error and I can't figure out why. There are multiple records in Contacts that need have matching records in Contacts_Detail.
Insert into Contacts_Detail (ID_D)
select id from Contacts c
left join Contacts_Detail cd
on c.id = cd.id_d
where id_d is null
I'm open to a better way...
thanks.
It sounds like you're inserting blank child-records into your Contacts_Detail table -- so the first question I'd ask is: Why?
As for why your specific SQL isn't working...
A few things you can check:
Contacts table -- do you have any records there WHERE id is null?
(delete them -- then make the id field a primary key)
Contacts_Detail
table -- do you have any records there WHEERE id_d is null?
(delete them -- then go into your designer and create a relationship
/ enforce referential integrity.)
Verify that c.id is the primary
key, and cd.id_d is the correct foreign key to relate the tables.
Hope that helps
Why not just have a trigger? This seems a little simpler than having to determine for all time which rows are missing - that seems more like something you would do periodically to correct for some anomalies, not something you should have to do after every insert. Something like this should work:
CREATE TRIGGER dbo.NewContacts
ON dbo.Contacts
FOR INSERT
AS
BEGIN
INSERT dbo.Contacts_Detail(ID_D) SELECT ID FROM inserted;
END
GO
But I suspect you have a trigger on the Contacts_Detail table that is not written to correctly handle multi-row inserts, and that's where your subquery error is coming from. Can you show the trigger on Contacts_Detail?

Deleting linked data across tables in PostgreSQL

I'm looking for the best way to handle deleting linked data across three PostgreSQL tables (technically more, but it's the same idea).
The three tables are agency, address, and agency_address. An agency can have multiple addresses so the agency_address is just a link table with two columns, agency_id and address_id (defined as their respective foreign keys)
I want to set it up so that when an agency is deleted it will remove the link table row in agency_address and the related address row automagically. (So if an agency is deleted, so are all its addresses)
I can get it to clear the link table, but not sure how to get to the address table.
(Also address is a generic model and will be referenced by others, like a 'site' which will have their own link tables.)
Use foreign keys with ON DELETE CASCADE in the table definition.
ALTER TABLE agency_address
ADD CONSTRAINT agency_address_agency_fkey FOREIGN KEY (agency_id)
REFERENCES agency (agency_id) ON DELETE CASCADE;
It seems uncertain that you should delete addresses automatically, too.
If so, your data model is wrong and addresses should depend on agencies directly, with a foreign key constraint similar to the one above, no n:m linking table necessary.
Edit after more info:
So addresses can be linked to agencies or sites, but never to both at the same time.
The model could work as you have it, but you would have to make sure somehow that an address linked to an agency isn't linked to a site, too.
The foreign key constraint between address and agency_address points in the "wrong" direction, so you cannot simply add another ON DELETE CASCADE. You could implement it with an additional foreign key, but that's tricky. This approach per trigger is much simpler:
CREATE OR REPLACE FUNCTION trg_agency_address_delaft()
RETURNS trigger AS
$BODY$
BEGIN
DELETE FROM address
WHERE address_id = OLD.address_id;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER delaft
AFTER DELETE ON agency_address
FOR EACH ROW EXECUTE PROCEDURE trg_agency_address_delaft();
More about trigger functions and triggers in the manual.