A PL/SQL motor minden procedurális utasítást végrehajt, azonban a PL/SQL programba beépített SQL utasításokat átadja az SQL motornak. Az SQL motor fogadja az utasítást, végrehajtja azt és esetleg adatokat szolgáltat vissza a PL/SQL motornak. Minden egyes ilyen motorváltás növeli a végrehajtási időt. Ha sok a váltás, csökken a teljesítmény. Különösen igaz ez, ha az SQL utasítás ciklus magjába van ágyazva, például egy kollekció elemeinek egyenkénti feldolgozásánál.
1. példa
DECLARE
/* Összegyűjtjük a lejárt kölcsönzésekhez tartozó ügyfeleket */
TYPE t_ugyfelek IS TABLE OF ugyfel%ROWTYPE;
TYPE t_konyvek IS TABLE OF konyv%ROWTYPE;
v_Ugyfelek t_ugyfelek := t_ugyfelek();
v_Konyvek t_konyvek := t_konyvek();
-- Rögzítjük a mát a példa kedvéért
v_Ma DATE := TO_DATE('2002-05-11', 'YYYY-MM-DD');
PROCEDURE feldolgoz(
p_Ugyfelek t_ugyfelek,
p_Konyvek t_konyvek
) IS
BEGIN
FOR i IN 1..p_Ugyfelek.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(p_Ugyfelek(i).nev || ' - ' || v_Konyvek(i).cim);
END LOOP;
END feldolgoz;
BEGIN
-- Ebben a ciklusban sok a SELECT, sok a motorváltás
FOR bejegyzes IN (SELECT * FROM kolcsonzes) LOOP
IF TRUNC(bejegyzes.datum) + 30*(bejegyzes.hosszabbitva + 1) < v_Ma THEN
v_Ugyfelek.EXTEND;
v_Konyvek.EXTEND;
SELECT *
INTO v_Ugyfelek(v_Ugyfelek.LAST)
FROM ugyfel
WHERE id = bejegyzes.kolcsonzo;
SELECT *
INTO v_Konyvek(v_Konyvek.LAST)
FROM konyv
WHERE id = bejegyzes.konyv;
END IF;
END LOOP;
END;
/
/* Eredmény:
József István - Piszkos Fred és a többiek
József István - ECOOP 2001 - Object-Oriented Programming
József István - Java - start!
József István - Matematikai zseblexikon
József István - Matematikai Kézikönyv
Jaripekka Hämälainen - A critical introduction to twentieth-century American drama - Volume 2
Jaripekka Hämälainen - The Norton Anthology of American Literature - Second Edition - Volume 2
A PL/SQL eljárás sikeresen befejeződött.
*/
Hozzárendelésnek hívjuk azt a tevékenységet, amikor egy PL/SQL változónak SQL utasításban adunk értéket. Egy kollekció minden elemének egyszerre történő hozzárendelését együttes hozzárendelésnek nevezzük. Az együttes hozzárendelés csökkenti a PL/SQL és SQL motorok közötti átváltások számát és így növeli a teljesítményt.
A PL/SQL oldali együttes hozzárendelés eszköze a FORALL utasítás, az SQL oldalié a BULK COLLECT utasításrész. A FORALL utasítás alakja:
FORALL index IN {alsó_határ..felső_határ
| INDICES OF kollekció
[BETWEEN alsó_határ AND felső_határ]
| VALUES OF indexkollekció_név}
[SAVE EXCEPTIONS] sql_utasítás;
Az index explicit módon nem deklarált változó, amelynek hatásköre a FORALL utasítás, és azon belül is csak kollekció indexeként használható fel. Kifejezésben nem szerepelhet és érték nem adható neki. Az alsó_határ és felső_határ numerikus értékű kifejezések, amelyek egyszer értékelődnek ki a FORALL végrehajtásának kezdetén, és értékük egész vagy egészre kerekítődik. Az egészeknek egy érvényes kollekcióindex-tartományt kell megadniuk.
Az INDICES OF utasításrész esetén az index a megadott kollekció elemeinek indexeit veszi fel. A BETWEEN segítségével ezt az indextartományt korlátozhatjuk le. Ha az indextartomány valamely indexe a kollekcióban nem létezik, akkor figyelmen kívül marad. Ez az utasításrész törölt elemeket tartalmazó beágyazott tábla vagy numerikus kulcsú asszociatív tömb esetén használható.
A VALUES OF utasításrész azt írja elő, hogy az index által felveendő értékeket egy, az indexkollekció_név által megnevezett kollekció tartalmazza. Ekkor a megadott indexkollekció tetszőleges (akár ismétlődő) indexeket tartalmazhat. Az indexkollekció beágyazott tábla, vagy numerikus kulcsú asszociatív tömb lehet, az elemek típusa pedig vagy PLS_INTEGER vagy BINARY_INTEGER. Ha az indexkollekció üres, akkor a FORALL nem fut le, és kivétel váltódik ki.
Az sql_utasítás egy olyan INSERT, DELETE vagy UPDATE utasítás, amely kollekcióelemeket hivatkozik WHERE, VALUES vagy SET utasításrészében. Összetett adattípust tartalmazó kollekciók esetén a ciklusváltozóval történő indexelés után a további alstruktúrákba történő hivatkozás nem megengedett, azaz nem hivatkozhatunk rekord elemek esetén a rekordelem mezőire, objektum elemeknél attribútumokra, kollekció elemeknél a beágyazott kollekciók egyes elemeire.
Az SQL motor az SQL utasítást a megadott indextartomány minden értéke mellett egyszer végrehajtja. Az adott indexű kollekcióelemeknek létezniük kell.
A FORALL utasítás csak szerveroldali programokban alkalmazható.
2. példa
CREATE OR REPLACE TYPE T_Id_lista IS TABLE OF NUMBER;
/
/* FORALL nélkül */
CREATE OR REPLACE PROCEDURE kolcsonzes_torol(
p_Ugyfelek T_Id_lista,
p_Konyvek T_Id_lista
) IS
/* Több kölcsönzésbejegyzést töröl, nem módosítja a
szabad példányok számát stb. */
BEGIN
FOR i IN 1..p_Ugyfelek.COUNT LOOP
DELETE FROM kolcsonzes
WHERE kolcsonzo = p_Ugyfelek(i)
AND konyv = p_Konyvek(i);
END LOOP;
END kolcsonzes_torol;
/
show errors
/* FORALL használatával */
CREATE OR REPLACE PROCEDURE kolcsonzes_torol(
p_Ugyfelek T_Id_lista,
p_Konyvek T_Id_lista
) IS
/* Több kölcsönzésbejegyzést töröl, nem módosítja a
szabad példányok számát stb. */
BEGIN
FORALL i IN 1..p_Ugyfelek.COUNT
DELETE FROM kolcsonzes
WHERE kolcsonzo = p_Ugyfelek(i)
AND konyv = p_Konyvek(i);
END kolcsonzes_torol;
/
show errors
/*
Megjegyzés:
Szintaktikájukban alig van különbség, viszont
a FORALL esetében csak egy hozzárendelés van, FORALL
nélkül annyi, ahányszor lefut a ciklusmag.
*/
3. példa
INDICES OF és VALUES OF használatára
CREATE TABLE t1 AS
SELECT ROWNUM id, LPAD('x', 10) adat FROM dual CONNECT BY LEVEL <= 4;
/
SELECT * FROM t1;
/*
ID ADAT
--- ----
1 x
2 x
3 x
4 x
*/
/* FORALL - INDICES OF */
DECLARE
TYPE T_Id_lista IS TABLE OF NUMBER;
v_Id_lista T_Id_lista := T_Id_lista(3,1);
BEGIN
-- Törüljük az 1. elemet, 1 elem marad
v_Id_lista.DELETE(1);
FORALL i IN INDICES OF v_Id_lista -- i: {2}
UPDATE t1
SET adat = 'INDICES OF'
WHERE id = v_Id_lista(i); -- T_Id_lista(2) = 1 -> 1-es Id-re fut le az UPDATE
END;
/
SELECT * FROM t1;
/*
ID ADAT
--- ----------
1 INDICES OF
2 x
3 x
4 x
*/
/* FORALL - VALUES OF */
DECLARE
TYPE T_Id_lista IS TABLE OF NUMBER;
TYPE T_Index_lista IS TABLE OF BINARY_INTEGER;
v_Id_lista T_Id_lista := T_Id_lista(5,1,4,3,2);
v_Index_lista T_Index_lista := T_Index_lista(4,5);
BEGIN
FORALL i IN VALUES OF v_Index_lista -- i: {4, 5}
UPDATE t1
SET adat = 'VALUES OF'
WHERE id = v_Id_lista(i);
-- p_Id_lista(4) = 3
-- p_Id_lista(5) = 2
-- -> 3-as és 2-es Id-kre fut le az UPDATE
END;
/
SELECT * FROM t1;
/*
ID ADAT
--- ----------
1 INDICES OF
2 VALUES OF
3 VALUES OF
4 x
*/
DROP TABLE t1;
Ha egy FORALL utasításban az SQL utasítás nem kezelt kivételt vált ki, akkor az egész FORALL visszagörgetődik. Ha viszont a kiváltott kivételt kezeljük, akkor csak a kivételt okozó végrehajtás görgetődik vissza az SQL utasítás előtt elhelyezett implicit mentési pontig, a korábbi végrehajtások eredménye megmarad.
4. példa
CREATE OR REPLACE PROCEDURE kolcsonoz(
p_Ugyfelek T_Id_lista,
p_Konyvek T_Id_lista
) IS
/* Több könyv kölcsönzését végzi el.
p_Ugyfelek és p_Konyvek mérete meg kell egyezzen. */
v_Most DATE := SYSDATE;
v_Tulkolcsonzes NUMBER;
BEGIN
SAVEPOINT kezdet;
/* Ha nem létezik az ügyfél vagy a könyv, akkor a ciklus
magjában lesz egy kivétel. */
FORALL i IN 1..p_Ugyfelek.COUNT
INSERT INTO kolcsonzes VALUES
(p_Ugyfelek(i), p_Konyvek(i), v_Most, 0,
/* Ha elfogy valamelyik könyv, akkor lesz egy kivétel */
FORALL i IN 1..p_Ugyfelek.COUNT
UPDATE konyv SET szabad = szabad -1
WHERE id = p_Konyvek(i);
/* Az ügyfelek konyvek táblájának bővítése */
FORALL i IN 1..p_Ugyfelek.COUNT
INSERT INTO TABLE(SELECT konyvek FROM ugyfel WHERE id = p_Ugyfelek(i))
VALUES (p_Konyvek(i), v_Most);
/* Nézzük, lett-e túlkölcsönzés ? */
SELECT COUNT(1) INTO v_Tulkolcsonzes
FROM ugyfel
WHERE max_konyv < (SELECT COUNT(1) FROM TABLE(konyvek));
IF v_Tulkolcsonzes > 0 THEN
RAISE_APPLICATION_ERROR(-20010,
'Valaki túl sok könyvet akar kölcsönözni');
END IF;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK TO kezdet;
RAISE;
END kolcsonoz;
/
show errors
SELECT COUNT(1) "Kölcsönzések" FROM kolcsonzes;
BEGIN
kolcsonoz(T_Id_lista(10,10,15,15,15), T_Id_lista(35,35,25,20,10));
END;
/
BEGIN
kolcsonoz(T_Id_lista(15), T_Id_lista(5));
END;
/
SELECT COUNT(1) "Kölcsönzések" FROM kolcsonzes;
/*
Kölcsönzések
------------
14
Hiba a(z) 1. sorban:
BEGIN
*
ORA-02290: ellenőrző megszorítás (PLSQL.KONYV_SZABAD) megsértése
ORA-06512: a(z) "PLSQL.KOLCSONOZ", helyen a(z) 40. sornál
ORA-06512: a(z) helyen a(z) 2. sornál
BEGIN
Hiba a(z) 1. sorban:
*
ORA-20010: Valaki túl sok könyvet akar kölcsönözni
ORA-06512: a(z) "PLSQL.KOLCSONOZ", helyen a(z) 40. sornál
ORA-06512: a(z) helyen a(z) 2. sornál
Kölcsönzések
------------
14
*/
A DML utasítások végrehajtásához az SQL motor felépíti az implicit kurzort (lásd 8. fejezet). A FORALL utasításhoz kapcsolódóan a szokásos kurzorattribútumok (%FOUND, %ISOPEN, %NOTFOUND, %ROWCOUNT) mellett az implicit kurzornál használható a %BULK_ROWCOUNT attribútum is. Ezen attribútum szemantikája megegyezik egy asszociatív tömbével. Az attribútum i. eleme a DML utasítás i. futásánál feldolgozott sorok számát tartalmazza. Értéke 0, ha nem volt feldolgozott sor. Indexeléssel lehet rá hivatkozni. A %BULK_ROWCOUNT indextartománya megegyezik a FORALL indextartományával.
5. példa
CREATE OR REPLACE PROCEDURE ugyfel_visszahoz(p_Ugyfelek T_Id_lista) IS
/* Több ügyfél minden könyvének visszahozatalát adminisztrálja
a függvény. Kiírja, hogy ki hány könyvet hozott vissza. */
BEGIN
/* Könyvek szabad példányainak adminisztrálása */
FOR k IN (
SELECT konyv, COUNT(1) peldany FROM kolcsonzes
WHERE kolcsonzo IN (SELECT COLUMN_VALUE
FROM TABLE(CAST(p_Ugyfelek AS T_Id_lista)))
GROUP BY konyv
) LOOP
UPDATE konyv SET szabad = szabad + k.peldany
WHERE id = k.konyv;
END LOOP;
/* Az ügyfelek konyvek tábláinak üresre állítása. */
FORALL i IN 1..p_Ugyfelek.COUNT
UPDATE ugyfel SET konyvek = T_Konyvek()
WHERE id = p_Ugyfelek(i);
/* A kölcsönzések törlése */
FORALL i IN 1..p_Ugyfelek.COUNT
DELETE FROM kolcsonzes WHERE kolcsonzo = p_Ugyfelek(i);
/* A %BULK_ROWCOUNT segítségével jelentés készítése */
FOR i IN 1..p_Ugyfelek.COUNT LOOP
DBMS_OUTPUT.PUT_LINE('Ügyfél: ' || p_Ugyfelek(i)
|| ', visszahozott könyvek: ' || SQL%BULK_ROWCOUNT(i));
END LOOP;
END ugyfel_visszahoz;
/
show errors
A FORALL utasítás SAVE EXCEPTIONS utasításrésze lehetőséget ad arra, hogy a FORALL működése közben kiváltódott kivételeket tároljuk, csak az utasítás végrehajtása után kezeljük őket. Az Oracle ehhez egy új kurzorattribútumot értelmez, amelynek neve %BULK_EXCEPTIONS. Ez rekordok asszociatív tömbje. A rekordoknak két mezőjük van. A %BULK_EXCEPTIONS(i). ERROR_INDEX a FORALL indexének azon értékét tartalmazza, amelynél a kivétel bekövetkezett, a %BULK_EXCEPTIONS(i). ERROR_CODE értéke pedig a megfelelő Oracle hibakód.
A %BULK_EXCEPTIONS mindig a legutoljára végrehajtott FORALL információit tartalmazza. Az eltárolt kivételek számát a %BULK_EXCEPTIONS.COUNT szolgáltatja, az indexek 1-től eddig mehetnek.
Ha a SAVE EXCEPTIONS utasításrészt nem adjuk meg, akkor egy kivétel bekövetkezte után a FORALL működése befejeződik. Ekkor a %BULK_EXCEPTIONS a bekövetkezett kivétel információit tartalmazza csak.
6. példa
CREATE OR REPLACE TYPE T_Id_lista IS
TABLE OF NUMBER;
CREATE OR REPLACE TYPE T_Szamok IS
TABLE OF NUMBER;
/
CREATE OR REPLACE FUNCTION selejtez(
p_Konyvek T_Id_lista,
p_Mennyit T_Szamok
) RETURN T_Id_lista IS
/* A könyvtárból selejtezi a megadott könyvekből
a megadott számú példányt. Visszaadja azoknak a könyveknek
az azonosítóit, amelyekből nem lehet ennyit selejtezni
(mert nincs annyi vagy kölcsönzik). Ha nem volt ilyen,
akkor üres kollekciót (nem NULL-t) ad vissza.
A paraméterek méretének meg kell egyeznie.
*/
v_Konyvek T_Id_lista := T_Id_lista();
v_Index PLS_INTEGER;
bulk_kivetel EXCEPTION;
PRAGMA EXCEPTION_INIT(bulk_kivetel, -24381);
BEGIN
/* Amit tudunk módosítunk */
FORALL i IN 1..p_Konyvek.COUNT SAVE EXCEPTIONS
UPDATE konyv SET szabad = szabad - p_Mennyit(i),
keszlet = keszlet - p_Mennyit(i)
WHERE id = p_Konyvek(i);
RETURN v_Konyvek;
EXCEPTION
WHEN bulk_kivetel THEN
/* A sikertelen módosítások összegyűjtése */
FOR i IN 1..SQL%BULK_EXCEPTIONS.COUNT LOOP
v_Index := SQL%BULK_EXCEPTIONS(i).ERROR_INDEX;
v_Konyvek.EXTEND;
v_Konyvek(v_Konyvek.LAST) := p_Konyvek(v_Index);
END LOOP;
RETURN v_Konyvek;
END selejtez;
/
show errors
DECLARE
/* Kipróbáljuk */
v_Konyvek T_Id_lista;
v_Konyv konyv%ROWTYPE;
BEGIN
DBMS_OUTPUT.NEW_LINE;
DBMS_OUTPUT.PUT_LINE('Könyvek selejtezése: 5, 35');
DBMS_OUTPUT.NEW_LINE;
v_Konyvek := selejtez(T_Id_lista(5, 35), T_Szamok(1, 1));
IF v_Konyvek.COUNT > 0 THEN
DBMS_OUTPUT.PUT_LINE('Voltak hibák - nem mindent lehetett törölni');
FOR i IN 1..v_Konyvek.COUNT LOOP
SELECT * INTO v_Konyv FROM konyv WHERE id = v_Konyvek(i);
DBMS_OUTPUT.PUT_LINE(v_Konyv.id || ', ' || v_Konyv.szabad
|| ', ' || v_Konyv.cim );
END LOOP;
END IF;
END;
/
/* Eredmény:
Könyvek selejtezése: 5, 35
Voltak hibák - nem mindent lehetett törölni
35, 0, A critical introduction to twentieth-century American drama - Volume 2
A PL/SQL eljárás sikeresen befejeződött.
*/
A SELECT, INSERT, DELETE, UPDATE, FETCH utasítások INTO utasításrészében használható a BULK_COLLECT előírás, amely az SQL motortól az együttes hozzárendelést kéri. Alakja:
BULK COLLECT INTO kollekciónév [,kollekciónév]…
A kollekciók elemtípusainak rendre meg kell egyezniük az eredmény oszlopainak típusaival. Több oszlop tartalmát egy megfelelő rekord elemtípusú kollekcióba is össze lehet gyűjteni.
7. példa
CREATE OR REPLACE FUNCTION aktiv_kolcsonzok
RETURN T_Id_lista IS
v_Id_lista T_Id_lista; -- BULK COLLECT-nél nem kell inicializálni
BEGIN
SELECT DISTINCT kolcsonzo
BULK COLLECT INTO v_Id_lista
FROM kolcsonzes
ORDER BY kolcsonzo;
RETURN v_Id_lista;
END aktiv_kolcsonzok;
/
SELECT * FROM TABLE(aktiv_kolcsonzok);
/*
COLUMN_VALUE
------------
10
15
20
25
30
35
6 sor kijelölve.
*/
DECLARE
/* Növeljük a kölcsönzött könyvek példányszámait 1-gyel.
A módosított könyvek azonosítóit összegyűjtjük. */
v_Konyvek T_Id_lista;
BEGIN
UPDATE konyv k
SET keszlet = keszlet + 1,
szabad = szabad + 1
WHERE EXISTS (SELECT 1 FROM kolcsonzes
WHERE konyv = k.id)
RETURNING id
BULK COLLECT INTO v_Konyvek;
DBMS_OUTPUT.PUT_LINE('count: ' || v_Konyvek.COUNT);
END;
/
/* Eredmény:
count: 10
A PL/SQL eljárás sikeresen befejeződött.
*/
A BULK COLLECT mind az implicit, mind az explicit kurzorok esetén használható. Az adatokat a kollekcióban az 1-es indextől kezdve helyezi el folyamatosan, felülírva az esetleges korábbi elemeket.
Az együttes hozzárendelést tartalmazó FETCH utasításnak lehet egy olyan utasításrésze, amely a betöltendő sorok számát korlátozza. Ennek alakja:
FETCH … BULK COLLECT INTO … LIMIT sorok;
ahol a sorok pozitív számértéket szolgáltató kifejezés. A kifejezés értéke egészre kerekítődik, ha szükséges. Ha értéke negatív, akkor az INVALID_NUMBER kivétel váltódik ki. Ezzel korlátozhatjuk a betöltendő sorok számát.
8. példa
DECLARE
CURSOR cur_ugyfel_konyvek IS SELECT nev, konyvek FROM ugyfel;
-- Kollekció rekord típusú elemekkel
TYPE t_ugyfel_adatok IS
VARRAY(4) OF cur_ugyfel_konyvek%ROWTYPE;
v_Buffer t_ugyfel_adatok;
BEGIN
OPEN cur_ugyfel_konyvek;
LOOP
FETCH cur_ugyfel_konyvek
BULK COLLECT INTO v_Buffer
LIMIT v_Buffer.LIMIT; -- Ennyi fér bele
EXIT WHEN v_Buffer.COUNT = 0;
FOR i IN 1..v_Buffer.COUNT LOOP
DBMS_OUTPUT.NEW_LINE;
DBMS_OUTPUT.PUT_LINE(v_Buffer(i).nev);
FOR j IN 1..v_Buffer(i).konyvek.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(' '
|| LPAD(v_Buffer(i).konyvek(j).konyv_id, 3) || ' '
|| TO_CHAR(v_Buffer(i).konyvek(j).datum, 'YYYY-MON-DD'));
END LOOP;
END LOOP;
END LOOP;
CLOSE cur_ugyfel_konyvek;
END;
/
/* Eredmény:
Kovács János
Szabó Máté István
30 2002-ÁPR. -21
45 2002-ÁPR. -21
50 2002-ÁPR. -21
József István
15 2002-JAN. -22
20 2002-JAN. -22
25 2002-ÁPR. -10
45 2002-ÁPR. -10
50 2002-ÁPR. -10
Tóth László
30 2002-FEBR. -24
Erdei Anita
35 2002-ÁPR. -15
Komor Ágnes
5 2002-ÁPR. -12
10 2002-MÁRC. -12
Jaripekka Hämälainen
35 2002-MÁRC. -18
40 2002-MÁRC. -18
A PL/SQL eljárás sikeresen befejeződött.
*/
A BULK COLLECT csak szerveroldali programokban alkalmazható.
A FORALL utasítás tartalmazhat olyan INSERT, DELETE és UPDATE utasítást, amelynek van BULK COLLECT utasításrésze, de nem tartalmazhat ilyen SELECT utasítást. A FORALL működése közben a BULK COLLECT által visszaadott eredményeket az egyes iterációk a megelőző iteráció eredményei után fűzik (ezzel szemben ha egy FOR ciklusban szerepelne a BULK COLLECT-et tartalmazó utasítás, akkor az minden iterációban felülírná az előző eredményeket).
9. példa
/* Hasonlítsa össze az ugyfel_visszahoz eljárás
előzőleg megadott implementációját a mostanival. */
CREATE OR REPLACE PROCEDURE ugyfel_visszahoz(p_Ugyfelek T_Id_lista) IS
/* Több ügyfél minden könyvének visszahozatalát adminisztrálja
a függvény. Kiírja, hogy ki hány könyvet hozott vissza. */
v_Konyvek T_Id_lista;
BEGIN
/* A kölcsönzések törlése a törölt könyvek azonosítóinak
összegyűjtése mellett, egy könyv azonosítója többször
is szerepelhet a visszaadott kollekcióban. */
FORALL i IN 1..p_Ugyfelek.COUNT
DELETE FROM kolcsonzes WHERE kolcsonzo = p_Ugyfelek(i)
RETURNING konyv BULK COLLECT INTO v_Konyvek;
/* A %BULK_ROWCOUNT segítségével jelentés készítése */
FOR i IN 1..p_Ugyfelek.COUNT LOOP
DBMS_OUTPUT.PUT_LINE('Ügyfél: ' || p_Ugyfelek(i)
|| ', visszahozott könyvek: ' || SQL%BULK_ROWCOUNT(i));
END LOOP;
/* Könyvek szabad példányainak adminisztrálása */
FORALL i IN 1..v_Konyvek.COUNT
UPDATE konyv SET szabad = szabad + 1
WHERE id = v_Konyvek(i);
/* Az ügyfelek konyvek tábláinak üresre állítása. */
FORALL i IN 1..p_Ugyfelek.COUNT
UPDATE ugyfel SET konyvek = T_Konyvek()
WHERE id = p_Ugyfelek(i);
END ugyfel_visszahoz;
/
show errors