Veri Tabanı Yönetim Sistemleri 2 Ders 6 PL/SQL Ek Bilgiler Yrd. Doç. Dr. Altan MESUT Trakya Üniversitesi Bilgisayar Mühendisliği Bölümü
Veri Tipi Dönüşümleri Eğer veri tipi dönüşümü Oracle tarafından otomatik olarak yapılıyorsa örtük (implicit), kullanıcı tarafından bir fonksiyon ile yapılıyorsa açık (explicit) dönüşüm olarak nitelendirilir. Aşağıdaki örnekte biri karakter diğeri sayı türünde olan iki değişken toplama işlemine tabi tutulduğunda, karakter türünde olan Oracle tarafından otomatik olarak sayıya dönüştürülür ve ekranda 7000 görüntülenir. DECLARE v_salary NUMBER(6):=6000; v_sal_increase VARCHAR2(5):='1000'; BEGIN v_salary := v_salary + v_sal_increase; DBMS_OUTPUT.PUT_LINE(v_salary); END;
Örtük dönüşümün handikapları Bu tip dönüşümler yavaş olabilir. Örtük dönüşümler Oracle tarafından yapıldığı için, eğer Oracle dönüşüm kuralını değiştirirse kod bundan etkilenebilir. Dönüşüm kuralları, kullanılan ortama göre farklı olabilir. Örneğin tarih biçimleri dil ve kurulum ayarlarına göre farklı olabilir. Dolayısıyla örtük dönüşüm farklı bir dildeki sunucuda çalışmayabilir. Database Programming with PL/SQL, Section 2, Lesson 5, Writing PL/SQL Executable Statements
Açık Dönüşümler Eğer dönüşüm işlemi TO_NUMBER, TO_CHAR, TO_DATE gibi bir fonksiyon kullanılarak yapılırsa hem daha hızlı hem de daha hatasız olur. TO_CHAR(SYSDATE,'Month YYYY') Mart 2016 TO_DATE('April-1999','Month-YYYY') 01/04/1999 Eğer Türkçe dil ayarlarında bir sistem kullanıyorsanız April yerine Nisan yazmalısınız. Aksi halde "not a valid month" hatası alırsınız. DECLARE v_a VARCHAR2(10) := '-123456'; v_b VARCHAR2(10) := '+987654'; v_c PLS_INTEGER; BEGIN v_c := TO_NUMBER(v_a) + TO_NUMBER(v_b); DBMS_OUTPUT.PUT_LINE(v_c); END;
İç İçe Bloklar Birçok programlama dilinde olduğu gibi PL/SQL’de de iç içe bloklar yaratılabilir. İç bloklar, dış blokta yaratılan değişkenleri ‘global değişken’ olarak kullanabilir. DECLARE v_outer_variable VARCHAR2(20):='GLOBAL VARIABLE'; BEGIN v_inner_variable VARCHAR2(20):='LOCAL VARIABLE'; DBMS_OUTPUT.PUT_LINE(v_inner_variable); DBMS_OUTPUT.PUT_LINE(v_outer_variable); END; Database Programming with PL/SQL, Section 2, Lesson 6, Nested Blocks and Variable Scope
Değişken Kapsama Alanı Eğer aynı değişken ismi hem lokal hem de global olarak kullanıldı ise iç blok lokal olanı kullanır. Nasıl ki C++’ta :: kullanımı ile global olana erişmek mümkün ise, PL/SQL’de de global olana erişmek istenirse dış blok için bir etiket tanımı yapılabilir:
SQL Fundamentals II – Les04 – Slayt 32-36 MERGE Kaynak ve hedef tablolarda eşleşen kayıtlar varsa hedef tablodaki kaydı güncelleyen, yoksa hedef tabloya yeni kayıt olarak ekleyen DML komutudur. MERGE INTO hedef_tablo h USING kaynak_tablo k ON (h.id = k.id) WHEN MATCHED THEN UPDATE SET h.ad = k.ad, h.soyad = k.soyad WHEN NOT MATCHED THEN INSERT VALUES (k.id, k.ad, k.soyad); Kaynak tablo yerine parantez içinde bir sorgu da yazılabilir. SQL Fundamentals II – Les04 – Slayt 32-36 Database Programming with PL/SQL, Section 3, Lesson 1, Review of SQL DML
TYPE ile nesne yaratma Nesneye Yönelik Programlama dillerinde kullanılan nesnelere benzer PL/SQL yapısı CREATE TYPE … AS OBJECT ile oluşturulur: CREATE TYPE address_typ AS OBJECT ( street VARCHAR2(30), city VARCHAR2(20), postal_code VARCHAR2(6) ); Bir nesne tanımı içinde başka bir nesne kullanılabilir: CREATE TYPE employee_typ AS OBJECT ( employee_id NUMBER(6), first_name VARCHAR2(20), last_name VARCHAR2(25), .. address address_typ ); Eğer OBJECT yerine RECORD yazılırsa yapı içinde fonksiyon tanımı yapılamaz. VideoRental.sql scriptinde Price_Type altında total_price_VAT fonksiyonunu inceleyiniz. CREATE TABLE employees OF employee_typ; şeklinde tablo oluşturulabilir.
TYPE ile dizi yaratma CREATE TYPE Phone_List_Type AS VARRAY(5) OF VARCHAR2(25) VideoRental.sql scriptinde yer alan bu satırı 4. derste vermiştik DECLARE TYPE isimdizisi IS VARRAY(4) OF VARCHAR2(10); TYPE notdizisi IS VARRAY(4) OF INTEGER; isimler isimdizisi; notlar notdizisi; toplam integer; BEGIN isimler := isimdizisi('Ali', 'Pınar', 'Ayhan', 'Veli'); notlar := notdizisi(98, 97, 78, 87); toplam := isimler.count; dbms_output.put_line('Toplam '|| toplam || ' Öğrenci'); FOR i in 1 .. total LOOP dbms_output.put_line('Öğrenci: ' || isimler(i) || ' Notu: ' || notlar(i)); END LOOP; END; VARRAY(4) en fazla 4 eleman alabileceğini gösterir. Eğer diziyi notlar := notdizisi(); şeklinde eleman vermeden başlatırsanız diziye değer atamadan önce EXTEND yöntemi ile yer istemelisiniz (döngü öncesinde notlar.EXTEND(4) veya döngü içinde notlar.EXTEND(1)). TYPE öncesinde CREATE kullanılırsa nesne olarak yaratılır ve veritabanında saklanır. DECLARE altında ise CREATE kullanılmaz (anonim blok bitince hafızadan silinir)
Nested Tables (İç içe geçmiş tablolar) Dizilerden farklı olarak eleman sayısı sabit değildir ve indis değerleri ardışık olmayabilir. DELETE(n) yöntemi ile n. eleman silinerek boyut azaltılabilir. NEXT(n) ve PRIOR(n) ile silinmiş elemanları atlayarak gezinme gerçekleştirilebilir. FIRST ve LAST ile ilk ve son elemanlara ulaşılabilir. Tanımlanması dizi ile benzerdir. Sadece VARRAY(..) yerine TABLE yazılır.
Index-By Tables (Dizin-bazlı tablolar) Diğer programlama dillerinde kullanılan hash tablolara benzer. İndis değeri olarak genellikle BINARY_INTEGER tipinde birincil anahtar kullanılır (İstenirse VARCHAR2 de olabilir). Aşağıdaki örnekte indis değerleri olarak personelin numarasını kullanan, veri olarak ise personelin görevini saklayan bir dizin-bazlı tablo kullanılmıştır: DECLARE TYPE t_gorev IS TABLE OF Personel.Gorev%TYPE INDEX BY BINARY_INTEGER; v_gorev_tab t_gorev; BEGIN FOR kayit IN (SELECT PerNo, Gorev FROM Personel) LOOP v_gorev_tab(kayit.PerNo) := kayit.Gorev; END LOOP; END; İstenirse indis değeri olarak sürekli artan bir sayaç değişkeni tanımlanıp kullanılabilir. Database Programming with PL/SQL, Section 6, Lesson 2, Indexing Tables of Records
Cursor'lar: Implicit & Explicit Önceki derslerde bahsedilen Cursor hakkında biraz daha ayrıntılı bilgi vermek gerekirse, cursor aslında sorgu sonucunun saklandığı alana bir isim verilmesi veya bu alanı gösteren bir pointer gibi düşünülebilir. Database Programming with PL/SQL, Section 5, Lesson 1'e göre iki tip Cursor'un tanımı şu şekildedir: Implicit Cursor: Tüm DML işlemleri için ve tek satır döndüren sorgular için ORACLE tarafından otomatik olarak tanımlanır. Explicit Cursor: Programcı tarafından çok satır döndüren sorguların saklanması için tanımlanır. Bazı kaynaklara göre çok satır döndüren bir sorgu eğer FOR döngüsü tanımında cursor olarak tanımlanmadan (isim verilmeden) kullanıldıysa, bu da bir Implicit Cursor kullanımıdır.
Implicit Cursor FOR döngüsü: BEGIN FOR kayit IN ( SELECT * FROM Personel WHERE "BÖLÜM" = 10 ORDER BY Soyad) LOOP DBMS_OUTPUT.put_line ( kayit.Ad || ' ' || Kayit.Soyad); END LOOP; END; Önceki dersimizde FOR döngüsünü adım sayısı bilinen bir işlem için FOR num IN 1..500 LOOP şeklinde kullanmıştık. Buradaki kullanım For Each döngüsüne benzer şekilde kayıt sayısını bilmemize gerek kalmadan tüm kayıtlar için çalışır.
Explicit Cursor FOR döngüsü: DECLARE CURSOR c1 IS SELECT * FROM Personel WHERE "BÖLÜM" = 10 ORDER BY Soyad; BEGIN FOR kayit IN c1 LOOP DBMS_OUTPUT.put_line ( kayit.Ad || ' ' || Kayit.Soyad); END LOOP; END; Buradaki fark, c1 adında bir cursor açık (explicit) şekilde tanımlandı. Burada da FOR döngüsü kayıt sayısını bilmemize gerek kalmadan çalışır. Geçen hafta slayt 18’deki FOR emp_rec IN c1 LOOP buna benzerdi.
FETCH kullanımı DECLARE CURSOR c1 IS SELECT Ad, Soyad FROM Personel WHERE "BÖLÜM" = 10 ORDER BY Soyad; Ad Personel.Ad%TYPE; Soyad Personel.Soyad%TYPE; BEGIN OPEN c1; LOOP FETCH c1 INTO Ad, Soyad; EXIT WHEN c1%NOTFOUND; DBMS_OUTPUT.put_line ( Ad || ' ' || Soyad); END LOOP; END; Cursor'lar ile çalışırken FOR döngüsü kullanmak yerine FETCH ile kayıtlar tek tek çekilebilir. Fakat bunun için önce Cursor'u OPEN ile açmak gerekir. Cursor sorgusunda elde edilen tüm alanlar için değişkenler tanımlamak ta gerekir. Sona geldiğimizi anlayıp döngüden çıkmak için %NOTFOUND özelliğini kullanabiliriz.
Cursor Özellikleri (Attributes) Description %FOUND Eğer bir INSERT, UPDATE veya DELETE ifadesi bir yada daha çok satırı etkilediyse veya bir SELECT INTO ifadesi bir yada daha çok satır döndürdüyse TRUE döndürür. %NOTFOUND %FOUND'un mantıksal tersidir. DML işleminde herhangi bir satır etkilenmediyse veya sorguda döndürülmediyse TRUE döndürür. %ISOPEN Cursor açık ise TRUE döndürür. Implicit Cursor'larda hep FALSE döndürür (çünkü Oracle ilgili SQL ifadesini yürüttükten sonra otomatik olarak Cursor'u kapatır). %ROWCOUNT INSERT, UPDATE veya DELETE ifadelerinden etkilenen, veya SELECT INTO ifadesinden dönen satır sayısını döndürür. Geçen hafta Hata İşleme ile ilgili örnekte «IF SQL%NOTFOUND THEN …» ifadesini görmüştük. Eğer Explicit değil de Implicit Cursor kullanılacak ise değişken ismi yerine SQL yazılır.
Execute Immediate ile Dinamik Sorgular Sorgunun yazıldığı ve derlendiği anda sorgunun parse edilmesi için gerekli bilgilerin tamamı yoksa, bu bilgilerin bir kısmı çalışma anında (runtime) elde ediliyorsa bu tip sorgulara dinamik sorgular denir. ORACLE'da EXECUTE IMMEDIATE ifadesi ile sorgunun dinamik olarak çalıştırılması mümkündür. Dinamik SQL hakkında detaylı bilgi için: Database Programming with PL/SQL, Section 12, Lesson 1, Using Dynamic SQL
EXECUTE IMMEDIATE Örneği: Parametre olarak aldığı tablo adı, kolon adı ve kriter bilgilerine göre sorgu oluşturup sonucu döndüren fonksiyon CREATE OR REPLACE FUNCTION single_number_value ( table_in IN VARCHAR2, column_in IN VARCHAR2, where_in IN VARCHAR2) RETURN NUMBER IS l_return NUMBER; BEGIN EXECUTE IMMEDIATE 'SELECT ' || column_in || ' FROM ' || table_in || ' WHERE ' || where_in INTO l_return; RETURN l_return; END; BEGIN DBMS_OUTPUT.put_line ( single_number_value ( 'employees', 'salary', 'employee_id=138')); END;
EXECUTE IMMEDIATE Örneği: Parametre olarak ismini aldığı tablodaki tüm kayıtları silen ve sildiği kayıt sayısını döndüren fonksiyon CREATE FUNCTION del_rows(p_table_name VARCHAR2) RETURN NUMBER IS BEGIN EXECUTE IMMEDIATE 'DELETE FROM '||p_table_name; RETURN SQL%ROWCOUNT; END; DECLARE v_count NUMBER; v_count := del_rows('EMPLOYEE_NAMES'); DBMS_OUTPUT.PUT_LINE(v_count|| ' rows deleted.');
Prosedürden Geri Değer Döndürme Prosedürlerde parametrenin ismi ile veri tipi arasında seçimlik olarak parametre modu verilebilir. Verilebilecek 3 mod vardır (mod yazılmaz ise varsayılan olarak IN kabul edilir): IN: Değeri prosedür içinde değiştirilemeyen girdi parametresi OUT: Çağırana geri değer döndürebilen parametre IN OUT: Girdi olarak aldığı değeri prosedür içinde değiştirip geri döndürebilen parametre Database Programming with PL/SQL, Section 8, Lesson 3, Passing Parameters
OUT parametresi ile değer döndürme CREATE PROCEDURE Ad_Soyad_Getir ( p_PNo IN Personel.PNo%TYPE, p_Ad OUT Personel.Ad%TYPE, p_Soyad OUT Personel.Soyad%TYPE) IS BEGIN SELECT Ad, Soyad INTO p_Ad, p_Soyad FROM Personel WHERE PNo = p_PNo; END; Ad_Soyad_Getir(178, a_Ad, a_Soyad); DBMS_OUTPUT.PUT_LINE(a_Ad || ' ' || a_Soyad);
IN OUT parametresi ile değer döndürme
Parametrelerin referans ile aktarımı: NOCOPY Veri yapıları dersinde gördüğünüz gibi bazen bir prosedürü çağırırken alacağı parametreyi değer ile değil referans ile veririz (call by value/reference). Her ne kadar değer ile çağırmak daha güvenli olsa da, parametrenin taşıyacağı değer çok büyük ise referans ile çağırmak bize hız kazandırır. PL/SQL’de IN türü parametreler varsayılan olarak referans ile çağırılırken OUT ve IN OUT parametreleri ise değer ile çağrılır. Eğer OUT veya IN OUT sonrasında NOCOPY yazılırsa onların da referans ile çağrılması mümkündür: TYPE t_emp IS TABLE OF employees%ROWTYPE; PROCEDURE emp_proc (p_small_arg IN NUMBER, p_big_arg OUT NOCOPY t_emp); Database Programming with PL/SQL, Section 12, Lesson 2, Improving PL/SQL Performance
Fonksiyon Tabanlı İndeksler SELECT * FROM Etkinlikler WHERE TO_CHAR(Tarih, 'month') = 'nisan'; Etkinlikler tablosundaki tarih alanı üzerinde index var olsa bile yukarıdaki sorguda TO_CHAR fonksiyonu kullanıldığı için bu indeks işe yaramaz. Aşağıdaki gibi fonksiyon tabanlı bir indeksin yaratılmış olması gereklidir: CREATE INDEX etkinlik_tar_ind ON Etkinlikler (TO_CHAR(Tarih, 'month')); Kendi oluşturduğumuz fonksiyonlar da eğer deterministik olarak tanımlanırlar ise indeks yaratmada kullanılabilir. Bunun için fonksiyon tanımında «RETURN NUMBER DETERMINISTIC IS» gibi bir ifade kullanmalıyız. Bir bölümdeki personelin toplam maaşını döndüren bir fonksiyon, maaş değerleri değiştikçe farklı değer üreteceği için (non-deterministik), bu tip fonksiyonlar indeks için kullanılmaz.