PostgreSQL'de trigger oluşturmak iki adımdır: önce RETURNS trigger tipinde, argümansız bir trigger fonksiyonu yazarsınız (genellikle PL/pgSQL ile), sonra CREATE TRIGGER ad BEFORE|AFTER|INSTEAD OF olay ON tablo FOR EACH ROW|STATEMENT EXECUTE FUNCTION fonksiyon() komutuyla onu bir tabloya ya da view'e bağlarsınız. Satır düzeyindeki trigger fonksiyonu içinde değişen satıra NEW ve OLD değişkenleriyle, işlemin türüne TG_OP ile erişirsiniz.

Bu yazıda trigger türlerini ve zamanlamayı, BEFORE trigger ile veri doğrulama ve düzeltme, AFTER trigger ile denetim (audit) tablosu, transition table ile komut düzeyinde özet, view üzerinde INSTEAD OF trigger, WHEN koşulu, UPDATE OF, trigger argümanları, sıralama kuralları ve kısaca event trigger konularını çalışan örneklerle ele alıyoruz. PL/pgSQL dilinin kendisi PL/pgSQL temelleri yazısında; seri PostgreSQL öğrenme rehberinde toplanıyor.

Trigger türleri: ne zaman ve kaç kez?

Trigger türleri: ne zaman ve kaç kez? Zaman, Düzey

