PL/SQL alapok
Doksi:
PL/SQL User’s Guide
1.
fejezetet (Overview) jó
áttekintést ad mindenről
Lexikális elemek:
delimeterek,
azonosítók (köztük a foglalt
szavak), literálok, commentek
Delimiterek:
egyszerű és
összetett szimbólumok pl. “+”
illetve “>=” ... stb.
Azonosítók:
betűvel
kezdődik, kötőjel, slash, szóköz tilos, de
lehet úgynevezett idézőjeles
azonosító pl. ”A + B ”
ilyenkor a kisbetű/nagybetű nem ugyanaz.
Literálok:
numerikus,
karakter, karakterlánc, dátum, timestamp, logikai
(TRUE és
FALSE lehet)
Megjegyzések:
Lehet egysoros
(-- után) és több soros /* ... */
között
Adattípusok: BINARY_INTEGER fontos lesz a
PL/SQL tábláknál
(vannak
altípusai is pl. NATURAL)
NUMBER (vannak altípusai
pl. REAL)
CHAR, RAW, VARCHAR2, ...
és altípusaik
DATE
BOOLEAN
Altípusok: ugyanazok a műveletek
alkalmazhatók rájuk mint az
alaptípusra,
az értelmezési
tartományuk
kisebb.
SUBTYPE
altípus_neve IS alaptípus;
nem adható meg
megszorítás
az alaptípusra pl.
SUBTYPE v1 IS
VARCHAR2(10) rossz
Adatkonverzió: van explicit és
implicit konverzió
Deklarációk: PL/SQL blokk, alprogram vagy
package deklaratív
részében adhatók meg.
Fontos
(!) beágyazott blokknak is lehet
deklaratív része. Példa az
alábbi:
<<cimke1>>
DECLARE
a
NUMBER;
BEGIN
a:=2;
<<cimke2>>
DECLARE
a number;
BEGIN
a:=4;
dbms_output.put_line(cimke1.a);
dbms_output.put_line(cimke2.a);
dbms_output.put_line(a);
END;
dbms_output.put_line(a);
END;
Futtatás
előtt SET
SERVEROUTPUT ON [SIZE n]
Ennek
hatására íródik ki
képernyőre a dbms_output.put_line() kimenete.
Értékadás
a deklarációval
egyidejűleg
v BOOLEAN
:= FALSE;
vagy
ezzel egyenértékű
v BOOLEAN
DEFAULT FALSE;
NOT
NULL is megadható a
deklarációval egyidejűleg, de ilyenkor
kötelező a kezdeti értékadás.
v NUMBER NOT NULL := 2;
Konstans
deklaráció
v
CONSTANT NUMBER := 2;
ilyenkor is kötelező az
értékadás
Érvényesség
és láthatóság:
A szokásos,
erre jó példa a fenti programrészlet.
Nevek feloldása: Lokális
változók elsőbbséget
élveznek a táblanevekkel
szemben, de az oszlopnevek elsőbbséget élveznek a
lokális változókkal szemben.
Pl. UPDATE emp SET ... --
rossz ha van emp nevű változó is.
Megoldás
a minősítés.
Minősíteni lehet cimkével vagy alprogram
nevével.
%TYPE:
Két legfőbb
előnye: 1.
Nem kell ismernem az
objektum típusát
2. Ha később
változik a típus a kód maradhat
Példa:
v
tablanev.oszlopnev%TYPE;
%ROWTYPE:
A
deklarációban nem szerepelhet
inicializáció, de rekordok
közötti értékadás
megengedett pl. rek1
:= rek2;
Példa:
rec
tablanev%ROWTYPE;
rec2
kurzornev%ROWTYPE;
Értékadás:
két formája
van ”:=”
és SELECT (FETCH) INTO az
utóbbival nem lehet logikai változónak
értéket adni.
Példa:
v :=
’Blabla’;
SELECT oszlop1,
oszlop2 INTO v1, v2 FROM … ;
SELECT oszlop1,
oszlop2 INTO rec FROM …;
FETCH kurzornev INTO
v1, v2;
FETCH kurzornev INTO
rec;
Kifejezések:
Műveletek
precedenciája (csökkenő sorrendben):
(**, NOT)
hatványozás, logikai
tagadás
(+, - ) azonosság,
negatív
(*, /)
szorzás, osztás
(+, -, ||)
összeadás, kivonás,
konkatenáció
(=, !=, <, >,
<=, >=, IS NULL, LIKE, BETWEEN, IN)
(AND)
és
(OR)
vagy
használjunk
zárójeleket
Beépített
függvények:
Nem
keverendők össze az SQL függvényekkel. A
beépített függvények
elsősorban
procedurális utasításokban
használhatók és nem SQL
utasításokban. Azért a
legtöbb használható SQL
utasításban is, de van ami nem pl. SQLCODE,
SQLERRM.
Ami viszont nem beépített
függvény az nem használható
csak SQL utasításban pl.
az aggregátor fv-ek.
SELECT
MAX(o) INTO valtozo FROM tábla;
-- Ez
így rendben
valtozo
:= MAX(o); -- Ez
így hibás
SELECT
SYSDATE INTO valtozo FROM dual; --
Ez
így rendben
valtozo
:= sysdate; -- Ez
is rendben
Rekordok:
(Lehet a
%ROWTYPE segítségével és
lehet saját rekordtípussal)
Rekord típus
definiálása:
TYPE
rekord_típus_neve
IS RECORD
(mező1, mező2 ...)
ahol
a mezők megadása a
következő:
mezőnév
adattípus
[[NOT NULL] { :=| DEFAULT} kifejezés ]
megadhatunk
beágyazot
rekordokat is
Rekord
deklarálása:
rekord_név
rekord_típus_neve;
Hivatkozás rekord
mezőire:
rekor_név.mező
beágyazott
rekord esetén:
rekord_név.mező.mező2
fv
által visszaadott rekord esetén:
fv(paraméter).mező
(!!!)
paraméter nélküli fv
esetén nem használható a fenti
jelölés
pl.
fv().mező -- rossz
Példa:
DECLARE
TYPE
rektip IS RECORD(m1 INTEGER, m2 VARCHAR2(10));
rec
rektip;
BEGIN
rec.m1
:= 1; rec.m2 := 'Bubu';
DBMS_OUTPUT.
PUT_LINE(rec.m2);
END;
PL/SQL táblák:
Lehetnek fv-ek
paraméterei is vagy a fv által visszaadott
érték is lehet tábla
típusú.
Tábla típus
definiálása:
TYPE
tábla_típus_neve
IS TABLE
OF adattípus INDEX BY BINARY_INTEGER;
az
adattípus lehet %TYPE
vagy %ROWTYPE-al
megadva, lehet
előre definiált típus vagy általunk
definiált rekordtípus.
Tábla
deklarálása:
tábla_név
tábla_típus_neve;
Hivatkozás a
tábla
elemeire: tábla_név(index)
Az index lehet
kifejezés is
rekordokból
álló tábla esetén:
tábla_név(index).mező
fv
által visszaadott tábla esetén:
fv(paraméter)(index)
ha a
tábla rekordokból áll:
fv(paraméter)(index).mező
(!!!)
paraméter nélküli fv
esetén nem használható a fenti
jelölés
pl. fv()(index) --
rossz
Amíg
egy táblaelemnek nem
adtunk értéket addig az nem létezik.
Ha hivatkozunk rá akkor a NO_DATA_FOUND
kivételt generálja a rendszer.
PL/SQL tábla
attribútumai:
EXISTS,
COUNT, FIRST, LAST, PRIOR, NEXT, DELETE
hivatkozás rá:
tábla_név.attribútum
táblanév.EXISTS(n)
létezik-e
az n-edik elem
tábla_név.COUNT
hány eleme van a táblának
FIRST
és LAST
a legkisebb és legnagyobb
index (NULL ha a tábla üres)
PRIOR(n)
NEXT(n)
az n index előtti index
(NULL ha nincs előtte már)
Az indexeknek nem kell egymás utáni
számoknak lenniük
(!)
táblanév.DELETE(n)
az n-edik elemet törli
(felszabadítja az erőforrást is)
táblanév.DELETE(m,
n) m-től
n-ig törli
táblanév.DELETE
az egész táblát
törli
Példa: -> lásd lejjebb a
kurzoroknál
Elágazás
END
IF;
külön írni, utána
pontosvessző
ELSIF
egybeírni és hiányzik
belőle egy
“E” betű (nem ELSEIF)
IF ... THEN
utasítás1
ELSIF ... THEN
utasítás2
ELSIF ... THEN
utasítás3
END IF;
Ciklus
1.
forma
LOOP utasítások
END LOOP; (végtelen
ciklus kilépés EXIT-tel.)
EXIT-tel
kilépni csak
ciklusból lehet, PL/SQL blokkból nem (->
RETURN)
EXIT
másik formája az
EXIT
WHEN feltétel
Ciklusoknak
címke adható,
hasonlóan mint a PL/SQL blokkoknak
<<címke1>>
LOOP
utasítások
LOOP
EXIT
cimke1
END LOOP;
END
LOOP;
A
fenti módon címke
segítségével beágyazott
ciklusok mélyéről is
kiléphetük.
2.
forma
WHILE
felt LOOP utasítások
END LOOP;
3.
forma
FOR
számláló IN alsó..felső
LOOP utasítások END LOOP;
Az
alsó és felső határ lehet
literál, változó vagy
kifejezés, de kiértékelés
után egésznek kell lennie. A
lépésköz csak 1 lehet. Az
utóbbi két ciklusnak is adható
címke és EXIT-tel ki
is lehet lépni belőlük.
CASE
CASE
WHEN felt THEN
utasítások
WHEN felt THEN
utasítások
ELSE
utasítások
END
CASE;
GOTO
címke utáni
utasításra ugrik (címke csak
végrehajtható utasítás
előtt lehet, így nem lehet
pl. az END LOOP előtt, de ilyenkor segíthet a NULL
utasítás.)
IF-be,
ciklusba és blokkba nem lehet belépni
vele
kivételkeelőből nem lehet az
aktuális blokkba lépni
A blokkból a külső blokkba
lehet lépni
Kurzorok
Több
formája lehet
1. ->
deklaráció (CURSOR
c_név IS ...), OPEN, FETCH, CLOSE
2.
deklaráció, FOR c_rec
IN c_név
LOOP ... END LOOP;
3.
FOR
c_rec IN (SELECT ... )
LOOP ... END LOOP;
Az
első két formánál
paraméter adható meg a kurzornév
után c_név(param) formában. Az első
forma
esetén a paraméter az OPEN-nél
adható meg.
A
két utóbbi formánál a
ciklusváltozó mindenképpen rekord
típusú még akkor is ha a
lekérdezésnek
egyetlen oszlopa van csak. Így a hivatkozás
rá ez esetben is c_rec.oszlop.
Kurzor
attribútumok:
%FOUND, %NOTFOUND, %ISOPEN, %ROWCOUNT
Ugyanezek
az attribútumok
implicit kurzor attribútumként is
használhatók, ekkor a legutóbbi SQL
utasításra vonatkozóan adnak
információt. Formájuk:
SQL%attribútum.
Példa:
(cursor_tomb.sql)
DECLARE
v_nev VARCHAR2(20);
CURSOR emp_cur IS
SELECT deptno, ename FROM emp;
rec emp_cur%ROWTYPE;
TYPE tab_tip IS TABLE OF emp_cur%ROWTYPE INDEX BY BINARY_INTEGER;
tabla tab_tip;
i NUMBER(4);
BEGIN
OPEN emp_cur;
LOOP
FETCH emp_cur INTO rec;
EXIT WHEN emp_cur%NOTFOUND;
i:= emp_cur%ROWCOUNT;
tabla(i) := rec;
dbms_output.put_line(to_char(tabla(i).deptno)||' - '||tabla(i).ename);
END LOOP;
CLOSE emp_cur;
END;
Módosítás
kurzor sorain
végighaladva
deklaráció
-> (CURSOR
c_név IS ... FOR
UPDATE)
UPDATE
tábla ... WHERE CURRENT OF c_név;
Kurzor típusú
változók
(a változó egy
kurzorra mutat, az OPEN utasításkor fogjuk
megadni a lekérdezést) Lehet pl.
alprogram paramétere is.
Kurzor
típus definiálása
TYPE
cursor_tipus IS REF CURSOR [RETURN
rekordtípus]
A
rekordtípus megadása
történhet %TYPE, %ROWTYPE, vagy saját
rekordtípussal. Van egy generikus
rekordtípus is, amikor nem adjuk meg a RETURN
részt.
Változó
deklarálása
c_változó
cursor_tipus
Kurzor
megnyitása, olvasása,
lezárása
OPEN
c_változó FOR SELECT ...
FETCH
c_változó INTO ...
CLOSE
c_változó
Ha
a változót paraméterül
adjuk át egy alprogramnak, amelyik megnyitja vagy
lezárja a kurzort, akkor a
paraméter IN OUT kell hogy legyen. Ha a
paraméterül kapott kurzor
változó nem
megfelelő típusú akkor a ROWTYPE_MISMATCH
hibát generálja a rendszer.
Package-ben
nem
deklarálhatunk REF CURSOR típusú
változót, mert ezek a
változók nem maradnak
életben a session egész időtartama alatt,
ellentétben a többi típusú
változóval.
Hiba és
kivételkezelés
Ha
valami olyan dolog
történik futás közben, ami
megsérti az Oracle szabályait akkor a rendszer
egy
hibakódot és egy hibaeseményt
generál. Ezeket kezelhetjük le a hibakezelő
részben, ami a blokk végén szerepelhet.
Nélkülük
minden utasítás után
ellenőrizni kellene, hogy nem volt-e hiba. (pl. C-be
ágyazásnál ezt tettük)
Így
elég egyszer megírni a hibaellenőrzést
a blokk végén.
Viszont
ha egy helyen van
több lehetséges hiba kezelése akkor nem
tudjuk pontosan, hogy hol merült fel a
hiba.
A
hibakezelő részben név
szerint hivatkozhatunk a hibaeseményre, így csak
olyan hibát tudunk lekezelni,
aminek van neve. A felhasználó is
létrehozhat névvel ellátott
eseményt és
vannak olyan hibaesemények, amiknek már van neve.
(előre definiált
hibaesemények)
Kivételek
deklarálása:
kivétel_név
EXCEPTION;
A
kivételekre ugyanazok az
érvényességi szabályok
vonatkoznak mint a változókra. Egy blokkban
deklarált
kivétel a blokkra nézve lokális,
cimkézett blokk esetén hivatkozhatunk a
kivételre cimke.kivétel_név módon ... stb.
Az
előre definiált kivételek
a STANDARD package-ben vannak deklarálva
az alábbi módon.
CURSOR_ALREADY_OPEN
exception;
pragma
EXCEPTION_INIT(CURSOR_ALREADY_OPEN,
'-6511');
Nem
minden belső hibának van
neve. Ezeket nem tudjuk lekezelni hacsak nem adunk nekik nevet. Ezt egy
fordítónak szóló
direktívával tehetjük meg, aminek a neve
EXCEPTION_INIT.
pragma
EXCEPTION_INIT(hiba_név, hibakód);
A
STANDARD package-ben ilyen
direktívák is vannak, lásd a fenti
példát.
A
legfontosabb előre
definiált kivételek a következők:
CURSOR_ALREADY_OPEN
exception;
DUP_VAL_ON_INDEX exception;
TIMEOUT_ON_RESOURCE
exception;
-TRANSACTION_BACKED_OUT
exception;
INVALID_CURSOR exception;
NOT_LOGGED_ON exception;
LOGIN_DENIED exception;
NO_DATA_FOUND exception;
ZERO_DIVIDE exception;
INVALID_NUMBER exception;
TOO_MANY_ROWS exception;
STORAGE_ERROR exception;
PROGRAM_ERROR exception;
VALUE_ERROR exception;
A
fentieket a következő
utasítással listázhatjuk ki:
SELECT
text FROM dba_source WHERE type =
‘PACKAGE’
AND
name = ‘STANDARD’ AND text LIKE
‘%exception%’;
A kivételek
meghívása:
A
belső hibákat a rendszer
automatikusan meghívja ha előfordul az esemény,
és ha névvel láttuk el őket akkor
ez egyben az adott nevű hibaesemény
előfordulását is jelenti.
Az
általunk deklarált
eseményeket explicit módon meg kell
hívni.
RAISE
hibanév;
A
fenti módon előre definiált
(és névvel ellátott)
eseményt is meghívhatunk.
Kivételek
lekezelése:
A
kivétel hívásakor (explicit vagy
implicit módon) a vezérlés az
aktuális blokk
kivételkezelő részére
adódik. Ha ott nincs lekezelve a kivétel akkor a
külső
blokknak adódik tovább, addig, amíg
valahol le lesz kezelve. (ellenkező esetben
hibaüzenet a futtató környezetnek)
EXCEPTION
WHEN
kivétel_név OR kivétel_név2
THEN
utasítások
A
WHEN OTHERS megadásával
minden hibát lekezelhetünk (a név
nélkülieket is)
Néhány
apró tudnivaló:
A
deklarációban felmerülő
hibákat rögtön a külső blokk
fogja megkapni.
Nyitott
kurzor esetén
felmerülő hiba lekezelése előtt a kurzor
automatikusan bezáródik, így arra
hivatkozni nem lehet a hibakezelőben.
Ha
a kivételkezelő részben
felmerül egy hiba akkor a vezérlés
rögtön a külső blokk hibakezelő
részére
adódik.
Hiba
lekezelése majd
továbadása a külső blokknak ->
RAISE; (kivételnév nélkül)
Ilyen formában
csak a kivételkezelőben fordulhat elő a raise.
Mi
legyen ha a SELECT INTO
nem ad vissza egyetlen sort sem? (NO_DATA_FOUND)
Megoldás
-> alblokkba írás
Alprogramok hibáinak
lekezelése:
A
DBMS_STANDARD package
raise_application_error(hibakód,
hibaüzenet) procedúrájával az
alprogramokból úgy térhetünk
vissza, hogy egy megfelelő hibakódot adunk vissza
a hívónak, amit az lekezelhet, ha a
deklarációjában adott neki egy nevet.
(A
megadható hibakódok –20000
és –20999 között kell hogy
legyenek.)
Enélkül
csak a WHEN OTHERS
résszel tudnánk lekezelni az alprogram
hibáit, és így nem tudnánk
megállapítani
a hiba fajtáját.
SQLCODE és SQLERRM fv-ek
A
felhasználó által definiált
hibára +1-et ad vissza az SQLCODE, a belső
hibákra pedig negatív számot.
(kivétel +100 -> NO_DATA_FOUND)
Milyen
hibaüzenetei vannak a
rendszernek?
FOR err_num IN
1..9999 LOOP
dbms_output.put_line(SQLERRM(-err_num));
END LOOP;
A
fenti két fv. nem
használható közvetlenül SQL
utasításban (pl. VALUES(SQLCODE)),
értékükek
lokális változóba kell tenni. (err_num
:= SQLCODE)
A
le nem kezelt hibák esetén a rendszer
különbözően viselkedik a futtató
környezettől függően. Pl. C-be ágyazott
program ROLLBACK-el, alprogram nem, és
az OUT tipusú változóinak sem ad
értéket.
Alprogramok
(procdúrák,
fv-ek)
PROCEDURE p_név(param) IS
... BEGIN ... END;
FUNCTION f_név(param)
RETURN típus IS ... BEGIN ...
END;
Mindkettő
két részből áll
specifikációból és
body-ból. A specifikáció az IS
kulcsszóig
tart. A body-nak van deklarációs,
végrehajtható és
kivételkezelő része.
A
paramétere NUMBER lehet de nem pl. NUMBER(3) és
nem lehet NOT NULL megszorítás
a paraméterre.
Az
így létrehozott fv-ek SQL
utasításban is
használhatók. (Pl. IS_NUMBER() )
Visszatérés
az alprogramból:
RETURN (fv esetén visszatérési
érték is kell)
Az
alprogram deklarációk egy
blokk deklarációs részének
végén lehetnek csak (egyéb, pl.
változó deklarációk
után)
Lehetőség
van az előre
deklarációra (forward declaration)
Tárolt
alprogramok: CREATE
FUNCTION/PROCEDURE
Paraméterátadás
pozíció
illetve név szerint fv(p_név =>
érték)
Paraméter
módok: IN,
OUT, IN
OUT
Paraméterek
DEFAULT
értéke
p_nev IN típus DEFAULT
érték
Package-beli
alprogramok
overload-olhatók ha a paraméterek
száma vagy típusa eltérő
Ahhoz
hogy egy fv-t SQL
utasításban is használhassunk, az
alábbi megszorításoknak kell eleget
tennie:
1.
tárolt fv legyen
2.
egy sorra vonatkozó legyen
és ne egy csoportra
3.
csak IN módú paraméterei
legyenek
4.
paramétereinek típusa
Oracle belső típus legyen
5.
a visszaadott értékének
típusa Oracle belső típus legyen
Package-ek
A
package-ben lehetnek procedúrák,
függvények, típus
definíciók, változó
deklarációk, konstansok, kivételek,
kurzorok.
Két
része a specifikációs rész
és a törzs (body). A
specifikációs részben vannak a
publikus deklarációk. Ennek
létrehozása (SQL
utasítással):
CREATE OR
REPLACE PACKAGE p_név IS
publikus
típus és objektum
deklarációk
alprogram
specifikációk
END;
A
body-ban vannak az alprogramok és a kurzorok
implementációi. Csak ezeknek van
implementációs része, így
ha a package csak más objektumokat tartalmaz
(változók, típusok,
kivételek ... stb.) akkor nem is kell hogy body-ja is
legyen.
A
kurzorok kétféleképpen is
megadhatók.
1.
Vagy a specifikációban
adjuk meg őket a szokásos módon, ekkor nem is
szerepelnek az implementációs
részben.
2.
A specifikációs részben
csak a nevét és a
sortípusát adjuk meg
(CURSOR
C1 RETURN
<sortípus>) és az
implementációs
részben adjuk meg a SELECT-et.
CREATE OR
REPLACE PACKAGE BODY p_név IS
privát
típus és objektum
deklarációk
alprogramok
törzse (PROCEDURE ... IS ...)
kurzorok
(CURSOR C1 RETURN
<sortípus> IS SELECT ...)
[BEGIN
inicializáló
utasítások ]
END;
A
body-ban vannak az
implementációk és lehet neki
inicializációs része is (BEGIN ... END
között),
ami csak egyszer fut le, amikor a package-re először
hivatkoznak.
A
package specifikációs
részében szereplő objektumok lokálisak
az adatbázissémára nézve
és globálisak a
package-re nézve. hivatkozás
package-beli objektumokra: p_név.obj
a
STANDARD package-beli
objektumokra hivatkozhatunk a p_név
nélkül.
Lehet
azonos a neve két package-ben levő alprogramnak, amelyeknek
más a paraméterezése.
Ilyenkor híváskor derül ki, hogy melyik
fog futni a formális és aktuális
paraméterek egyeztetésekor (overloading).
Például a STANDARD package-ben van
több verzió is a TO_CHAR fv-re.
A
package-ek legfontosabb
előnyei:
Modularítás
Információ
elrejtés
Egészben
töltődik be a
memóriába minden objektuma az első
hivatkozáskor.
A
package-ben deklarált változók
és kurzorok a session végéig
léteznek, így
közösen használhatják azokat a
többi programok. (Kivétel a REF CURSOR, ami
package-ben nem deklarálható.)
Túlterhelt
alprogramok
írhatók (a lokális alprogramok is
túlterhelhetők, csak a tároltak nem)
A
package-ek forrásszövege a
DBA_SOURCE táblában megnézhető.
A
legfontosabb package-ek:
STANDARD
Beépített
függvények és alprogramok ebben vannak
DBMS_SQL
DDL és dinamikus SQL
végrehajtására
DBMS_OUTPUT
pl.
put_line()
DBMS_STANDARD
az alkalmazás
és az Oracle közötti
interakciót segíti
UTL_FILE
op. rendszer fájlok
írása, olvasása
A PL/SQL nyelv
használata
SQL*PLUS környezetben:
PL/SQL
procedúrákat a
következőképpen hívhatunk meg
SQL*PLUS-ból:
EXECUTE
proc(param); -- vagy CALL
proc(param);
A
fenti mód ekvivalens azzal,
mintha a következő pl/sql blokkot írnánk
be:
BEGIN
proc(param);
END;
SQL*PLUS-ban
definiálhatunk úgynevezett session
változót, ami a session
végéig él. Ezt
használhatjuk pl/sql blokkban is, úgy, mintha az
egy host változó lenne
(:változó). Pl. egy függvény
által visszaadott értéket
tehetünk bele, lehet egy
procedúra IN OUT paramétere ... stb.
Végül az aktuális
értékét kiírhatjuk a
képernyőre (vagy fájlba -> SPOOL)
Létrehozása:
VARIABLE
v_név
típus
Kiírása:
PRINT
v_név
Használata
különböző
helyzetekben:
EXECUTE :v_név :=
érték;
(BEGIN
:v_név := érték; END;)
EXECUTE :v_név :=
fv(param); (BEGIN :v_név :=
fv(param); END;)
EXECUTE
proc(:v_név)
(BEGIN
proc(:v_név;
END;)
Függvényhívás
szintaxisa:
Séma.Package.Fv_név@Db_link(paraméterek)
Megszorítások
fv-ekre:
Ahhoz,
hogy egy fv-t SQL
utasításban lehessen használni a
következő kritériumokat kell
teljesítenie:
Tárolt
fv legyen
Az argumentumai
csak egyszerű típusúak
lehetnek (nem lehet pl. oszlop)
Az
összes formális paramétere IN
módú legyen
Az
összes formális paramétere belső Oracle
adattípusú legyen
A
visszatérési értéke belső
Oracle típusú
legyen
Mellékhatások
fv-ekben:
Mellékhatást
okozhat ha egy
fv adatbázis táblára vagy package
változóra hivatkozik. Az ilyen
függvényeket
nem használhatjuk tetszőleges SQL
utasításban. Pl.
create or replace function
rossz_fv return
number is
begin
INSERT INTO
emp(ename) VALUES('kiss');
RETURN 11;
end;
select
rossz_fv from dual;
06571:
Function ROSSZ_FV does
not guarantee not to update database
Vagyis
végrehajtáskor hibaüzenetet
kapunk.
A
tárolt fv-ek esetén az Oracle ellenőrizni tudja,
hogy milyen mellékhatásai
lehetnek a fv-nek és ennek megfelelően engedi meg a
függvény használatát
különböző esetekben. A package-beli
függvények viszont rejtve vannak így
ezekre
nekünk kell közölni a rendszerrel, hogy
milyen mellékhatásai lehetnek a fv-nek.
Ezt a package specifikációban egy PRAGMA-val
tesszük meg, ami a deklaráció
után
kell hogy szerepeljen.
PRAGMA
RESTRICT_REFERENCES(fv_név,
WNDS [,WNPS] [,RNDS] [,RNPS]);
Ahol
WNDS: writes
no database state (nem módosítja az
adatbázist)
WNPS:
writes no package state (nem
módosítja package változók
értékét)
RNDS:
reads no database state (nem kérdez le
táblát)
RNPS:
reads no package state (nem hivatkozik package
változókra)
Az
első megadása kötelező, a többi
opcionális. Ezzel mondjuk meg az Oracle-nek,
hogy a fv milyen mellékhatásokkal rendelkezhet
(mennyire „tiszta” a fv) és az
Oracle ez alapján dönti el, hogy milyen
környezetekben fogja engedni a fv
használatát.
Ha
be akarjuk csapni és
”szebbnek” mondjuk a fv-t mint amilyen azt
fordításkor észreveszi a
fordító és
szól:
0/0
PL/SQL: Compilation unit analysis terminated
2/3
PLS-00452: Subprogram 'ROSSZ_FV' violates its associated
pragma
Triggerek
A
triggert SQL utasítással hozhatjuk
létre (CREATE TRIGGER), de a trigger
végrehajtható részét PL/SQL
nyelven kell megírnunk. A trigger valamilyen
esemény hatására automatikusan elindul
és végrehajtja a PL/SQL blokkban
megadott utasításokat. Ezen
utasítások
végrehajtásához a trigger
tulajdonosának
kell, hogy joga legyen, méghozzá
közvetlenül és nem role-okon
keresztül. Az
esemény lehet DML utasítás (pl.
insert, update), DDL utasítás (pl. create, drop),
vagy adatbázis esemény (pl. startup, login).
DML triggerek
CREATE [OR REPLACE]
TRIGGER [schema.]trigger
{BEFORE | AFTER | INSTEAD OF}
{DELETE | INSERT | UPDATE [OF column [,
column] ...]}
[OR {DELETE | INSERT
| UPDATE [OF column [, column] ...]}] ...
ON [schema.]table
[ [REFERENCING { OLD [AS] old [NEW [AS]
new]
| NEW [AS] new [OLD [AS]
old] } ]
[ FOR EACH ROW
[WHEN (condition)] ]
pl/sql_block
A
triggerhez tartozik egy kiváltó
(elsütő) művelet (INSERT, DELETE, UPDATE).
A
trigger egy objektumhoz
(tábla vagy esetleg nézet) kötődik.
Időzítés: A trigger egy
módosító művelet előtt vagy
után (vagy
helyette) fut le.
Trigger
típusa: Ha megadjuk a FOR EACH ROW
opciót akkor a trigger minden sorra
egyszer végrehajtódik. Az ilyen triggereket sor-triggernek
hívjuk.
Ellenkező esetben csak utasításonként
egyszer hajtódik végre a trigger. Ekkor a
neve utasítás-trigger.
When feltétel csak sortriggerre adható meg. Ilyenkor a trigger csak azokra a sorokra fut le, amelyek kielégítik a feltételt.
Triggerek
engedélyezhetők
vagy letilthatók (ALTER TRIGGER)
Ha egy művelet több triggert is aktivizál akkor azok futási sorrendje nem garantált.
Triggeren
belül nem adható ki
tranzakciókezelő utasítás. COMMIT,
ROLLBACK, SAVEPOINT
Az
oszlopok régi és új
értékére a PL/SQL blokkban
úgy hivatkozhatunk mint host
változókra. (kettőspont a
változó előtt:
:NEW.oszlop,
:OLD.oszlop)
BEFORE triggerben az új értéket meg is változtathatjuk és ekkor ez kerül be majd az oszlopba. AFTER trigger esetén ezt nem tehetjük meg.
Egy AFTER trigger viszont már használhatja a ROWID-jét a sornak.
A triggerek
aktivizálódási
sorrendje:
1.
BEFORE
utasítás szintű triggerek
2.
Minden egyes érintett sorra
a)
a BEFORE sor szintű triggerek
b)
maga a DML utasítás és az
integritási feltételek ellenőrzése
c)
az AFTER sor szintű triggerek
3.
AFTER
utasítás szintű triggerek
A
trigger futása alatt a
rendszer egy READ konzisztens állapotát
garantálja minden hivatkozott
táblának,
így a trigger nem látja a futása alatt
történt változásokat.
Mire
vigyázzunk triggerek
megadásakor?
Amit
deklaratív módon is meg
tud oldani az Oracle arra ne írjunk triggert.
Ne
hozzunk létre rekurzív
triggereket. (Pl. egy AFTER UPDATE trigger ne adjon ki update
utasítást.)
INSERT
esetén csak a NEW értékeknek
van értelme, a régiek NULL-ok.
DELETE
esetén csak az OLD
értékeknek van értelme, az
újak NULL-ok.
A
WHEN után még nem kell
kettőspont az OLD és NEW elé, csak a blokkban.
Ha
több művelet elsütheti a
triggert akkor így dönthetjük el melyik
volt a tényleges:
IF
INSERTING ...
IF UPDATING [(‘oszlop’)]...
IF DELETING ...
Update
esetén még az oszlopot
is megtudhatjuk.
Ha
a trigger közben kivétel
lép fel, amit nem kezeltek le akkor a trigger és
az elsütő művelet is
ROLLBACK-elve lesz. Így lehet pl. triggerből
visszacsévélni az eredeti
műveletet. Gyakran erre a célra a
RAISE_APPLICATION_ERROR(hibakód, hibaüzenet)
procedúrát használják, mert
ekkor a kiváltó műveletet kiadó
program kultúráltan
lekezelheti a hibát.
DDL
és tranzakció-kezelő
utasítás
nem lehet a triggerben.
Információk
a
triggerekről: DBA_TRIGGERS
Megszorítások
a triggerek
használatával kapcsolatban:
Hivatkozó
tábla elsődleges és
idegen kulcs oszlopát nem módosíthatja
a trigger. (Hivatkozó tábla az, amelyik
idegen kulcs hivatkozásban van a
módosított táblával, amire
épp sor szintű
trigger fut.) Így az alábbi csak akkor
működik ha nincs idegen kulcs definíció
a két tábla között.
Ez
a 9i verziótól már akkor
is működik, ha van idegen kulcs.
CREATE
OR REPLACE TRIGGER cascade_upd
AFTER
UPDATE OF deptno ON dept
BEGIN
UPDATE emp SET emp.deptno
= :new.deptno
WHERE
emp.deptno = :old.deptno;
END;
Változás
alatt lévő táblát
nem olvashat a trigger. (Változás alatti az a
tábla, amelynek módosítása
alatt
épp sor szintű trigger fut.) Vagyis egy sor szintű trigger
nem olvashat egy
éppen módosítás alatt levő
táblából.
DDL és
Adatbázis triggerek
Kiváltó
eseményük lehet csak
egy adott sémára, vagy az egész
adatbázisra vonatkozó művelet.
Lehetséges
DDL események: CREATE,
DROP, RENAME, DDL …
Lehetséges
adatbázis
események: SERVERERROR,
LOGON, STARTUP …
DBA_TRIGGERS.base_object_type
-> database, schema, table, view
További
infók: Application
Developer’s Guide 9. fejezet
Alprogramokkal
kapcsolatos
rendszerjogosultságok:
CREATE
(ANY) PROCEDURE …
EXECUTE
(ANY) PROCEDURE …
Alprogramokkal
kapcsolatos katalógusok:
DBA_OBJECTS
CREATED
amikor létrehozták
LAST_DDL
utolsó
módosítás
TIMESTAMP
utolsó fordítás
STATUS
VALID/INVALID
DBA_SOURCE -- ebből olvas
az SQLPLUS DESCRIBE utasítása
DBA_ERRORS -- ebből olvas
az SQLPLUS SHOW ERRORS
utasítása
SELECT line||
'/' ||position POS,text
FROM
user_errors
WHERE name
= 'proc_nev'
ORDER
BY line;
DBA_TRIGGERS
(típus, esemény,
tábla, when feltétel, státusz,
forrás)
Pl.
egy procedúra hivatkozik
egy táblára, egy függvényre,
egy nézetre.
Ha
a hivatkozott objektum
megváltozik, akkor a hivatkozó INVALID
állapotba kerül, és a
legközelebbi
hivatkozáskor újra fordítja a
rendszer. Van közvetlen függőség
és közvetett
függőség.
USER_DEPENDENCIES
és
DBA_DEPENDENCIES nézetek
SELECT
name, type, referenced_name, referenced_type
FROM user_dependencies
WHERE referenced_name IN ('EMP'
, 'NEW_EMP' );
A
fentinél elegánsabb módon
nézhetők meg a függőségek két
nézetből (DEPTREE, IDEPTREE). Ezek
létrehozása és
megfelelő feltöltése -> UTLDTREE.SQL
PUBLIC_DEPENDENCY
(obj,
hivatkozott_obj) nézet
PL/SQL
komponensek újrafordítása
1.
Automatikusan, amikor
futás közben a rendszer INVALID-nak
találja
2.
Manuálisan
ALTER
PROCEDURE <név> COMPILE
ALTER
PACKAGE <név>
COMPILE {PACKAGE|BODY}
Újrafordításkor
először
minden invalid objektumot újrafordít a rendszer,
amitől az illető függ.
Feltételes
fordítás (10.2-es
verziótól)
Példák
a dokumentációból.
BEGIN
$IF DBMS_DB_VERSION.VER_LE_10_1 $THEN
$ERROR 'unsupported database release' $END
$ELSE
DBMS_OUTPUT.PUT_LINE ('Release ' || DBMS_DB_VERSION.VERSION || '.' ||
DBMS_DB_VERSION.RELEASE || ' is supported.');
COMMIT WRITE IMMEDIATE NOWAIT;
$END
END;
/
CREATE PROCEDURE circle_area(radius my_pkg.my_real) IS
my_area my_pkg.my_real;
my_datatype VARCHAR2(30);
BEGIN
my_area := my_pkg.my_pi * radius;
DBMS_OUTPUT.PUT_LINE('Radius: ' || TO_CHAR(radius)
|| ' Area: ' || TO_CHAR(my_area) );
$IF $$my_debug $THEN -- if my_debug is TRUE, run some debugging code
SELECT DATA_TYPE INTO my_datatype FROM USER_ARGUMENTS
WHERE OBJECT_NAME = 'CIRCLE_AREA' AND ARGUMENT_NAME = 'RADIUS';
DBMS_OUTPUT.PUT_LINE('Datatype of the RADIUS argument is: ' || my_datatype);
$END
END;
/
ALTER PROCEDURE circle_area COMPILE PLSQL_CCFLAGS = 'my_debug:TRUE'
REUSE SETTINGS;