【问题标题】:Use of auxiliary tables in Postgresql if temporary tables don't work如果临时表不起作用,则在 Postgresql 中使用辅助表
【发布时间】:2022-12-11 12:48:59
【问题描述】:

我正在尝试在 Postgresql 中正确实现触发器功能。数据库由三个表组成,其中两个由第三个连接,第三个是中介:

表 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

作曲家 (PK = FK = id_musician, id_song)

id_musician id_song
1 1
2 1
3 2
4 3
4 4
5 5

表歌(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

在表SONG上实现触发函数,计算表MUSICIAN中的列num_awards,预期结果如下:

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

如您所见,列 num_awards 汇总了表 SONG 中每个作曲家的所有歌曲的奖项。为此,我编写了以下触发函数:

CREATE OR REPLACE FUNCTION update_num_awards()
RETURNS trigger AS $$ 
BEGIN
    CREATE TABLE IF NOT EXISTS bakcomp AS TABLE COMPOSER;
    CREATE TABLE IF NOT EXISTS baksong 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 bakcomp AS c JOIN baksong
                            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 bakcomp AS c JOIN baksong
                            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();

该函数在表 SONG 中插入、删除或更新后触发。插入时,编码似乎非常简单,但是当涉及到删除时,事情开始变得困难——我编码的子查询不起作用,因为行可能已经从 COMPOSER 和 SONG 表中消失了。我试过使用临时表,但它们似乎不起作用——不知何故,它们在删除操作开始前就消失了。因此,我唯一的解决方案是创建两个永久辅助表 bakcomp 和 baksong,以便在每次删除操作之前制作 COMPOSER 和 SONG 的副本。

我的问题是,在这种情况下如何执行临时表?就我个人而言,尽管我的一段代码可以正确地满足我的需求,但我还是想改进它或使其更简单和优雅。

任何帮助将不胜感激。

问候

【问题讨论】:

    标签: sql postgresql


    【解决方案1】:

    似乎在歌曲中这样做是很困难的。它是作曲家,具有大部分效果。删除和插入很容易。更新可以被视为删除+插入。

    • 作曲家
      • 删除时:musician.num_awards = musician.num_awards - song.awards
      • 插入时:musician.num_awards = musician.num_awards + song.awards
      • 更新中
        • old.musician.num_awards = old.musician.num_awards - old.song.awards
        • new.musician.num_awards = new.musician.num_awards + new.song.awards

    插入歌曲时,这无关紧要,因为它与音乐家无关。当一首歌曲被删除时,它会级联删除相关的作曲家(假设你已经设置了级联删除)。当一首歌曲更新时,这就留下了一个触发器。

    • 歌曲
      • 更新:对于创作歌曲的所有音乐家,musician.num_awards = musician.num_awards - old.awards + new.awards

    不需要临时表。

    【讨论】:

    • 嘿@Schwern,非常感谢您的回答,我从中明白了要点。如果函数和触发器与表 COMPOSER 相关联,则该过程更易于实现,涵盖更多情况,并且不需要额外的表或可延迟性。比较这两种可能性是很有趣的——在 COMPOSER 表上或在 SONG 上表演。
    【解决方案2】:

    这个答案由一个更新函数和一个在表 COMPOSER 上执行的触发器组成。

    它实施起来更简单,涵盖更多案例,并且不需要辅助表或可延期性。

    CREATE OR REPLACE FUNCTION update_num_awards()
    RETURNS trigger AS $$ 
    BEGIN 
        IF (TG_OP = 'INSERT') THEN 
            UPDATE MUSICIAN
            SET num_awards = num_awards + (SELECT s.awards
                                           FROM SONG AS s
                                           WHERE s.id_song IN
                                           (SELECT s.id_song
                                            FROM SONG AS s JOIN COMPOSER
                                            ON(s.id_song = NEW.id_song)))
            WHERE id_musician = NEW.id_musician;
        ELSIF (TG_OP = 'UPDATE') THEN
            UPDATE MUSICIAN
            SET num_awards = num_awards + (SELECT s.awards
                                           FROM SONG AS s
                                           WHERE s.id_song IN
                                           (SELECT s.id_song
                                            FROM SONG AS s JOIN COMPOSER
                                            ON(s.id_song = NEW.id_song)))
            WHERE id_musician = NEW.id_musician;
            UPDATE MUSICIAN
            SET num_awards = num_awards - (SELECT s.awards
                                           FROM SONG AS s
                                           WHERE s.id_song IN
                                           (SELECT s.id_song
                                            FROM SONG AS s JOIN COMPOSER
                                            ON(s.id_song = OLD.id_song)))
            WHERE id_musician = OLD.id_musician;    
        ELSIF (TG_OP = 'DELETE') THEN
            UPDATE MUSICIAN
            SET num_awards = num_awards - (SELECT s.awards
                                           FROM SONG AS s
                                           WHERE s.id_song IN
                                           (SELECT s.id_song
                                            FROM SONG AS s JOIN COMPOSER
                                            ON(s.id_song = OLD.id_song)))
            WHERE id_musician = OLD.id_musician;
        END IF;
        RETURN NULL;
    END;
    $$ LANGUAGE plpgsql;
    
    CREATE TRIGGER trigger_update_num_awards AFTER INSERT OR DELETE OR UPDATE OF
    id_musician, id_song ON COMPOSER
    FOR EACH ROW EXECUTE PROCEDURE update_num_awards();
    

    【讨论】:

    • 我认为您在更新歌曲时也需要一个触发器,因为它的奖励数量可能会发生变化。另外,我认为这会更简单、更快,因为每种类型都有三个不同的函数;他们不共享代码。
    • 再次感谢@Schwern,我根据您的建议发布了一个新答案。问候
    【解决方案3】:

    按照@Schwern 的建议:

    /*Function and trigger for the COMPOSER table*/
    
    CREATE OR REPLACE FUNCTION update_composer()
    RETURNS trigger AS $$ 
    BEGIN 
        IF (TG_OP = 'INSERT') THEN 
            UPDATE MUSICIAN
            SET num_awards = num_awards + (SELECT s.awards
                                           FROM SONG AS s
                                           WHERE s.id_song IN
                                           (SELECT s.id_song
                                            FROM SONG AS s JOIN COMPOSER
                                            ON(s.id_song = NEW.id_song)))
            WHERE id_musician = NEW.id_musician;
        ELSIF (TG_OP = 'UPDATE') THEN
            UPDATE MUSICIAN
            SET num_awards = num_awards + (SELECT s.awards
                                           FROM SONG AS s
                                           WHERE s.id_song IN
                                           (SELECT s.id_song
                                            FROM SONG AS s JOIN COMPOSER
                                            ON(s.id_song = NEW.id_song)))
            WHERE id_musician = NEW.id_musician;
            UPDATE MUSICIAN
            SET num_awards = num_awards - (SELECT s.awards
                                           FROM SONG AS s
                                           WHERE s.id_song IN
                                           (SELECT s.id_song
                                            FROM SONG AS s JOIN COMPOSER
                                            ON(s.id_song = OLD.id_song)))
            WHERE id_musician = OLD.id_musician;    
        ELSIF (TG_OP = 'DELETE') THEN
            UPDATE MUSICIAN
            SET num_awards = num_awards - (SELECT s.awards
                                           FROM SONG AS s
                                           WHERE s.id_song IN
                                           (SELECT s.id_song
                                            FROM SONG AS s JOIN COMPOSER
                                            ON(s.id_song = OLD.id_song)))
            WHERE id_musician = OLD.id_musician;
        END IF;
        RETURN NULL;
    END;
    $$ LANGUAGE plpgsql;
    
    CREATE TRIGGER trigger_update_composer AFTER INSERT OR DELETE OR UPDATE OF
    id_musician, id_song ON COMPOSER
    FOR EACH ROW EXECUTE PROCEDURE update_composer();
    
    /*Function and trigger for the SONG table*/
    
    CREATE OR REPLACE FUNCTION update_song()
    RETURNS trigger AS $$ 
    BEGIN
        IF (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 COMPOSER AS c JOIN SONG
                                ON(c.id_song = OLD.id_song));   
        END IF;
        RETURN NULL;
    END;
    $$ LANGUAGE plpgsql;
    
    CREATE CONSTRAINT TRIGGER trigger_update_song AFTER UPDATE OF awards ON SONG 
    DEFERRABLE INITIALLY DEFERRED
    FOR EACH ROW EXECUTE PROCEDURE update_song();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-01-03
      • 2023-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多