PL/pgSQL, PostgreSQL'in yerleşik prosedürel dilidir: SQL sorgularının yanına değişken, IF/CASE koşulları, LOOP/FOR/WHILE/FOREACH döngüleri ve hata yakalama ekler. Kodu bir fonksiyon, procedure ya da tek seferlik bir DO bloğu içinde yazarsınız; her gövde DECLARE ... BEGIN ... END biçiminde bir bloktur. Dil her yeni veritabanında hazır gelir, ayrıca kurmanız gerekmez.

Bu yazıda PL/pgSQL'in temel yapı taşlarını çalışan örneklerle ele alıyoruz: blok yapısı ve DO, değişken tanımlama, %TYPE ve %ROWTYPE, SELECT INTO ve FOUND, koşullar, döngüler, RETURN QUERY ile küme döndürme, EXECUTE ... USING ile dinamik SQL, GET DIAGNOSTICS ve en sık karşılaşılan hatalar. CREATE FUNCTION sözdiziminin kendisi (oynaklık, SECURITY DEFINER vb.) CREATE FUNCTION ve PROCEDURE yazısında, hata yakalama ve RAISE ayrıntıları PL/pgSQL hata yakalama yazısında. Serinin geneli için PostgreSQL öğrenme rehberine bakın.

Blok yapısı ve DO komutu

PL/pgSQL blok yapılı bir dildir. Fonksiyon gövdesinin tamamı tek bir blok olmalıdır ve blok şu şekildedir:

sql
[ <<etiket>> ]
[ DECLARE
    tanımlar ]
BEGIN
    komutlar
END [ etiket ];

Her tanım ve komut noktalı virgülle biter. İç içe bir bloğun END ifadesinden sonra noktalı virgül gerekir, gövdeyi kapatan son END için gerekmez. Anahtar sözcükler büyük/küçük harfe duyarlı değildir; tanımlayıcılar, normal SQL'de olduğu gibi çift tırnak içinde değilse küçük harfe çevrilir. Yorumlar SQL'deki gibidir: -- satır sonuna kadar, /* */ blok yorum.

Kılavuzun özellikle altını çizdiği bir ayrım var: PL/pgSQL içindeki BEGIN/END yalnızca komutları gruplar, transaction başlatmaz veya bitirmez. Transaction kontrolü procedure ve DO bloklarında COMMIT/ROLLBACK ile yapılır; bu konu procedure yazısında örneklendi. Bir de EXCEPTION yan tümcesi içeren blok bir alt transaction oluşturur; ayrıntısı hata yakalama yazısında.

DO komutu, parametresiz ve void döndüren geçici bir fonksiyon gibi davranan anonim bir kod bloğunu bir kez ayrıştırıp çalıştırır. Dil belirtilmezse plpgsql varsayılır. Tek seferlik bakım işleri, deneme ve öğrenme için idealdir; bu yazıdaki ilk örnek de bir DO bloğu. Hem gövde metni hem DO kodu bir metin sabiti olduğu için dolar tırnağı ($$ ... $$) kullanmak, içerideki tek tırnakları ikileme zahmetini ortadan kaldırır.

Değişkenler: DECLARE, %TYPE, %ROWTYPE, RECORD

Değişkenler: DECLARE, %TYPE, %ROWTYPE, RECORD: Başlangıç değeri verilmezse değişken NULL ile başlar., CONSTANT değişkene ilk değerden sonra atama yapılmasını engeller., NOT NULL...

Blokta kullanılan tüm değişkenler DECLARE bölümünde tanımlanır. İki istisna vardır: tamsayı aralığında dönen FOR döngüsünün değişkeni otomatik olarak integer tanımlanır, imleç üzerinde dönen FOR döngüsünün değişkeni de otomatik olarak record olur. Genel sözdizimi şöyledir:

