CodeGym /Kursy /SQL SELF /Typy triggerów w PostgreSQL: BEFORE, AFTER, INSTEAD OF

Typy triggerów w PostgreSQL: BEFORE, AFTER, INSTEAD OF

SQL SELF
Poziom 57 , Lekcja 1
Dostępny

Triggery w PostgreSQL dzielą się na trzy główne kategorie:

  1. BEFORE — odpalają się przed wykonaniem głównej operacji (np. przed INSERT, UPDATE albo DELETE). Możesz ich użyć, żeby zablokować operację albo zmodyfikować dane zanim zostaną zapisane.

  2. AFTER — odpalają się po tym, jak główna operacja się skończy. Ten typ często używa się do logowania, tworzenia powiązanych rekordów albo robienia rzeczy, które zależą od sukcesu operacji.

  3. INSTEAD OF — odpalają się zamiast faktycznej operacji. Używane tylko dla widoków (views). Na przykład, jeśli ktoś próbuje wrzucić dane do widoku, możesz tym sterować przez trigger INSTEAD OF.

Trigger BEFORE

Triggery BEFORE odpalają się zanim PostgreSQL zrobi główną operację. Są spoko, jeśli chcesz sprawdzić albo zmienić dane tuż przed ich zapisaniem. Wyobraź sobie, że to jak kontrola bagażu przed wejściem do samolotu: jeśli bagaż nie pasuje, można go zmienić albo w ogóle zablokować.

Dajmy przykład z walidacją danych przed insertem. Załóżmy, że mamy tabelę students, gdzie trzymamy info o studentach. Chcemy się upewnić, że wiek studenta nie przekracza 100 lat (raczej mało prawdopodobne, nie?).

Tworzymy tabelę:

CREATE TABLE students (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    age INT NOT NULL
);

Tworzymy funkcję dla triggera:

CREATE OR REPLACE FUNCTION validate_age()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.age > 100 THEN
        RAISE EXCEPTION 'Wiek studenta nie może być większy niż 100 lat!';
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Tworzymy trigger:

CREATE TRIGGER before_insert_students
BEFORE INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION validate_age();

Teraz, jeśli spróbujesz wrzucić studenta z wiekiem powyżej 100, PostgreSQL wywali błąd:

INSERT INTO students (name, age) VALUES ('Ivan Ivanov', 120);
-- Błąd: Wiek studenta nie może być większy niż 100 lat!

No i taka walidacja!

Trigger AFTER

Triggery AFTER odpalają się po tym, jak główna operacja zakończy się sukcesem. Są przydatne do robienia rzeczy, które zależą od wyniku operacji. Na przykład logowanie albo tworzenie powiązanych rekordów.

Scenariusz: mamy tabelę students i chcemy zapisywać wszystkie zmiany do osobnej tabeli logów.

Tworzymy tabelę logów:

CREATE TABLE students_log (
    id SERIAL PRIMARY KEY,
    student_id INT,
    operation TEXT,
    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Tworzymy funkcję dla triggera:

CREATE OR REPLACE FUNCTION log_student_changes()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO students_log (student_id, operation)
    VALUES (NEW.id, TG_OP);  -- TG_OP zawiera typ operacji: INSERT, UPDATE albo DELETE
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Tworzymy trigger:

CREATE TRIGGER after_insert_students
AFTER INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION log_student_changes();

Teraz, jak dodasz nowego studenta, PostgreSQL automatycznie zapisze operację do loga:

INSERT INTO students (name, age) VALUES ('Anna Ling', 22);

SELECT * FROM students_log;
-- Wynik:
-- id | student_id | operation |        timestamp
--  1 |          1 | INSERT    | 2023-11-15 12:00:00

Trigger INSTEAD OF

Triggery INSTEAD OF odpalają się zamiast wykonania operacji. To jedyny typ triggerów, który można użyć z widokami (views). Dają elastyczność w obsłudze operacji, których nie da się zrobić bezpośrednio na widoku.

Scenariusz: mamy dwie tabele courses i teachers. Stworzymy widok, który je łączy, i napiszemy trigger do obsługi insertów przez ten widok.

Tworzymy tabele:

CREATE TABLE courses (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    teacher_id INT NOT NULL
);

CREATE TABLE teachers (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL
);

Tworzymy widok:

CREATE VIEW course_details AS
SELECT 
    courses.id AS course_id,
    courses.name AS course_name,
    teachers.name AS teacher_name
FROM courses
JOIN teachers ON courses.teacher_id = teachers.id;

Problem: nie możemy po prostu wrzucić danych do widoku, bo on agreguje dane z dwóch tabel. Rozwiązanie: używamy triggera INSTEAD OF.

Tworzymy funkcję dla triggera:

CREATE OR REPLACE FUNCTION insert_course_details()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO teachers (name) VALUES (NEW.teacher_name) RETURNING id INTO NEW.teacher_id;
    INSERT INTO courses (name, teacher_id) VALUES (NEW.course_name, NEW.teacher_id);
    RETURN NULL;  -- W widoku dane nie są zapisywane
END;
$$ LANGUAGE plpgsql;

Tworzymy trigger:

CREATE TRIGGER instead_of_insert_course_details
INSTEAD OF INSERT ON course_details
FOR EACH ROW
EXECUTE FUNCTION insert_course_details();

Teraz możesz wrzucać dane bezpośrednio do widoku:

INSERT INTO course_details (course_name, teacher_name)
VALUES ('Matematyka', 'Alex Ming');

SELECT * FROM courses;
-- Wynik:
-- id | name        | teacher_id
--  1 | Matematyka  | 1

SELECT * FROM teachers;
-- Wynik:
-- id | name
--  1 | Alex Ming

Porównanie typów triggerów

Typ triggera Kiedy się wykonuje Główne zastosowanie
BEFORE Przed wykonaniem operacji Walidacja, przygotowanie danych
AFTER Po udanym zakończeniu Logowanie, aktualizacja powiązanych danych
INSTEAD OF Zamiast wykonania operacji Obsługa operacji na widokach

Cechy i ograniczenia

BEFORE triggery mogą zmieniać dane przed wykonaniem operacji. Na przykład możesz automatycznie formatować imiona (np. robić wielkie litery).

AFTER triggery nie mogą już zmienić danych, bo operacja już się skończyła. Służą tylko do dalszych akcji.

INSTEAD OF triggery stosuje się tylko do widoków. Pozwalają ogarnąć złożoną logikę insertów/zmian danych w kilku powiązanych tabelach.

No i to wszystko na dziś! Jeśli BEFORE, AFTER i INSTEAD OF wydają się trudne, nie przejmuj się. Najważniejsze — pamiętać ich główną zasadę i scenariusze użycia. Spróbuj zrobić kilka przykładów samodzielnie, żeby utrwalić materiał.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION