web-dev-qa-db-fra.com

Comment créer `au lieu de" déclencher sur une table à Postgres?

Je veux créer un déclencheur pour une table dans PostgreSQL. Ma table contient des données sur les événements et comprend le numéro de la pièce, l'heure de début et la durée d'événement. Lors de la nouvelle insertion au tableau, je veux vérifier si le numéro de la pièce du nouvel événement sera occupé pour l'heure du nouvel événement. Si oui, je veux soulever une exception d'autre, insérez le nouvel événement dans la table. Je pensais déclarer la gâchette comme instead of déclencheur pour insert mais Postgres n'autorise pas instead of déclenche sur insert Opération sur des tables (et je ne veux pas créer de vue juste pour la gâchette).

Comment puis-je travailler autour de ce problème? Théoriquement, je pourrais créer un déclencheur after, puis vérifier si les données nouvellement insérées sont valides et supprimées si ce n'est pas le cas. Mais cela semble être une mauvaise approche conceptuellement et peut-être pratiquement également (je ne suis pas sûr de savoir si des déclencheurs sont atomiques et que quelqu'un pourrait peut-être déjà une décision incorrecte sur la base des mauvaises données).

C'est la définition de la table:

create table room_schedule(
  start_date date,
  start_time time,
  room_no int,
  event_id int,
  duration interval,
  primary key(start_date, start_time, room_no, event_id)
);

Ceci est mon déclencheur et mes définitions de fonction:

create or replace function inserttrigfunc() returns trigger as $$
declare count int;

begin

with end_time_table(eid, stime, etime) as (
        select event_id, start_time, (start_time + duration) as etime from room_schedule
        where room_no=new.room_no
    ),
        overlapping_time_table(eid, stime, etime) as(
            select * from end_time_table
            where (stime <= new.start_time and new.start_time <= etime) or
            (stime <= (new.start_time + new.duration) and (new.start_time + new.duration) <= etime)
        )
        select count(*) from overlapping_time_table into count;

        if count > 0
        then
            begin
                raise exception 'the room is already occupied at the time';
            end;
        else
            begin
                insert into room_schedule(start_date, start_time, room_no, event_id, duration)
                    values(new.start_date, new.start_time, new.room_no, new.event_id, new.duration);
            end;
end;
$$language plpgsql;





create trigger TRIG1 instead of insert
  on room_schedule
  for each row
  execute procedure inserttrigfunc();
2
Yos

Vous pouvez obtenir ce que vous voulez avec un déclencheur régulier de niveau de ligne. Si cette gâchette jette une exception, le insert n'arrivera pas. Si vous revenez new de la gâchette, l'insert se poursuivra.

citation du manuel

Si une valeur non nulle est renvoyée, l'opération se poursuit avec cette valeur de ligne. Le retour d'une valeur de ligne différente de la valeur initiale de la nouvelle modifie la ligne qui sera insérée ou mise à jour. Ainsi, si la fonction de déclenchement souhaite que l'action de déclenchement fonctionne normalement sans modifier la valeur de la ligne, une nouvelle (ou une valeur égale à celle-ci) doit être renvoyée

(mettre l'accent sur le mien)

essentiellement quelque chose comme ça:

if count > 0 then
   raise exception 'the room is already occupied at the time';
else 
   return new;
end if;

Puis définissez la gâchette comme suit:

create trigger TRIG1 before insert
  on room_schedule
  for each row
  execute procedure inserttrigfunc();

Je ne sais pas si les déclencheurs sont atomiques et que quelqu'un pourrait peut-être déjà faire une décision incorrecte basée sur les mauvaises données

Oui, ils sont et ils font partie de la transaction qui a déclenché la gâchette.


Mais vous n'avez pas besoin d'une gâchette du tout.

Cela peut facilement être réalisé avec une contrainte d'exclusion à l'aide des deux dates comme daterange

create table room_schedule(
  start_date date,
  start_time time,
  room_no int,
  event_id int,
  duration interval,
  primary key(start_date, start_time, room_no, event_id),
  EXCLUDE USING Gist (room_no with =, daterange(start_date, end_date, '[]') WITH &&)
);

Notez que vous avez besoin du Extension Bree Gist pour ce qui précède car par défaut, un indice de gist ne prend pas en charge l'égalité nécessaire, car la contrainte ne doit s'appliquer que pour le même room_no

4