sql
ad [ CONSTANT ] tip [ COLLATE collation_adi ] [ NOT NULL ] [ { DEFAULT | := | = } ifade ];
  • Başlangıç değeri verilmezse değişken NULL ile başlar.
  • CONSTANT değişkene ilk değerden sonra atama yapılmasını engeller.
  • NOT NULL verilen değişkenin NULL olmayan bir varsayılanı olmalıdır; sonradan NULL atamak çalışma anı hatası verir.
  • Varsayılan değer fonksiyon çağrısında bir kez değil, bloğa her girişte yeniden hesaplanır. Bu yüzden now() ile başlatılan bir değişken o anki çağrının zamanını taşır.
  • tablo.sutun%TYPE bir sütunun tipini kopyalar; sütun tipi ileride değişirse fonksiyonu değiştirmeniz gerekmeyebilir.
  • tablo%ROWTYPE bir tablonun tüm satırını tutan satır değişkeni tanımlar; alanlara p.ad gibi nokta ile erişilir.
  • RECORD önceden yapısı olmayan bir yer tutucudur; ilk atandığı satırın yapısını alır. Atanmadan önce bir alanına erişmek çalışma anı hatası verir.

Atama := (ya da =) ile yapılır. Arka planda ifade bir SQL SELECT olarak ana motora gönderilir ve tek bir değer üretmelidir. Sonuç tipi değişkenin tipinden farklıysa atama dönüşümü uygulanır; uygun dönüşüm yoksa değer metne çevrilip değişken tipine okunmaya çalışılır, bu da çalışma anı hatasına yol açabilir.

SELECT INTO, FOUND ve IF

Tek satır döndüren bir sorgunun sonucunu değişkene almak için sorguya INTO eklenir. Kılavuz bunun SQL'deki SELECT INTO komutuyla (yeni tablo oluşturan) karıştırılmaması gerektiğini vurgular; fonksiyon içinde tablo oluşturmak için CREATE TABLE ... AS SELECT kullanılmalıdır. STRICT yazılmazsa değişkene ilk satır atanır, satır yoksa değişkenler NULL olur ve özel FOUND değişkeni false olur. Aşağıdaki DO bloğu bu üç parçayı birleştiriyor:

sql
CREATE TABLE personel (
    id      integer PRIMARY KEY,
    ad      text NOT NULL,
    birim   text NOT NULL,
    maas    numeric(10,2) NOT NULL
);
INSERT INTO personel VALUES
    (1, 'Deniz Acar', 'yazılım', 72000),
    (2, 'Ece Polat', 'yazılım', 81000),
    (3, 'Kerem Şahin', 'destek', 48000),
    (4, 'Leyla Koç', 'satış', 55000);

DO $$
DECLARE
    p          personel%ROWTYPE;
    aranan     personel.ad%TYPE := 'Ece Polat';
    zam_orani  CONSTANT numeric := 0.10;
    seviye     text;
BEGIN
    SELECT * INTO p FROM personel WHERE ad = aranan;
    IF NOT FOUND THEN
        RAISE NOTICE '% bulunamadı', aranan;
        RETURN;
    END IF;

    IF p.maas >= 80000 THEN
        seviye := 'kıdemli';
    ELSIF p.maas >= 60000 THEN
        seviye := 'orta';
    ELSE
        seviye := 'başlangıç';
    END IF;

    RAISE NOTICE '%: birim=%, seviye=%, zamlı maaş=%',
        p.ad, p.birim, seviye, round(p.maas * (1 + zam_orani), 2);
END
$$;
text
NOTICE:  Ece Polat: birim=yazılım, seviye=kıdemli, zamlı maaş=89100.00
DO

RAISE NOTICE metindeki her % işaretinin yerine sıradaki argümanı koyar ve mesajı istemciye bir bildirim olarak gönderir; psql bunu NOTICE: satırı olarak gösterir. IF ... ELSIF ... ELSE ... END IF koşul zinciri ilk doğru dalı çalıştırır. Arama değerini değiştirip olmayan bir ad verirseniz FOUND false olur ve blok erken RETURN ile biter.

FOUND her fonksiyon çağrısında false ile başlar ve yalnızca belirli komutlar tarafından ayarlanır: SELECT INTO, PERFORM, en az bir satırı etkileyen INSERT/UPDATE/DELETE/MERGE, FETCH, MOVE, en az bir kez dönen FOR/FOREACH ve RETURN QUERY. Dikkat: dinamik EXECUTE, FOUND değerini değiştirmez; onun sonucunu GET DIAGNOSTICS ile okumalısınız.

CASE ve döngüler

CASE ve döngüler: LOOP ... END LOOP, EXIT [etiket] [WHEN koşul], CONTINUE [etiket] [WHEN koşul], WHILE koşul LOOP

