Kõigepealt käsitlen jõudluse aspekti. PostgreSQL 18 (ja ka varasemate versioonide) puhul on teooria selline:
  • WHEN klausel: Kontroll toimub enne trigeri protseduuri (funktsiooni) väljakutsumist. Kui tingimus on väär (false), siis PL/pgSQL täitmiskeskkonda ei initsialiseerita üldse – see on oluline võit jõudluses.
  • IF lause funktsiooni sees: Funktsioon kutsutakse alati välja, tekitades täiendava kulu (funktsiooni stacki loomine jne) ja alles siis kontrollitakse tingimust.
Hüpotees on, et WHEN klausli kasutus on kiirem, sest sellisel juhul ei käivita PostgreSQL PL/pgSQL mootorit nende ridade jaoks, mis on korrektsed (tingimus on väär). 
IF lause funktsiooni sees puhul käivitatakse funktsioon iga rea uuendamisel, mis tekitab märgatava protsessori lisakulu (call stack setup, kontekstkommutatsioon).

Kasutan selle hüpoteesi testimiseks pgbench utiliiti. Siin on selle utiliidi tutvustus ja siin dokumentatsioon. Teen seda viisil, et loon ise andmebaasi, genereerin sinna ise testandmeid ning lasen pgbenchil käivitada selle andmebaasi põhjal enda koostatud tehinguid. 

Loon andmebaasi trigerite_test, kus loon tabeli Teenus ja lisan sinna miljon rida. Igal teenusel on seisund, mida tähistavad koodid 1-4. Loon trigeri, mis kontrollib seisundimuudatuste lubatavust. pgbench on loodud nii, et kui SQL käsk annab veateate (antud juhul RAISE EXCEPTION trigeri protseduuris), loetakse see tehing ebaõnnestunuks. Vaikimisi käitumisena katkestab pgbench kliendi töö kohe, kui viga tekib. Seetõttu asendan trigerites RAISE EXCEPTION ... => RETURN NULL.

CREATE TABLE Teenus (teenus_id INTEGER NOT NULL,
teenuse_seisundi_liik_kood SMALLINT NOT NULL DEFAULT 1,
CONSTRAINT pk_teenus PRIMARY KEY (teenus_id));
 
INSERT INTO Teenus (teenus_id, teenuse_seisundi_liik_kood)
SELECT arv, random(1,4) AS teenuse_seisundi_liik_kood
FROM generate_series(1,1_000_000) AS arv;
 
CREATE OR REPLACE FUNCTION f_kontroll_raske() RETURNS trigger AS $$
BEGIN    
    IF (NOT (OLD.teenuse_seisundi_liik_kood=NEW.teenuse_seisundi_liik_kood 
OR OLD.teenuse_seisundi_liik_kood=1 AND NEW.teenuse_seisundi_liik_kood=2
OR OLD.teenuse_seisundi_liik_kood=1 AND NEW.teenuse_seisundi_liik_kood=3
OR OLD.teenuse_seisundi_liik_kood=2 AND NEW.teenuse_seisundi_liik_kood=3)) THEN
        RETURN NULL;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;
 
DROP TRIGGER IF EXISTS tr_kontroll ON Teenus;
CREATE TRIGGER tr_kontroll
BEFORE UPDATE OF teenuse_seisundi_liik_kood ON Teenus
FOR EACH ROW 
EXECUTE FUNCTION f_kontroll_raske();

Kontrollitavas tingimuses on näidatud kõik üleminekud, mis on lubatud. Need pannakse sulgudesse ja ette NOT - seega kui üleminek pole lubatud üleminekute hulgas, siis käivitub trigeri protseduur. 
Üldkuju: NOT (lubatud seisundimuudatused)

Loon oma kodukataloogis faili seisundimuutus.sql, mis sisaldab:

\set s random(1, 4)
\set t random(1, 1000000)
UPDATE Teenus SET teenuse_seisundi_liik_kood=:s WHERE teenus_id=:t;

Transaktsiooni sisuks on valida juhuslikult üks teenus ja muuta selle seisundit.
Käivitan seda transaktsiooni simuleerides 10-t kasutajat, kasutades selleks serveri 2 protsessori lõime ja pommitades andmebaasi täpselt 60 sekundit.
Käivitasin selle käsu shelli promptis.

pgbench -d trigerite_test -f seisundimuutus.sql -c 10 -j 2 -T 60

Käivitamisel sain veateateid nagu:
pgbench: error: ERROR:  relation "pgbench_tellers" does not exist
Need on tingitud sellest, et antud juhul ei looda automaatselt nelja tabelit, mida pgbenchi abil testides sageli kasutatakse. Seda viga võib ignoreerida.

Seejärel muutsin andmebaasis trigeri realisatsiooni nii, et kasutan WHEN klauslit.

