Need to create a trigger that increments a value in a table after insertion

孤人 提交于 2021-01-29 02:17:53

问题


I'm currently having a problem with triggers on a current prototype of a game database i'm working on

So, I have these two tables

CREATE TABLE public.hunters
(   id integer NOT NULL,
    name character varying(30) COLLATE pg_catalog."default" NOT NULL,
    weapon character varying(30) COLLATE pg_catalog."default" NOT NULL,
    ranking character varying(30) COLLATE pg_catalog."default" NOT NULL,
    nhunts integer NOT NULL,
    sex character(1) COLLATE pg_catalog."default" NOT NULL,
    title character varying(30) COLLATE pg_catalog."default",
)

CREATE TABLE public.hunts
(
    id_h integer NOT NULL,
    id_m integer NOT NULL,
    id_l integer NOT NULL,
    code integer NOT NULL,
    huntname character varying(50) COLLATE pg_catalog."default",
)

There are other tables, but the problem revolves around those two.

See, a hunt is a table that contains the id of a hunter, the monster to be hunted and the location where the monster will be hunted - and also the name of que hunting quest. Everytime a hunter hunts some monster, it should increment the "nhunts" value, there on the table hunters.

Many hunters may hunt many different monster, on many different locations. The code column is an ordinal value which represents how recent or old is a hunt (so, if code equals 20, it would be the 20th hunt since "ever" - or at least, since it first became registered)

Problem is I don't know how to create this trigger. I tried everything and the value never is updated

This is what I tried. But it just doesn't update of increment anything

CREATE OR REPLACE FUNCTION Hunter_HuntsIncrement() RETURNS TRIGGER AS $$
DECLARE
    idv integer;
BEGIN
    idv := TG_ARGV[0];  
    IF (new.id_h = old.id_h AND new.code = old.code) THEN
        UPDATE hunters
            SET nhunts = nhunts + 1
        WHERE id = idv;
    END if;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER updating_nhunts AFTER INSERT ON hunts
FOR EACH STATEMENT EXECUTE PROCEDURE hunter_huntsincrement()

I know it's probably terribly wrong, but I'm new to triggers and I'd really like a helping hand here


回答1:


Maintaining summary value is tricky - it's easy to create a possibility to deadlock your program.

If you really have to do this, because you know that you'll have performance problems otherwise (like nhunts in hundreds or more), then it's better to create a separate summary table for nhunts, something like:

CREATE TABLE hunts_summary
(
    id_hs bigserial primary key,
    id_h integer NOT NULL,
    nhunts integer NOT NULL
);
CREATE INDEX hunts_summary_id_h_idx on hunts_summary(id_h);

The trigger for hunts:

  • runs for each added, removed, updated row;
  • adds a row (id_h, nhunts) = (NEW.id_h, 1) on each insert;
  • adds a row (id_h, nhunts) = (OLD.id_h, -1) on each delete;
  • both of the above on update that changes id_h.

As the trigger will only add new rows it does not lock existing rows and therefore it can't deadlock.

But this is not enough - as described above the summary table will grow rows as fast or faster than hunts table, so it's not very helpful. So we need to add some way to merge existing rows periodically - some way to change:

id_h nhunts
1    1
1    1
2    1
2    -1
1    1
1    -1
2    1
1    1
2    1

To:

id_h nhunts
1    3
2    2

This should not run on each trigger invocation, as it will then be quite slow, but it can run randomly - for example every 1/1024th invocation at random. This function will use "skip locked" keyword to avoid touching already locked rows, avoiding otherwise possible deadlock.

Such trigger would look something like this:

create or replace function hunts_maintain() returns trigger
as $hunts_maintain$
        begin
                if (tg_op = 'INSERT') then
                        insert into hunts_summary(id_h, nhunts)
                                values (NEW.id_h, 1);
                elsif (tg_op = 'DELETE') then
                        insert into hunts_summary(id_h, nhunts)
                                values (OLD.id_h, -1);
                elsif (tg_op = 'UPDATE' and NEW.id_h!=OLD.id_h) then
                        insert into hunts_summary(id_h, nhunts)
                                values (OLD.id_h, -1), (NEW.id_h, 1);
                end if;

                if (random()*1024 < 1) then
                        with deleted_ids as (
                                select id_hs from hunts_summary for update skip locked
                        ),
                        deleted_nhunts as (
                                delete from hunts_summary where id_hs in (select id_hs from deleted_ids) returning id_h, nhunts
                        )
                        insert into hunts_summary (id_h, nhunts) select id_h, sum(nhunts) from deleted_nhunts group by id_h;
                end if;

                return NEW;
        end;
$hunts_maintain$ language plpgsql;

create trigger hunts_maintain
        after insert or update or delete on hunts
        for each row execute procedure hunts_maintain();

The trigger runs fast enough on my laptop to insert 1M rows to hunts table in 45s.

This view below will make it easy to extract current nhunts from summary. Querying it will take a small number or ms even if hunts table will be in billions:

create or replace view hunts_summary_view as
        select id_h, sum(nhunts) as nhunts
        from hunts_summary
        group by id_h;


来源:https://stackoverflow.com/questions/56900002/need-to-create-a-trigger-that-increments-a-value-in-a-table-after-insertion

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!