PL/pgSQL'de iki CASE biçimi vardır. Basit biçim bir ifadeyi sırayla WHEN değerleriyle eşitlik açısından karşılaştırır; aramalı biçim her WHEN koşulunu sırayla değerlendirip ilk doğru olanı çalıştırır. Önemli fark şudur: hiçbir dal eşleşmez ve ELSE de yoksa, IF sessizce hiçbir şey yapmazken komut olarak CASE bir CASE_NOT_FOUND hatası yükseltir. Aşağıdaki örnekteki CASE ise SQL ifadesi olarak kullanılan CASE ... END'dir, komut değildir.

Döngü komutları şunlardır:

KomutNe yapar
LOOP ... END LOOPEXIT veya RETURN gelene kadar koşulsuz tekrar
EXIT [etiket] [WHEN koşul]Döngüden (etiketle bir bloktan) çıkar
CONTINUE [etiket] [WHEN koşul]Sonraki tura geçer
WHILE koşul LOOPHer turdan önce koşulu denetler
FOR i IN [REVERSE] a..b [BY n] LOOPTamsayı aralığında döner, i otomatik integer
FOR kayit IN sorgu LOOPSorgunun her satırında döner
FOR kayit IN EXECUTE metin LOOPDinamik sorgunun satırlarında döner
FOREACH x IN ARRAY dizi LOOPDizinin elemanlarında döner (SLICE ile dilimlerde)

Tamsayı FOR döngüsünde sınırlar döngüye girerken bir kez hesaplanır; alt sınır üst sınırdan büyükse (REVERSE ile tersi) gövde hiç çalışmaz ve hata da verilmez. Sorgu üzerinde dönen FOR döngüsünde EXIT ile çıkılırsa, son atanan satır döngüden sonra da erişilebilir kalır. FOREACH çok boyutlu dizilerde de elemanları depolama sırasıyla dolaşır.

Küme döndürme: RETURN QUERY ve RETURN NEXT

RETURNS SETOF veya RETURNS TABLE ile tanımlanan PL/pgSQL fonksiyonu sonuç satırlarını adım adım biriktirir. RETURN NEXT ifade sonuca bir satır ekler, RETURN QUERY sorgu bir sorgunun tüm satırlarını ekler. İkisi de fonksiyondan çıkmaz; yürütme bir sonraki komutla devam eder. Sonunda argümansız bir RETURN yazılır ya da akış fonksiyonun sonuna ulaşır. İkisini aynı fonksiyonda karıştırabilirsiniz, sonuçlar art arda eklenir.

sql
CREATE FUNCTION birim_raporu(p_birimler text[])
    RETURNS TABLE (birim text, kisi integer, toplam_maas numeric)
    LANGUAGE plpgsql STABLE
AS $$
DECLARE
    b text;
BEGIN
    FOREACH b IN ARRAY p_birimler LOOP
        RETURN QUERY
            SELECT b, count(*)::integer, coalesce(sum(p.maas), 0)
            FROM personel p
            WHERE p.birim = b;
    END LOOP;
END
$$;

SELECT * FROM birim_raporu(ARRAY['yazılım', 'satış', 'muhasebe']);

CREATE FUNCTION taksitler(tutar numeric, adet integer)
    RETURNS SETOF text
    LANGUAGE plpgsql IMMUTABLE
AS $$
DECLARE
    kalan numeric := tutar;
    pay   numeric;
BEGIN
    FOR i IN 1..adet LOOP
        pay := CASE WHEN i = adet THEN kalan ELSE round(tutar / adet, 2) END;
        kalan := kalan - pay;
        RETURN NEXT format('%s. taksit: %s', i, pay);
    END LOOP;
    RETURN;
END
$$;

SELECT * FROM taksitler(100, 3);
text
  birim   | kisi | toplam_maas
----------+------+-------------
 yazılım  |    2 |   153000.00
 satış    |    1 |    55000.00
 muhasebe |    0 |           0
(3 rows)

    taksitler
------------------
 1. taksit: 33.33
 2. taksit: 33.33
 3. taksit: 33.34
(3 rows)