CREATE OR REPLACE FUNCTION f_kontroll_kerge() RETURNS trigger AS $$
BEGIN    
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;
 
DROP TRIGGER IF EXISTS tr_kontroll ON Teenus;
CREATE TRIGGER tr_kontroll
BEFORE UPDATE OF teenuse_seisundi_liik_kood ON Teenus
FOR EACH ROW 
WHEN (NOT (OLD.teenuse_seisundi_liik_kood=NEW.teenuse_seisundi_liik_kood 
OR OLD.teenuse_seisundi_liik_kood=1 AND NEW.teenuse_seisundi_liik_kood=2
OR OLD.teenuse_seisundi_liik_kood=1 AND NEW.teenuse_seisundi_liik_kood=3
OR OLD.teenuse_seisundi_liik_kood=2 AND NEW.teenuse_seisundi_liik_kood=3))
EXECUTE FUNCTION f_kontroll_kerge();
 
Käivitasin uuesti:
pgbench -d trigerite_test -f seisundimuutus.sql -c 10 -j 2 -T 60

Tulemused
Number of transactions actually processed näitab täidetud tehingute arvu, latency average näitab keskmist tehingu täitmise aega ning tps (transactions per second) tehingute arvu sekundis.

IF lause
number of transactions actually processed: 507856
latency average = 1.180 ms
initial connection time = 70.209 ms
tps = 8472.066333 (without initial connection time)
 
WHEN klausel
number of transactions actually processed: 410077
latency average = 1.460 ms
initial connection time = 137.478 ms
tps = 6848.202016 (without initial connection time)

Antud juhul oli IF lause kasutus kiirem. Gemini 3 kommentaar tulemustele:

Konkreetses testis on IF lause kiirem, sest trigeri kontrollitav tingimus on keeruline (palju OR ja AND tehteid) ja trigeri funktsioon ise on väga kerge (vähe ülesandeid). PL/pgSQL (funktsiooni keel) suudab keerulist boolean-loogikat sageli efektiivsemalt täita kui CREATE TRIGGER lause WHEN hindaja, ning kuna "võit" funktsiooni mitte-väljakutsumisest on väike (sest funktsioon ise ei tee peaaegu midagi), jääbki WHEN variant kaotajaks.

Siin on detailne analüüs, miks tulemused sellised on.
  • Tingimuse keerukus vs. Täitmise koht
    • IF lause (PL/pgSQL): Kui funktsioon on LANGUAGE plpgsql, siis PostgreSQL kompileerib funktsiooni esimesel käivitamisel baidikoodiks. See tähendab, et keeruline IF (A OR B OR C...) loogika on optimeeritud ja valmis kiireks täitmiseks mälus.
    • WHEN klausel: Seda hindab PostgreSQL-i peamine SQL täitja (mitte PL/pgSQL mootor) iga rea jaoks. Kuigi see on samuti kiire, on keeruliste avaldiste (palju sulge ja loogilisi tehteid) puhul PL/pgSQL-i täitur sageli efektiivsem, eriti kui tegemist on muutujate võrdlemisega (OLD vs. NEW).
  • "Hit rate" ehk kui tihti triger tegelikult käivitub
    • Väärtused on juhuslikud 1 kuni 4.
    • Tingimus kontrollib "keelatud" üleminekuid.
    • Matemaatiliselt on 16 võimalikku kombinatsiooni.
    • Trigeri loogika põhjal on lubatud üleminekuid vähem kui keelatuid (või ligikaudu pooleks).
    • See tähendab, et väga suurel osal juhtudest (nt ~50-60%) on WHEN tingimus tõene ja funktsioon tuleb ikkagi välja kutsuda.
    • Järeldus: WHEN klausli peamine eelis on funktsiooni väljakutse (call overhead) vältimine. Kuna funktsiooni peab niikuinii tihti välja kutsuma, on see eelis väike, samas kui keerulise WHEN tingimuse sõelumine iga rea jaoks on "kallim" kui funktsiooni sees oleva kompileeritud IF-i täitmine.
  • Funktsiooni kergus
    • Testfunktsioon f_kontroll_kerge on äärmiselt lihtne – see sisaldab ainult RETURN NULL.
    • PostgreSQL-is on funktsiooni väljakutsumise kulu (overhead) üsna väike (mikrosekundites).
    • Kuna funktsiooni sisu on olematu, siis WHEN klausli kasutamisel säästetakse ainult selle mikrosekundi murdosa, mis kulub konteksti vahetuseks.
    • Kui funktsioon teeks rohkem tööd (nt SELECT andmebaasist), oleks WHEN klausel kindlasti kiirem, sest see jätaks raske töö tegemata. Praegu võrreldakse omavahel sisuliselt kahte loogika-hindajat.
