I’m trying to implement properly a trigger function in Postgresql. The database consists of three tables, two of which are connected by a third one which is an intermediary:
Table MUSICIAN (PK = id_musician)
id_musician | name | birth | death | gender | nationality | num_awards |
---|---|---|---|---|---|---|
1 | John Lennon | 1940-10-09 | 1980-12-08 | M | British | 0 |
2 | Paul McCartney | 1942-06-18 | NULL | M | British | 0 |
3 | Joep Beving | 1976-01-09 | NULL | M | Dutch | 0 |
4 | Amy Winehouse | 1983-09-14 | 2011-07-23 | F | British | 0 |
5 | Wim Mertens | 1953-05-14 | NULL | M | Belgian | 0 |
TABLE COMPOSER (PK = FK = id_musician, id_song)
id_musician | id_song |
---|---|
1 | 1 |
2 | 1 |
3 | 2 |
4 | 3 |
4 | 4 |
5 | 5 |
TABLE SONG (PK = id_song; FK = id_album)
id_song | title | duration | id_album | awards |
---|---|---|---|---|
1 | A Hard Day’s Night | 00:02:34 | 1 | 1 |
2 | Henosis | 00:06:44 | 2 | 1 |
3 | Rehab | 00:03:34 | 3 | 6 |
4 | Back To Black | 00:04:01 | 3 | 2 |
5 | Close Cover | 00:03:31 | 4 | 0 |
The trigger function is implemented on the table SONG for calculating the column num_awards in the table MUSICIAN and the results expected are as follows:
id_musician | name | birth | death | gender | nationality | num_awards |
---|---|---|---|---|---|---|
1 | John Lennon | 1940-10-09 | 1980-12-08 | M | British | 1 |
2 | Paul McCartney | 1942-06-18 | NULL | M | British | 1 |
3 | Joep Beving | 1976-01-09 | NULL | M | Dutch | 1 |
4 | Amy Winehouse | 1983-09-14 | 2011-07-23 | F | British | 8 |
5 | Wim Mertens | 1953-05-14 | NULL | M | Belgian | 0 |
As you can see, the column num_awards sums the awards from all the songs present in the table SONG for each composer. For that purpose I’ve coded the following trigger function:
CREATE OR REPLACE FUNCTION update_num_awards()
RETURNS trigger AS $$
BEGIN
CREATE TABLE IF NOT EXISTS temp1 AS TABLE COMPOSER;
CREATE TABLE IF NOT EXISTS temp2 AS TABLE SONG;
IF (TG_OP = 'INSERT') THEN
UPDATE MUSICIAN
SET num_awards = num_awards + NEW.awards
WHERE id_musician IN (SELECT c.id_musician
FROM COMPOSER AS c JOIN SONG
ON(c.id_song = NEW.id_song));
ELSIF (TG_OP = 'UPDATE') THEN
UPDATE MUSICIAN
SET num_awards = num_awards + NEW.awards
WHERE id_musician IN (SELECT c.id_musician
FROM COMPOSER AS c JOIN SONG
ON(c.id_song = NEW.id_song));
UPDATE MUSICIAN
SET num_awards = num_awards - OLD.awards
WHERE id_musician IN (SELECT c.id_musician
FROM temp1 AS t JOIN temp2
ON(c.id_song = OLD.id_song));
ELSIF (TG_OP = 'DELETE') THEN
UPDATE MUSICIAN
SET num_awards = num_awards - OLD.awards
WHERE id_musician IN (SELECT c.id_musician
FROM temp1 AS t JOIN temp2
ON(c.id_song = OLD.id_song));
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE CONSTRAINT TRIGGER trigger_update_num_awards AFTER INSERT OR DELETE OR UPDATE ON SONG
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW EXECUTE PROCEDURE update_num_awards();
The function is triggered AFTER insert, delete or update in the table SONG. When inserting, coding seems pretty straightforward but when it comes to deleting things start to get difficult – the subquery I’ve coded doesn’t work because rows may have already disappeared from the COMPOSER and SONG tables. I’ve tried to use temporary tables but they don’t seem to work – somehow they vanish before the delete operation begins. So the only solution I’ve got through with is creating two permanent auxiliary tables, temp1 and temp2, to make a copy of COMPOSER and SONG with before each delete operation.
And my question is, how could temporary tables be performed in this case? Personally, despite my piece of code working correctly for what I want, I would like to improve it or to make it more simple and elegant.
Any help would be greatly appreciated.
Regards
3
Answers
This answer consists of an update function and a trigger which perform on the table COMPOSER.
It's simpler to implement, covers more cases and no auxiliary tables or deferrability are necessary.
It seems like doing this on song is doing it the hard way. It’s composer that has most of the effects. Delete and insert are easy. Update can be treated as a delete + insert.
When a song is inserted, it doesn’t matter because it is not associated with a musician. When a song is deleted, it will cascade to delete the associated composers (assuming you’ve set up cascade deletes). That leaves one trigger, when a song is updated.
No temp tables should be necessary.
Following @Schwern’s suggestions: