Triggery w PostgreSQL dzielą się na trzy główne kategorie:
BEFORE— odpalają się przed wykonaniem głównej operacji (np. przedINSERT,UPDATEalboDELETE). Możesz ich użyć, żeby zablokować operację albo zmodyfikować dane zanim zostaną zapisane.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.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 triggerINSTEAD 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ł.
GO TO FULL VERSION