İlk fonksiyonda FOREACH her birim için bir RETURN QUERY çalıştırıyor. Sorguda tablo takma adı p ve p.birim kullanmamızın sebebi, RETURNS TABLE içindeki birim sütun adının fonksiyon içinde bir değişken gibi davranmasıdır; nitelemeden birim yazsaydık aşağıda göreceğimiz belirsizlik hatası çıkardı. İkinci fonksiyon FOR ile taksitleri hesaplayıp her birini RETURN NEXT ile ekliyor; kuruş farkı son taksite yazılıyor.

Dinamik SQL: EXECUTE, format() ve USING

PL/pgSQL, gövdede doğrudan yazılan sorguların planlarını hazırlayıp önbelleğe alabilir. Bunun bir sonucu, doğrudan yazılan bir SQL komutunda tablo veya sütun adının değişken olamamasıdır. Tablo adı çalışma anında belirleniyorsa, ya da CREATE gibi parametre kabul etmeyen bir yardımcı komut çalıştırıyorsanız, komut metnini oluşturup EXECUTE ile çalıştırırsınız. EXECUTE ile çalışan komutun planı önbelleğe alınmaz, her seferinde yeniden planlanır.

Kılavuzun önerdiği güvenli düzen iki parçalıdır: tanımlayıcıları (tablo, sütun adı) format() fonksiyonunun %I belirteciyle, veri değerlerini ise $1, $2 yer tutucuları ve USING ile vermek. %I otomatik olarak quote_ident uygular. USING değerleri metne çevirip tırnaklamaya gerek bırakmaz; kılavuz bunu hem daha verimli hem de SQL enjeksiyonuna karşı çok daha dayanıklı bulur. Yer tutucular yalnızca veri değerleri için kullanılabilir.

sql
CREATE TABLE arsiv_2025 (id integer, not_metni text);
CREATE TABLE arsiv_2026 (id integer, not_metni text);
INSERT INTO arsiv_2026 VALUES (1, 'ilk'), (2, 'ikinci'), (3, 'üçüncü');

CREATE FUNCTION arsiv_temizle(yil integer, en_kucuk_id integer)
    RETURNS integer
    LANGUAGE plpgsql
AS $$
DECLARE
    silinen integer;
BEGIN
    EXECUTE format('DELETE FROM %I WHERE id >= $1', 'arsiv_' || yil)
        USING en_kucuk_id;
    GET DIAGNOSTICS silinen = ROW_COUNT;
    RETURN silinen;
END
$$;

SELECT arsiv_temizle(2026, 2) AS silinen;
SELECT arsiv_temizle(2024, 1) AS silinen;
text
 silinen
---------
       2
(1 row)

ERROR:  relation "arsiv_2024" does not exist
LINE 1: DELETE FROM arsiv_2024 WHERE id >= $1
                    ^
QUERY:  DELETE FROM arsiv_2024 WHERE id >= $1
CONTEXT:  PL/pgSQL function arsiv_temizle(integer,integer) line 5 at EXECUTE

İlk çağrı iki satırı sildi; GET DIAGNOSTICS silinen = ROW_COUNT son komutun etkilediği satır sayısını okudu. İkinci çağrıda arsiv_2024 tablosu olmadığı için hata alındı; QUERY: satırı EXECUTE'un çalıştırdığı gerçek metni, CONTEXT: satırı da hatanın fonksiyonun kaçıncı satırında çıktığını gösteriyor. Bu ikisi dinamik SQL hatalarını ayıklarken en değerli bilgidir.

Kılavuz bir performans ayrıntısını da not eder: parametreli sabit bir komutu EXECUTE ile çalıştırmak, aynı komutu doğrudan yazmakla işlevsel olarak eşdeğerdir; fark, EXECUTE'un her seferinde o anki değerlere özel bir plan üretmesi, doğrudan yazılan komutun ise genel bir planın önbelleğe alınmasına izin vermesidir. En iyi plan parametre değerine çok bağlıysa EXECUTE bilinçli bir seçim olabilir.

Procedure çağırmak ve INOUT parametreler

PL/pgSQL içinden bir procedure'ü CALL ile çağırdığınızda çıkış parametreleri düz SQL'den farklı işlenir: her OUT veya INOUT parametresine bir değişken karşılık gelmeli ve procedure dönünce değer o değişkene yazılır. Düz SQL'deki gibi NULL yazmak burada işe yaramaz.

