Statikus kurzorok
Egy kurzor segítségével végig tudunk haladni egy select eredményhalmazán, és jelen esetben kiíratni azt a képernyőre.
set serveroutput on;
--Server kimenet bekapcsolása
create table test_for_cursor (
a varchar2(10),
b number
);
--elkészítjük a táblát
insert into test_for_cursor values ('one', 1);
insert into test_for_cursor values ('two', 2);
insert into test_for_cursor values ('three', 3);
insert into test_for_cursor values ('four', 4);
insert into test_for_cursor values ('five', 5);
commit;
--feltöltöm alapadatokkal
declare
cursor cur_t is select * from test_for_cursor;
--létrehozom a kursort és egyenlővé teszem a select eredményhalmazával
rec_t test_for_cursor%rowtype;
--felveszek egy változót amelynek a segítségével végig tudok haladni a
--cursor minden egyes során.
begin
open cur_t;
--megnyitom a cursort
Loop
--elindítok egy ciklust
--sorra kiveszem a változóba az eredmény halmaz sorait
fetch cur_t into rec_t;
--Amikor véget ér az eredmény halmaz akkor kiléptetem
exit when cur_t%notfound;
--egyébként pedig kiíratom az output képernyőre az eredményt
dbms_output.put_line('a: ' || rec_t.a || ', b: ' || rec_t.b);
end loop;
end;
/
Dinamikus kurzorok
A dinamikus kurzor abban tér el a statikustól, hogy nem hoz létre átmeneti táblákat. A kurzorral sétáló alkalmazás észreveszi, ha új sort
szúrnak be a táblába, mert az új sorok megjelennek a kurzor eredményhalmazában.
A package-ekről részletes leírás található.
--Létrehozok egy package-et ami tartalmazza a dinamikus kurzor deklaráló --és kiírató részét
create or replace package dynamic_cursor is
type t_crs is ref cursor;
procedure dyn_sel (
tab_name in varchar2,
field_name in varchar2,
val in varchar2,
crs in out t_crs);
procedure openCursor;
end dynamic_cursor;
/
--elkészítjük a kifejtő részt
create or replace package body dynamic_cursor as
procedure dyn_sel (
tab_name in varchar2,
field_name in varchar2,
val in varchar2,
crs in out t_crs)
--kimeneti változója a crs változó ami a selectet fogja hordozni
is
stmt varchar2(100);
begin
--felveszek egy változót ami a select-tel lesz egyenlő amit az eljárás
--átad a dinamikus cursor eljárásnak ami ezekután lekezeli
stmt := 'select * from ' || tab_name || ' where ' || field_name || ' = :1 ';
open crs for stmt using val;
end dyn_sel;
procedure openCursor is
tc t_crs;
f1 varchar2(50);
f2 varchar2(50);
begin
--meghívjuk a dinamikus curzort paraméterekkel
dyn_sel('test_for_cursor','a','two',tc);
loop
fetch tc into f1,f2;
exit when tc%notfound;
dbms_output.put_line(f2);
end loop;
end openCursor;
end dynamic_cursor;
/
begin
--Meghívjuk a package opencursor eljárását
dynamic_cursor.openCursor;
end;
/
Kivételkezelés
Az alábbi példában nem használtunk kurzort, azaz csak egysoros eredményeket tudunk kezelni. Az ettől eltérő eredmények kezelését kivételkezeléssel oldottuk meg.
Bővebben a kivételkezelés fejezetben.
set feedback off
set serveroutput on
create table foo (
a varchar2(10),
b varchar2(10),
i number
);
insert into foo values ('xxx','yyy',1);
insert into foo values ('zzz','aaa',1);
insert into foo values ('qqq','mmm',3);
commit;
declare
l_a foo.a%type;
l_b foo.b%type;
begin
select a,b into l_a, l_b from foo where i=1;
dbms_output.put_line('a: ' || l_a || ', b: ' || l_b);
--kimenetnél mivel nem hoztunk létre curzort ezért csak egy eredményt
--írhat vissza. Kivételre fut ha több sor van vagy ha egyáltalán nincs sor.
exception
when too_many_rows then
dbms_output.put_line('*** Exc: too many rows');
when no_data_found then
dbms_output.put_line('*** Exc: no data');
end;
/
declare
l_a foo.a%type;
l_b foo.b%type;
begin
select a,b into l_a, l_b from foo where i=2;
dbms_output.put_line('a: ' || l_a || ', b: ' || l_b);
exception
when too_many_rows then
dbms_output.put_line('*** Exc: too many rows');
when no_data_found then
dbms_output.put_line('*** Exc: no data');
end;
/
declare
l_a foo.a%type;
l_b foo.b%type;
begin
select a,b into l_a, l_b from foo where i=3;
dbms_output.put_line('a: ' || l_a || ', b: ' || l_b);
exception
when too_many_rows then
dbms_output.put_line('*** Exc: too many rows');
when no_data_found then
dbms_output.put_line('*** Exc: no data');
end;
/
Trigger
A triggerek olyan speciális procedúrák egy adatbankban, amelyet az INSERT, UPDATE, DELETE parancsok végrehajtások előtt vagy után hív meg a rendszer. Ezzel lehetőség nyílik olyan logikai hibák megakadályozására, amelyeket egyszerü CHECK paranccsal nem lehetne megakadályozni. Röviden: a trigger vagy engedélyezi vagy elveti az adott táblázaton történt módosításokat.
A trigger csak Smith nevű vásárlókat enged beszúrni a táblába.
create table Customer (
Name varchar(20) not null
);
--Trigger létrehozás
CREATE OR REPLACE TRIGGER NoSmiths
--mielőtt a beszúrnánk vagy update-elnék a táblába azelőtt fut le
BEFORE INSERT OR UPDATE OF Name
ON Customer
FOR EACH ROW
BEGIN
--ha az új elem name mezője Smith-től különbőzik akkor adjon hibát (kivétel)
--és nem teszi be a táblába az elemet
IF :new.name not like '%SMITH%' THEN
RAISE_APPLICATION_ERROR(-20000, 'No People Named Smith Allowed!');
END IF;
END NoSmiths;
insert into Customer values ('Terry SMITH');
insert into Customer values ('papa');
insert into Customer values ('JOHN SMITH');
commit;
Select * from Customer;
A példákat az Oracle Enterprise Manager és az SQL*Plus Worksheet segítségével teszteltem.
Alapok, eljárások
Képzeljünk el egy könyvkiadót, ami szerzőket alkalmaz! Adatokat tárolnak az alkalmazott szerzőkről, az általuk írt könyvekről.
--Tabla letrehozasa a szerzok tarolasara.
create table szerzo(
id number not null, --azonosito
vezeteknev varchar2(50) not null, --vezeteknev
keresztnev varchar2(50) not null, --keresztnev
alnev varchar(50), --iroi alnev
cim varchar(80) not null, --a szerzo lakhelye
belepes date not null, --a szerzo csatlakozasa a kiadohoz
fizetes number not null, --a szerzo fizetese
konyvek_szama number default 0 --a szerzo konyveinek szama
);
--Vegyuk eszre a fenti utasitasban a modositokat!
-- not null: a mezoben nem szerepelhet null ertek
-- default value: alapertelmezett ertek, ha nem adunk meg erteket beszurasnal,
-- akkor ez lesz beszurva
--Elsodleges kulcs hozzaadasa utolag a szerzo tablahoz, az id mezo lesz a kulcs.
alter table szerzo add primary key (id);
--Tabla letrehozasa a szerzok konyveinek tarolasara.
create table konyv(
kid number not null primary key, --konyv azonositoja
kcim varchar2(50), --konyc cime
szerzo number --konyv szerzojenek azonositoja
);
--A fenti utasitasban lathato, hogy az elsodleges kulcsot itt is meg lehet adni,
--amennyiben az csak egy attributombol all.
--Idegen kulcs hozzaadasa a konyv tablahoz.
--Ezzel osszekotjuk a konyv tabla szerzo mezojet a szerzo tabla id mezojevel.
alter table konyv add foreign key (szerzo) references szerzo(id);
-- Szerzo tabla feltoltese pelda adatokkal.
insert into szerzo values (1,'Szabó','József',null,'Budapest',to_date('2010.01.01','yyyy.mm.dd'),100000,0);
insert into szerzo(id,vezeteknev,keresztnev,cim,belepes,fizetes,alnev) values
(2,'Nagy','Béla','Szeged',to_date('2010.02.10','yyyy.mm.dd'),50000,'nabo');
-- A fenti ket utasitas az insert szintaxisara mutat peldat. Az elso sorban pozicio
-- alapjan, a masodik sorban nev szerint szurja be a tablaba az adatokat.
-- Szerzok lekerdezese.
select * from szerzo;
-- Hozzunk letre egy szekvenciat a konyv tabla id mezojehez, hogy ne manualisan
-- kelljen az id-t kitolteni!
CREATE SEQUENCE KONYV_ID_SEQ minvalue 0 INCREMENT BY 1 START WITH 0 ORDER;
-- A szekvencia 1-tol indul es egyesevel no.
-- Hozzunk letre egy eljarast, mellyel megadhatunk egy szerzohoz egy konyvet.
create or replace procedure uj_konyv(szerzo_azonosito number, konyv_cim varchar2) is
begin
-- A konyvet beszurjuk a konyv tablaba.
-- Lathatjuk a szekvencia hasznalatat, az id feltoltesekor lekerjuk a kovetkezo szabad id-t.
insert into konyv(kid, kcim, szerzo) values (konyv_id_seq.nextval, konyv_cim, szerzo_azonosito);
-- Frissitjuk a konyvek szamat a szerzok tablaban.
update szerzo set konyvek_szama=konyvek_szama+1 where id=szerzo_azonosito;
end;
/
-- Az alabbiakban az elobbi eljaras 3 fele meghivasa latszik.
-- A call parancsot rutinok meghivasara hasznaljak.
-- Az exec[ute] parancsot pedig egy PL/SQL parancs lefuttatasara.
call uj_konyv(1,'Zsiráfok Afrikában');
exec uj_konyv(1,'Zsiráfok és zebrák');
execute uj_konyv(2,'Zöld és szürke');
/
-- Konyv tabla lekerdezese.
select * from konyv;
--A tranzakciot veglegesitjuk.
commit;
-- Listazzuk ki egy azonositohoz tartozo szerzo osszes konyvet!
declare
NINCS_SZERZO exception; --Nincs az adott azonositohoz tartozo szerzo exception.
--beagyazott eljaras
procedure szerzo_konyvei(szerzo_azonosito in number) is
nev varchar2(100);
alnev varchar2(50);
darab number; -- az azonositohoz tartozo szerzok szama
begin
-- Ellenorizzuk, hogy tartozik-e az azonositohoz szerzo.
select count(*) into darab from szerzo where id = szerzo_azonosito;
if darab = 0 then
raise NINCS_SZERZO; -- Ha nem tartozik, akkor kivetelt dobunk;
end if;
-- select into utasitassal lekerdezzuk az adott szerzo nevet es alnevet.
-- Lathatjuk, hogy sql muveletek is hasznalhatoak, itt peldaul a concat fuggveny,
-- amely osszefuz ket szoveget.
select concat(concat(vezeteknev, ' '), keresztnev) nev, alnev into nev, alnev from szerzo
where id = szerzo_azonosito;
-- itt egy elore definialt csomagnak (dbms_output) egy eljarasat (put_line) hivjuk meg
dbms_output.put_line('Szerzo azonositoja: ' || szerzo_azonosito);
dbms_output.put_line('Szerzo neve: ' || nev);
-- elagazas
-- Vegyuk eszre a is [not] null operator hasznalatat!
-- Nem pedig alnev != null.
if alnev is not null then
dbms_output.put_line('Szerzo alneve: ' || alnev);
end if;
dbms_output.put_line('');
dbms_output.put_line(rpad('id',17) || 'Konyv cime');
dbms_output.put_line(lpad(' ',40,'-'));
-- cursor valtozo hasznalata
-- A cur valtozo sorban felveszi a lekerdezes altal visszaadott sorok ertekeit.
for cur in (select kid,kcim from konyv where (szerzo_azonosito = konyv.szerzo)) loop
-- Lathatjuk, hogy a sorok egyes mezoit a . operatorral erjuk el.
dbms_output.put_line(rpad(cur.kid,17) || cur.kcim);
end loop;
end;
begin
-- A beagyazott eljaras meghivasa a 1-es azonositoval.
szerzo_konyvei(1);
-- hibakezeles
exception
when NINCS_SZERZO then
dbms_output.put_line('Nincsen ilyen szerzo.');
end;
/
-- Fontos, hogy a blokkok veget lezarjuk egy / jellel, igy a kornyezet egyetlen utasitaskent kezeli azt.
-- Szamoljuk ki egy fuggvennyel, hogy a kiadonak mennyi az ossz kiadasa!
create or replace function ossz_kiadas return number is
osszeg number;
begin
-- fizetesek osszegenek lekerdezese
select sum(fizetes) into osszeg from szerzo;
-- Noveljuk meg az osszeget a 20%-os jarulekokkal!
osszeg := osszeg*1.2;
-- Visszaadjuk a jarulekokkal novelt osszeget.
return osszeg;
end;
/
-- TAKARITAS
drop table konyv;
drop table szerzo;
drop sequence konyv_id_seq;
drop procedure uj_konyv;
drop function ossz_kiadas;
A példa Oracle SQLDeveloperrel készült.
(Rácz Gábor, 2011 2. félév)
Dinamikus SQL
DBMS_SQL csomag használata. A program paraméterként kap két táblanevet, illetve két oszlop sorszámot, amire elvégez néhány számítást, úgy mint: átlag, szumma, szórás.
(Pl.: két cég dolgozóinak a fizetésének összehasonlítása)
create or replace
PROCEDURE compare_stat(
tabla1 IN VARCHAR2 ,
col1 IN NUMBER ,
tabla2 IN VARCHAR2 ,
col2 IN NUMBER
)
AS
type results
IS
TABLE OF NUMBER;
res results := results(1);
first_col NUMBER;
summa NUMBER;
avarage NUMBER;
elteres_negyzet NUMBER;
elteres_sum NUMBER;
elteres_atlag NUMBER;
mycursor INTEGER;
result_rows INTEGER;
counter NUMBER;
trigger_str VARCHAR2(1000);
type array_t IS varray(2) OF VARCHAR2(20);
name_array array_t := array_t(tabla1, tabla2);
type array_t2 IS varray(2) OF NUMBER;
col_array array_t2 := array_t2(col1, col2);
BEGIN
dbms_output.put_line('szumma atlag szoras');
FOR i IN 1..2
LOOP
counter := 1;
summa := 0;
mycursor := dbms_sql.open_cursor;
dbms_sql.parse(mycursor, 'SELECT * FROM ' || name_array(i), dbms_sql.native);
dbms_sql.define_column(mycursor, col_array(i), first_col);
result_rows := dbms_sql.execute(mycursor);
LOOP
EXIT
WHEN dbms_sql.fetch_rows (mycursor) = 0;
dbms_sql.column_value(mycursor, col_array(i), first_col);
res.extend(counter);
res(counter) := first_col;
summa := summa + first_col;
counter := counter +1;
END LOOP;
dbms_sql.close_cursor(mycursor);
avarage := summa/(counter-1);
elteres_sum := 0;
FOR i IN 1..counter-1
LOOP
elteres_negyzet := (avarage - res(i))**2;
elteres_sum := elteres_sum + elteres_negyzet;
IF i = counter -1 THEN
elteres_atlag := elteres_sum /i;
END IF;
END LOOP;
dbms_output.put(summa);
dbms_output.put(TO_CHAR(avarage, '999999999.99'));
dbms_output.put_line(TO_CHAR(sqrt(elteres_atlag), '999999999.99'));
end loop;
END compare_stat;
A példa Oracle SQLDeveloperrel készült.
(Frank Krisztina, 2013 2. félév)
Hierarchikus lekérdezés
Hierarchikus lekérdezés bemutatása. A tábla sorai családfát reprezentálnak, ezt be lehet járni, és egy adott gyökérelemtől lévő távolságot egy Oracle pszeudo-oszlopban lekérdezni.
-- Tábla előkészítése
CREATE TABLE bibliai_nevek
(
id number not null , -- rekord azonosító sorszám
nev varchar2(20) , -- név
nem varchar(1) , -- neme, lehet "F" vagy "N", aszerint hogy férfi vagy nő
apja_id number not null , -- apja id-je
anyja_id number not null -- anyja id-je
);
alter table bibliai_nevek add primary key (id) ;
insert into bibliai_nevek ( id , nev , nem , apja_id , anyja_id ) values ( 1 , 'Sára' , 'N' , 0 , 0 ) ;
insert into bibliai_nevek ( id , nev , nem , apja_id , anyja_id ) values ( 2 , 'Hágár' , 'N' , 0 , 0 ) ;
insert into bibliai_nevek ( id , nev , nem , apja_id , anyja_id ) values ( 3 , 'Rebeka' , 'N' , 0 , 0 ) ;
insert into bibliai_nevek ( id , nev , nem , apja_id , anyja_id ) values ( 4 , 'Ábrahám' , 'F' , 0 , 0 ) ;
-- Ábrahám leszármazottai Izsák, Ismáel és Ismeretlen
-- Izsák anyja Sára
-- Ismáel anyja Hágár
insert into bibliai_nevek ( id , nev , nem , apja_id , anyja_id ) values ( 5 , 'Izsák' , 'F' , 4 , 1 ) ;
insert into bibliai_nevek ( id , nev , nem , apja_id , anyja_id ) values ( 6 , 'Ismáel' , 'F' , 4 , 2 ) ;
insert into bibliai_nevek ( id , nev , nem , apja_id , anyja_id ) values ( 7 , 'Ismeretlen' , 'N' , 4 , 0 ) ;
-- Izsák leszármazottai (fiai) Jákob és Ézsau, anyjuk Rebeka
insert into bibliai_nevek ( id , nev , nem , apja_id , anyja_id ) values ( 8 , 'Jákob' , 'F' , 5 , 3 ) ;
insert into bibliai_nevek ( id , nev , nem , apja_id , anyja_id ) values ( 9 , 'Ézsau' , 'F' , 5 , 3 ) ;
commit ;
-- CONNECT BY PRIOR: a szülő sor megfelelő oszlopát helyettesíti be
-- LEVEL pszeudo-oszlop: a gráfban a gyökérelemtől való távolság, >=1
-- SYS_CONNECT_BY_PATH: elérési út stringként a gyökérelemtől az adott elemig
-- leszármazottaktól az apák felé
select id, nem, nev, level as "Generáció" , apja_id , substr(sys_connect_by_path(nev , '/'),1,20) as Rokonsag
from bibliai_nevek start with nev='Ábrahám'
connect by prior id = apja_id order by id
;
-- Output:
/*
ID N NEV Generáció APJA_ID ROKONSAG
---------- - -------------------- ---------- ---------- ------------------------------
4 F Abrahám 1 0 /Abrahám
5 F Izsák 2 4 /Abrahám/Izsák
6 F Ismáel 2 4 /Abrahám/Ismáel
7 N Ismeretlen 2 4 /Abrahám/Ismeretlen
8 F Jákob 3 5 /Abrahám/Izsák/Jákob
9 F Ézsau 3 5 /Abrahám/Izsák/Ézsau
6 rows selected.
*/
-- }}}2
-- leszármazottaktól az anyák felé
select id, nem, nev, level as "Generáció" , anyja_id , substr(sys_connect_by_path(nev , '/'),1,20) as Rokonsag
from bibliai_nevek start with nev in ('Sára', 'Hágár', 'Rebeka')
connect by prior id = anyja_id order by id
;
-- Output:
/*
ID N NEV Generáció ANYJA_ID ROKONSAG
---------- - -------------------- ---------- ---------- ------------------------------
1 N Sára 1 0 /Sára
2 N Hágár 1 0 /Hágár
3 N Rebeka 1 0 /Rebeka
5 F Izsák 2 1 /Sára/Izsák
6 F Ismáel 2 2 /Hágár/Ismáel
8 F Jákob 2 3 /Rebeka/Jákob
9 F Ézsau 2 3 /Rebeka/Ézsau
7 rows selected.
*/
A példaprogram és outputja gvim text editor és SQL*Plus command line utility segítségével készült.
(Princz Péter, 2013 2. félév)
Táblából CREATE TABLE utasítás generálása
Egy táblanévből és a tábla tulajdonosának a nevéből kiírja a táblához tartozó CREATE TABLE utasítást. (Természetesen csak az oszlop definíciókat írja ki)
-- Eljaras ami kiirja a egy tabla create table utasitasat
-- owner: tabla tulajdonosa
-- tbl: tabla neve
-- eljaras meghivasa: CALL print_create_table('galovics', 'emp')
CREATE OR REPLACE PROCEDURE print_create_table(owner VARCHAR2, tbl VARCHAR2)
IS
-- Letrehozunk egy kurzort, ami a kovetkezo lekerdezes eredmenyhalmazat hasznalja,
-- amint lathato az owner es tbl parameterek nem erzekenyek kis es nagy beture
CURSOR tbl_data IS SELECT * FROM all_tab_columns atc WHERE upper(atc.table_name) like upper(tbl) AND upper(atc.owner) = upper(owner);
-- Ebben a valtozoban taroljuk a CREATE TABLE utasitast
create_table_code VARCHAR2(2048);
-- A lekerdezes altal visszaadott eredmenyhalmaz tipusa, jelen esetben minden oszlopot tartalmazni fog a rekord
tbl_row tbl_data%rowtype;
BEGIN
-- CREATE TABLE utasitas kezdete
create_table_code := 'CREATE TABLE ' || tbl || '(';
-- kurzor kinyitasa
OPEN tbl_data;
-- Ciklussal vegigmegyunk az eredmenyhalmazon
LOOP
-- Egy sort betoltunk a tbl_row valtozoba
FETCH tbl_data INTO tbl_row;
-- Kilepunk ha mar nincs tobb sor
EXIT WHEN tbl_data%NOTFOUND;
-- Beszurunk egy uj sort (CHR(10) = Line feed = ujsor) majd hozzafuzzuk az oszlop nevet es az oszlop tipusat
create_table_code := create_table_code || CHR(10) || ' ' || tbl_row.COLUMN_NAME || ' ' || tbl_row.DATA_TYPE ;
-- Ha van karakter hosszusag tulajdonsag, akkor azt hozzairjuk
IF tbl_row.CHAR_COL_DECL_LENGTH IS NOT NULL AND tbl_row.DATA_TYPE LIKE '%CHAR%' THEN
create_table_code := create_table_code || '(' || tbl_row.CHAR_COL_DECL_LENGTH || ')';
-- Ha van DATA_SCALE es DATA_PRECISION tulajdonsag akkor azt is hozzairjuk
ELSIF tbl_row.DATA_SCALE IS NOT NULL THEN
create_table_code := create_table_code || '(' || tbl_row.DATA_PRECISION;
IF tbl_row.DATA_SCALE > 0 THEN
create_table_code := create_table_code || ',' || tbl_row.DATA_SCALE;
END IF;
create_table_code := create_table_code || ')';
END IF;
create_table_code := create_table_code || ',';
END LOOP;
CLOSE tbl_data;
-- Berakunk egy ujsort a konnyebb olvashatosag miatt, viszont elotte levagjuk a legutolso ,-t
create_table_code := RTRIM(create_table_code,',') || CHR(10) || ');';
-- Kiirjuk a CREATE TABLE utasitast
dbms_output.put_line(create_table_code);
END;
Tesztelés
CALL print_create_table('usernev', 'emp');
A példa Oracle SQLDeveloperrel készült.
(Gálovics Arnold, 2014 1. félév)
Paraméterként kapott karaktersorozattal kezdődő táblák kiírása
Az eljárás kiírja az összes olyan tábla nevét, amely táblák a paraméterben megkapott karaktersorozattal kezdődnek.
-- Kiirja azokat a tablakat, amik a parameterben adott szoval kezdodik
-- p_kar: a karaktersorozat amivel a tablanak kezdodnie kell
-- meghivas: CALL print_table('a');
CREATE OR REPLACE PROCEDURE print_table(p_kar VARCHAR2) IS
-- Cursor a megadott lekerdezeshez
CURSOR curs IS SELECT table_name, owner FROM ALL_TABLES WHERE upper(table_name) LIKE upper(p_kar)||'%';
-- A lekerdezes tipusanak megfelelo rekord.
rec curs%ROWTYPE;
BEGIN
-- Kinyitjuk a kurzort
OPEN curs;
-- Ciklusban kiirjuk az osszes tabla nevet es tulajdonosat amit a lekerdezes visszaadott
LOOP
-- Betoltunk egy sort a rekordba
FETCH curs INTO rec;
-- Ha nincs mar tobb sor a kurzorban
EXIT WHEN curs%NOTFOUND;
-- Kirrjuk a tabla nevet es a tabla tulajdonosat
dbms_output.put_line(rec.table_name||' - '||rec.owner);
END LOOP;
CLOSE curs;
END;
Tesztelés
CALL print_table('a');
A példa Oracle SQLDeveloperrel készült.
(Gálovics Arnold, 2014 1. félév)
Faktoriális függvény
Rekurzív módon implementált faktoriális függvény
-- Rekurziv faktorialis szamolo fuggveny
-- BEGIN
-- dbms_output.put_line(factorial(5));
-- END;
CREATE OR REPLACE FUNCTION factorial(n NUMBER) RETURN NUMBER IS
BEGIN
-- Ha n == 1 akkor a visszateresi ertek 1
IF n = 1 THEN
return 1;
END IF;
-- Egyebkent ha n <> 1 akkor pedig visszaterunk n*factorial(n-1)-el.
return n*factorial(n-1);
END;
Tesztelés
BEGIN
dbms_output.put_line(factorial(5));
END;
A példa Oracle SQLDeveloperrel készült.
(Gálovics Arnold, 2014 1. félév)
BEFORE trigger
BEFORE trigger az EMP táblára vonatkozóan.
-- Egy salary update trigger. Amint akarmelyik sorban megvaltozik a salary oszlop erteke, a trigger torzse lefut.
-- teszt: update emp set sal=100 where ename='SMITH';
CREATE OR REPLACE TRIGGER salary_updated_trigger
-- Ezzel a kulcsszoval donthetjuk el, hogy a trigger mikor fusson le. (BEFORE/AFTER)
BEFORE
-- Itt definialhatjuk, hogy milyen esemeny hatasara fusson le. (INSERT, UPDATE, ...)
UPDATE OF sal
-- Tabla neve amire a triggert rakjuk.
ON emp
-- Minden egyes sorra ervenyes legyen.
FOR EACH ROW
-- Deklaracios resz
DECLARE
salary NUMBER(10);
BEGIN
-- Annak eldontesere, hogy milyen esemeny tortent egy switch vezerlesi szerkezetet hasznalunk
CASE
-- Ha a 'sal' oszlop updatelve lett valamelyik sorban, akkor ez az ag fut le.
WHEN UPDATING('sal') THEN
dbms_output.PUT_LINE('Updating salary');
-- Betoltjuk az uj 'salary' erteket a salary valtozoba
SELECT :new.sal INTO salary FROM dual;
dbms_output.PUT_LINE('Updated salary is: ' || salary);
END CASE;
END;
Tesztelés
UPDATE emp SET sal=100 WHERE ename='SMITH';
A példa Oracle SQLDeveloperrel készült.
(Gálovics Arnold, 2014 1. félév)
AFTER trigger
AFTER trigger az EMP táblára vonatkozóan
-- Egy deptno update trigger. Amint akarmelyik sorban megvaltozik a salary oszlop erteke, a trigger torzse lefut.
-- teszt: update emp set deptno=10 where ename='SMITH';
CREATE OR REPLACE TRIGGER deptno_updated_trigger
-- Ezzel a kulcsszoval donthetjuk el, hogy a trigger mikor fusson le. (BEFORE/AFTER)
AFTER
-- Itt definialhatjuk, hogy milyen esemeny hatasara fusson le. (INSERT, UPDATE, ...)
UPDATE OF deptno
-- Tabla neve amire a triggert rakjuk.
ON emp
-- Minden egyes sorra ervenyes legyen.
FOR EACH ROW
-- Deklaracios resz
DECLARE
deptnumber NUMBER(3);
BEGIN
-- Annak eldontesere, hogy milyen esemeny tortent egy switch vezerlesi szerkezetet hasznalunk
CASE
-- Ha a 'deptno' oszlop updatelve lett valamelyik sorban, akkor ez az ag fut le.
WHEN UPDATING('deptno') THEN
-- Betoltjuk a regi 'deptno' erteket a deptnumber valtozoba
SELECT :old.deptno INTO deptnumber FROM dual;
dbms_output.PUT_LINE('Old deptno: ' || deptnumber);
-- Betoltjuk az uj 'deptno' erteket a deptnumber valtozoba
SELECT :new.deptno INTO deptnumber FROM dual;
dbms_output.PUT_LINE('New deptno: ' || deptnumber);
END CASE;
END;
Tesztelés
UPDATE emp SET deptno=10 WHERE ename='SMITH';
A példa Oracle SQLDeveloperrel készült.
(Gálovics Arnold, 2014 1. félév)
Dinamikus SQL
PL/SQL kódba beágyazhatunk SQL utasításokat. Ezeket az utasításokat
a PL/SQL lefordítja, és minden futás során ugyanazokat az eredményeket
fogják produkálni (az adatbázis tartalmától függően). Ezeknek az
utasításoknak az alakja fordítási időben már ismert, ezért az
ilyen kódrészleteket statikus SQL utasításoknak nevezzük.
Azonban PL/SQL-ben lehetőségünk van olyan SQL utasításokat megfogalmazni,
melyek nem feltétlenül ismertek teljes egészükben fordítási időben,
egy részük csak futási időben dől el, hogy micsoda. Ezeket az
utasítsokat dinamikus SQL utasításoknak nevezzük, mert lehetőségünk
van futási időben - dinamikusan - változtatni azt. A dinamikus
SQL utasítások formailag karakterláncok egy változóban tárolva,
melyet kedvünkre módosíthatunk céljainktól függően.
Több esetben is szükségünk lehet arra munkánk közben, hogy
olyan módosítást eszközöljünk az adatbázison, aminek a
mibenléte csak futás közben derül ki: új objektumot (tárolt eljárást,
adatbázis táblát) szeretnénk létrehozni, csak egy utasítás
WHERE feltételén szeretnénk változtatni futás közben. Ilyen esetekben
dinamikus SQL-t kell használnunk.
Execute Immediate
Ilyen futási időben indított SQL utasításokat az EXECUTE IMMEDIATE
parancs segítségével tudunk futtatni, melynek szintaxisa a következő:
EXECUTE IMMEDIATE dinamikus_sztring
[INTO {változó [,változó]…|rekord}]
[USING [IN|OUT|IN OUT] kapcsoló_argumentum
[,[IN|OUT|IN OUT] kapcsoló_argumentum]…]
[{RETURNING|RETURN} INTO kapcsoló_argumentum
[,kapcsoló_argumentum]…];
A "dinamikus sztring" rendszerint egy varchar2 változóban tárolt
karakterlánc, maga az utasítás szövege. Ebbe az utasításba nem
szabad pontosvesszőt írnunk, mert akkor a PL/SQL invalid character
hibát fog dobni, és nem fog futni a kódunk.
A "változó" egy egyetlen adatot tároló adattag, a "rekord" pedig
egy teljes rekordot tároló adattag.
Az "INTO" kulcsszó akkor használatos, ha egy sort ad vissza a
select-ünk.
A USING kulcsszó után kötött változókat sorolhatunk fel, melyek
a dinamikus SQL utasításunkban (kettős ponttal) megjelölt helyekre
fognak behelyettesítődni. Érdemes így oldani meg a parancs felépítését,
mert ezen a módon megvédjük a programunkat az "SQL-injection" nevű
támadási formától.
A RETURNING INTO utasításrész olyan dinamikus DML utasítások esetén
használható, amelyek RETURNING utasításrészt tartalmaznak.
A kulcsszó után felsorolt változókba fog elmentődni a lekérdezés
által kinyert adat.
Minden EXECUTE IMMEDIATE végrehajtásakor a végrehajtandó parancs
elemzésre kerül, és érvénytelen utasítás esetén ugyanolyan hibát
kapunk, mint statikus SQL esetében.
create or replace package EMP_EI_TEST is
-- megkezdjuk a package header definialasat
-- a lenti 4 sor komment a PL/SQL Developer (amugy egeszen jo) fejlesztoi
-- kornyezete altal lett generalva
-- Author : Adam Ujvari
-- Created : 2014.05.08. 12:22:46
-- Purpose : Demonstrate the power of Execute Immediate
-- Tested on : 10g
-- deklaraljuk a publikus lathatosagu eljarasokat es fuggvenyeket
procedure INSERT_WITH_USING
(
pDeptNo in number,
pDname in varchar2,
pLoc in varchar2
);
function SELECT_INTO return number;
procedure MULTI_ROW_QUERY(pSal in number);
procedure CREATE_TABLES;
procedure DROP_TABLES;
procedure INJECT_SQL_UPD_SAL(userName in varchar2);
procedure INJECT_SQL_UPD_SAL_SAFE(userName in varchar2);
end EMP_EI_TEST; -- package header vege
/
create or replace package body EMP_EI_TEST is
-- package body, ide kerulnek a public lathatosagu fuggvenyek torzsei valamint a private lathatosagu eljarasok es fuggvenyek.
-- fuggveny definicio
procedure INJECT_SQL_UPD_SAL(userName in varchar2) is
-- 2000 hosszu varchar2 (azaz string) valtozo deklaralasa
command varchar2(2000);
begin
-- konkatenacioval szeretnenk osszeepiteni egy stringet amit majd kesobb futtatunk.
command := 'update emp set sal = 10000 where ename = ''' || userName || '''';
-- execute immediate futtatja futasi idoben a parancsot amit osszeepitettunk az elobb
execute immediate command;
end INJECT_SQL_UPD_SAL;
procedure INJECT_SQL_UPD_SAL_SAFE(userName in varchar2) is
begin
-- ha nem konkatenacioval epitunk parancsoakt, hanem hasznaljuk az execute imediate
-- using kulcsszavat, akkor megvedjuk a kodunkat (es a cegunket) a gonosz hackerek
-- tamadasaitol.
execute immediate 'update emp set sal = 10000 where ename = :param' using userName;
end INJECT_SQL_UPD_SAL_SAFE;
procedure INSERT_WITH_USING
(
-- parameterek megadasanal nem kell megadnunk, hogy mekkora lesz
-- a parameter valtozo (tehat nem kell szamot irnunk a number
-- es a varchar2 utan.
pDeptNo in number,
pDname in varchar2,
pLoc in varchar2
) is
-- konstans definicio: igy nem kell szenvednunk az
-- esetleges aposztrofokkal.
insert_script constant varchar2(2000) := Q'(
insert into dept values (:1, :2, :3)
)';
begin
-- hasznaljuk a using kulcsszot kotott parameterekhez
execute immediate insert_script
using pDeptNo, pDname, pLoc;
commit;
end;
function SELECT_INTO return number is
l_cnt varchar2(20);
-- konstans definicio: igy nem kell szenvednunk az
-- esetleges aposztrofokkal.
select_script constant varchar2(2000) := Q'(
select count(1) from emp
)';
begin
-- ha select parancsot futtatunk execute immediate-ben, akkor
-- használjuk az into kulcsszot, hogy egy valtozoba olvassuk
-- a lekerdezes eredmenyet. no_data_found exception persze
-- ilyenkor is kivaltodhat.
execute immediate select_script
into l_cnt;
return l_cnt;
end;
procedure MULTI_ROW_QUERY(pSal in number) is
-- szurjunk be tobb sort a temp tablaba
ins_multirow_script constant varchar2(2000) := Q'(
insert into temp(empno, ename)
select empno, ename
from emp where sal > :1
)';
begin
-- ismet kotott valtozot hasznalunk
execute immediate ins_multirow_script
using pSal;
end;
-- ezzel az eljarassal hozzuk letre a tablakat amiken dolgozni
-- fogunk majd a package tobbi fuggvenyevel
procedure CREATE_TABLES is
-- a fent bemutatott konstans definicios modszert
-- alkalmazzuk, mert igy konyebben atlathato
-- a kod amit majd futtatni fogunk
tbl_gen_script1 constant varchar2(30000) := Q'(
CREATE TABLE DEPT(DEPTNO NUMBER(2),
DNAME VARCHAR2(14),
LOC VARCHAR2(13)))';
tbl_gen_script2 constant varchar2(30000) := Q'( CREATE TABLE EMP(EMPNO NUMBER(4) NOT NULL,
ENAME VARCHAR2(10),
JOB VARCHAR2(9),
MGR NUMBER(4),
HIREDATE DATE,
SAL NUMBER(7, 2),
COMM NUMBER(7, 2),
DEPTNO NUMBER(2)))';
tbl_gen_script3 constant varchar2(30000) := Q'( CREATE TABLE TEMP(EMPNO NUMBER(4) NOT NULL,
ENAME VARCHAR2(10),
JOB VARCHAR2(9),
MGR NUMBER(4),
HIREDATE DATE,
SAL NUMBER(7, 2),
COMM NUMBER(7, 2),
DEPTNO NUMBER(2)))';
insert_script1 constant varchar2(30000) := Q'( INSERT INTO EMP
VALUES
(7369,
'SMITH',
'CLERK',
7902,
TO_DATE('1960.01.01', 'yyyy.mm.dd'),
800,
NULL,
20)
)';
insert_script2 constant varchar2(30000) := Q'( INSERT INTO EMP
VALUES
(7499,
'ALLEN',
'SALESMAN',
7698,
TO_DATE('1960.01.01', 'yyyy.mm.dd'),
1600,
300,
30)
)';
insert_script3 constant varchar2(30000) := Q'(
INSERT INTO EMP
VALUES
(7521,
'WARD',
'SALESMAN',
7698,
TO_DATE('1960.01.01', 'yyyy.mm.dd'),
1250,
500,
30)
)';
insert_script4 constant varchar2(30000) := Q'(
INSERT INTO EMP
VALUES
(7566,
'JONES',
'MANAGER',
7839,
TO_DATE('1960.01.01', 'yyyy.mm.dd'),
2975,
NULL,
20)
)';
insert_script5 constant varchar2(30000) := Q'(INSERT INTO EMP
VALUES
(7654,
'MARTIN',
'SALESMAN',
7698,
TO_DATE('1960.01.01', 'yyyy.mm.dd'),
1250,
1400,
30)
)';
insert_script6 constant varchar2(30000) := Q'(
INSERT INTO DEPT VALUES (10, 'ACCOUNTING', 'NEW YORK')
)';
insert_script7 constant varchar2(30000) := Q'(
INSERT INTO DEPT VALUES (20, 'RESEARCH', 'DALLAS')
)';
begin
-- itt futtatjuk le a fent definialt programkodokat
execute immediate (tbl_gen_script1);
execute immediate (tbl_gen_script2);
execute immediate (tbl_gen_script3);
execute immediate (insert_script1);
execute immediate (insert_script2);
execute immediate (insert_script3);
execute immediate (insert_script4);
execute immediate (insert_script5);
execute immediate (insert_script6);
execute immediate (insert_script7);
end;
procedure DROP_TABLES is
-- a fent bemutatott konstans definicios modszert
-- alkalmazzuk, mert igy konyebben atlathato
-- a kod amit majd futtatni fogunk
drop_tbl_script1 constant varchar2(30000) := Q'( DROP TABLE EMP)';
drop_tbl_script2 constant varchar2(30000) := Q'( DROP TABLE DEPT)';
drop_tbl_script3 constant varchar2(30000) := Q'( DROP TABLE TEMP)';
begin
-- itt futtatjuk le a fent definialt programkodokat
execute immediate (drop_tbl_script1);
execute immediate (drop_tbl_script2);
execute immediate (drop_tbl_script3);
exception
when others then
-- hiba lehet, ha a tablak mar leteznek, vagy
-- valami miatt nincs jogunk torolni oket, ekkor
-- nem fog elszallni a programunk, mert kezeljuk
-- a kivetelt.
dbms_output.put_line('Cant drop tables.');
end;
end EMP_EI_TEST; -- ez a package definicio vege.
/
A package tárolt eljárásban először a package header-t definiáljuk.
Itt adjuk meg azoknak a függvényeknek a szingatúráját, amelyeket
publikus láthatósággal szeretnénk ellátni. Azok a függvények,
amelyek törzse implementálva lett a package törzsében, de
a szingnatúrájuk nincsen feltüntetve a headerben, azokat
private láthatósággal látja el az Oracle.
A package body-ban kell definiálnunk a függvényeket, esetleg
az objektumokat amelyeket létrehozunk (a példakódban nics ilyen),
valamint itt hozhatunk létre package szinten globális változókat is.
A csomagot a lenti névtelen blokkban található parancsok segítségével tudjuk
tesztelni olyan módon, hogy egy adott sorban eltávolítjuk a kommentet, és
futtatjuk a teljes névtelen blokkot.
declare
temp number(3);
begin
--EMP_EI_TEST.DROP_TABLES;
--EMP_EI_TEST.CREATE_TABLES;
--EMP_EI_TEST.INSERT_WITH_USING(70, 'AIRFORCE', 'SZEGED');
--temp := EMP_EI_TEST.SELECT_INTO;
--dbms_output.put_line(temp);
--EMP_EI_TEST.MULTI_ROW_QUERY(20);
--EMP_EI_TEST.INJECT_SQL_UPD_SAL('JONES'); -- works
--EMP_EI_TEST.INJECT_SQL_UPD_SAL(''' or ''1''=''1'); -- I'm bad
--EMP_EI_TEST.INJECT_SQL_UPD_SAL_SAFE('JONES'); -- works
--EMP_EI_TEST.INJECT_SQL_UPD_SAL_SAFE(''' or ''1''=''1');
end;
A példa package PL/SQL Developer IDE-ben készült
Ujvári Ádám 2013-2014 / 2
Függvény, eljárás, csomag
Adatbázis táblái: MUNKA, mely tartalmazza a beosztást, a hozzá tartozó azonosítót, fizetést, és százalékmegjelölést, amely megadja, hogy mennyi lehet az adott beosztással járó minimális fizetés. A második tábla a BEOSZTAS, mely a MUNKA táblával megegyezően épül fel. A harmadik tábla a TELEPHELY, amely tartalmazza a telephely azonosítóját, nevét, helyét. A negyedik tábla a DOLGOZO, mely tartalmazza a dolgozók azonosítóját, nevét, beosztásuk azonosítóját, főnökük azonosítóját, belépésük dátumát, fizetésük és jutalékuk összegét, telephelyük azonosítóját.
A táblák létrehozása és feltöltése:
create table munka
(
m_azon number(2) not null,
m_fogl varchar2(20) not null,
m_fiz number(10) not null,
m_szaz number(3) default 80
);
create table telephely
(
telephely_azon number(2) not null,
nev varchar2(15) not null,
hely varchar2(30) not null
);
create table beosztas
(
fogl_azon number(2) not null,
beosztas varchar2(20) not null,
fizetes number(10) not null,
szazalek number(3) default 80
);
create table dolgozo(
dolgozo_azon number(4) not null,
nev varchar2(20) not null,
fogl_azon number(2) not null,
fonok number(4),
belepes_dat date,
fizetes number(10),
jutalek number(10),
telephely_azon number(2)
);
insert into telephely (telephely_azon, nev, hely) values (11,'Központ','Budapest');
insert into telephely (telephely_azon, nev, hely) values (12,'Kutatás','Debrecen');
commit;
insert into beosztas (fogl_azon, beosztas, fizetes) values (10,'Elnök',500000);
insert into beosztas (fogl_azon, beosztas, fizetes, szazalek) values (20,'Igazgató',280000,90);
insert into beosztas (fogl_azon, beosztas, fizetes, szazalek) values (30,'Elemzõ',100000,100);
commit;
insert into dolgozo (dolgozo_azon, nev, fogl_azon, fonok, belepes_dat, fizetes, jutalek, telephely_azon) values
(1111,'Albert',30,1234,to_date('1981.02.20','YYYY.MM.DD'),160000,30000,12);
insert into dolgozo (dolgozo_azon, nev, fogl_azon, fonok, belepes_dat, fizetes, jutalek, telephely_azon) values
(1234,'Smidt',20,1222,to_date('1980.12.17','YYYY.MM.DD'),80000,null,11);
insert into dolgozo (dolgozo_azon, nev, fogl_azon, fonok, belepes_dat, fizetes, jutalek, telephely_azon) values
(1222,'Kiss',10,null,to_date('1981.04.02','YYYY.MM.DD'),125000,50000,11);
commit;
insert into munka (m_azon, m_fogl, m_fiz) values (15,'Alelnök',450000);
insert into munka (m_azon, m_fogl, m_fiz,m_szaz) values (20, 'Igazgató',400000,90);
insert into munka (m_azon,m_fogl,m_fiz,m_szaz) values (25,'Fõelemzõ',330000,100);
commit;
Az alábbiakban létrehozunk egy csomagot, amely egy eljárásból és két függvényből épül fel.
--Először létre kell hozni a csomag specifikációs részét.
create or replace package irodai_csomag as
function min_fiz(p_dolg in dolgozo.dolgozo_azon%type) return number;
function telephely_nev(p_telephely in telephely.telephely_azon%type) return varchar2;
procedure beosztas_update;
end irodai_csomag;
--Majd létrehozzuk a csomag törzsét.
Create or replace package body irodai_csomag as
function min_fiz(p_dolg in dolgozo.dolgozo_azon%type) return number as
/* Megadja, hogy az adott dolgozónak mennyi a foglalkozásának megfelelo minimum */
/* fizetés. (a foglalkozas tablaban megadott százalék szerinti fizetés) */
ret_val number;
v_beoszt number;
begin
select fogl_azon
into v_beoszt
from dolgozo
where dolgozo_azon = p_dolg;
select round(fizetes*szazalek/100,-2)
into ret_val
from beosztas
where fogl_azon=v_beoszt;
return ret_val;
exception when no_data_found then return null;
end min_fiz;
function telephely_nev(p_telephely in telephely.telephely_azon%type) return varchar2 as
/* Az iroda_nev függvény visszaadja az iroda azonosítója alapján a nevét. Ha nem megfelelo */
/* azonosítót kap, akkor a neve helyett 'Nem létezo iroda' szöveget adja vissza. */
ret_val telephely.nev%type;
begin
if p_telephely is null
then return null;
end if;
select nev into ret_val
from telephely where telephely_azon=p_telephely;
return ret_val;
exception when no_data_found then
return 'Nem létezo telephely';
end telephely_nev;
procedure beosztas_update as
/*A munka tábla adatait összefésüli a foglalkozas tábla adataiva: a létezoket karbantartja, */
/*a nem létezoket beszúrja a táblába. */
db number;
begin
for r in (select * from munka)
loop
begin
select count(*) into db
from beosztas
where fogl_azon=r.m_azon;
if db=0 then
insert into beosztas (fogl_azon, beosztas ,fizetes,szazalek) values (r.m_azon,r.m_fogl,r.m_fiz,r.m_szaz);
else
update beosztas
set beosztas = r.m_fogl,
fizetes=r.m_fiz,
szazalek=r.m_szaz
where fogl_azon=r.m_azon;
end if;
commit;
end;
end loop;
end beosztas_update;
end irodai_csomag;
A példacsomag PL/SQL Developer-ben készült
NOTE: a következő példa nincs teljesen kész, jelenlegi állapotában ellenőrzi és végrehajtja a bejövő ügyfélregisztrációkat,
valamint fel van készítve, hogy a további - az ügyfelekhez tartozó - tényleges vásárlási tranzakciókat feldolgozza,
regisztrálja, az utána járó kedvezményeket kikalkulálja. Közepes mennyiségű munkával elég nagy méretű és komplexitású
programmá is fejleszthető, illetve jó gyakorlási lehetőség, tekintettel arra, hogy általában nehéz találni normális PL/SQL kódtömeget.
Bármilyen kérdés esetén küldj e-mailt a drdarkman@vipmail.hu címre!
------------------------------
--** DDL scripts
------------------------------
CREATE SEQUENCE GLOBAL_SEQ INCREMENT BY 1 START WITH 1 MAXVALUE 999999 MINVALUE 1;
CREATE TABLE input_table_reg
(
input_id NUMBER,
name VARCHAR2(300),
status VARCHAR2(50),
creation_date DATE
);
CREATE TABLE input_table_data
(
id NUMBER,
input_id NUMBER,
line_num NUMBER,
data VARCHAR2(1000)
);
CREATE TABLE customer
(
cust_id NUMBER,
name VARCHAR2(300),
email VARCHAR2(300),
city VARCHAR2(300),
is_vip NUMBER,
crd DATE,
lmd DATE,
app_date DATE
);
CREATE TABLE cust_x_cards
(
cust_id NUMBER,
card_id NUMBER
);
CREATE TABLE cust_balance
(
cust_id NUMBER,
balance NUMBER,
lmd DATE
);
CREATE TABLE shops
(
shop_id NUMBER,
name VARCHAR2(300),
city VARCHAR2(300),
crd DATE,
lmd DATE
);
CREATE TABLE products
(
prod_id NUMBER,
description VARCHAR2(300),
default_value NUMBER,
crd DATE,
lmd DATE
);
CREATE TABLE error_log
(
error_id NUMBER,
program_unit VARCHAR2(200),
message VARCHAR2(1000),
crd DATE
);
------------------------------
--** Program Units
------------------------------
--------------------------------------------------------
-- Procedure PROC_ERROR_LOG
--------------------------------------------------------
CREATE OR REPLACE PROCEDURE "SYSTEM"."PROC_ERROR_LOG"
(
p_prog_unit VARCHAR2 DEFAULT NULL,
p_msg VARCHAR2 DEFAULT NULL
) AS
/*Create error log with a few params, easily callable in every program unit and
get the stack's content with it*/
v_msg VARCHAR2(1000) := p_msg || ' ' || SUBSTR(DBMS_UTILITY.FORMAT_ERROR_BACKTRACE(), 1, (1000 - length(p_msg)));
BEGIN
INSERT INTO error_log(error_id,program_unit,message,crd)
VALUES(global_seq.nextval , p_prog_unit, v_msg, SYSDATE);
COMMIT;
END proc_error_log;
/
--------------------------------------------------------
-- Procedure PROCESS_INPUT
--------------------------------------------------------
CREATE OR REPLACE PROCEDURE "SYSTEM"."PROCESS_INPUT"
AS
--Purpose: Process all proper input, depends on its statuses
-- from the input_table
--2014.05.08.; DARKMAN;
--Constants
c_proc_name CONSTANT VARCHAR2(18) := 'PROCESS_INPUT';
c_file_name CONSTANT VARCHAR2(4) := 'FILE';
c_call_name CONSTANT VARCHAR2(4) := 'CALL';
--Variables
v_name input_table_reg.name%TYPE;
v_error_msg error_log.message%TYPE;
error_excp EXCEPTION;
CURSOR cr_input(p_status VARCHAR2) IS
SELECT *
FROM input_table_reg
WHERE status = p_status
ORDER BY creation_date;
BEGIN
--open cursor and process the incoming data per input_type
FOR cr_i IN cr_input(pk_const.g_start_status) LOOP
SELECT name
INTO v_name
FROM input_table_reg
WHERE input_id = cr_i.input_id;
IF upper(substr(v_name, 1, 4)) = c_file_name THEN
process_input_file(cr_i.input_id);
ELSIF upper(substr(v_name, 1, 4)) = c_call_name THEN
process_input_call(cr_i.input_id);
END IF;
END LOOP;
EXCEPTION
WHEN error_excp THEN
proc_error_log(c_proc_name, v_error_msg);
WHEN OTHERS THEN
proc_error_log(c_proc_name, v_error_msg);
END process_input;
/
--------------------------------------------------------
-- Procedure PROCESS_INPUT_CALL
--------------------------------------------------------
CREATE OR REPLACE PROCEDURE "SYSTEM"."PROCESS_INPUT_CALL" (p_id NUMBER)
AS
--Purpose: Process a specified input calls per ID,
--execute the customer registration, without input-checks(!)
--Processable incoming pattern:
--APP_DATE(YYYYMMDDHH24MISS);NAME;EMAIL;CITY;CUST_ID
--2014.05.08.; DARKMAN;
c_proc_name CONSTANT VARCHAR2(18) := 'PROCESS_INPUT_CALL';
v_app_date customer.app_date%TYPE;
v_name customer.name%TYPE;
v_email customer.email%TYPE;
v_city customer.city%TYPE;
v_cust_id customer.cust_id%TYPE;
v_error_msg error_log.message%TYPE;
v_count NUMBER := 0;
error_excp EXCEPTION;
CURSOR cr_data(p_id NUMBER) IS
SELECT *
FROM input_table_data
WHERE input_id = p_id;
BEGIN
FOR cr_d IN cr_data(p_id) LOOP
v_app_date := to_date(substr(cr_d.data,0,12), 'YYYYMMDDHH24MI');
v_name := rtrim(substr(cr_d.data,14,100));
v_email := rtrim(substr(cr_d.data,115,50));
v_city := rtrim(substr(cr_d.data,166,50));
v_cust_id := to_number(substr(cr_d.data,217,7));
INSERT INTO customer(cust_id , name , email , city , is_vip, crd , lmd , app_date)
VALUES(v_cust_id, v_name, v_email, v_city, 0 , SYSDATE, SYSDATE, v_app_date );
END LOOP;
UPDATE input_table_reg
SET status = pk_const.g_proc_status
WHERE input_id = p_id;
COMMIT;
EXCEPTION
WHEN error_excp THEN
proc_error_log(c_proc_name, v_error_msg);
ROLLBACK;
WHEN OTHERS THEN
proc_error_log(c_proc_name, v_error_msg);
ROLLBACK;
END;
/
--------------------------------------------------------
-- Procedure PROCESS_INPUT_FILE
--------------------------------------------------------
CREATE OR REPLACE PROCEDURE "SYSTEM"."PROCESS_INPUT_FILE" (p_id NUMBER) AS
--Purpose: Process a specified input files per ID
--NOTE: the "game" begins here, checks are created,
-- but the further logic is all on you :)
--2014.05.08.; DARKMAN; Created.
c_proc_name CONSTANT VARCHAR2(18) := 'PROCESS_INPUT_FILE';
v_card_id cust_x_cards.card_id%TYPE;
v_shop_id shops.shop_id%TYPE;
v_prod_id products.prod_id%TYPE;
v_points NUMBER;
v_quantity NUMBER;
v_date DATE;
v_error_msg error_log.message%TYPE;
v_count NUMBER := 0;
error_excp EXCEPTION;
CURSOR cr_data(p_id NUMBER) IS
SELECT *
FROM input_table_data
WHERE input_id = p_id;
BEGIN
FOR cr_d IN cr_data(p_id) LOOP
v_card_id := to_number(substr(cr_d.data,0,7));
v_shop_id := to_number(substr(cr_d.data,9,3));
v_prod_id := to_number(substr(cr_d.data,13,5));
v_points := to_number(substr(cr_d.data,19,7));
v_quantity := to_number(substr(cr_d.data,27,6));
v_date := to_date(substr(cr_d.data,34,12), 'YYYYMMDDHH24MI');
--CHECK card_id
BEGIN
SELECT COUNT(1)
INTO v_count
FROM cust_x_cards
WHERE card_id = v_card_id;
EXCEPTION
WHEN INVALID_NUMBER THEN
v_error_msg := 'Hiba a kártyaszám kinyerésekor: Hibás formátum! ';
RAISE error_excp;
WHEN NO_DATA_FOUND THEN
v_error_msg := 'Hiba a kártyaszám kinyerésekor: Nincs ilyen kártyaszám! ';
RAISE error_excp;
END;
--CHECK shop_id
BEGIN
SELECT COUNT(1)
INTO v_count
FROM shops
WHERE shop_id = v_shop_id;
EXCEPTION
WHEN INVALID_NUMBER THEN
v_error_msg := 'Hiba az üzlet kinyerésekor: Hibás formátum! ';
RAISE error_excp;
WHEN NO_DATA_FOUND THEN
v_error_msg := 'Hiba az üzlet kinyerésekor: Nincs ilyen üzlet! ';
RAISE error_excp;
END;
--CHECK prod_id
BEGIN
SELECT COUNT(1)
INTO v_count
FROM products
WHERE prod_id = v_prod_id;
EXCEPTION
WHEN INVALID_NUMBER THEN
v_error_msg := 'Hiba a termék kinyerésekor: Hibás formátum! ';
RAISE error_excp;
WHEN NO_DATA_FOUND THEN
v_error_msg := 'Hiba a termék kinyerésekor: Nincs ilyen termék! ';
RAISE error_excp;
END;
END LOOP;
EXCEPTION
WHEN error_excp THEN
proc_error_log(c_proc_name, v_error_msg);
WHEN OTHERS THEN
proc_error_log(c_proc_name, v_error_msg);
END;
/
--------------------------------------------------------
-- Package PK_CONST
--------------------------------------------------------
CREATE OR REPLACE PACKAGE pk_const AS
/*
Contains all database-global constant variables
*/
g_start_status CONSTANT VARCHAR2(11) := 'INPUT_START';
g_check_status CONSTANT VARCHAR2(13) := 'INPUT_CHECKED';
g_proc_status CONSTANT VARCHAR2(15) := 'INPUT_PROCESSED';
g_finish_status CONSTANT VARCHAR2(13) := 'INPUT_FINISHED';
END pk_const;
/
A példa Oracle SQLDeveloperrel készült, Oracle 12c adatbázison.
(Tóth Gábor, 2014, II. félév)