Kılavuz bir trigger'ı, belirli bir işlem yapıldığında veritabanının bir fonksiyonu kendiliğinden çalıştırması için yapılan tanım olarak açıklar. Trigger'lar tablolara (bölümlenmiş olsun ya da olmasın), view'lere ve yabancı tablolara bağlanabilir. İki eksende sınıflandırılırlar:

  • Zaman: BEFORE (işlemden önce, kısıtlar denetlenmeden), AFTER (işlem ve kısıt denetimi tamamlandıktan sonra) veya INSTEAD OF (işlemin yerine, yalnızca view'lerde).
  • Düzey: FOR EACH ROW değişen her satır için bir kez; FOR EACH STATEMENT komut başına bir kez. Hiçbiri yazılmazsa FOR EACH STATEMENT varsayılır. Sıfır satır değiştiren bir komut bile komut düzeyindeki trigger'ları çalıştırır.

Olay olarak INSERT, UPDATE (isteğe bağlı sütun listesiyle), DELETE ve TRUNCATE kullanılabilir; birden çok olay OR ile birleştirilir. TRUNCATE trigger'ları yalnızca komut düzeyinde olabilir. SELECT satır değiştirmediği için SELECT trigger'ı yoktur. MERGE için de ayrı trigger tanımlanmaz; MERGE yaptığı işlemlere göre INSERT, UPDATE ve DELETE trigger'larını çalıştırır.

ZamanOlaySatır düzeyiKomut düzeyi
BEFOREINSERT/UPDATE/DELETETablolar ve yabancı tablolarTablolar, view'ler, yabancı tablolar
BEFORETRUNCATEYokTablolar ve yabancı tablolar
AFTERINSERT/UPDATE/DELETETablolar ve yabancı tablolarTablolar, view'ler, yabancı tablolar
AFTERTRUNCATEYokTablolar ve yabancı tablolar
INSTEAD OFINSERT/UPDATE/DELETEView'lerYok

Bir trigger her zaman onu tetikleyen komutla aynı transaction içinde çalışır; komut veya trigger hata verirse ikisinin etkileri birlikte geri alınır. Trigger fonksiyonu ise trigger'dan önce tanımlanmış olmalı, argümansız olmalı ve trigger tipini döndürmelidir. Kılavuza göre trigger fonksiyonu düz SQL fonksiyon diliyle yazılamaz; PL/pgSQL gibi prosedürel bir dil ya da C kullanılır. Aynı fonksiyon birden çok trigger tarafından paylaşılabilir.

Trigger fonksiyonundaki özel değişkenler

PL/pgSQL fonksiyonu trigger olarak çağrıldığında en dış blokta şu değişkenler kendiliğinden tanımlanır:

Değişkenİçerik
NEWINSERT/UPDATE için yeni satır (satır düzeyi); komut düzeyinde ve DELETE'te NULL
OLDUPDATE/DELETE için eski satır (satır düzeyi); komut düzeyinde ve INSERT'te NULL
TG_OPINSERT, UPDATE, DELETE veya TRUNCATE
TG_WHENBEFORE, AFTER veya INSTEAD OF
TG_LEVELROW veya STATEMENT
TG_NAMEÇalışan trigger'ın adı
TG_TABLE_NAME, TG_TABLE_SCHEMATrigger'ı tetikleyen tablo ve şeması
TG_NARGS, TG_ARGVCREATE TRIGGER içinde verilen argüman sayısı ve dizisi (0'dan başlar)

TG_RELNAME da hâlâ vardır ama kılavuz onu kullanımdan kalkmış sayar ve TG_TABLE_NAME kullanmanızı önerir. TG_ARGV dizisinde geçersiz bir indeks NULL döndürür.

Dönüş değeri kuralları

Trigger fonksiyonu ya NULL ya da tetiklendiği tablonun yapısında bir satır döndürmelidir. Dönüş değerinin anlamı trigger türüne göre değişir ve en çok hata buradan çıkar:

  • BEFORE ROW: NULL döndürürse o satır için işlem atlanır (sonraki trigger'lar da çalışmaz). NEW döndürürse işlem bu satırla devam eder; NEW üzerinde yaptığınız değişiklikler yazılacak satırı değiştirir. DELETE için genel kalıp OLD döndürmektir; çünkü DELETE'te NEW zaten NULL'dır ve onu döndürmek silmeyi atlar.
  • AFTER ROW ve tüm komut düzeyi trigger'lar: dönüş değeri yok sayılır, NULL döndürmek yeterlidir. Yine de hata yükselterek tüm işlemi iptal edebilirler.
  • INSTEAD OF: değişikliği gerçekten yaptıysa NEW (silmede OLD) döndürmeli; NULL döndürürse satır yapılmamış sayılır ve etkilenen satır sayısına eklenmez.

BEFORE trigger: doğrulama ve satırı düzeltme

BEFORE trigger: doğrulama ve satırı düzeltme

Kılavuz, satır düzeyindeki BEFORE trigger'ları eklenecek veya güncellenecek veriyi denetlemek ya da değiştirmek için tipik araç olarak tanımlar; örneğin bir zaman damgası sütununu doldurmak veya satırdaki iki alanın tutarlılığını kontrol etmek. Aşağıdaki trigger ürün adını kırpıp baş harflerini büyütüyor, fiyatı doğruluyor ve güncellenme zamanını yazıyor:

sql
CREATE TABLE urun (
    id           integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    ad           text NOT NULL,
    fiyat        numeric(10,2) NOT NULL,
    guncellenme  timestamptz
);

CREATE FUNCTION urun_hazirla() RETURNS trigger
    LANGUAGE plpgsql
AS $$
BEGIN
    NEW.ad := initcap(btrim(NEW.ad));
    IF NEW.fiyat <= 0 THEN
        RAISE EXCEPTION 'Fiyat pozitif olmalı (ürün: %)', NEW.ad;
    END IF;
    NEW.guncellenme := now();
    RETURN NEW;
END
$$;

CREATE TRIGGER urun_hazirla
    BEFORE INSERT OR UPDATE ON urun
    FOR EACH ROW EXECUTE FUNCTION urun_hazirla();

INSERT INTO urun (ad, fiyat) VALUES ('  bakır cezve ', 540), ('çay tabağı', 30);
SELECT id, ad, fiyat, guncellenme IS NOT NULL AS damgali FROM urun ORDER BY id;
INSERT INTO urun (ad, fiyat) VALUES ('hatalı ürün', 0);
text
 id |     ad      | fiyat  | damgali
----+-------------+--------+---------
  1 | Bakır Cezve | 540.00 | t
  2 | Çay Tabağı  |  30.00 | t
(2 rows)

ERROR:  Fiyat pozitif olmalı (ürün: Hatalı Ürün)
CONTEXT:  PL/pgSQL function urun_hazirla() line 5 at RAISE

Baştaki ve sondaki boşluklar silindi, adlar düzeltildi ve damga yazıldı. Sıfır fiyatlı satır RAISE EXCEPTION ile reddedildi; aynı komuttaki diğer satırlar da geri alınır, çünkü trigger komutla aynı transaction'dadır. Bu örnekteki ad dönüşümü kümenin varsayılan sıralama kuralına (burada builtin C.UTF-8) göre yapıldı; Türkçe harflerin büyük/küçük dönüşümünde collation farkları collation ve Türkçe sıralama yazısında.

Üretilen sütunlarla ilgili bir ayrıntı: saklanan (stored) üretilen sütunlar BEFORE trigger'lardan sonra, AFTER trigger'lardan önce hesaplanır. Bu yüzden BEFORE trigger'da NEW içindeki üretilen değer henüz güncel değildir ve ona yapılan değişiklikler yok sayılır. Sanal (virtual) üretilen sütunlar trigger çalışırken hiç hesaplanmaz. PostgreSQL 18'de sanal üretilen sütunların varsayılan olduğu düşünülürse, trigger içinde üretilen sütuna güvenmeyin; konu CREATE TABLE ve ALTER TABLE yazısında.

AFTER trigger: denetim (audit) tablosu

Satır düzeyindeki AFTER trigger'lar, kılavuza göre değişiklikleri başka tablolara yaymak ya da başka tablolarla tutarlılık denetimi yapmak için en uygun yerdir; çünkü AFTER trigger satırın son hâlini gördüğünden emin olabilir, oysa bir BEFORE trigger'dan sonra başka BEFORE trigger'lar çalışıp satırı değiştirebilir. Aşağıdaki genel amaçlı fonksiyon her değişikliği eski ve yeni satırın JSON hâliyle bir denetim tablosuna yazıyor; TG_TABLE_NAME sayesinde aynı fonksiyon başka tablolarda da kullanılabilir.

sql
CREATE TABLE denetim (
    id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    tablo      text NOT NULL,
    islem      text NOT NULL,
    kullanici  text NOT NULL DEFAULT current_user,
    eski       jsonb,
    yeni       jsonb
);

CREATE FUNCTION denetim_yaz() RETURNS trigger
    LANGUAGE plpgsql
AS $$
BEGIN
    IF TG_OP = 'DELETE' THEN
        INSERT INTO denetim (tablo, islem, eski)
        VALUES (TG_TABLE_NAME, TG_OP, to_jsonb(OLD));
    ELSIF TG_OP = 'UPDATE' THEN
        INSERT INTO denetim (tablo, islem, eski, yeni)
        VALUES (TG_TABLE_NAME, TG_OP, to_jsonb(OLD), to_jsonb(NEW));
    ELSE
        INSERT INTO denetim (tablo, islem, yeni)
        VALUES (TG_TABLE_NAME, TG_OP, to_jsonb(NEW));
    END IF;
    RETURN NULL;  -- AFTER trigger: dönüş değeri yok sayılır
END
$$;

CREATE TRIGGER urun_denetim
    AFTER INSERT OR UPDATE OR DELETE ON urun
    FOR EACH ROW EXECUTE FUNCTION denetim_yaz();

UPDATE urun SET fiyat = 35 WHERE ad = 'Çay Tabağı';
DELETE FROM urun WHERE ad = 'Bakır Cezve';
SELECT id, tablo, islem, eski->>'fiyat' AS eski_fiyat, yeni->>'fiyat' AS yeni_fiyat
FROM denetim ORDER BY id;
text
 id | tablo | islem  | eski_fiyat | yeni_fiyat
----+-------+--------+------------+------------
  1 | urun  | UPDATE | 30.00      | 35.00
  2 | urun  | DELETE | 540.00     |
(2 rows)

UPDATE satırında eski ve yeni fiyat, DELETE satırında yalnızca eski fiyat var. to_jsonb(OLD) satırın tamamını tek bir JSON değeri olarak saklar; sorgulama ayrıntıları JSONB kullanımı yazısında. Kılavuz bir performans notu da düşer: BEFORE ya da AFTER seçmek için özel bir nedeniniz yoksa BEFORE daha verimlidir, çünkü işlem bilgisinin komut sonuna kadar saklanması gerekmez. Denetim kaydı gibi satırın kesin son hâline ihtiyaç duyan işler için ise AFTER doğru seçimdir.

Rol ile ilgili bir PostgreSQL 18 değişikliği burada önemlidir. Sürüm notlarına göre AFTER trigger'lar artık trigger olayının kuyruğa alındığı andaki rol ile çalışır; önceden, trigger'ın çalıştığı andaki rol (örneğin COMMIT anı) kullanılıyordu. Kılavuzun genel kuralı da aynıdır: trigger fonksiyonu SECURITY DEFINER değilse, olayı kuyruğa alan rolün yetkileriyle çalışır. Denetim tablosundaki current_user değeri bu kurala göre belirlenir. Sürüm ayrıntıları PostgreSQL 18 yenilikleri yazısında.

Transition table: komut düzeyinde tüm satırları görmek

Komut düzeyindeki trigger'lar varsayılan olarak tek tek satırları göremez. AFTER trigger'larda REFERENCING NEW TABLE AS ad veya OLD TABLE AS ad yazarak komutun eklediği, güncellediği veya sildiği tüm satırları içeren salt okunur geçiş tabloları (transition relation) isteyebilirsiniz. Bu özellik yalnızca sıradan tablolardaki AFTER trigger'larda kullanılabilir; UPDATE OF sütun listesiyle ve birden çok olayla birlikte kullanılamaz.

sql
CREATE FUNCTION toplu_ozet() RETURNS trigger
    LANGUAGE plpgsql
AS $$
DECLARE
    adet integer;
    ortalama numeric;
BEGIN
    SELECT count(*), round(avg(fiyat), 2) INTO adet, ortalama FROM yeni_satirlar;
    RAISE NOTICE '% komutu % satır ekledi, ortalama fiyat %', TG_OP, adet, ortalama;
    RETURN NULL;
END
$$;

CREATE TRIGGER urun_toplu_ozet
    AFTER INSERT ON urun
    REFERENCING NEW TABLE AS yeni_satirlar
    FOR EACH STATEMENT EXECUTE FUNCTION toplu_ozet();

INSERT INTO urun (ad, fiyat)
SELECT 'ürün ' || i, i * 10 FROM generate_series(1, 4) AS i;
INSERT INTO urun (ad, fiyat) SELECT ad, fiyat FROM urun WHERE false;
text
NOTICE:  INSERT komutu 4 satır ekledi, ortalama fiyat 25.00
INSERT 0 4
NOTICE:  INSERT komutu 0 satır ekledi, ortalama fiyat <NULL>
INSERT 0 0

Trigger dört satırlık ekleme için bir kez çalıştı ve geçiş tablosundan özet çıkardı. İkinci INSERT hiç satır eklemediği hâlde trigger yine çalıştı; kılavuzun dediği gibi komut düzeyindeki trigger'lar sıfır satırda da tetiklenir. RAISE, NULL değeri <NULL> olarak yazdı. Binlerce satırlık toplu işlemlerde her satır için ayrı ayrı denetim kaydı yazmak yerine böyle bir komut düzeyi trigger ile tek seferde işlem yapmak daha az çağrı demektir.

INSTEAD OF trigger ile view'i güncellenebilir yapmak

Birden fazla tabloyu birleştiren ya da gruplama yapan bir view otomatik olarak güncellenemez. INSTEAD OF trigger, view'e gelen komutu yakalayıp alttaki tablolara uygun değişikliği yapma görevini üstlenir. Bu trigger'lar yalnızca view'lerde ve yalnızca satır düzeyinde tanımlanabilir ve WHEN koşulu desteklemez.

sql
CREATE TABLE stok (urun_id integer PRIMARY KEY, adet integer NOT NULL);
INSERT INTO stok SELECT id, 10 FROM urun;

CREATE VIEW urun_stok AS
    SELECT u.id, u.ad, u.fiyat, s.adet
    FROM urun u JOIN stok s ON s.urun_id = u.id;

UPDATE urun_stok SET adet = 0 WHERE id = 2;

CREATE FUNCTION urun_stok_guncelle() RETURNS trigger
    LANGUAGE plpgsql
AS $$
BEGIN
    UPDATE urun SET fiyat = NEW.fiyat WHERE id = OLD.id;
    UPDATE stok SET adet = NEW.adet WHERE urun_id = OLD.id;
    RETURN NEW;
END
$$;

CREATE TRIGGER urun_stok_guncelle
    INSTEAD OF UPDATE ON urun_stok
    FOR EACH ROW EXECUTE FUNCTION urun_stok_guncelle();

UPDATE urun_stok SET adet = 0, fiyat = 32.50 WHERE id = 2;
SELECT * FROM urun_stok WHERE id = 2;
text
ERROR:  cannot update view "urun_stok"
DETAIL:  Views that do not select from a single table or view are not automatically updatable.
HINT:  To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
CREATE FUNCTION
CREATE TRIGGER
UPDATE 1
 id |     ad     | fiyat | adet
----+------------+-------+------
  2 | Çay Tabağı | 32.50 |    0
(1 row)

Trigger yokken PostgreSQL view'i güncelleyemedi ve ipucu satırında INSTEAD OF UPDATE trigger önerdi. Trigger eklendikten sonra aynı komut iki tabloyu birden güncelledi. Fonksiyon NEW döndürdüğü için komut "UPDATE 1" bildirdi; NULL döndürseydi "UPDATE 0" görürdük. View tarafındaki kurallar view ve materialized view yazısında anlatılıyor.

WHEN, UPDATE OF, argümanlar ve trigger yönetimi

WHEN (koşul) trigger'ın çalışıp çalışmayacağını belirler; satır düzeyinde koşul OLD.sutun ve NEW.sutun değerlerine bakabilir, ancak alt sorgu içeremez. BEFORE trigger'da WHEN kullanmak, aynı koşulu fonksiyonun başında denetlemekten pek farklı değildir. AFTER trigger'da ise fark büyüktür: koşul doğru değilse olay kuyruğa hiç alınmaz ve satır komut sonunda yeniden okunmaz; kılavuz bunun çok satır değiştiren komutlarda belirgin hız kazandırabileceğini belirtir. UPDATE OF sutun ise trigger'ı yalnızca o sütun UPDATE komutunun SET listesinde geçtiğinde çalıştırır.

sql
CREATE FUNCTION fiyat_degisim_notu() RETURNS trigger
    LANGUAGE plpgsql
AS $$
BEGIN
    RAISE NOTICE '[%] % fiyatı: % -> % (argüman: %)',
        TG_NAME, NEW.ad, OLD.fiyat, NEW.fiyat, TG_ARGV[0];
    RETURN NULL;
END
$$;

CREATE TRIGGER fiyat_izle
    AFTER UPDATE OF fiyat ON urun
    FOR EACH ROW
    WHEN (OLD.fiyat IS DISTINCT FROM NEW.fiyat)
    EXECUTE FUNCTION fiyat_degisim_notu('kampanya');

UPDATE urun SET ad = 'Çay Tabağı Altılı' WHERE id = 2;
UPDATE urun SET fiyat = fiyat WHERE id = 2;
UPDATE urun SET fiyat = 29.90 WHERE id = 2;

SELECT tgname, tgenabled FROM pg_trigger
WHERE tgrelid = 'urun'::regclass AND NOT tgisinternal ORDER BY tgname;

ALTER TABLE urun DISABLE TRIGGER fiyat_izle;
UPDATE urun SET fiyat = 31 WHERE id = 2;
ALTER TABLE urun ENABLE TRIGGER fiyat_izle;
text
UPDATE 1
UPDATE 1
NOTICE:  [fiyat_izle] Çay Tabağı Altılı fiyatı: 32.50 -> 29.90 (argüman: kampanya)
UPDATE 1
     tgname      | tgenabled
-----------------+-----------
 fiyat_izle      | O
 urun_denetim    | O
 urun_hazirla    | O
 urun_toplu_ozet | O
(4 rows)

ALTER TABLE
UPDATE 1
ALTER TABLE

İlk güncelleme fiyata dokunmadığı için UPDATE OF fiyat trigger'ı tetiklemedi. İkincisi fiyatı SET listesine yazdı ama değeri değiştirmedi; bu kez WHEN koşulu engelledi. Yalnızca üçüncü güncelleme bildirim üretti; TG_ARGV[0] de CREATE TRIGGER içinde verilen 'kampanya' metnini getirdi. Kılavuza göre trigger argümanları metin sabitleridir; sayı yazsanız bile metne çevrilir.

pg_trigger kataloğu trigger'ları listeler; tgenabled sütunundaki O trigger'ın etkin olduğunu gösterir. ALTER TABLE ... DISABLE TRIGGER ile geçici olarak kapatılan trigger son güncellemede bildirim üretmedi. psql'de \d urun komutu da tablonun trigger'larını gösterir. Silmek için DROP TRIGGER ad ON tablo, tanımı değiştirmek için CREATE OR REPLACE TRIGGER kullanılır. Trigger oluşturmak için tabloda TRIGGER yetkisi ve fonksiyonda EXECUTE yetkisi gerekir.

Aynı olay için birden çok trigger varsa, kılavuza göre trigger adlarının alfabetik sırasıyla çalışırlar. BEFORE ve INSTEAD OF trigger'larda her trigger'ın döndürdüğü satır bir sonrakinin girdisi olur; biri NULL döndürürse o satır için işlem bırakılır ve sonraki trigger'lar çalışmaz. Sırayı garanti etmek istiyorsanız adları buna göre seçin. Satır düzeyindeki AFTER trigger'lar komutun sonunda, komut düzeyindeki AFTER trigger'lardan önce çalışır.

Veri görünürlüğü ve zincirleme trigger'lar

Trigger fonksiyonu kendi tablosunu sorguluyorsa görünürlük kurallarını bilmeniz gerekir. Komut düzeyindeki BEFORE trigger komutun hiçbir değişikliğini görmez, AFTER trigger hepsini görür. Satır düzeyindeki BEFORE trigger, tetikleyen değişikliği henüz göremez ama aynı komutun daha önce işlediği satırların değişikliklerini görür; satırların işlenme sırası öngörülemediği için bu dikkat ister. Satır düzeyindeki AFTER trigger çalıştığında komutun tüm değişiklikleri tamamlanmıştır. Bu kurallar yalnızca fonksiyon VOLATILE ise geçerlidir; STABLE veya IMMUTABLE fonksiyonlar çağıran komutun değişikliklerini hiç görmez.

Trigger içinden çalıştırılan SQL komutları başka trigger'ları, hatta aynı trigger'ı yeniden tetikleyebilir. Kılavuz, zincirleme düzeyi için doğrudan bir sınır olmadığını ve sonsuz özyinelemeden kaçınmanın trigger yazarının sorumluluğu olduğunu vurgular. Benzer şekilde, yabancı anahtarın ON DELETE CASCADE gibi eylemleri sıradan UPDATE/DELETE komutlarıyla yapılır ve hedef tablodaki trigger'ları çalıştırır; bu trigger'lar işlemi engellerse veya değiştirirse referans bütünlüğü bozulabilir.

Bölümlenmiş tablolarda ise şunu bilin: üst tabloya satır düzeyinde trigger tanımlamak, her bölümde aynı trigger'ın bir kopyasını oluşturur; sonradan eklenen bölümler de bu kopyayı alır. Üst tabloya yönelik bir komut yalnızca üst tablonun komut düzeyi trigger'larını çalıştırır. Bölümleme için tablo partitioning yazısına bakın.

Event trigger: DDL olaylarına tepki

Sıradan trigger'lar veri değişikliğine bağlıdır; event trigger ise veritabanı düzeyindeki olaylara bağlanır. Desteklenen olaylar login, ddl_command_start, ddl_command_end, table_rewrite ve sql_drop'tur. Fonksiyonu RETURNS event_trigger ile tanımlanır ve event trigger'ı yalnızca süper kullanıcılar oluşturabilir. Kısa bir örnek:

sql
CREATE FUNCTION ddl_bildir() RETURNS event_trigger
    LANGUAGE plpgsql
AS $$
DECLARE
    k record;
BEGIN
    FOR k IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP
        RAISE NOTICE 'DDL: % (%)', k.command_tag, k.object_identity;
    END LOOP;
END
$$;

CREATE EVENT TRIGGER ddl_bildir ON ddl_command_end
    EXECUTE FUNCTION ddl_bildir();

CREATE TABLE kampanya (id integer);
DROP EVENT TRIGGER ddl_bildir;
text
CREATE FUNCTION
CREATE EVENT TRIGGER
NOTICE:  DDL: CREATE TABLE (public.kampanya)
CREATE TABLE
DROP EVENT TRIGGER

Sık yapılan hatalar

Sık yapılan hatalar: BEFORE trigger'da RETURN NEW unutmak, DELETE trigger'da NEW döndürmek, Trigger'ı LANGUAGE SQL ile yazmaya çalışmak, Trigger fonksiyonuna parametre tanımlamak

En sık görülen iki hatayı küçük bir tabloda deneyelim: NULL döndüren bir BEFORE trigger ve RETURN yazılmamış bir trigger fonksiyonu.

sql
CREATE TABLE deneme (a int);
CREATE FUNCTION f_null() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RETURN NULL; END $$;
CREATE TRIGGER t_null BEFORE INSERT ON deneme FOR EACH ROW EXECUTE FUNCTION f_null();
INSERT INTO deneme VALUES (1);
DROP TRIGGER t_null ON deneme;
CREATE FUNCTION f_yok() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN NEW.a := 2; END $$;
CREATE TRIGGER t_yok BEFORE INSERT ON deneme FOR EACH ROW EXECUTE FUNCTION f_yok();
INSERT INTO deneme VALUES (1);
DROP TABLE deneme;
text
CREATE TABLE
CREATE FUNCTION
CREATE TRIGGER
INSERT 0 0
DROP TRIGGER
CREATE FUNCTION
CREATE TRIGGER
ERROR:  control reached end of trigger procedure without RETURN
CONTEXT:  PL/pgSQL function f_yok()
DROP TABLE

İlk INSERT hata vermedi ama "INSERT 0 0" bildirdi: satır sessizce atlandı. İkinci fonksiyon NEW üzerinde değişiklik yaptı ama döndürmeyi unuttu ve çalışma anı hatası aldı. Diğer tipik hatalar:

  • BEFORE trigger'da RETURN NEW unutmak: PL/pgSQL fonksiyonu RETURN olmadan bitemez; NULL döndürmek ise satırı sessizce atlar ve komut "INSERT 0 0" gibi bir sonuç verir.
  • DELETE trigger'da NEW döndürmek: NEW NULL olduğundan silme atlanır; OLD döndürün.
  • Trigger'ı LANGUAGE SQL ile yazmaya çalışmak: desteklenmez.
  • Trigger fonksiyonuna parametre tanımlamak: fonksiyon argümansız olmalı, değerler TG_ARGV ile gelir.
  • Kendi tablosunu güncelleyen trigger: aynı trigger'ı yeniden tetikleyip sonsuz döngüye girebilir.
  • Sıra varsaymak: aynı olaydaki trigger'lar ada göre alfabetik çalışır, oluşturma sırasına göre değil.
  • Her şeyi trigger'a yüklemek: gizli iş mantığı hata ayıklamayı zorlaştırır; basit kurallar için kısıt, toplu işler için komut düzeyi trigger düşünün.

Sık Sorulan Sorular

EXECUTE FUNCTION mı, EXECUTE PROCEDURE mı?

CREATE TRIGGER içinde ikisi eşdeğerdir ama çağrılan şey her durumda bir fonksiyon olmalıdır, procedure değil. Kılavuz PROCEDURE sözcüğünü tarihsel ve kullanımdan kalkmış olarak niteler; EXECUTE FUNCTION yazın.

Bir trigger'ı transaction sonuna erteleyebilir miyim?

Evet, CREATE CONSTRAINT TRIGGER ile. Constraint trigger'lar AFTER ROW olmalıdır ve DEFERRABLE INITIALLY DEFERRED ile transaction sonunda çalışabilir; zamanlamaları SET CONSTRAINTS ile değiştirilebilir.

UPSERT trigger'ları nasıl çalıştırır?

INSERT ... ON CONFLICT DO UPDATE satır düzeyinde önce BEFORE INSERT, gerekirse ardından BEFORE UPDATE trigger'larını çalıştırabilir. Komut düzeyinde ise kılavuz sırayı şöyle verir: BEFORE INSERT, BEFORE UPDATE, AFTER UPDATE, AFTER INSERT. Ayrıntı için UPSERT ve MERGE.

Trigger'daki hatayı nasıl yakalarım?

Trigger fonksiyonu içinde EXCEPTION bloğu kullanabilirsiniz; yakalanmayan hata tetikleyen komutu iptal eder. Ayrıntılar PL/pgSQL hata yakalama yazısında.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: PostgreSQL öğrenme rehberi · PL/pgSQL temelleri · View ve materialized view · PL/pgSQL hata yakalama · JSONB kullanımı