sql
CREATE PROCEDURE zam_uygula(p_birim text, oran numeric, INOUT etkilenen integer DEFAULT 0)
    LANGUAGE plpgsql
AS $$
BEGIN
    UPDATE personel SET maas = round(maas * (1 + oran), 2) WHERE birim = p_birim;
    GET DIAGNOSTICS etkilenen = ROW_COUNT;
END
$$;

DO $$
DECLARE n integer;
BEGIN
    CALL zam_uygula('yazılım', 0.05, n);
    RAISE NOTICE '% kişiye zam yapıldı', n;
END
$$;

SELECT ad, maas FROM personel WHERE birim = 'yazılım' ORDER BY id;
text
NOTICE:  2 kişiye zam yapıldı
DO
     ad     |   maas
------------+----------
 Deniz Acar | 75600.00
 Ece Polat  | 85050.00
(2 rows)

Procedure'ün dönüş değeri olmadığı için RETURN zorunlu değildir; erken çıkmak isterseniz argümansız RETURN yazarsınız. Çıkış parametrelerinin son değerleri çağırana döner.

Sık yapılan hatalar ve mesajları

Sık yapılan hatalar ve mesajları: BEGIN; yazmak, Sonucu atılan SELECT, INTO STRICT ve sıfır satır, Değişken ile sütun adı çakışması

PL/pgSQL, fonksiyonu ilk çağrıldığında ayrıştırır, ancak içindeki SQL ifadelerini o satıra ilk ulaşıldığında analiz eder. Kılavuz bunun bir dezavantajını açıkça yazar: basit sözdizimi hataları ilk ayrıştırmada yakalanır, daha derin hatalar ise ancak ilgili satır çalıştığında ortaya çıkar. Yeni başlayanların en sık gördüğü beş hatayı art arda üretelim:

sql
-- 1) BEGIN sonrasında noktalı virgül
DO $$ BEGIN; RAISE NOTICE 'merhaba'; END $$;

-- 2) INTO olmadan SELECT
DO $$ BEGIN SELECT count(*) FROM personel; END $$;

-- 3) INTO STRICT ve satır yok
DO $$
DECLARE m numeric;
BEGIN
    SELECT maas INTO STRICT m FROM personel WHERE birim = 'muhasebe';
END $$;

-- 4) Değişken ile sütun adı çakışması
CREATE FUNCTION birim_maasi(birim text) RETURNS numeric
    LANGUAGE plpgsql AS $$
BEGIN
    RETURN (SELECT sum(maas) FROM personel WHERE personel.birim = birim);
END $$;
SELECT birim_maasi('yazılım');

-- 5) RETURN unutulmuş fonksiyon
CREATE FUNCTION iki_kati(n integer) RETURNS integer
    LANGUAGE plpgsql AS $$
BEGIN
    IF n > 0 THEN
        RETURN n * 2;
    END IF;
END $$;
SELECT iki_kati(-1);
text
ERROR:  syntax error at or near ";"
LINE 1: DO $$ BEGIN; RAISE NOTICE 'merhaba'; END $$;
                   ^
ERROR:  query has no destination for result data
HINT:  If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT:  PL/pgSQL function inline_code_block line 1 at SQL statement
ERROR:  query returned no rows
CONTEXT:  PL/pgSQL function inline_code_block line 4 at SQL statement
CREATE FUNCTION
ERROR:  column reference "birim" is ambiguous
LINE 1: ...SELECT sum(maas) FROM personel WHERE personel.birim = birim)
                                                                 ^
DETAIL:  It could refer to either a PL/pgSQL variable or a table column.
QUERY:  (SELECT sum(maas) FROM personel WHERE personel.birim = birim)
CONTEXT:  PL/pgSQL function birim_maasi(text) line 3 at RETURN
CREATE FUNCTION
ERROR:  control reached end of function without RETURN
CONTEXT:  PL/pgSQL function iki_kati(integer)
  • BEGIN; yazmak: kılavuz bunu sık yapılan bir hata olarak anar. PL/pgSQL'de BEGIN'den sonra noktalı virgül konmaz.
  • Sonucu atılan SELECT: satır döndüren bir komut INTO olmadan yazılırsa reddedilir; ipucu satırı da PERFORM kullanmanızı söylüyor.
  • INTO STRICT ve sıfır satır: STRICT tam olarak bir satır bekler; satır yoksa NO_DATA_FOUND, birden fazlaysa TOO_MANY_ROWS hatası verir. Bu hataları yakalamak EXCEPTION bloğu ile yapılır.
  • Değişken ile sütun adı çakışması: varsayılan olarak PL/pgSQL, bir ad hem değişkeni hem sütunu gösterebiliyorsa hata verir. plpgsql.variable_conflict ayarı bunu use_variable veya use_column olarak değiştirebilir, ancak kılavuz en temiz çözümün adı değiştirmek ya da nitelemek olduğunu gösterir.
  • RETURN unutmak: skaler değer döndüren bir fonksiyonda akış RETURN görmeden sona ulaşırsa çalışma anı hatası olur. OUT parametreli ve void fonksiyonlarda bu kural geçerli değildir.