Test näitas, et optimeerimine ei ole alati intuitiivne. Kui lähtuda parima variandi valikul ainult jõudlusest, siis oleks soovitus:

Kasuta IF-i funktsiooni sees, kui:
  • Kontrollitav loogika on väga keeruline (palju AND/OR/sulgusid).
  • Tingimus on enamikul juhtudel täidetud (funktsioon käivitub niikuinii tihti).
  • Trigeri funktsioon ise on väga lihtne (ei tee SQL päringuid).
Kasuta WHEN klauslit, kui:
  • Tingimus on lihtne (nt OLD.teenuse_seisundi_liik_kood<>NEW.teenuse_seisundi_liik_kood).
  • Tingimus on harva tõene (nt tahad logida ainult vigu, mis juhtuvad 1% ajast) – siis oleks võit väga suur.
  • Trigeri funktsioon teeb palju tööd (nt teeb päringuid, kirjutab logitabelisse).

Samas on arhitektuurilisi ja halduslikke argumente, mis suunavad eelistama WHEN klauslit.
  • Koodi loetavus ja "enesedokumenteerimine"
    • Kui andmebaasi administraator või arendaja uurib tabeli struktuuri (\d Teenus psql-is või vaatab DDL skripti), on WHEN klausel kohe näha trigeri definitsioonis.
      • WHEN puhul: On kohe selge, millal triger käivitub, ilma, et peaks avama ja lugema PL/pgSQL funktsiooni koodi.
      • IF puhul: Triger näib käivituvat "alati". Arendaja peab minema eraldi funktsiooni sisse, et aru saada, et tegelikult on seal peidetud loogika. See suurendab kognitiivset koormust süsteemi mõistmisel.
  • Funktsiooni taaskasutatavus (DRY printsiip)
    • WHEN klausel võimaldab hoida trigeri funktsiooni geneerilisena.
    • Stsenaarium: Oletame, et on viis erinevat tabelit, kus on vaja teha sama tegevust (nt logida muudatus auditi tabelisse), aga tingimused logimiseks on tabeliti erinevad.
      • WHEN lahendus: Saab luua universaalse funktsiooni logi_muudatus(). Iga tabeli trigeri juures defineeritakse WHEN klausliga eraldi reeglid.
      • IF lahendus: Peab kirjutama viis erinevat funktsiooni (logi_teenus, logi_klient jne), sest filtreerimise loogika on "kooditud" funktsiooni sisse.
  • Tulevikukindlus (Future-proofing)
    • Praegu on funktsioon f_kontroll_kerge sisuliselt tühi (või viskab vaid veateate). Kui tulevikus peaks äriloogika muutuma ja see funktsioon hakkab tegema midagi rasket (nt päringut teise tabelisse, keerulist jsonb töötlust või võrguühendust), muutub WHEN klausel automaatselt jõudluse seisukohalt kriitiliseks. Kui loogika on WHEN klauslis, ei pea funktsiooni sisu täienedes muretsema, et see "tühjade" käivitustega süsteemi koormab.
  • Statistika ja monitooringu täpsus
    • PostgreSQL peab statistikat funktsioonide kasutuse kohta (pg_stat_user_functions).
      • WHEN puhul: Selles vaates suureneb funktsiooni väljakutsete arv (calls) ainult siis, kui triger päriselt oma töö ära tegi. See annab hea ülevaate, kui tihti äriline sündmus toimus.
      • IF puhul: Funktsiooni calls loendur kasvab iga UPDATE lausega, sõltumata sellest, kas tingimus täideti või mitte. See muudab statistika "müraseks" – me ei tea, kui palju oli sisulisi käivitusi ja kui palju tühje kontrolli.
  • Vastutuse lahusus (Separation of Concerns)
    • Hea tava on hoida:
      • SQL tasemel (trigeri definitsioon): Deklaratiivne loogika – millal midagi juhtub.
      • PL/pgSQL tasemel (funktsioon): Imperatiivne loogika – mis täpselt juhtub.
    • WHEN klausli kasutamine järgib seda printsiipi paremini. See muudab koodi puhtamaks.
Gemini kokkuvõte: 

Sinu valik sõltub prioriteedist:
  • Kui eesmärk on pigistada välja absoluutne maksimum toorest jõudlust (high-frequency trading vms) ja tingimus on keeruline + funktsioon kerge -> Kasuta IF-i funktsiooni sees.
  • Kui eesmärk on olla hallatav, loetav ja standardne infosüsteem -> Kasuta WHEN klauslit.
Tavapärases ettevõtte infosüsteemis on hooldatavus (punkt 2) tavaliselt tähtsam kui 15-20% võit mikro-optimeerimises, välja arvatud juhul, kui tegemist on süsteemi kõige kitsama pudelikaelaga.