количество строк, которые будут затронуты до обновления в триггере - PullRequest
8 голосов
/ 01 апреля 2010

Я хочу знать количество строк, на которые будет влиять запрос UPDATE в BEFORE на триггер оператора. Это возможно?

Проблема в том, что я хочу разрешить только запросы, которые будут обновлять до 4 строк. Если количество затронутых строк равно 5 или более, я хочу вызвать ошибку.

Я не хочу делать это в коде, потому что мне нужна эта проверка на уровне БД. Это вообще возможно?

Заранее спасибо за любые подсказки по этому поводу

Ответы [ 6 ]

2 голосов
/ 02 апреля 2010

У Саймона была хорошая идея, но его реализация излишне сложна. Это мое предложение:

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;
2 голосов
/ 01 апреля 2010

Напишите функцию, которая обновляет строки для вас или выполняет откат. Извините за плохое форматирование стиля.

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

Тогда назовите это как

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

где первый параметр - это ваше sql-выражение, а второй - ваш максимум строк.

1 голос
/ 02 апреля 2010

Я создал что-то вроде этого:

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;

Основная идея состоит в том, чтобы иметь триггер «до обновления для каждой строки», который создает (при необходимости) временную таблицу (которая удаляется в конце транзакции). В этой таблице есть только одна строка с одним значением, то есть количеством обновленных строк в текущей транзакции. Для каждого обновления значение увеличивается. Если значение больше 4, транзакция останавливается.

Но я думаю, что это неправильное решение вашей проблемы. В чем проблема, если вы дважды запускаете такой неправильный запрос, о котором вы написали 8 строк. А как насчет удаления строк или их усечения?

1 голос
/ 01 апреля 2010

Посмотрите на использование Сериализуемого уровня изоляции. Я считаю, что это даст вам согласованное представление данных базы данных в вашей транзакции. Тогда вы можете использовать опцию №1, о которой упоминал MusiGenesis, без временной ошибки. Проверьте это, конечно, чтобы проверить.

1 голос
/ 01 апреля 2010

PostgreSQL имеет два типа триггеров : триггеры строк и операторов. Триггеры строки работают только в контексте строки, поэтому вы не можете их использовать. К сожалению, оператор «before» вызывает , но не видит , какие изменения произойдут, поэтому я не верю, что вы тоже можете их использовать.

Исходя из этого, я бы сказал, что вряд ли вы сможете встроить такую ​​защиту в базу данных с помощью триггеров, если только вы не возражаете против использования триггера "после" и отката транзакции, если условие не удовлетворен Не против того, чтобы оказаться неправым. :)

0 голосов
/ 01 апреля 2010

Я никогда не работал с postgresql, поэтому мой ответ может не применяться. В SQL Server ваш триггер может вызывать хранимую процедуру, которая будет выполнять одно из двух:

  1. Выполните команду SELECT COUNT (*), чтобы определить количество записей, на которые повлияет ОБНОВЛЕНИЕ, а затем выполните ОБНОВЛЕНИЕ, только если число равно 4 или меньше
  2. Выполните ОБНОВЛЕНИЕ в транзакции и зафиксируйте транзакцию только в том случае, если возвращаемое количество затронутых строк равно 4 или менее

Нет. 1 является уязвимым по времени (количество записей, на которые влияет UPDATE, может изменяться между проверкой COUNT (*) и фактическим UPDATE. № 2 довольно неэффективно, если во многих случаях число обновленных строк превышает 4.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...