2010-04-01 18 views
8

Quiero saber el número de filas que se verán afectadas por la consulta UPDATE en BEFORE por desencadenante de sentencia. ¿Es eso posible?cuenta el número de filas que se verán afectadas antes de la actualización en el desencadenante

El problema es que quiero permitir solo las consultas que se actualizarán hasta en 4 filas. Si el número de filas afectadas es 5 o más, quiero generar un error.

No quiero hacer esto en el código porque necesito esta verificación en el nivel de db. ¿Es esto posible?

Gracias de antemano por cualquier pista sobre la que

+0

Consideré el uso de la consulta normal + retrotracción si las filas afectadas son mayores que max, y eso parece tener el mejor valor según veo. Bueno, en el 99% + habrá buenas consultas (4 filas actualizadas como máximo), pero esto es solo seguridad adicional para el sistema. La tabla con este 'problema' es bastante grande y crítica para el sistema, por lo que restaurarla después de una consulta errónea puede ser doloroso para todos. Gracias a todos por las respuestas. No sé cuál aceptar porque todos son útiles :) – sbczk

+0

¿Por qué quieres hacer eso? Tal vez hay una manera mucho más fácil de hacer eso que una consulta tan extraña. Además ... el uso del recuento (si fuera posible) será más lento mientras la tabla crece. –

+0

¿Entonces piensas que cuando tienes un máximo de 4 filas modificadas, la consulta no puede ser mal escrita? Esto suena como una especie de sensación de seguridad falsa. –

Respuesta

1

He creado algo como esto:

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; 

La idea principal es tener un disparador en 'antes de actualización para cada fila' que crea (si es necesario) una tabla temporal (que se deja caer en el fin de la transacción). En esta tabla solo hay una fila con un valor, es decir, el número de filas actualizadas en la transacción actual. Para cada actualización, el valor se incrementa. Si el valor es mayor que 4, la transacción se detiene.

Pero creo que esta es una solución incorrecta para su problema. ¿Cuál es el problema para ejecutar una consulta incorrecta sobre la que ha escrito, dos veces, por lo que tendrá 8 filas modificadas? ¿Qué pasa con las filas de borrado o truncarlas?

0

Nunca he trabajado con PostgreSQL, así que mi respuesta no se pueden aplicar. En SQL Server, el disparador puede llamar a un procedimiento almacenado que hacer una de dos cosas:

  1. realizar una SELECT COUNT (*) para determinar el número de registros que se verán afectados por la actualización, y sólo EJECUTAR la actualización si el recuento es 4 o menos
  2. realizar la actualización dentro de una transacción, y sólo confirmar la transacción si el número devuelto de filas afectadas es 4 o menos

No. 1 es el tiempo vulnerable (el número de registros afectados por la ACTUALIZACIÓN puede cambiar entre el COUNT (*) verificación y la ACTUALIZACIÓN real. N º 2 es bastante ineficiente, si hay muchos ca ses donde el número de filas actualizadas es mayor que 4.

1

PostgreSQL tiene dos types of triggers: activadores de filas y sentencias. Los activadores de fila solo funcionan en el contexto de una fila, por lo que no puede usarlos. Desafortunadamente, la declaración "anterior" desencadena el don't see qué tipo de cambio está a punto de suceder, así que tampoco creo que pueda usarlo.

Basado en eso, diría que es poco probable que pueda generar ese tipo de protección en la base de datos utilizando activadores, a menos que no le importe usar un activador "después" y anular la transacción si el la condición no está satisfecha No me importaría que se demuestre lo contrario. :)

1

Eche un vistazo al uso del nivel de aislamiento serializable. Creo que esto le dará una visión coherente de los datos de la base de datos dentro de su transacción. Entonces puedes usar la opción n. ° 1 que mencionó MusiGenesis, sin la vulnerabilidad de sincronización. Pruébalo por supuesto para validar.

+0

Sí, la mejor manera de colocar la eficiencia de la base de datos ya que todas las transacciones se realizarían una por una :( –

2

Escriba una función que actualice las filas o realice una reversión. Perdón por el pobre formato de estilo.

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 

Entonces llamarlo como

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

donde el primer parámetro no está en su sentencia-SQL y el 2º sus filas max.

2

Simon tenía una buena idea pero su implementación es innecesariamente complicada. Esta es mi proposición:

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; 
Cuestiones relacionadas