Dördüncü hatanın düzeltilmiş hâli: tabloya takma ad verip sütunu p.birim, parametreyi de fonksiyon adıyla birim_maasi.birim olarak nitelemek. Kılavuz, her fonksiyon gövdesinin fonksiyon adıyla etiketlenmiş gizli bir dış blok içinde olduğunu, bu yüzden parametrelerin fonksiyon adıyla nitelenebildiğini açıklar.

sql
CREATE OR REPLACE FUNCTION birim_maasi(birim text) RETURNS numeric
    LANGUAGE plpgsql AS $$
BEGIN
    RETURN (SELECT sum(p.maas) FROM personel p WHERE p.birim = birim_maasi.birim);
END $$;
SELECT birim_maasi('yazılım');
text
 birim_maasi
-------------
   160650.00
(1 row)

Sonuç, önceki örnekte zam yapılmış maaşların toplamıdır. Plan önbelleğinin bir başka yan etkisi de record değişkenleriyle ilgilidir: bir ifadede kullanılan record alanlarının tipi çağrıdan çağrıya değişmemelidir, çünkü ifade ilk ulaşıldığı andaki tiple analiz edilir. Gerekirse EXECUTE bu sorunu aşar.

PL/pgSQL ne zaman, SQL fonksiyonu ne zaman?

Tek bir sorgu veya ifade yeterliyse SQL dilinde fonksiyon yazmak daha sadedir. Değişken, ara sonuç, koşullu akış, döngü, dinamik SQL veya hata yakalama gerektiğinde PL/pgSQL'e geçin. Trigger fonksiyonları da düz SQL ile yazılamadığı için genellikle PL/pgSQL kullanılır; konu PostgreSQL trigger oluşturma yazısında. Dizi ve FOREACH ile çalışırken dizi fonksiyonlarına PostgreSQL dizi tipi yazısından bakabilirsiniz.

PostgreSQL 18 sürüm notları PL/pgSQL için imleç (cursor) argümanlarında => ile adlı argüman sözdiziminin desteklendiğini belirtir; imleçler bu temel yazının kapsamı dışında. Diğer yenilikler PostgreSQL 18 yenilikleri yazısında.

Sık Sorulan Sorular

PL/pgSQL kurmam gerekir mi?

Hayır. DO komutunun belgesine göre plpgsql varsayılan olarak kuruludur; diğer prosedürel diller ise CREATE EXTENSION ile eklenmelidir.

DO bloğu bir değer döndürebilir mi?

Hayır. DO bloğu void döndüren parametresiz bir fonksiyon gibi davranır. Sonuç görmek için RAISE NOTICE kullanabilir ya da bir tabloya yazabilirsiniz; değer döndürmek gerekiyorsa fonksiyon tanımlayın.

= ile := arasında fark var mı?

Atama ve değişken tanımında ikisi de kabul edilir. := PL/SQL uyumlu biçimdir; kılavuz = kullanımına da izin verir.

Fonksiyonun hangi satırında hata çıktığını nasıl anlarım?

Hata mesajının CONTEXT: satırı fonksiyon adını ve satır numarasını verir; yukarıdaki örneklerde "line 5 at EXECUTE" gibi. Kendi bağlam bilginizi üretmek için GET DIAGNOSTICS ... PG_CONTEXT kullanılabilir; hata yakalama yazısında anlatılıyor.

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 · CREATE FUNCTION ve PROCEDURE · PL/pgSQL hata yakalama · Trigger oluşturma · SQL injection önleme