2010-04-01 6 views
8

Je souhaite connaître le nombre de lignes qui seront affectées par la requête UPDATE dans le déclencheur d'instruction BEFORE. Est-ce possible?Nombre de lignes à affecter avant la mise à jour dans le déclencheur

Le problème est que je veux autoriser uniquement les requêtes qui mettront à jour jusqu'à 4 lignes. Si le nombre de lignes affectées est de 5 ou plus, je souhaite signaler une erreur.

Je ne veux pas faire cela dans le code parce que j'ai besoin de cette vérification sur le niveau de DB. Est-ce possible?

Merci à l'avance pour des indices sur ce

+0

Je considérais l'utilisation de la requête normale + rollback si les lignes affectées sont supérieures à max, et cela semble être la meilleure valeur que je vois. Eh bien dans 99% + il y aura de bonnes requêtes (4 lignes max mises à jour) mais ce n'est qu'une sécurité supplémentaire pour le système. La table avec ce 'problème' est assez énorme et critique pour le système, donc la restaurer après qu'une telle requête mal posée peut être pénible pour tout le monde. Merci à tous pour vos réponses. Je ne sais pas lequel accepter parce que tous les aider :) – sbczk

+0

Pourquoi voulez-vous faire cela? Peut-être y a-t-il un moyen beaucoup plus facile de le faire qu'une requête aussi étrange. De plus ... l'utilisation de count (si possible) sera plus lente pendant que la table se développe. –

+0

Donc, vous pensez que lorsque vous avez changé 4 lignes max, la requête ne peut pas être mal traitée? Cela ressemble à une sorte de faux sentiment de sécurité. –

Répondre

1

J'ai créé quelque chose comme ceci:

begin; 

create table test (
    id integer 
); 

insert into test(id) select generate_series(1,100); 


create or replace function trg_check_max_4_updated_records() 
returns trigger as $$ 
declare 
    counter_ integer := 0; 
    tablename_ text := 'temptable'; 
begin 
    raise notice 'trigger fired'; 
    select count(42) into counter_ 
     from pg_catalog.pg_tables where tablename = tablename_; 
    if counter_ = 0 then 
     raise notice 'Creating table %', tablename_; 
     execute 'create temporary table ' || tablename_ || ' (counter integer) on commit drop'; 
     execute 'insert into ' || tablename_ || ' (counter) values(1)'; 

     execute 'select counter from ' || tablename_ into counter_; 
     raise notice 'Actual value for counter= [%]', counter_; 
    else 
     execute 'select counter from ' || tablename_ into counter_; 
     execute 'update ' || tablename_ || ' set counter = counter + 1'; 
     raise notice 'updating'; 
     execute 'select counter from ' || tablename_ into counter_; 
     raise notice 'Actual value for counter= [%]', counter_; 

     if counter_ > 4 then 
      raise exception 'Cannot change more than 4 rows in one trancation'; 
     end if; 

    end if; 
    return new; 
end; $$ language plpgsql; 


create trigger trg_bu_test before 
    update on test 
    for each row 
    execute procedure trg_check_max_4_updated_records(); 

update test set id = 10 where id <= 1; 
update test set id = 10 where id <= 2; 
update test set id = 10 where id <= 3; 
update test set id = 10 where id <= 4; 
update test set id = 10 where id <= 5; 

rollback; 

L'idée principale est d'avoir un déclencheur sur « avant la mise à jour pour chaque ligne » qui crée (si nécessaire) une table temporaire (qui est tombé à la fin de la transaction). Dans cette table, il n'y a qu'une ligne avec une valeur, c'est-à-dire le nombre de lignes mises à jour dans la transaction en cours. Pour chaque mise à jour, la valeur est incrémentée. Si la valeur est supérieure à 4, la transaction est arrêtée.

Mais je pense que c'est une mauvaise solution pour votre problème. Quel est le problème pour exécuter une mauvaise requête que vous avez écrite, deux fois, de sorte que vous aurez 8 lignes modifiées. Qu'en est-il des lignes de suppression ou de les tronquer?

0

Je ne l'ai jamais travaillé, postgresql donc ma réponse ne peut appliquer. Dans SQL Server, votre déclencheur peut appeler une procédure stockée qui ferait l'une des deux choses:

  1. effectuer une commande SELECT COUNT (*) pour déterminer le nombre d'enregistrements qui seront touchés par la mise à jour et n'exécutons la mise à jour si le nombre est de 4 ou moins
  2. Effectuer la mise à jour dans une transaction, et engagent uniquement la transaction si le nombre de retour de lignes affectées est 4 ou moins

n ° 1 est le timing vulnérable (le nombre des enregistrements affectés par la mise à jour peut changer entre le contrôle COUNT (*) et la mise à jour réelle, le numéro 2 est très inefficace, s'il y où le nombre de lignes mises à jour est supérieur à 4.

1

PostgreSQL a deux types of triggers: déclencheurs de ligne et d'instruction. Les déclencheurs de ligne ne fonctionnent que dans le contexte d'une ligne, vous ne pouvez donc pas les utiliser. Malheureusement, "avant" l'instruction don't see déclenche quel genre de changement est sur le point d'avoir lieu, donc je ne crois pas que vous pouvez utiliser ceux-ci non plus. Sur cette base, je dirais qu'il est peu probable que vous puissiez construire ce type de protection dans la base de données en utilisant des déclencheurs, à moins que cela ne vous dérange pas d'utiliser un déclencheur "après" et de revenir sur la transaction si la condition n'est pas satisfaite. Cela ne m'ennuierait pas d'avoir tort. :)

1

Jetez un coup d'œil sur l'utilisation du niveau d'isolement sérialisable. Je crois que cela vous donnera une vue cohérente des données de la base de données dans votre transaction. Ensuite, vous pouvez utiliser l'option # 1 mentionnée par MusiGenesis, sans la vulnérabilité de synchronisation. Testez-le bien sûr pour valider.

+0

Yep, la meilleure façon de déposer l'efficacité de la base de données que toutes les transactions seraient effectuées un par un :( –

2

Ecrivez une fonction qui met à jour les lignes pour vous ou effectue une restauration. Désolé pour le formatage de style médiocre.

create function update_max(varchar, int) 
RETURNS void AS 
$BODY$ 

DECLARE 

    sql ALIAS FOR $1; 
    max ALIAS FOR $2; 
    rcount INT; 

BEGIN 

    EXECUTE sql; 
    GET DIAGNOSTICS rcount = ROW_COUNT; 

    IF rcount > max THEN 

     --ROLLBACK; 
     RAISE EXCEPTION 'Too much rows affected (%).', rcount; 

    END IF; 

    --COMMIT; 

END; 

$BODY$ LANGUAGE plpgsql 

Ensuite, appelez comme

select update_max('update t1 set id=id+10 where id < 4', 3); 

où le premier param ist votre requête SQL et 2e vos lignes max.

2

Simon avait une bonne idée mais sa mise en œuvre est inutilement compliquée. C'est ma proposition:

create or replace function trg_check_max_4()     
returns trigger as $$ 
begin 
     perform true from pg_class 
       where relname='check_max_4' and relnamespace=pg_my_temp_schema(); 
     if not FOUND then 
       create temporary table check_max_4 
         (value int check (value<=4)) 
         on commit drop; 
       insert into check_max_4 values (0); 
     end if; 

     update check_max_4 set value=value+1; 
     return new; 
end; $$ language plpgsql; 
Questions connexes