FÖ 11: Databaskursen 1 1. PLSQL 2 2. Select into 3. Anchored declaration 4. Cursorvariabler 5. Olika typer av cursors 6. Cursorattribut 7. Cursorloop 8. Datatypen record: %rowtype 9. Cursor for loop 10. Några kodexempel 11. Felhantering 12. Olika typer av undantag 13. no_data_found d 14. raise_application_error Pär Douhan, pdo@du.se
PLSQL 2 Vi börjar med cursorvariabler 2
Select into KUND kundnr fnamn enamn email mobil 56482 Rolf Björk rb@gmail.com 0730211142 89658 Malin Ek maek@du.se 0703658974 58476 Jossef Mdoud jossef@du.se 0730533347 # knr: number(8) fnamn: varchar2(40) enamn: varchar2(60) email: varchar2(30) mobil: varchar2(10) Maria exekverar följande anonyma PL/SQL-block vi tidpunkt T1: v_email varchar2(30); v_mobil varchar2(10); begin select email,mobil into v_email,v_mobil from kund "select into" fungerar bara med "singelton select" where knr = 56482; dbms_output.put_line('mobil: ' v_mobil ',' 'email: ' v_email); end; / 3
Problem KUND kundnr fnamn enamn email mobil 56482 Rolf Björk rb@gmail.com 0730211142 89658 Malin Ek maek@du.se 0703658974 58476 Jossef Mdoud jossef@du.se 0730533347 mobil: varchar2(15) Någon annan skriver följande kod vid tidpunkt T2: alter table kund modify mobil varchar2(15); update kund set mobil = '+46730277742' where knr = 56482; commit; 4
ORA-06502 Vad händer vid tidpunkt T3, när Maria exekverar sitt anonyma PL/SQL-block igen? v_email varchar2(30); v_mobil varchar2(10); begin select email,mobilmobil into v_email,v_mobil from kund where knr = 56482; dbms_output.put_line('mobil: ' v_mobil ',' 'email: ' v_email); end; / ORA-06502: PL/SQL: numeric or value error select length('+46730277742') from dual; LENGTH('+46730277742') 12 v_mobil varchar2(10); 5
"Ankardeklaration" kl " Lösningen på problemet är anchored declaration %type v_email kund.email%type; v_mobil kund.mobil%type; Hämtar datatypen från databasen. tabell kolumn 6
Vad är en cursor? Synonymer cursor ~ recordset ~ recordgroup ~ resultset ~ länkad lista "en packe med rader", d.v.s. resultatet av en select-sats. select kursnr,poäng,nivå from kurs; cursor-deklaration KURSNR POÄNG NIVÅ ------ ----- ---- 100 5 A 101 5 A first record 102 5 B cursor = resultatet av sql-satsen 103 10 C 104 5 D 105 5 B last record 7
Varför använda en cursor? När behöver vi använda oss av en cursor? "en packe med rader" När vår SQL-sats reurnerar fler än en rad. Om vi gör en singelton select, d.v.s. where-villkoret matchar en kolumn som innehåller unika värden (PK eller unique) så kan vi använda select-into. Får vi tillbaka fler än en rad, så kan vi inte använda select-into, utan vi måste använda en cursor. 8
Olika typer av cursors 1. Statiska cursors - Refererar alltid till samma SQL. 2. Dynamiska cursors - Kan referera till olika SQL-satser vid olika tidpunkter. Statiska cursors - två olika typer 1. Implicit cursor - Hanteras av DBMS. 2. Explicit it cursor 1. Deklarera den 2. Öppna den 3. Loopa igenom raderna 4. Stänga den 9
Deklarera en cursor cursor c_kunder is select fnamn,enamn,adress from kund; begin end; / null; namnet på cursorn cursor c_storkunder is select fnamn,enamn,adress from kund where knr in(select knr from kundorder where ordnr in(select orderrad.ordnr from orderrad,artikel where orderrad.artnr = artikel.artnr having sum(artikel.pris * orderrad.antal) > 20000 group by orderrad.ordnr)); ordnr)); 10
Cursorattribut t %isopen true, false %found %notfound %rowcount true, false true, false number select fnamn,enamn from kund; FNAMN ENAMN -------------------- --------- olof andersson %rowcount = 1 maria andersson %rowcount = 2 tomas kvist %rowcount = 3 hans rosenboll %rowcount = 4 11 NULL EOF,%notfound = true
Cursor loop cursor c_kundmobil is select fnamn,enamn,mobil O from kund; L v_fnamn kund.fnamn%type; v_enamn kund.enamn%type; F v_mobil kund.mobil%type; E begin E if not c_kundmobil%isopen then -- öppna cursorn C open c_kundmobil; end if; loop -- loopa igenom resultatet t t av SQL-satsen fetch c_kundmobil -- ställ pekaren på första raden into v_fnamn,v_enamn,v_mobil; -- hämta data från aktuell rad till variablerna exit when c_ kundmobil%notfound; -- sluta loopa när EOF inträffar dbms_output.put_line(v_fnamn ', ' v_enamn ', mobil: ' v_mobil); end loop; close c_ kundmobil; -- stäng cursorobjektet end; / 12
Datatypen t record: %rowtype cursor c_kundmobil is select fnamn,enamn,mobil from kund; v_fnamn kund.fnamn%type; v_enamn kund.enamn%type; irriterande med v_mobil kund.mobil%type; flera variabler! rec c_ kundmobil%rowtype; rec fnamn enamn mobil begin if not c_kundmobil%isopen then open c_ kundmobil; end if; loop fetch c_kundmobil into rec; v_fnamn,v_enamn,v_mobil; exit when c_kundmobil%notfound; dbms_output.put_line(rec.fnamn ', ' rec.enamn ', mobil: ' rec.mobil); end loop; close c_kundmobil; end; / 13
Cursor for loop For loop innebär minst kod! Cursorn behöver varken öppnas eller stängas. Detta sker implicit. cursor c_kundmobil is begin for rec in c_kundmobil loop select fnamn,enamn,mobil mobil from kund; dbms_output.put_line(rec.fnamn ', ' rec.enamn ', mobil: ' rec.mobil); end loop; end; / 14
Några kodexempel Uppgift: Skriv ett PLSQL-program som kopierar över alla artiklar, som kostar mer än 2000 kr, från tabellen ARTIKEL till tabellen LYXARTIKEL. ARTIKEL artnr artnamn varumärke lagerantal pris 1 Såskastrull Sofoni 5 498 2 Traktörpanna Hackman 13 795 3 Atlantis kastrull Demeyere 13 2999 4 Opera Såskastrull i koppar 18 cm Ruffoni 2 2599 5 Pi Prima Matera Såskastrull i koppar de Buyer 2 2595 LYXARTIKEL artnr artnamn varumärke lagerantal pris 15
Basic loop set serveroutput on cursor c_lyxartiklar is select artnr,artnamn,varumärke,lagerantal,pris from artikel where pris > 2000; v_rec c_lyxartiklar%rowtype; begin if not c_lyxartiklar%isopen then open c_lyxartiklar; end if; O L F E E C loop fetch c_lyxartiklar into v_rec; exit when c_lyxartiklar%notfound; insert into lyxartikel(artnr,artnamn,varumärke,lagerantal,pris) artnamn lagerantal values(v_rec.artnr,v_rec.artnamn,v_rec.varumärke,v_rec.lagerantal,v_rec.pris); end loop; close c_lyxartiklar; commit; dbms_output.put_line('kopieringen är klar!'); end; 16
For loop Samma sak men med en for loop: set serveroutput on cursor c_lyxartiklar is select artnr,artnamn,varumärke,lagerantal,pris from artikel where pris > 2000; begin for v_rec in c_lyxartiklar loop insert into lyxartikel(artnr,artnamn,varumärke,lagerantal,pris) values(v_rec.artnr,v_rec.artnamn,v_rec.varumärke,v_rec.lagerantal,v_rec.pris); artnr v artnamn v varumärke v lagerantal v end loop; commit; dbms_output.put_line( line('kopieringen är klar!'); end; 17
Resultat t anonymous block completed Kopieringen är klar! select * from lyxartikel; ARTNR ARTNAMN VARUMÄRKE LAGERANTAL PRIS 3 Atlantis kastrull Demeyere 13 2999 4 Opera Såskastrull i koppar 18 cm Ruffoni 2 2599 5 Prima Matera Såskastrull i koppar de Buyer 2 2595 18
Generera HTML set serveroutput on cursor c_lyxartiklar is select artnr,artnamn,varumärke,lagerantal,pris from artikel where pris > 2000; begin dbms_output.put_line('<table>'); dbms_output.put_line('<tr>'); dbms_output.put_line( line('<th>artnamn</th>'); dbms_output.put_line('<th>varumärke</th>'); dbms_output.put_line('<th>pris</th>'); dbms_output.put_line('</tr>'); for v_rec in c_lyxartiklar loop dbms_output.put_line('<tr>'); dbms_ output.put p _ line('<td>' v _ rec.artnamn '</td>'); dbms_output.put_line('<td>' v_rec.varumärke '</td>'); dbms_output.put_line('<td>' v_rec.pris '</td>'); dbms_output.put_line('</tr>'); end loop; dbms_output.put_line('</table>'); end; 19
Resultat t <table> <tr> <th>artnamn</th> <th>varumärke</th> <th>pris</th> </tr> <tr> <td>atlantis kastrull</td> <td>demeyere</td> <td>2999</td> </tr> <tr> <td>opera Såskastrull i koppar 18 cm</td> <td>ruffoni</td> <td>2599</td> </tr> <tr> </tr> </table> <td>prima Matera Såskastrull i koppar</td> <td>de Buyer</td> <td>2595</td> 20
I webbläsaren 21
PLSQL PLSQL: Felhantering 22
Exception Felhantering exceptions = errors När ett fel inträffar i programblocket, så förflyttas kontrollen av programblocket till felhanteringsdelen. l v_mobil kund.mobil%type; begin select mobil into v_mobil *** E R R O R *** inträffar här from kund where knr = 3; exception -- MÅSTE ligga sist i programblocket end; / *** E R R O R *** hanteras här 23
Exception handler Rätt exception handler (felhanterare) spåras via en namnreferens till det inträffade felet. Om ett exception (undantag) inträffar och det inte finns någon exception handler, så kommer others att ta hand om det. Om inte others heller finns, så kommer en felkod (error code) och ett felmeddelande (error message) att visas. 24
Olika typer av undantag Tre olika typer av exceptions: 1. Fördefinierade Oracle Server Exceptions Har ett fördefinierat namn, t. ex. no_data_found, zero_divide, to_many_rows, cursor_allready_open, value_error etc. ~ 20 st, "raised implicitly" av servern. 2. Icke fördefinierade Standard Oracle Server-fel som inte är namngivna av systemet. Har en felkod och ett felmeddelande. "raised implicitly" av servern. 3. Användardefinierade Deklareras och namnges av användaren (programmeraren). "raised explicitly" av användaren. 25
sqlcode och sqlerrm sqlcode felkoden number sqlerrm felmeddelandet varchar2 v_mobil kund.mobil%type; begin select mobil into v_ mobil v_mobil innehåller således null from kund where knr = 138; det finns ingen kund med PK = 138 exception when others then dbms_output.put_line('fel: ' sqlerrm ', felkod: ' sqlcode); end; / fel: ORA-01403: data saknas, felkod: 100 26
no_data_found d Undantaget t no_data_found d "kastas" " när en select into-sats t inte returnerar någon data. Detta gäller bara när select into-satsen inte jobbar mot en aggregatfunktion. studera: v_antal number(1); begin select count(*) returnerar alltid 1 eller 0 into v_antal from kund where knr = 138; exception when no_data_found _ then inget kommer att fångas här dbms_output.put_line('data saknas!'); end; / 27
no_data_found d i en cursor Undantaget t no_data_found d fungerar inte i en cursor! Här måste vi hantera undantaget själva: studera: cursor c_kundmobil is begin select mobil from kund where knr = 2; for rec in c_kundmobil loop if rec.mobil is null then raise_application_error(-20001,'mobilnummer saknas!'); end if; end; / dbms_output.put_line(', mobil: ' rec.mobil); end loop; 28
raise_application_error Vi kan anropa proceduren raise_application_error raise_application_error(-20001,'error!, here is some text about it'); Egen felkod: -20001 till -20999 Eget felmeddelande 29
Egendefinierade i d undantag Vi kan skapa s.k. användardefinerade undantag: Studera: cursor c_kundmobil is select mobil from kund where knr = 2; no_mobil exception; egendefinierat undantag begin for rec in c_kundmobil loop if rec.mobil is null then raise no_mobil; end if; undantaget kastas explicit dbms_output.put_line('mobil: t t bil ' rec.mobil); end loop; exception when no_mobil then undantaget t hanteras dbms_output.put_line('kunden saknar mobiltelefon!'); end; / 30
The End 31