2010-04-01 8 views
8

Chcę znać liczbę wierszy, na które będzie miało wpływ zapytanie UPDATE w BEFORE dla wyzwalacza instrukcji. Czy to jest możliwe?liczba wierszy, których należy dotyczyć przed aktualizacją w wyzwalaczu

Problem polega na tym, że chcę zezwolić tylko na zapytania, które będą aktualizować do 4 wierszy. Jeśli liczba dotkniętych wierszy wynosi 5 lub więcej, chcę podnieść błąd.

Nie chcę tego robić w kodzie, ponieważ potrzebuję tego sprawdzenia na poziomie db. Czy to w ogóle możliwe?

Dzięki z góry za wszelkie wskazówki na temat tego

+0

Zastanawiam się, czy użyć normalnego zapytania + wycofania, jeśli liczba dotkniętych błędów jest większa niż maksymalna, i wydaje się, że jest to najlepsza wartość, jaką widzę. Dobrze w 99% + będą dobre zapytania (4 zaktualizowane wiersze max), ale jest to tylko dodatkowe zabezpieczenie dla systemu. Tabela z tym "problemem" jest dość olbrzymia i krytyczna dla systemu, więc przywracanie jej po takim błędnym pytaniu może być bolesne dla wszystkich. Dziękuję wszystkim za odpowiedzi. Nie wiem, który z nich zaakceptować, ponieważ wszystkie one są pomocne :) – sbczk

+0

Dlaczego chcesz to zrobić? Być może jest to o wiele łatwiejszy sposób niż takie dziwne zapytanie. Co więcej ... używanie licznika (jeśli było to możliwe) będzie wolniejsze, gdy tabela rośnie. –

+0

Więc myślisz, że gdy zmienisz maksymalnie 4 wiersze, zapytanie nie może być źle wpisane? To brzmi jak jakieś fałszywe poczucie bezpieczeństwa. –

Odpowiedz

1

stworzyłem coś takiego:

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; 

Główną ideą jest, aby mieć na spust „przed aktualizacją dla każdego wiersza, który tworzy” (jeśli to konieczne) tabeli tymczasowej (czyli spadła u koniec transakcji). W tej tabeli znajduje się tylko jeden wiersz z jedną wartością, czyli liczba zaktualizowanych wierszy w bieżącej transakcji. Dla każdej aktualizacji wartość jest zwiększana. Jeśli wartość jest większa niż 4, transakcja zostaje zatrzymana.

Ale myślę, że to niewłaściwe rozwiązanie twojego problemu. Na czym polega problem polegający na dwukrotnym uruchomieniu takiego błędnego zapytania, o którym piszesz, więc zmienisz 8 wierszy. Co powiesz na usuwanie wierszy lub ich obcinanie?

0

Nigdy nie pracowałem z PostgreSQL, więc moja odpowiedź może nie mieć zastosowania. W SQL Server, wyzwalacz może wywołać procedurę przechowywaną, która będzie zrobić jedną z dwóch rzeczy:

  1. wykonać SELECT COUNT (*), aby określić liczbę rekordów, które zostaną dotknięte UPDATE, a następnie tylko wykonują UPDATE, jeśli liczba wynosi 4 lub mniej
  2. wykonać aktualizację w transakcji, a jedynie zatwierdzenia transakcji jeśli zwracany liczbę wierszy wynosi 4 lub mniej

nr 1 jest czas podatne (numer rekordów objętych AKTUALIZACJĄ może się zmieniać pomiędzy sprawdzeniem COUNT (*) a aktualną AKTUALIZACJĄ 2. Nie. 2 jest dość nieefektywne, jeśli istnieje wiele ca ses, w których liczba zaktualizowanych wierszy jest większa niż 4.

1

PostgreSQL ma dwa wyzwalacze types of triggers: wiersz i instrukcja. Rzędy wiersza działają tylko w kontekście wiersza, więc nie możesz ich użyć. Niestety, stwierdzenie "przed" wyzwala don't see, jaki rodzaj zmian ma nastąpić, więc nie sądzę, że możesz z nich skorzystać.

Na tej podstawie powiedziałbym, że jest mało prawdopodobne, że będziesz w stanie zbudować tego rodzaju ochronę w bazie danych za pomocą wyzwalaczy, chyba że nie masz nic przeciwko używaniu wyzwalacza "po" i wycofaniu transakcji, jeśli warunek nie jest spełniony. Nie miałby nic przeciwko udowodnieniu, że jest źle. :)

1

Spójrz na użycie Serializable Isolation Level. Wierzę, że zapewni to spójny widok danych bazy danych w ramach transakcji. Następnie możesz użyć opcji nr 1 wspomnianej przez MusiGenesis, bez luki czasowej. Przetestuj to oczywiście w celu sprawdzenia poprawności.

+0

Tak, najlepszy sposób na upuszczenie wydajność bazy danych, ponieważ wszystkie transakcje byłyby wykonywane jeden po drugim :( –

2

Napisz funkcję, która aktualizuje wiersze lub wykonuje wycofanie. Przepraszamy za złe formatowanie stylu.

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 

Następnie nazwać jak

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

gdzie pierwszy param ist swojej instrukcja_sql i 2. maks wiersze.

2

Simon miał dobry pomysł, ale jego implementacja jest niepotrzebnie skomplikowana. To jest moja propozycja:

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;