Oracle veritabanı sistemlerinde prosedürel mantığın SQL ile entegrasyonu ihtiyacı, PL/SQL'in doğuşunun temel motivasyonudur. 1990'lı yılların başında, Oracle 6 sürümü ile birlikte sunulan PL/SQL 1.0, temel blok yapısı, değişken tanımlama ve DML (Data Manipulation Language) ifadelerini çalıştırma kabiliyeti ile ortaya çıktı. Bu ilk sürüm, temel olarak saklı prosedür ve fonksiyonları desteklemekteydi ve veritabanı iş mantığını sunucu katmanına taşıma konusunda devrimsel bir adım olarak kabul edildi. Dilin ismi, Procedural Language extensions to SQL ifadesinin kısaltmasından gelmektedir.
Temel kavramsal olarak PL/SQL, blok yapılı ve derlenmiş bir dildir. Her bir PL/SQL birimi, tanımlama (DECLARE), yürütme (BEGIN) ve istisna işleme (EXCEPTION) bölümlerinden oluşan bloklar halinde yapılandırılır. Bu blok yapısı, hata yönetimi açısından sağlam bir altyapı sunar. Dil, SQL'in veri manipülasyonu ve sorgulama gücünü, prosedürel dillerin kontrol yapıları, döngüleri ve koşul ifadeleri ile birleştirir.
Dilin tip sistemi, SQL veri tipleriyle tam uyumludur. %TYPE ve %ROWTYPE gibi nitelikler kullanılarak, veritabanı şemasındaki değişikliklerden programatik kodun minimum düzeyde etkilenmesi sağlanır. Bu, sürdürülebilirlik açısından kritik bir özelliktir.
PL/SQL'in mimarisi, Oracle Database Sunucusu içine gömülüdür. Kod, veritabanı sunucusunda yürütülür, bu da ağ trafiğini azaltarak ve batch processing verimliliğini artırarak performans avantajları sağlar.
Oracle'ın her yeni sürümüyle dil, nesne yönelimli özelliklr, gelişmiş optimizasyon teknikleri ve daha zengin API'ler kazanmıştır.
| Oracle Sürümü | PL/SQL Sürümü | Önemli Yenilikler |
|---|---|---|
| Oracle 6 | 1.0 | Basit saklı prosedürler, blok yapısı |
| Oracle 7 | 2.x | Tetikleyiciler, kayıt paketleri, kullanıcı tanımlı hatalar |
| Oracle 8i | 8.x | Java entegrasyonu, gelişmiş koleksiyonlar (VARRAY, nested tables) |
| Oracle 9i | 9.x | Nesne yönelimli programlama desteği, CASE ifadesi |
Sonuç olarak, PL/SQL, Oracle ekosisteminde veri merkezli uygulama geliştirmenin vazgeçilmez bir bileşenidir.
PL/SQL Yapı Taşları ve Sözdizimi
PL/SQL'in temel yürütülebilir birimi bloktur. Her blok, isteğe bağlı olarak bir tanımlama kısmı (DECLARE), zorunlu bir yürütme kısmı (BEGIN-END) ve isteğe bağlı bir hata işleme kısmı (EXCEPTION) içerir. Bu yapı, modüler ve hata toleranslı kod yazımını teşvik eder.
Değişken ve sabit tanımlamaları DECLARE bölümünde yapılır. Dil, SCALAR, COMPOSITE, REFERENCE ve LOB olmak üzere dört ana kategoriye ayrılan zengin bir veri tipi kümesine sahiptir. Özellikle, veritabanı sütunlarının tipine referans veren %TYPE ve bir satırın tüm yapısını referans alan %ROWTYPE nitelikleri, kodun veritabanı şema değişikliklerine uyum sağlamasında esneklik sağlar.
Bir değişkenin veritabanındaki bir sütunun tipini miras alması, programın bakımını önemli ölçüde kolaylaştırır. Örneğin, v_employee_name employees.last_name%TYPE; deklarasyonu, v_employee_name değişkeninin tipini employees tablosunun last_name sütununun tipi olarak belirler.
DECLARE
-- %TYPE Kullanımı
v_emp_salary employees.salary%TYPE;
-- %ROWTYPE Kullanımı
v_emp_record employees%ROWTYPE;
-- Sabit Tanımlama
c_tax_rate CONSTANT NUMBER(2,2) := 0.18;
-- Skaler Tip
v_bonus NUMBER(8,2);
BEGIN
SELECT salary INTO v_emp_salary FROM employees WHERE employee_id = 100;
SELECT * INTO v_emp_record FROM employees WHERE employee_id = 100;
v_bonus := v_emp_salary * 0.10;
DBMS_OUTPUT.PUT_LINE('Salary: ' || v_emp_salary || ', Bonus: ' || v_bonus);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Kayıt bulunamadı.');
END;
PL/SQL'de atama operatörü iki nokta üst üste eşittir (:=) şeklindedir. Karşılaştırma operatörleri ise SQL'deki gibi eşittir (=) gibi tek eşittir işaretidir. Bu ayrım, yeni başlayanlar için sıkça karıştırılan bir noktadır.
| Değişken Kategorisi | Tanım | Örnek Tipler |
|---|---|---|
| Skaler (Scalar) | Tek bir değer tutan basit tipler. | NUMBER, VARCHAR2, DATE, BOOLEAN |
| Bileşik (Composite) | Birden fazla bileşeni olan karmaşık tipler. | RECORD, TABLE (INDEX BY), VARRAY |
| Referans (Reference) | Bellek adresini tutan, işaretçi benzeri tipler. | REF CURSOR, %TYPE, %ROWTYPE |
| Büyük Nesne (LOB) | Büyük veri nesnelerini tutan tipler. | BLOB, CLOB, NCLOB, BFILE |
Operatörler ve ifadeler, veri üzerinde işlem yapmayı sağlar. PL/SQL, SQL'in tüm operatörlerine ek olarak üstel alma (**) gibi prosedürel operatörleri de destekler. Ayrıca, karakter birleştirme için çift pipe (||) operatörü yoğun şekilde kullanılır.
Fonksiyonlar ve prosedürler, PL/SQL programlama birimlerinin temelini oluşturur. Ancak bu birimlerin daha modüler ve yeniden kullanılabilir şekilde organize edilmesi için paket (PACKAGE) yapısı kullanılır. Bir paket, iki bölümden oluşur: spesifikasyon (spec) ve gövde (body). Spesifikasyon, arayüzü (public değişken, sabit, tip, fonksiyon ve prosedür imzalarını) tanımlarken, gövde bu imzaların gerçekleştirimlerini ve private öğeleri içerir.
- Spesifikasyon (Specification): Uygulamanın arayüzüdür. Hangi program birimlerinin çağrılabileceğini ve hangi public değişkenlerin erişilebilir olduğunu belirtir.
- Gövde (Body): Spesifikasyonda belirtilen alt programların ve fonksiyonların gerçek kodunu, ayrıca private tipleri ve değişkenleri içerir.
- Başlatma Bölümü (Initialization Section): Paket gövdesinin sonunda bulunabilen, paket ilk kez oturuma yüklendiğinde bir kere çalıştırılan bir koddur.
Bu yapı taşları, PL/SQL'in esnek ve güçlü bir dil olmasının temelini oluşturur ve geliştiricilere veritabanı katmanında karmaşık iş mantıklarını verimli bir şekilde kodlama imkanı tanır.
Kod Blokları ve Kontrol Yapıları
PL/SQL'de temel programlama mantığı, şartlı dallanma ve döngüsel tekrarlama yapıları üzerine kuruludur. Bu kontrol yapıları, BEGIN-END bloğu içinde tanımlanır ve SQL'in veri odaklı yapısına algoritmik esneklik kazandırır. En temel şartlı ifade IF-THEN-ELSE yapısıdır, ancak dil, çoklu koşullar için ELSIF ve değer bazlı dallanma için CASE ifadelerini de destekler.
IF-THEN-ELSIF yapısı, geleneksel programlama dillerindekine benzer şekilde çalışır. Ancak, PL/SQL'de karşılaştırma operatörünün (=) ve bitiş ifadesinin (END IF) yazımına dikkat edilmelidir. ELSIF bloğu, tek bir kelimedir ve ELSE bloğu isteğe bağlıdır. Bu yapılar, bir koşulun doğruluğuna bağlı olarak farklı SQL veya PL/SQL ifadelerinin yürütülmesini sağlar.
DECLARE
v_grade CHAR(1) := 'B';
v_result VARCHAR2(20);
BEGIN
IF v_grade = 'A' THEN
v_result := 'Mükemmel';
ELSIF v_grade = 'B' THEN
v_result := 'İyi';
ELSIF v_grade = 'C' THEN
v_result := 'Orta';
ELSE
v_result := 'Geliştirme Gerekli';
END IF;
DBMS_OUTPUT.PUT_LINE('Sonuç: ' || v_result);
END;
CASE ifadesi, daha okunabilir ve sürdürülebilir bir çoklu dallanma mekanizması sunar. PL/SQL, hem basit CASE hem de arama CASE'i (searched CASE) destekler. Basit CASE, bir ifadenin değerini sabitlerle karşılaştırırken, aranan CASE daha karmaşık Boolean koşullarını değerlendirmek için kullanılır.
Döngü yapıları, belirli işlemleri tekrarlamak için kullanılır. PL/SQL'de temel olarak LOOP, WHILE-LOOP ve FOR-LOOP olmak üzere üç tür döngü bulunur. Basit LOOP, içinden çıkılmadığı sürece sonsuz döngü oluşturur; çıkış genellikle EXIT WHEN koşulu ile sağlanır. WHILE-LOOP, döngü girişinde bir koşulu kontrol eder. FOR-LOOP ise belirli bir sayı aralığında veya bir imleç (cursor) üzerinde yineleme yapar.
Döngü kontrolü için EXIT ve CONTINUE ifadeleri kritik öneme sahiptir. EXIT, döngüden tamamen çıkmayı sağlarken, CONTINUE (Oracle 11g ve sonrasında mevcuttur) mevcut iterasyonun kalan kısmını atlayıp döngünün bir sonraki turuna geçer. Bu yapılar, döngü mantığını daha ince bir seviyede kontrol etmeye olanak tanır.
Saklı Prosedürler ve Fonksiyonlar
Saklı prosedürler ve fonksiyonlar, PL/SQL programlama birimlerinin en temel ve yaygın şekilde kullanılan formlarıdır. Veritabanı sunucusunda derlenmiş ve saklanmış olan bu birimler, yeniden kullanılabilirlik, performans ve güvenlik avantajları sağlar. Temel fark, bir fonksiyonun bir değer döndürmesi ve SQL ifadeleri içinde çağrılabilmesi, bir prosedürn ise değer döndürmemesi (ancak OUT parametreleri ile değer iletebilmesi) ve genellikle bağımsız bir ifade olarak EXECUTE veya başka bir PL/SQL bloğundan çağrılmasıdır.
Bir saklı prosedür oluşturulurken, adı, parametre listesi (IN, OUT veya IN OUT modunda) ve yerel değişkenler için isteğe bağlı bir DECLARE bölümü olan bir gövde tanımlanır. IN parametreleri salt okunurdur, prosedüre değer aktarır. OUT parametreleri yazılabilirdir, prosedürden değer döndürür. IN OUT parametreleri ise her iki amaç için de kullanılabilir. Bu parametre modları, prosedürlerin esnek bir şekilde veri alışverişi yapmasını sağlar.
-- Saklı Prosedür Örneği
CREATE OR REPLACE PROCEDURE adjust_salary (
p_emp_id IN employees.employee_id%TYPE,
p_percent IN NUMBER,
p_status OUT VARCHAR2
) IS
v_current_salary employees.salary%TYPE;
v_new_salary employees.salary%TYPE;
BEGIN
-- IN parametresini kullanarak veriyi al
SELECT salary INTO v_current_salary
FROM employees WHERE employee_id = p_emp_id;
-- Hesaplama yap
v_new_salary := v_current_salary * (1 + p_percent/100);
-- Veriyi güncelle
UPDATE employees SET salary = v_new_salary
WHERE employee_id = p_emp_id;
-- OUT parametresi aracılığıyla sonucu ilet
p_status := 'Maaş ' || p_emp_id || ' için ' || p_percent || '% artırıldı. Yeni maaş: ' || v_new_salary;
COMMIT;
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_status := 'Çalışan ID bulunamadı: ' || p_emp_id;
ROLLBACK;
END adjust_salary;
| Birim Türü | Tanım | Dönüş Değeri | SQL İçinde Çağrılabilir mi? | Ana Kullanım Amacı |
|---|---|---|---|---|
| Saklı Prosedür (Procedure) | Belirli bir işi gerçekleştiren, adı verilen PL/SQL bloğu. | Yok (OUT parametreleri ile çıktı) | Hayır (CALL veya EXEC ile) | Eylem gerçekleştirme (INSERT, UPDATE, iş mantığı). |
| Fonksiyon (Function) | Bir değer hesaplayıp döndüren, adı verilen PL/SQL bloğu. | Tek bir değer (RETURN ifadesi ile) | Evet (SELECT, WHERE içinde) | Hesaplama yapma ve sonuç döndürme. |
Fonksiyonlar, bir RETURN ifadesi ile sonlanmalı ve bir veri tipi döndürmelidir. Deterministic bir fonksiyon, aynı giriş için her zaman aynı çıktıyı üretir ve bu, fonksiyonun sorgu performansını artırmak için önbelleğe alınmasına olanak tanır. Fonksiyonlar, SQL ifadelerinin WHERE koşulunda veya SELECT listesinde kullanılarak, karmaşık iş mantığını sorgu katmanına taşımak için ideal bir araçtır.
Her iki birim türü de veritabanında CREATE OR REPLACE sözdizimi ile oluşturulur. Bu, aynı isimdeki eski bir birimin üzerine yazılmasını sağlar. Bu birimlerin bağımlılıkları, veritabanının data dictionary görünümlerinden takip edilebilir. Ayrıca, AUTHID yan tümcesi (DEFINER veya CURRENT_USER) ile birimin hangi kullanıcının haklarıyla çalışacağı belirlenerek güvenlik seviyesi kontrol edilebilir.
Saklı birimlerin kullanımı, uygulama kodunun veritabanı sunucusuna yakınlaştırılması (thick database paradigm) anlamına gelir. Bu, ağ üzerinden gönderilmesi gereken veri miktarını azaltır, transaction bütünlüğünü kolaylaştırır ve merkezi bir noktadan iş kurallarının uygulanmasını sağlar.
Tetikleyiciler ve İleri Seviye Konular
Tetikleyiciler (Triggers), belirli bir veritabanı olayı meydana geldiğinde otomatik olarak yürütülen özel türde saklı PL/SQL program birimleridir. Bu olaylar tipik olarak bir DML (INSERT, UPDATE, DELETE) işlemi, bir DDL (CREATE, ALTER, DROP) işlemi veya bir veritabanı sistemi olayı (LOGON, STARTUP) olabilir. Tetikleyiciler, deklaratif bütünlük kısıtlamalarının ötesinde karmaşık iş kurallarını, denetim kayıtlarını (audit trail) veya türetilmiş veri güncellemelerini uygulamak için güçlü bir mekanizma sağlar.
Bir DML tetikleyicisi, ilişkilendirildiği tablo veya görünüm üzerindeki bir işlemden önce (BEFORE) veya sonra (AFTER) çalıştırılabilir. Ayrıca, satır seviyesinde (FOR EACH ROW) veya ifade seviyesinde (statement level) tetiklenebilir. Satır seviyesi tetikleyiciler, etkilenen her bir satır için bir kez çalışır ve :OLD ve :NEW pseudorecord'larına erişebilir. Bu pseudorecord'lar, sırasıyla işlem öncesi ve sonrası satır değerlerini tutar. Örneğin, bir güncelleme tetikleyicisinde, :OLD.salary eski maaşı, :NEW.salary ise yeni atanmak istenen maaşı temsil eder.
-- Denetim (Audit) Tetikleyicisi Örneği
CREATE OR REPLACE TRIGGER trg_audit_salary_change
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
WHEN (OLD.salary != NEW.salary) -- Koşullu Tetikleme
DECLARE
v_change_type VARCHAR2(10);
BEGIN
IF :NEW.salary > :OLD.salary THEN
v_change_type := 'ARTIS';
ELSE
v_change_type := 'AZALIS';
END IF;
INSERT INTO salary_audit (employee_id, old_salary, new_salary, change_date, change_type)
VALUES (:OLD.employee_id, :OLD.salary, :NEW.salary, SYSDATE, v_change_type);
END;
Tetikleyicilerin karmaşıklık ve örtük (implicit) yürütme doğası, dikkatli kullanılmalarını gerektirir. Kötü tasarlanmış bir tetikleyici, beklenmeyen yan etkilere, performans sorunlarına veya özyinelemeli (recursive) tetikleme döngülerine neden olabilir. Bu nedenle, tetikleyici mantığı mümkün olduğunca basit tutulmalı ve iş mantığının önemli bir kısmı daha açık ve test edilebilir olan saklı prosedürlere taşınmalıdır.
İleri seviye PL/SQL konuları arasında, dinamik SQL kullanımı öne çıkar. EXECUTE IMMEDIATE ifadesi veya DBMS_SQL paketi kullanılarak, çalışma zamanında (runtime) oluşturulan ve yürütülen SQL ifadeleri çalıştırılabilir. Bu teknik, DDL çalıştırmak, şema adı veya tablo adı gibi çalışma zamanında belli olan nesneler üzerinde işlem yapmak için gereklidir. Ancak, SQL Injection güvenlik açıklarına karşı çok dikkatli olunmalı ve kullanıcı girdileri her zaman uygun şekilde doğrulanmalı veya bağ değişkenleri (bind variables) kullanılmalıdır.
Paketler (Packages), ileri seviye modüler programlamanın temel taşıdır. Bir paket, ilgili tipler, değişkenler, sabitler, istisnalar, imleçler, prosedürler ve fonksiyonlar için bir kapsayıcı görevi görür. Paket spesifikasyonu (spec), public arayüzü tanımlarken, paket gövdesi (body) bu arayüzün gerçekleştirimini ve private elemanları saklar. Paketler, encapsulation, arayüz ve uygulamanın ayrılması, ve yükleme performansı sağlar. Bir paketin içindeki bir prosedür ilk kez çağrıldığında, tüm paket belleğe yüklenir; bu da sonraki çağrılar için performans avantajı yaratır.
Hata Yönetimi ve Optimizasyon
Sağlam bir PL/SQL uygulaması, öngörülbilir ve öngörülemez tüm istisnai durumları zarif bir şekilde ele almalıdır. PL/SQL'de hata yönetimi, EXCEPTION bölümünde yapılır. Bu bölümde, önceden tanımlanmış Oracle istisnaları (NO_DATA_FOUND, TOO_MANY_ROWS, DUP_VAL_ON_INDEX, ZERO_DIVIDE vb.), kullanıcı tanımlı istisnalar veya diğer hataları yakalamak için WHEN OTHERS yapısı kullanılabilir. PRAGMA EXCEPTION_INIT derleyici direktifi, bir kullanıcı tanımlı istisna adını belirli bir Oracle hata numarasıyla ilişkilendirmek için kullanılır.
Etkili hata yönetimi, sadece hatayı yakalamak değil, aynı zamanda uygulamanın durumunu anlamlı hale getiren tanısal bilgileri kaydetmek ve mümkünse işlemi güvenli bir duruma geri döndürmektir. SQLCODE ve SQLERRM fonksiyonları, yakalanan hatanın numarasını ve mesajını döndürür. DBMS_UTILITY.FORMAT_ERROR_BACKTRACE gibi yardımcı prosedürler, hatanın tam olarak hangi satırda meydana geldiğini izlemeye yardımcı olur.
- Önceden Tanımlanmış İstisnalar: Oracle tarafından atanan standart hatalar. Kod içinde direkt olarak yakalanabilirler (örn. WHEN NO_DATA_FOUND THEN ...).
- Kullanıcı Tanımlı İstisnalar: Geliştirici tarafından tanımlanır ve RAISE ifadesi ile açıkça fırlatılır. İş kuralı ihlalleri gibi durumları bildirmek için idealdir.
- İstisna Yayılımı: Bir bloğun EXCEPTION bölümünde yakalanmayan bir hata, bir üst bloka (çağıran bloğa) yayılır. Bu, hata işleme stratejisinin hiyerarşik olarak tasarlanmasına olanak tanır.
- WHEN OTHERS Kullanımı: Spesifik olarak yakalanmamış tüm hataları yakalar. Bu yapı içinde genellikle hata bilgisi loglanır ve ardından işlem ROLLBACK yapılıp hata yeniden RAISE edilir veya uygun bir değer döndürülür.
PL/SQL performans optimizasyonu, çok katmanlı bir yaklaşım gerektirir. Temel düzeyde, bağ değişkenleri (bind variables) kullanımı en kritik optimizasyon tekniklerinden biridir. Dinamik SQL'de veya statik SQL içinde PL/SQL değişkenleri kullanıldığında, Oracle aynı SQL ifadesini farklı değerlerle tekrar tekrar ayrıştırmak (hard parse) yerine, bir kere ayrıştırıp (soft parse) yürütebilir. Bu, kütüphane önbelleği (library cache) üzerindeki yükü ve yeniden ayrıştırma maliyetini büyük ölçüde azaltır.
Toplu SQL (BULK SQL) işlemleri, FORALL ifadesi ve BULK COLLECT yan tümcesi kullanılarak gerçekleştirilir. Bu teknikler, PL/SQL motoru ile SQL motoru arasında gerçekleşen ve bağlam değişimi (context switch) olarak adlandırılan pahalı geçişleri minimize eder. FORALL, birden fazla DML ifadesini tek bir bağlam değişimi ile yürütür. BULK COLLECT ise bir sorgudan birden fazla satırı tek seferde PL/SQL koleksiyonlarına (örneğin, INDEX BY tablolarına) alır. Özellikle binlerce satırı etkileyen işlemlerde performansı yüzlerce kat artırabilir.
-- Toplu İşlem (BULK COLLECT ve FORALL) Örneği
DECLARE
TYPE t_emp_id IS TABLE OF employees.employee_id%TYPE INDEX BY PLS_INTEGER;
TYPE t_salary IS TABLE OF employees.salary%TYPE INDEX BY PLS_INTEGER;
v_emp_ids t_emp_id;
v_salaries t_salary;
BEGIN
-- Tüm çalışan ID'lerini ve maaşlarını toplu olarak koleksiyona al
SELECT employee_id, salary BULK COLLECT INTO v_emp_ids, v_salaries
FROM employees WHERE department_id = 50;
-- Koleksiyondaki her öğe için %10 zam hesapla
FOR i IN 1..v_emp_ids.COUNT LOOP
v_salaries(i) := v_salaries(i) * 1.10;
END LOOP;
-- Tüm güncellemeleri tek bir bağlam değişimi ile yap (FORALL)
FORALL i IN 1..v_emp_ids.COUNT
UPDATE employees SET salary = v_salaries(i)
WHERE employee_id = v_emp_ids(i);
DBMS_OUTPUT.PUT_LINE(v_emp_ids.COUNT || ' çalışanın maaşı güncellendi.');
COMMIT;
END;
Ek optimizasyon stratejileri arasında, uygun veri tiplerinin seçimi, gereksiz imleç (cursor) kullanımından kaçınmak, fonksiyonel olarak indekslenmiş fonksiyonların tasarımı ve PL/SQL kodu derlenirken PLSQL_OPTIMIZE_LEVEL parametresinin ayarlanması yer alır. Ayrıca, DBMS_PROFILER veya DBMS_HPROF gibi araçlarla kod profili çıkarılarak performans darboğazları tespit edilebilir.
Sonuç olarak, etkili hata yönetimi ve sistematik performans optimizasyonu, üretim seviyesindeki PL/SQL uygulamalarının güvenilirliğini, sürdürülebilirliğini ve ölçeklenebilirliğini garanti altına alan birbirini tamamlayan iki disiplindir.