Welcome To Oracle1 PDF
Welcome To Oracle1 PDF
Welcome to Oracle
Corso introduttivo al Database Oracle
Architettura
Installazione
SQL
PL/SQL
XML DB
Sommario............................................................................................... 2
Nota sul diritto d’autore .......................................................................... 8
Introduzione ......................................................................................... 10
1 Architettura di Oracle .................................................................. 12
1.1 Istanza e database ............................................................. 12
1.2 Istanza - Processi di base .................................................. 13
1.2.1 PMON – Process Monitor .............................................. 13
1.2.2 SMON – System Monitor ............................................... 13
1.2.3 DBWn – Database Writer............................................... 13
1.2.4 LGWR – Log Writer ........................................................ 13
1.2.5 CKPT – Checkpoint ....................................................... 13
1.3 Istanza - Strutture di memoria ............................................ 13
1.3.1 SGA – System Global Area ........................................... 14
1.3.2 PGA – Program Global Area.......................................... 14
1.4 Database - Strutture per la memorizzazione dei dati ......... 15
1.4.1 Datafile e blocchi ............................................................ 15
1.4.2 Extent ............................................................................. 15
1.4.3 Tablespace..................................................................... 16
1.4.4 Segment ......................................................................... 16
1.5 Database - File di controllo e di log .................................... 16
1.5.1 Il control file .................................................................... 16
1.5.2 I Redo Log file ................................................................ 17
2 Installazione ................................................................................ 18
2.1 Prerequisiti per l’installazione del db .................................. 18
2.1.1 Requisiti Hardware ......................................................... 18
2.1.2 Requisiti Software .......................................................... 19
2.2 Scaricare il software ........................................................... 19
2.3 Installazione del database .................................................. 22
2.4 Installazione del client ........................................................ 33
3 Operazioni preliminari ................................................................. 37
3.1 Avviare ed arrestare Oracle ............................................... 37
3.1.1 Startup............................................................................ 38
2
3.1.2 Shutdown ....................................................................... 38
3.2 Connettersi al database ..................................................... 40
3.2.1 Utenti e schemi .............................................................. 40
3.2.2 Connettersi dal server dove è installato il db ................. 40
3.2.3 Connettersi da un client remoto ..................................... 41
3.2.4 Creare un nuovo utente ................................................. 49
4 SQL*Plus e SQL Developer ........................................................ 50
4.1 SQL*Plus ............................................................................ 50
4.1.1 Perché ancora SQL*Plus ............................................... 51
4.1.2 Personalizzare SQL*Plus (Windows) ............................ 51
4.1.3 Parametri e comandi di SQL*Plus ................................. 54
4.1.4 Scrittura e modifica di un’istruzione SQL ....................... 56
4.1.5 esecuzione di uno script SQL ........................................ 60
4.1.6 Cambio del prompt SQL ................................................ 60
4.2 SQL Developer ................................................................... 61
4.2.1 Avvio di SQL Developer ................................................. 61
4.2.2 Parametri e comandi di SQL Developer ........................ 63
5 Oggetti del DB ............................................................................. 64
5.1 Il dizionario dati .................................................................. 64
5.2 Tabelle ................................................................................ 67
5.2.1 Colonne di una tabella ................................................... 68
5.2.2 Constraint ....................................................................... 69
5.3 Indici ................................................................................... 70
5.3.1 Indici B-Tree ................................................................... 71
5.3.2 Indici Bitmap .................................................................. 72
5.4 IOT ..................................................................................... 72
5.5 Cluster di tabelle................................................................. 73
5.6 Viste ................................................................................... 74
5.6.1 Check option constraint ................................................. 74
5.7 Viste materializzate ............................................................ 75
5.8 Sequenze ........................................................................... 75
5.9 Sinonimi .............................................................................. 76
5.10 Database link...................................................................... 76
5.11 Oggetti PL/SQL .................................................................. 76
5.11.1 Procedure .................................................................. 77
5.11.2 Funzioni ..................................................................... 77
5.11.3 Package ..................................................................... 77
5.11.4 Trigger ....................................................................... 77
5.12 Estensioni Object-Oriented ................................................ 77
5.13 XML DB .............................................................................. 78
6 CASE STUDY ............................................................................. 79
6.1 Progettazione ..................................................................... 79
6.2 Il database d’esempio ........................................................ 80
6.2.1 Tabelle ........................................................................... 80
6.2.2 Dati ................................................................................. 82
6.3 SCOTT/TIGER ................................................................... 83
3
7 SQL ............................................................................................. 86
7.1 Tipi di dato .......................................................................... 87
7.1.1 Dati alfanumerici ............................................................ 88
7.1.2 Numeri............................................................................ 90
7.1.3 Date ed orari .................................................................. 90
7.1.4 Dati binari ....................................................................... 91
7.2 Comandi DDL ..................................................................... 91
7.2.1 CREATE......................................................................... 91
7.2.2 ALTER.......................................................................... 111
7.2.3 RENAME ...................................................................... 121
7.2.4 DROP ........................................................................... 122
7.2.5 TRUNCATE.................................................................. 124
7.2.6 PURGE ........................................................................ 125
7.2.7 FLASHBACK TABLE ................................................... 127
7.3 Comandi DML .................................................................. 128
7.3.1 Valori fissi ..................................................................... 128
7.3.2 INSERT ........................................................................ 129
7.3.3 UPDATE....................................................................... 138
7.3.4 DELETE ....................................................................... 140
7.3.5 MERGE ........................................................................ 141
7.3.6 Gestione degli errori in SQL con LOG ERRORS......... 146
7.4 SELECT, il comando di ricerca ........................................ 152
7.4.1 Proiezioni ..................................................................... 153
7.4.2 La tabella DUAL ........................................................... 155
7.4.3 Pseudo colonne ........................................................... 157
7.4.4 Selezioni....................................................................... 159
7.4.5 Ordinamento ................................................................ 178
7.4.6 Raggruppamenti .......................................................... 182
7.4.7 Tornando su ROWNUM ............................................... 193
7.4.8 Le clausole PIVOT ed UNPIVOT ................................. 195
7.4.9 Query gerarchiche ....................................................... 200
7.4.10 La clausola MODEL ................................................. 209
7.5 Manipolazione dei dati...................................................... 220
7.5.1 Operatore di concatenazione ....................................... 220
7.5.2 Operatori aritmetici ....................................................... 222
7.5.3 Funzioni sulle stringhe ................................................. 223
7.5.4 Funzioni sui numeri ...................................................... 234
7.5.5 Funzioni sulle date ....................................................... 239
7.5.6 Funzioni di conversione ............................................... 247
7.5.7 Formati per la rappresentazione delle date ................. 250
7.5.8 Formati per la rappresentazione dei numeri ................ 254
7.5.9 Gestione dei valori nulli ................................................ 255
7.5.10 Espressioni regolari ................................................. 260
7.5.11 DECODE e CASE.................................................... 265
7.5.12 GREATEST e LEAST .............................................. 269
7.6 JOIN: ricerche da più tabelle ............................................ 271
4
7.6.1 Sintassi per la JOIN ..................................................... 271
7.6.2 Prodotto cartesiano di tabelle ...................................... 271
7.6.3 Inner join ...................................................................... 273
7.6.4 Outer join ..................................................................... 276
7.7 Gli operatori insiemistici ................................................... 282
7.7.1 UNION.......................................................................... 282
7.7.2 UNION ALL .................................................................. 285
7.7.3 INTERSECT ................................................................. 286
7.7.4 MINUS.......................................................................... 287
7.8 Ricerche innestate............................................................ 288
7.8.1 Operatori di confronto .................................................. 289
7.8.2 Subquery correlate e scorrelate ................................... 293
7.8.3 La clausola WITH......................................................... 296
7.9 Comandi TCL ................................................................... 297
7.9.1 Gestione delle transazioni ........................................... 297
7.9.2 COMMIT ...................................................................... 297
7.9.3 ROLLBACK .................................................................. 299
7.9.4 SAVEPOINT ................................................................ 300
7.9.5 SET TRANSACTION ................................................... 302
7.9.6 Lock di dati ed oggetti .................................................. 306
7.9.7 Una situazione particolare, il deadlock ........................ 307
7.10 Comandi DCL ................................................................... 309
7.10.1 GRANT .................................................................... 309
7.10.2 REVOKE .................................................................. 310
7.10.3 RUOLI ...................................................................... 310
8 PL/SQL...................................................................................... 312
8.1 Blocchi PL/SQL anonimi .................................................. 313
8.2 Struttura di un blocco PL/SQL anonimo ........................... 313
8.3 Dichiarazione di costanti e variabili .................................. 316
8.3.1 Tipi di dato ................................................................... 316
8.3.2 Dichiarazione di variabili .............................................. 316
8.3.3 Dichiarazione di costanti .............................................. 317
8.4 Costrutti di base ............................................................... 318
8.4.1 Assegnazione di un valore. .......................................... 318
8.4.2 Strutture Condizionali................................................... 318
8.4.3 Strutture Iterative ......................................................... 321
8.4.4 Strutture Sequenziali.................................................... 325
8.5 Utilizzo dell’SQL ............................................................... 327
8.5.1 SQL Statico .................................................................. 327
8.5.2 SQL Dinamico .............................................................. 328
8.5.3 CURSORI .................................................................... 332
8.6 Gestione delle eccezioni .................................................. 337
8.6.1 Blocco Exception ......................................................... 337
8.6.2 Eccezioni predefinite .................................................... 338
8.6.3 SQLCODE e SQLERRM.............................................. 342
8.6.4 Sollevare un’eccezione ................................................ 343
5
8.6.5 Eccezioni utente ........................................................... 344
8.6.6 Eccezioni mute ............................................................. 345
8.6.7 Eccezioni in un ciclo ..................................................... 346
8.6.8 Propagazione delle eccezioni ...................................... 348
8.7 Tipi di dato complessi ....................................................... 350
8.7.1 Record.......................................................................... 350
8.7.2 Array associativi (PL/SQL Tables) ............................... 353
8.7.3 Varray........................................................................... 357
8.7.4 Nested Table ................................................................ 358
8.7.5 Bulk collect ................................................................... 359
8.7.6 Metodi delle collezioni .................................................. 360
8.8 Oggetti PL/SQL nel DB .................................................... 371
8.8.1 Procedure..................................................................... 371
8.8.2 Funzioni........................................................................ 379
8.8.3 Package ....................................................................... 385
8.8.4 Database Trigger ......................................................... 395
8.8.5 Dipendenze tra oggetti del DB ..................................... 410
8.8.6 Diritti d’esecuzione ....................................................... 414
8.8.7 Controllo del codice PL/SQL ........................................ 417
8.9 Argomenti avanzati di PL/SQL ......................................... 421
8.9.1 Gestione dei BLOB ...................................................... 421
8.9.2 Crittografia in PL/SQL .................................................. 424
8.9.3 Inviare email dal DB ..................................................... 433
8.9.4 Il package DBMS_METADATA.................................... 436
8.9.5 Oracle Scheduler. ........................................................ 441
8.9.6 Compilazione condizionale .......................................... 445
8.9.7 Le direttive di compilazione PRAGMA ......................... 450
8.9.8 PL/SQL Trace .............................................................. 458
8.9.9 PL/SQL Profiling .......................................................... 464
9 XML DB ..................................................................................... 471
9.1 Il tipo XMLType ................................................................ 471
9.2 Query SQL in formato XML .............................................. 472
9.3 Aggiornamento di documenti XML ................................... 496
9.4 Alcune nuove funzioni ...................................................... 502
9.5 Supporto di XML Schema. ............................................... 507
9.6 Creare documenti Office con XML e PL/SQL. ................. 517
10 Prerequisiti ................................................................................ 538
10.1 Database e database relazionali ...................................... 538
10.1.1 database .................................................................. 538
10.1.2 database relazionali ................................................. 539
10.1.3 DBA ......................................................................... 540
10.2 Progettazione di un db relazionale ................................... 541
10.2.1 Modello entità/relazioni ............................................ 541
10.2.2 Normalizzazione ...................................................... 541
10.2.3 Definizione dello schema fisico ............................... 545
10.3 Architettura di un computer .............................................. 546
6
10.3.1 Architettura Hardware.............................................. 546
10.3.2 Architettura Software ............................................... 546
10.4 Memorizzazione dei dati .................................................. 547
10.4.1 I sistemi binario ed esadecimale ............................. 547
10.4.2 Dal bit al TeraByte ................................................... 549
10.4.3 La tabella ASCII ....................................................... 549
10.5 Rappresentazioni numeriche ........................................... 550
10.5.1 Virgola fissa ............................................................. 550
10.5.2 Virgola mobile .......................................................... 550
10.6 Algoritmi di ricerca ............................................................ 551
10.6.1 Ricerca sequenziale ................................................ 551
10.6.2 Ricerca dicotomica (o binaria) ................................. 551
10.7 Algoritmi e funzioni di HASH ............................................ 551
10.8 Teoria degli insiemi .......................................................... 552
10.8.1 Insiemi e sottoinsiemi .............................................. 552
10.8.2 Intersezione di insiemi ............................................. 552
10.8.3 Unione di insiemi ..................................................... 553
10.8.4 Complemento di insiemi .......................................... 553
10.8.5 Prodotto cartesiano di insiemi ................................. 553
10.9 Logica delle proposizioni .................................................. 553
10.9.1 Operatore di congiunzione ...................................... 554
10.9.2 Operatore di disgiunzione........................................ 554
10.9.3 Operatore di negazione ........................................... 554
10.9.4 Regole di precedenza.............................................. 554
10.9.5 Leggi di De Morgan ................................................. 555
10.10 Logica delle proposizioni e teoria degli insiemi ........... 555
Indice Analitico................................................................................... 557
Indice delle figure............................................................................... 565
7
Nota sul diritto d’autore
8
Sta ai lettori innescare questo circolo virtuoso ricompensando gli
autori meritevoli, incentivandoli, in questo modo, a fornire opere di
sempre migliore qualità.
Mi piacerebbe, tra qualche anno, poter scegliere liberamente i
libri che mi servono in un ampio catalogo su internet, verificarne la
qualità e poi pagare un giusto prezzo direttamente all’autore.
Un’utopia? Dipende solo da noi!
Discorso diverso, ovviamente, bisogna fare per le copie
cartacee del manuale. Questo libro è stato pubblicato sul sito di self
publishing http://ilmiolibro.kataweb.it, una copia cartacea può essere
acquistata su quel sito, sul sito http://www.lafeltrinelli.it/, oppure
ordinata in una qualunque libreria Feltrinelli. Il prezzo d’acquisto è
dovuto quasi interamente ai costi dei servizi forniti dalle aziende che si
occupano di stampare e distribuire il manuale.
Alla pagina web
http://oracleitalia.wordpress.com/welcome_to_oracle
è possibile trovare tutte le informazioni su come scaricare
l’ultima versione del manuale, acquistarne una copia cartacea,
effettuare una donazione.
Massimo Ruocchio
9
Introduzione
10
In tutto il manuale è stato utilizzato il simbolo , seguito da un
numero di paragrafo, per indicare i concetti di base introdotti nel
capitolo 10.
Questo manuale nasce senza l’iter consueto di una casa editrice
dove, prima di arrivare alla pubblicazione, l’opera è letta e riletta
dall’autore e dai revisori di bozze. Dichiaro fin d’ora la mia gratitudine
verso tutti coloro che vorranno segnalarmi errori di qualunque genere
e possibili miglioramenti del manuale. Potete inviare tutte le
segnalazioni all’indirizzo email
[email protected]
11
1 Architettura di Oracle
12
1.2 Istanza - Processi di base
Un’istanza Oracle include almeno i seguenti processi
obbligatori:
13
1.3.1 SGA – System Global Area
È un’area di memoria condivisa da tutti i processi di base. È
composta principalmente dalle seguenti strutture di memoria:
• Database Buffer Cache: include tutti i blocchi contenenti dati
prelevati dal database (quindi dal disco fisso) e messi in RAM per
essere letti e/o modificati.
• Redo Log Buffer: contiene le informazioni che saranno scritte dal
LGWR sui Redo Log Files.
• Shared Pool: contiene informazioni di sistema (parametri,
istruzioni SQL o PL/SQL già parzialmente elaborate, contenuto del
dizionario dati) che sono state già utilizzate e potrebbero essere di
nuovo utili per successive elaborazioni.
• Large Pool: è un’area di memoria opzionale destinata a contenere
ciò che è troppo grande per essere contenuto nella Shared Pool.
• Java Pool: è un’area dedicata a contenere i programmi java che
vengono eseguiti nell’ambito del DB
È importante sottolineare che la SGA, essendo condivisa, è
utilizzata da tutti i processi che accedono al DB. Per fare un esempio,
ipotizziamo che, mediante un’applicazione web, un utente visualizzi i
dati del cliente 111111. Oracle accede al disco fisso dove sono
conservati i dati del cliente e li porta in memoria RAM, mettendoli nella
Database Buffer Cache. Successivamente i dati vengono restituiti alla
pagina web e visualizzati. Se un secondo utente si collega al DB,
anche in un’altra modalità, ad esempio utilizzando il tool Oracle
Sql*Plus (vedi capitolo 4), e cerca i dati del cliente 111111 Oracle
potrà rispondere leggendo direttamente la SGA, senza dover di nuovo
accedere al file contenente i dati che si trova sul disco fisso. Ciò
consente un grande miglioramento dei tempi di risposta perché un
accesso al disco fisso è enormemente più lento di un accesso alla
memoria RAM.
14
1.4 Database - Strutture per la memorizzazione dei
dati
1.4.1 Datafile e blocchi
Come già accennato in precedenza, i dati vengono archiviati nel
database in file che si trovano sul disco fisso. Questi file prendono il
nome di datafile. Per gestire con grande precisione i singoli dati Oracle
suddivide ogni datafile in blocchi (data block) di una dimensione fissa,
solitamente quattro oppure otto kilobyte( 10.4.2). La dimensione del
blocco viene decisa in fase di installazione del DB e non può più
essere modificata successivamente.
All’interno di un blocco ci possono essere diverse righe della
stessa tabella, non righe di tabelle diverse (a meno di una piccola
eccezione, i cluster di tabelle, di cui diremo più avanti). Ogni blocco è
composto da
• una testata, in cui Oracle conserva informazioni sul blocco e sui
dati che esso contiene,
• uno spazio libero da utilizzare nel caso in cui i dati contenuti nel
blocco vengano modificati in momenti successivi all’inserimento,
• i dati di una serie di righe provenienti da una stessa tabella.
I blocchi non vengono riempiti mai per intero. Ogni volta che un
utente inserisce una riga in una tabella, Oracle aggiunge una nuova
riga nel blocco corrente facendo sempre attenzione a lasciare libero lo
spazio riservato per gli aggiornamenti dei dati. Se Oracle determina
che l’inserimento della nuova riga nel blocco farebbe diminuire
eccessivamente lo spazio libero, passa al blocco successivo ed
inserisce lì la nuova riga. La dimensione dello spazio libero minimo per
ogni blocco può essere definita in fase di creazione del database o
delle singole tabelle.
1.4.2 Extent
Vista l’esigua dimensione di un blocco, questo potrà contenere
poche righe di una tabella. Quante righe possono essere contenute in
un blocco dipende dal numero di dati presenti in tabella e da quanto
spazio questi occupano. In ogni caso sarebbe antieconomico per
Oracle dover riservare un blocco alla volta ogni volta che il blocco
corrente è saturo. Per semplificare le cose Oracle riserva alla tabella
un certo numero di blocchi consecutivi presenti nel datafile. Un
insieme di blocchi consecutivi che vengono prenotati da Oracle tutti
per la stessa tabella prendono il nome di extent. Dunque possiamo
dire che un datafile è logicamente suddiviso in tanti extent. Ogni extent
15
è un insieme di blocchi consecutivi tutti dedicati a contenere i dati della
stessa tabella. Quando Oracle non ha più spazio per inserire
nell’ultimo blocco disponibile nell’extent corrente prenota un nuovo
extent. Quanti blocchi compongono l’extent? Dipende. Si può fare in
modo che sia Oracle a gestire in autonomina questo aspetto oppure
lasciare al DBA la gestione degli extent. La scelta di default,
fortemente consigliata da Oracle, è la prima.
1.4.3 Tablespace
Oracle mette a disposizione dei contenitori logici per le tabelle
che possono essere costituiti da uno o più datafile. Questi contenitori
prendono il nome di tablespace. In pratica quando si crea una tabella
bisogna che Oracle sappia in quale tablespace deve andare a mettere
i dati, sceglierà poi autonomamente quale dei file del tablespace
utilizzare. I dati di una tabella stanno sempre tutti sullo stesso
tablespace, ma possono essere scritti in datafile diversi. Il tablespace
che si intende utilizzare può essere esplicitamente indicato in fase di
creazione della tabella, oppure Oracle utilizzerà un tablespace di
default.
1.4.4 Segment
Fin qui abbiamo descritto le modalità di memorizzazione dei dati
delle tabelle. In un database, però, ci possono essere anche altri tipi di
oggetto. Il capitolo 5 è dedicato ad una carrellata sui diversi oggetti
che si possono trovare in un database Oracle, alcuni di questi
contengono dati e dunque occupano spazio come le tabelle, altri no.
Gli oggetti del primo tipo vengono detti segment. Tutto ciò che
abbiamo detto nel paragrafo 1.4 si adatta a tutti i segment, di cui le
tabelle sono un caso particolare.
16
tabella, infatti, questi non vengono subito modificati nel datafile, ma in
blocchi che si trovano nella SGA, quindi in memoria RAM. Il processo
di base CKPT periodicamente decide di eseguire l’operazione di
checkpoint, dà ordine al processo DBW0 di eseguire la scrittura e
registra quest’informazione nel control file. Senza il checkpoint tutte le
modifiche fatte andrebbero perse allo spegnimento del server, con
l’azzeramento della memoria RAM.
Il control file è definito in fase di creazione del db ed è così
importante che è sempre multiplexed, cioè esiste in più copie identiche
in locazioni diverse, in modo che se una copia viene persa ce n’è
sempre almeno un’altra.
17
2 Installazione
18
2.1.2 Requisiti Software
Le versioni di Windows supportate sono le seguenti
• Windows Server 2003 – tutte le edizioni
• Windows Server 2003 R2 - tutte le edizioni
• Windows XP Professional
• Windows Vista - Business, Enterprise ed Ultimate edition
• Windows Server 2008 - Standard, Enterprise, Datacenter,
Web, e Foundation edition.
• Windows 7 - Professional, Enterprise ed Ultimate edition
Quanto a Linux, sono supportate le seguenti versioni minime
• Asianux Server 3 SP2
• Oracle Enterprise Linux 4 Update 7
• Oracle Enterprise Linux 5 Update 2
• Red Hat Enterprise Linux 4 Update 7
• Red Hat Enterprise Linux 5 Update 2
• SUSE Linux Enterprise Server 10 SP2
• SUSE Linux Enterprise Server 11
19
Passo 2 – Accettare l’accordo di licenza e cliccare sui file relativi al
proprio sistema operativo
20
Passo 4 – Avviare il download dei singoli file, per ogni file sarà
richiesto di accettare l’accordo di licenza.
21
2.3 Installazione del database
La seguente procedura mostra l’installazione più semplice del
Database, per quasi tutti i parametri saranno utilizzati i valori di default.
Passo 1 – Fare doppio clic su Setup.exe, si aprirà una finestra
analoga a quella che segue. Attendere.
22
Passo 3 – Se non si è fornito l’indirizzo email sarà presentato questo
messaggio, fare clic su Sì.
23
Passo 5 – Per una installazione di prova scegliere “Classe desktop” e
fare clic su Avanti.
Passo 6 – Selezionare
• nel campo “Oracle Base” la directory in cui si vuole installare
Oracle, nell’esempio D:\Oracle;
• nel campo “Nome di database globale” il nome che si intende
assegnare al database, nell’esempio CORSO;
• nei campi “Password amministrativa” e “Confermare la password”
il valore che si intende assegnare alla password degli utenti SYS e
SYSTEM. Nel caso in cui la password immessa sia ritenuta poco
sicura, nell’esempio è stata utilizzata la password “corsopwd”, il
campo “Messaggi” conterrà un warning.
Fare clic su Avanti.
24
Figura 2-11 Scelta dei parametri essenziali
25
Figura 2-13 Verifica dei prerequisiti
26
Passo 10 – Parte l’installazione, non bisogna fare nulla.
27
Passo 12 – Automaticamente si apre la finestra dell’assistente alla
configurazione del database. Non bisogna fare nulla.
28
Passo 14 – Nella pagina di gestione delle password è possibile
modificare le password dei singoli utenti. Fare clic su Annulla e poi su
OK.
29
Passo 15 – Viene visualizzata la pagina di chiusura dell’installazione.
Fare clic su Chiudi.
30
Passo 18 – Nei servizi di Windows sono presenti alcuni servizi Oracle.
Quasi tutti hanno avvio automatico.
31
Passo 20 – Per tutto ciò che sarà descritto in questo manuale è
sufficiente che sia avviato il solo servizio OracleServiceCorso. Tutti gli
altri possono essere spenti.
Il servizio OracleOraDb11g_home1TNSListener dovrà essere
avviato solo se client remoti avranno bisogno di collegarsi al database
che abbiamo appena creato. Se questo database, invece, riceverà
solo connessioni locali il listener può restare spento.
32
2.4 Installazione del client
Nel caso in cui ci fosse già un database Oracle disponibile su un
server a cui si ha accesso, è sufficiente installare sul proprio computer
il solo client. La seguente procedura illustra brevemente come fare:
Passo 1 – Download del software. A partire dalla pagina indicata in
Figura 2-2 fare clic sul collegamento “See All” posto alla destra del
proprio sistema operativo. Accettato l’accordo di licenza si può
scaricare il file “Oracle Database 11g Release 2 Client”.
33
Passo 3 – Scegliere il tipo di installazione. L’instant client contiene
solo le librerie minime, l’installazione Amministratore è completa, la
Runtime contiene il software di sviluppo. Scegliamo questa. Avanti.
34
Passo 5 – Selezionare la cartella di destinazione dell’installazione e
fare clic su Avanti.
35
Passo 7 – Fine installazione. Fare clic su Chiudi.
36
3 Operazioni preliminari
37
molto semplice: in Windows si può utilizzare l’apposita voce presente
nel menù Avvio di Windows (vedi Figura 2-21) oppure, in qualunque
sistema, da linea di comando basta digitare
sqlplus / as sysdba
3.1.1 Startup
La procedura di startup consiste nell’avviare un database
spento e si concretizza nel susseguirsi dei seguenti quattro stati:
• SHUTDOWN, l’istanza è ferma ed il database non è disponibile
• NOMOUNT, l’istanza è attiva ed il database non è disponibile
• MOUNT, l’istanza è attiva ed il db è disponibile solo per operazioni
di amministrazione
• OPEN, l’istanza è attiva ed il database è disponibile per tutte le
operazioni.
Il comando che consente di passare dallo stato SHUTDOWN
direttamente ad uno degli altri tre stati è STARTUP, specificando la
parola chiave NOMOUNT o MOUNT se si desidera andare in uno di
questi due stati intermedi. Il comando STARTUP da solo porta
direttamente allo stato OPEN. Il comando che invece consente di
passare dallo stato NOMOUNT a MOUNT, da MOUNT ad OPEN o
direttamente da NOMOUNT ad OPEN è ALTER DATABASE seguito
dallo stato desiderato. Ad esempio ALTER DATABASE OPEN porta il
DB in stato OPEN sia partendo dallo stato NOMOUNT che partendo
dallo stato MOUNT.
3.1.2 Shutdown
Il processo di shutdown esegue gli step inversi di quello di
startup. Il comando SQL*Plus da utilizzare è SHUTDOWN che rende
indisponibile il db ed arresta l’istanza a partire da qualunque stato.
Esistono quattro diverse modalità di SHUTDOWN:
• SHUTDOWN NORMAL, è la modalità di default ed equivale al
semplice SHUTDOWN. Il database non viene spento
immediatamente, non vengono accettate nuove connessioni ma il
db resta in piedi fino a quando tutti gli utenti già collegati si siano
volontariamente disconnessi. Lo shutdown effettivo può quindi
avvenire anche molto tempo dopo l’esecuzione del comando.
38
• SHUTDOWN TRANSACTIONAL, Il database non viene spento
immediatamente, non vengono accettate nuove connessioni ma il
db resta in piedi fino a quando tutti gli utenti già collegati decidano
volontariamente di salvare o annullare le modifiche apportate ai
dati. Una volta che un utente salva o annulla le sue modifiche
viene disconnesso automaticamente e non può più riconnettersi.
Come prima, lo shutdown può avvenire anche molto tempo dopo
l’esecuzione del comando.
• SHUTDOWN IMMEDIATE , Oracle esegue un checkpoint e butta
fuori tutti gli utenti collegati. Le modifiche degli utenti non salvate
andranno perse.
• SHUTDOWN ABORT , Equivale a staccare la spina del server su
cui Oracle è installato, non c’è tempo per fare nulla, alla ripartenza
di Oracle sarà sicuramente eseguito un recovery.
Un esempio di shutdown e startup del database da Sql*Plus è
presentato in Figura 3-1.
39
3.2 Connettersi al database
Ora che il database è installato ed avviato possiamo collegarci
per cominciare a creare le nostre tabelle e finalmente giocare con i
dati. Prima, però, dobbiamo capire come sono suddivisi i dati dei
diversi utenti che si possono collegare al database e come avviene
praticamente la connessione.
40
3.2.3 Connettersi da un client remoto
Un po’ più complessa è la connessione ad Oracle quando
vogliamo collegarci da un altro computer che nel seguito chiameremo
client. Chiameremo invece server il computer su cui è installato
Oracle. In questo caso bisogna fornire, oltre ad username e password,
le seguenti informazioni:
• Nome o indirizzo IP del server.
• Nome del database scelto in fase di installazione.
• Protocollo di rete utilizzato per la connessione tra il client ed il
server.
• Porta su cui è in ascolto il listener sul server.
Prima di procedere spiegando come si forniscono queste
informazioni descriviamo velocemente come avviene tecnicamente la
connessione.
Un programma che gira sul client e richiede la connessione ad
un database Oracle è detto servizio client. Il servizio client deve
comunicare col server, quindi prima di tutto ci deve essere una rete tra
client e server. Di norma client e server utilizzano come protocollo di
comunicazione il TCP/IP e il client deve conoscere il nome del server
oppure il suo indirizzo IP. Una volta che il client ha note queste due
informazioni può aprire una connessione verso il server. A questo
punto è necessario capire cosa succede sul server. Per poter
accettare connessioni dall’esterno un server deve avere attivo un
programma che sia in grado di ricevere le connessioni TCP/IP e
smistarle verso Oracle. Questo programma si chiama listener.
Siccome sul server ci potrebbero essere più programmi in attesa di
connessioni TCP/IP, programmi che servono ad Oracle oppure ad
altro, è necessario che il listener sia in ascolto su una porta particolare
e che il client quando si connette conosca questa porta. Per default la
porta su cui i listener Oracle si mettono in ascolto è la 1521, ma questo
parametro può essere modificato a piacere dall’amministratore del
database.
Se avete installato Oracle su Windows è stato creato un servizio
che consente di avviare ed arrestare il listener (il quinto dall’alto in
Figura 2-25).
In qualunque sistema operativo il listener si controlla con il tool
lsnrctl che può essere utilizzato da prompt dei comandi.
Per avviare il listener useremo il comando
lsnrctl start
41
per stopparlo, invece, useremo il comando
lsnrctl stop
42
trova nel menù avvio in Avvio Æ Programmi Æ Oracle –
OraClient11g_home1 Æ Strumenti di configurazione e
migrazioneÆ Net Manager
Ovviamente il menù può cambiare per versioni diverse di
Oracle, è infatti in generale possibile collegarsi ad un server Oracle di
una certa versione con un client di versione differente. Vediamo passo
per passo la configurazione di una nuova connect string.
Passo 1 – Lanciare Net Manager dal menù Avvio
43
Passo 3 – Inserire la nuova connect string che si intende creare e fare
clic su Avanti
44
Passo 5 – Indicare il nome (oppure l’indirizzo IP) del server e la porta
su cui il listener è in ascolto e fare clic su Avanti.
45
Passo 7 – Cliccare su Test per effettuare il test della connessione
46
Passo 9 – Indicare le credenziali corrette (in questo esempio esiste un
utente che si chiama “corso”, si può anche usare SYSTEM o un
qualunque altro utente) e fare clic su OK e poi su Test
47
Passo 11 – Chiudere Net Manager e salvare le modifiche
miodb =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = servername)(PORT = 1521))
)
(CONNECT_DATA =
(SERVICE_NAME = CORSO)
)
)
sqlplus corso/corsopwd@miodb
sqlplus
48
Figura 3-13 Connessione con Sql*Plus da client remoto.
49
4 SQL*Plus e SQL Developer
4.1 SQL*Plus
SQL*Plus è un tool a linea di comando (non grafico) presente in
Oracle fin dalla versione 5. È stato per anni l’unico strumento fornito
con il DB sia per l’amministrazione che per la gestione dei dati. Dalla
versione 7 alla versione 10g Release 2 del database, e solo in
ambiente Windows, Oracle ha fornito anche una versione
graficamente più evoluta di SQL*Plus che è stata eliminata nella
versione 11g. Da questa scelta traspare l’orientamento di Oracle a
continuare il supporto di SQL*Plus solo al fine di consentire agli utenti
la realizzazione di alcune attività molto specifiche, in particolare quelle
che richiedono una forte automatizzazione, e spingere il grosso degli
50
utenti all’utilizzo del nuovo strumento grafico, SQL Developer, fornito a
partire dalla Release 2 di Oracle 10g.
51
Comparirà la finestra mostrata in Figura 4-2. La prima opzione
da selezionare è “Modalità Modifica Rapida” che consente di
agevolare di molto il “Copia-Incolla”.
52
I parametri indicati sono adatti ad uno schermo con risoluzione
1280x1024, con risoluzioni diverse si può fare qualche tentativo per
trovare il giusto numero di righe e colonne della finestra.
Quando si chiude la finestra delle proprietà bisogna scegliere la
voce “Modifica il collegamento che ha aperto questa finestra”, come
mostrato in Figura 4-4, in modo che le impostazioni scelte saranno
utilizzate ogni qual volta si lancia SQL*Plus dal menù.
53
Nel campo “Destinazione” dopo il nome del programma
aggiungiamo le informazioni relative a nome utente, password e
stringa di connessione. Ovviamente se si intende tenere riservata la
password questa può essere omessa e sarà richiesta da SQL*Plus
alla partenza. Conviene anche modificare il campo “Da”. Tutti i file letti
e scritti da SQL*Plus saranno per default cercati ed inseriti nella
cartella indicata, nell’esempio il campo è stato valorizzato con la
cartella D:\Corso. Ovviamente sarà sempre possibile da SQL*Plus
leggere o scrivere file che si trovano in cartelle differenti, sarà solo
necessario indicare esplicitamente il path oltre al nome dei file.
54
Se invece avessimo voluto visualizzare il valore di uno specifico
parametro avremmo dovuto lanciare il comando.
Show <nomeparametro>
Ad esempio
Show linesize
55
che mostra la struttura della tabella DICT. Questa tabella (in
verità non si tratta proprio di una tabella ma di una vista, al momento
non fa alcuna differenza), contiene l’indice del “dizionario dati”. Il
dizionario dati è un insieme di tabelle che contengono informazioni su
tutti gli oggetti presenti nel database. L’output del comando DESC,
come evidenziato in Figura 4-8, mostra un riepilogo delle colonne della
tabella DICT. Per ogni colonna viene visualizzato il nome, se è sempre
valorizzata oppure no ed il tipo di dati contenuti nella colonna. Non ci
soffermiamo adesso su queste informazioni perché saranno
ampiamente trattate nel capitolo dedicato al linguaggio SQL.
56
un punto e virgola “;” oppure uno slash “/”. Se si decide di utilizzare lo
slash è necessario posizionarlo da solo sull’ultima riga dell’istruzione.
57
del comando “ed”, si è aperto in notepad il file afiedit.buf. Una volta
modificato, chiuso e salvato il file si ottiene la visualizzazione della
nuova istruzione SQL, si veda l’immagine successiva, che viene poi
eseguita mediante il comando “r”.
58
Questo modo di procedere può sembrare all’inizio un po’ ostico,
ma ci si fa velocemente l’abitudine.
Alcuni comandi speciali di SQL*plus (“i”, “del”, “c”) consentono di
modificare l’ultima istruzione SQL eseguita che è conservata in
memoria. Mediante questi comandi è possibile inserire o cancellare
una linea dell’istruzione oppure sostituire una stringa con un’altra. Un
esempio è fornito in Figura 4-13. Tutte le righe precedute da un doppio
trattino “--“ sono commenti che vengono ignorati da SQL*Plus e sono
stati aggiunti per chiarire ciò che si stava facendo.
59
In alternativa all’uso dei comandi di modifica appena illustrati è
possibile riscrivere l’istruzione SQL direttamente sulla linea di
comando, magari utilizzando il copia-incolla.
oppure
@c:\test.sql
60
Come mostrato nella figura seguente
61
Figura 4-16 Pagina principale di SQL Developer.
Come in SQL*Plus, la prima attività da realizzare è la
configurazione delle connessioni. Posizionandosi sulla scritta
“Connessioni” nell’elenco di sinistra e facendo clic sul tasto “più” di
colore verde immediatamente sopra, si aprirà la finestra di
configurazione rappresentata in Figura 4-17.
In questa finestra bisogna indicare il nome che si intende
assegnare alla connessione, il nome dell’utente e la relativa password.
È possibile salvare la password con l’apposita opzione.
Se la connessione che si sta definendo è già presente sul client
nel file tnsnames.ora sarà sufficiente scegliere TNS come tipo di
connessione e selezionare da una lista denominata “Alias Rete” la
connect string desiderata, come nell’esempio illustrato.
Se invece non è stata fatta una configurazione del tnsnames.ora
si può lasciare “Tipo di connessione” al valore Basic ed indicare i soliti
parametri: nome o indirizzo del server, porta del listener, nome del
servizio di database.
SQL Developer consente anche semplicemente di accedere ad
un database Microsoft Access. Selezionando Access invece di Oracle
sarà possibile indicare il nome del file mdb contenente il database.
62
Nella lista di sinistra compare la nuova connessione e,
cliccandoci sopra, SQL Developer si collega al db e si presenta come
in Figura 4-18. A sinistra, sotto il nome della connessione, compare
l’elenco di tutti i tipi di oggetto che possono essere contenuti nel
database. Ispezionando i vari nodi si possono visualizzare tutti gli
oggetti effettivamente contenuti. Al centro della pagina si apre una
finestra utilizzabile per eseguire un’istruzione SQL. Basta scrivere
l’istruzione, ovviamente senza punto e virgola, e fare clic sulla freccia
verde che si trova subito sopra la finestra. Nell’esempio è stata
eseguita la medesima lettura della data di sistema che era stata
utilizzata anche in SQL*Plus.
Il risultato dell’istruzione è visualizzato in basso.
63
5 Oggetti del DB
64
SELECT * FROM <NOME_TABELLA>
65
Figura 5-1 Lettura del dizionario da SQL*Plus.
In alternativa si può utilizzare SQL Developer (Figura 5-2).
66
Le tabelle del dizionario possono essere classificate in quattro
gruppi:
• Tabelle il cui nome inizia per “USER_” – contengono
informazioni su tutti gli oggetti di proprietà dell’utente che esegue
la query.
• Tabelle il cui nome inizia per “ALL_” – contengono informazioni
su tutti gli oggetti a cui l’utente che esegue la query ha diritto di
accedere. Un utente ha sicuramente diritto di accedere agli oggetti
di sua proprietà, ma anche agli oggetti di proprietà di altri utenti per
cui è stato autorizzato mediante il comando SQL GRANT.
• Tabelle il cui nome inizia per “DBA_” – contengono informazioni
su tutti gli oggetti contenuti nel database, queste tabelle sono
accessibili solo agli utenti “speciali” che hanno privilegi tali da
essere considerati amministratori di database.
• Tabelle il cui nome inizia per “V$” e “GV$” – Si tratta delle
cosiddette “viste dinamiche”. Il contenuto di queste tabelle cambia
continuamente in funzione dello stato del database e non soltanto
degli oggetti che vengono creati e distrutti. Da queste tabelle si
può, per esempio, sapere in un determinato istante quanti utenti
sono collegati al database e cosa stanno facendo oppure quanta
memoria è in uso. Anche queste tabelle sono utilizzabili solo dagli
utenti che hanno privilegi di amministratore.
Le tabelle degli ultimi due gruppi contengono informazioni
essenziali per l’amministrazione del database, quindi vanno oltre gli
obiettivi introduttivi di questo corso. Le tabelle ALL sono praticamente
identiche alle USER a meno del fatto che nelle ALL c’è in più la
colonna OWNER che indica chi è il proprietario dell’oggetto (nella
USER non ce n’è bisogno perché il proprietario è per definizione chi
sta eseguendo la query). Per questi motivi nel seguito saranno
menzionate sempre le tabelle USER.
5.2 Tabelle
Sono le protagoniste indiscusse del database. I concetti che
sono alla base delle tabelle relazionali sono stati già descritti nel
paragrafo 10.1.3. Come visto è possibile visualizzarne la struttura
utilizzando il comando DESC di SQL*Plus oppure navigando
nell’elenco di sinistra di SQL Developer, come in Figura 5-3.
WTO> DESC PROVA
Nome Nullo? Tipo
----------------------------- -------- ----------------
A NUMBER
B NOT NULL VARCHAR2(20)
C DATE
67
Figura 5-3 Caratteristiche principali di una tabella.
Le tecniche utilizzate da Oracle per la memorizzazione dei dati
di una tabella sono stati già discusse, si può aggiungere che in casi del
tutto particolari i dati di una tabella possono risiedere al di fuori del
database (External Table) oppure essere suddivisi in sottotabelle
(Partizioni) per velocizzare l’accesso ai dati. Questi tipi particolari di
tabelle si comportano, dal punto di vista dell’utente, in maniera del
tutto analogo alle tabelle standard.
68
5.2.2 Constraint
Un constraint è una regola che devono verificare i dati per poter
essere contenuti in tabella. Il primo tipo di constraint definito
implicitamente che è stato già incontrato è la conformità al tipo di dato.
Quando, in fase di creazione o di modifica della tabella, si indica che
una colonna è di tipo numerico si sta implicitamente richiedendo ad
Oracle di controllare i dati che saranno inseriti e rigettare tutto ciò che
non è numerico.
Anche l’obbligatorietà di una colonna è un constraint. Definire
una colonna “NOT NULL” equivale a definire un vincolo che Oracle
dovrà verificare ogni qual volta i dati saranno inseriti o modificati.
Altri constraint che possono essere definiti sono quelli che
vincolano Oracle ad attenersi ai principi di integrità referenziale come
definiti al paragrafo 10.1.2:
• Primary Key – Chiave primaria. È un constraint che consente
all’utente di definire che una colonna, oppure un insieme di
colonne, funge da chiave primaria della tabella. Conseguenza
dell’esistenza di questo constraint sarà che
1. La colonna o le colonne inserite in chiave primaria diventano
obbligatorie.
2. Non sarà mai possibile inserire due righe in tabella aventi lo
stesso valore, oppure la stessa combinazione di valori, nella
chiave primaria.
• Foreign Key – Chiave esterna. È un constraint che consente
all’utente di definire che una colonna, oppure un insieme di
colonne, è un riferimento alla chiave primaria di un’altra tabella. La
tabella su cui è definita la chiave esterna tabella “figlia” mentre la
tabella su cui si trova la chiave primaria referenziata si dice tabella
“madre”. Conseguenza dell’esistenza di questo constraint sarà
che:
1. La colonna o le colonne inserite in chiave esterna nella tabella
figlia possono assumere solo valori già contenuti nella chiave
primaria della tabella madre.
2. Non sarà mai possibile cancellare una riga dalla tabella madre
se ci sono righe nella tabella figlia che contengono quei valori.
Per maggiore chiarezza si faccia riferimento agli esempi del
paragrafo 10.1.2.
In alcuni casi oltre alla primary key della tabella può succedere
che altre colonne, o combinazioni di colonne, non debbano poter
69
assumere gli stessi valori in righe differenti. Si pensi come esempio al
codice fiscale nelle tabelle di anagrafe dei clienti. Il codice fiscale non
può quasi mai essere utilizzato come chiave primaria perché spesso
non viene fornito o viene fornito in un secondo momento dai clienti e
dunque raramente si può considerare un campo obbligatorio. Quando
viene fornito, però, è normalmente un dato univoco. Non ci possono
essere due clienti in anagrafe che hanno lo stesso codice fiscale,
altrimenti c’è un errore oppure si tratta della stessa persona. Per
queste situazioni esiste il constraint unique key, chiave univoca.
Un altro tipo di vincolo molto utilizzato è il check constraint che
consente di fissare una regola che i dati di una riga devono verificare
in fase di inserimento o aggiornamento. Ad esempio si può verificare
che un numero o una data siano compresi in un dato intervallo oppure
che una colonna assuma solo i valori “S” oppure “N”. Il controllo può
coinvolgere anche più colonne della stessa riga, ad esempio se si sta
definendo la tabella dei prestiti di una biblioteca si può imporre che la
data restituzione sia maggiore o uguale della data del prestito.
Tutti i constraint possono essere temporaneamente disabilitati.
La loro definizione, in questo caso, resta associata alla tabella ma essi
non vengono più controllati. Alla riabilitazione del constraint Oracle
ricontrolla i dati per verificare se questi violano il constraint da abilitare,
in tal caso viene sollevato un errore.
5.3 Indici
Uno dei maggiori problemi nell’utilizzo dei database è
l’ottimizzazione dei tempi di risposta delle ricerche. Frequentemente le
tabelle contengono milioni di righe e spesso vengono lette insieme a
molte altre tabelle in istruzioni SQL molto complesse.
L’ottimizzazione di un database Oracle è un’attività di elevata
complessità che richiede competenze di tipo amministrativo e
sicuramente gli si potrebbe dedicare un manuale a se stante. In questo
corso il tema sarà approcciato solo da un particolare punto di vista,
quello dell’utente di database che scrive una query. Volendosi
concentrare solo sull’aspetto “utente” dell’ottimizzazione il tema
fondamentale da trattare è sicuramente quello degli indici: strutture di
dati che consentono l’individuazione veloce di un valore specifico in
una colonna di database.
Si ipotizzi dunque di avere la solita tabella CLIENTI contenente
il codice del cliente, il cognome, il nome e tutti gli altri dati anagrafici. Si
ipotizzi poi che la tabella contenga un milione di righe.
I record della tabella non sono registrati nel database in nessun
ordine specifico, se eseguiamo quindi la ricerca dei clienti il cui
cognome è “ROSSI” Oracle sarà costretto a scorrere un milione di
70
righe, scartare tutti i clienti che non si chiamano Rossi e prendere gli
altri. Sarebbe tutto molto più semplice se i record fossero ordinari per
cognome, in quel caso infatti mediante una ricerca dicotomica (
10.6) sarebbe possibile trovare i Rossi in, al più, 21 letture. Oracle
però non può certo archiviare i dati sul disco in uno specifico ordine,
quindi è necessario utilizzare delle strutture dati aggiuntive che
consentano di velocizzare l’accesso.
Da A- a M
Da N- a Z
Da A- a F Da N- a S
Da G- a M Da T- a Z
71
Questo tipo di indice si chiama B-Tree (balanced tree, albero
bilanciato). Se in questo tipo di indice ogni gruppo intermedio puntasse
ad esattamente due gruppi di livello inferiore, come nell’esempio,
sarebbe praticamente equivalente all’utilizzo di una ricerca dicotomica.
Oracle però può decidere quanti gruppi intermedi creare in funzione
dei dati da gestire.
Un indice è ovviamente un segment e può essere definito anche
su insiemi di colonne della stessa tabella. Ovviamente in questo caso
la posizione delle colonne nell’indice fa la differenza visto che la
distribuzione nell’albero avverrà in base al valore della prima colonna e
poi, solo a parità di valore nella prima colonna, su quello della seconda
e su tutte le altre.
Il B-Tree è il tipo di indice predefinito in Oracle ma genera dei
notevoli problemi quando una colonna indicizzata assume pochi valori
distinti. Ipotizzando di indicizzare il campo SESSO, che assume solo
due possibili valori, la ricerca di tutti i clienti cha hanno SESSO=M sarà
più lenta di una ricerca sequenziale. Ipotizzando infatti che la metà dei
clienti sia maschio l’accesso all’indice restituirà cinquecentomila rowid
e per ognuno di esso sarà fatto un accesso al datafile per recuperarne
i dati. In questo scenario la presenza dell’indice B-Tree non solo non
agevola, ma addirittura peggiora drasticamente i tempi di esecuzione
della ricerca.
5.4 IOT
Una tabella index-organized ha tutte le caratteristiche di una
tabella classica a parte il fatto che, internamente, i dati sono archiviati
in una struttura B-Tree basata sulla primary key.
Ovviamente, mentre una tabella classica può essere priva di
primary key, una IOT ne deve essere necessariamente fornita.
72
Dal punto di vista dell’utente non c’è alcuna differenza tra una
tabella di questo tipo ed una classica.
Conviene utilizzate una IOT quando la maggior parte delle
ricerche sulla tabella includono tra i criteri di ricerca una condizione
sulla primary key.
Una index-organized table è, ovviamente, un segment.
73
5.6 Viste
Una tabella è, come visto, un contenitore di dati generalmente
relativi ad una specifica entità del sistema. Ad esempio si potrebbe
definire la tabella dei clienti, quella degli ordini oppure quella delle
fatture. Normalmente accade di dover accedere ai dati di più tabelle
contemporaneamente, voler quindi vedere contemporaneamente i dati
di clienti, ordini e fatture. Ciò può essere effettuato mediante una query
su più tabelle, si tratta di una istruzione SQL complessa del tipo
descritto nel paragrafo 7.5.11 e successivi. Una vista non è altro che
un nome assegnato ad una tale query SQL complessa. Una vista
dunque non contiene dati, ma solo la definizione di una istruzione
SQL. L’utente però interagisce con la vista come se fosse una
qualunque tabella, può vederne la struttura utilizzando il comando
DESC e leggerne tutti i dati con il comando
SELECT * FROM <NOME_VISTA>
74
dalla vista MASCHI. È quindi possibile inserire in una vista una riga di
dati che poi non potrà mai essere letta attraverso quella stessa vista.
Per evitare che si verifichi questa situazione è possibile, in fase
di creazione della vista, attivare il constraint check option.
Quando questo constraint è attivo Oracle impedisce di inserire
un record in una vista se questo non può essere poi letto usando la
stessa vista. Nel nostro esempio tentando di inserire una riga in
FEMMINE con SESSO=M si otterrebbe un errore.
5.8 Sequenze
Le sequenze sono generatori di numeri progressivi univoci.
Sono spesso utilizzate per valorizzare la chiave primaria di una tabella.
75
Una sequenza può partire dal numero uno oppure da un altro
numero a scelta, incrementarsi o decrementarsi di una o più unità ad
ogni utilizzo, avere un valore massimo predefinito oppure no,
riprendere dal numero iniziale quando si esaurisce oppure no.
La sequenza non è un segment.
5.9 Sinonimi
Un sinonimo è un nome alternativo che viene assegnato ad un
oggetto del database. Abbiamo già incontrato alcuni sinonimi, DICT è
sinonimo di SYS.DICTIONARY, CAT è sinonimo di
SYS.USER_CATALOG.
Non c’è un limite al numero di sinonimi che possono essere
assegnati ad un oggetto. Il sinonimo può essere privato, cioè
utilizzabile solo dall’utente che lo definisce, oppure pubblico cioè
utilizzabile da tutti gli utenti del database. DICT e CAT sono sinonimi
pubblici.
Un sinonimo può essere pubblico o privato. Un sinonimo privato
può essere ovviamente utilizzato dall’utente che lo ha creato, gli altri
utenti possono utilizzarlo solo se sono stati autorizzati e devono
anteporre al nome del sinonimo il nome dello schema in cui è stato
definito. Un sinonimo pubblico può essere utilizzato da tutti gli utenti
del database senza anteporre mai il nome di uno scema. Di fatto il
sinonimo pubblico appartiene ad uno schema speciale, pubblico
appunto, i cui oggetti sono accessibili a chiunque utilizzandone solo il
nome.
I sinonimi non sono segment.
76
standard. Programmi di diverso tipo scritti in PL/SQL possono essere
salvati nel DB al fine di essere richiamati quando serve. Accenniamo
qui brevemente a questi oggetti rimandando al capitolo dedicato al
PL/SQL per gli approfondimenti.
5.11.1 Procedure
Una procedura PL/SQL è un programma registrato nel DB che
accetta un determinato numero di parametri di input/output ed esegue
un’attività senza tornare uno specifico valore al chiamante. Una
procedura non può essere utilizzata in un comando SQL, ma solo in
un altro PL/SQL.
5.11.2 Funzioni
Una funzione PL/SQL è un programma registrato nel DB che
accetta un determinato numero di parametri di input/output ed esegue
un’attività tornando uno specifico valore al chiamante. Le funzioni
possono essere utilizzate anche all’interno dei comandi SQL a patto di
non modificare nulla nel database.
5.11.3 Package
Un Package PL/SQL è un insieme di procedure, funzioni e
variabili PL/SQL che vengono riunite in un unico oggetto.
L’organizzazione degli oggetti PL/SQL in package garantisce vari
vantaggi che saranno approfonditi nel capitolo dedicato al PL/SQL.
5.11.4 Trigger
Un Trigger è un programma PL/SQL registrato nel DB ed
associato ad un evento. Il trigger non può essere richiamato per
l’esecuzione esplicitamente ma scatta automaticamente quando si
verifica l’evento a cui è associato. Ad esempio è possibile creare un
trigger BEFORE INSERT sulla tabella CLIENTI che scatta
automaticamente subito prima dell’inserimento di qualunque record in
tabella.
77
internet). Le estensioni OO hanno dunque perso molta visibilità ed
hanno finito per essere complessivamente poco utilizzate. Sono state
comunque conservate per compatibilità con il passato in tutte le
versioni successive di Oracle. In questo corso non saranno
approfondite.
5.13 XML DB
Con la seconda release della versione 9 del DB Oracle ha
introdotto un insieme di nuove funzionalità che consentono di
conservare nel db, e manipolare molto più semplicemente del passato,
contenuti in formato XML. Tali funzionalità sono accorpate sotto il
nome di Oracle XML DB. Queste caratteristiche sono state nelle
versioni successive ulteriormente implementate raggiungendo un
buono stato di maturità e completezza. In questo corso è stato
riservato all’argomento il capitolo 9. In quel capitolo non ci si propone
di trattare il tema in modo organico, tale trattazione richiederebbe
infatti troppo spazio ed è sicuramente al di là degli obiettivi di questo
corso. L’obiettivo del capitolo 9, invece, è di fornire alcuni spunti di
riflessione relativi ad alcune specifiche tematiche che accendano
l’interesse del lettore sull’argomento.
78
6 CASE STUDY
6.1 Progettazione
Un’applicazione software è a tutti gli effetti un prodotto
ingegneristico. Nessuna persona sana di mente penserebbe di poter
costruire un ponte o un edificio senza prima averne fatto realizzare ad
un professionista competente un progetto completo. Nel mondo del
software, invece, capita spesso di cominciare a produrre, cioè a
“scrivere il codice”, senza avere prima condotto una fase di
progettazione completa e rigorosa oppure avendo affidato la fase di
progettazione a personale non sufficientemente qualificato. Questa è
indubbiamente la causa della maggior parte dei fallimenti di progetti
informatici.
La progettazione di un’applicazione software, dunque, è
un’attività molto delicata che rientra nel campo di studio di una scienza
rigorosa detta “ingegneria del software”.
Uno dei rami dell’ingegneria del software è quello che definisce
le tecniche di progettazione dei database relazionali. Un
approfondimento sulla progettazione dei database relazionali sarebbe
fuori contesto in questo manuale. Un veloce accenno alla materia si
può trovare nel capitolo dei prerequisiti( 10.2).
Lo schema tabellare descritto nel seguito è in terza forma
normale.
79
6.2 Il database d’esempio
Il piccolo database utilizzato per gli esempi di questo manuale è
pensato per contenere informazioni relative alla gestione di ordini e
fatture. Sono previste tabelle per contenere i dati anagrafici dei clienti,
gli ordini che questi dispongono e le fatture che vengono emesse. Le
tabelle del database sono descritte nel prossimo paragrafo, è possibile
scaricare gli script completi per la creazione ed il popolamento delle
tabelle e degli altri oggetti di database all’indirizzo web
http://oracleitalia.wordpress.com/WelcomeToOracle. Alla stessa
pagina sono anche disponibili le informazioni sulle modalità di
esecuzione degli script.
L’utente WTO_ESEMPIO che viene creato dallo script, nel cui
schema sono definiti tutti gli oggetti che seguono, è stato munito dei
ruoli e privilegi minimi al fine di poter eseguire tutti gli esempi presenti
nel corso. In particolare gli sono stati attribuiti i ruoli CONNECT e
RESOURCE ed i privilegi di sistema CREATE VIEW, CREATE
MATERIALIZED VIEW, CREATE SYNONYM, CREATE DATABASE
LINK, CREATE PUBLIC SYNONYM.
6.2.1 Tabelle
Per ogni tabella prevista è fornito l’elenco delle colonne. Per
ogni colonna viene indicato il nome, il tipo di dato, se la colonna è
obbligatoria, se fa parte della chiave primaria, se fa parte di una chiave
univoca, se fa parte di una chiave esterna ed eventuali note. Il tipo di
dato è espresso utilizzando i tipi Oracle descritti all’inizio del prossimo
capitolo.
CLIENTI
Nome Colonna Tipo Obbl. PK UK FK Note
COD_CLIENTE NUMBER(8) SI SI I valori di questa
colonna sono
generati
mediante la
sequenza
SEQ_CLIENTI
NOME VARCHAR2(30) SI
COGNOME VARCHAR2(30) SI
COD_FISC CHAR(16) SI
INDIRIZZO VARCHAR2(30) SI
CAP CHAR(5) SI
COMUNE CHAR(4) SI SI Referenzia la
tabella COMUNI
80
COMUNI
PRODOTTI
ORDINI
PRODOTTI_ORDINATI
81
FATTURE
Nome Colonna Tipo Obbl. PK UK FK Note
NUM_FATTURA NUMBER(8) SI SI I valori di questa
colonna sono generati
mediante la sequenza
SEQ_FATTURE
NUM_ORDINE NUMBER(8) SI SI Referenzia la tabella
ORDINI
DATA_FATTURA DATE SI
IMPORTO NUMBER(7,2) SI
PAGATA CHAR(1) SI Valori ammessi ‘S’, ‘N’
6.2.2 Dati
Una volta lanciati i due script sarà creato uno schema
denominato WTO_ESEMPIO contenente tutte le tabelle sopra
descritte ed i seguenti dati:
WTO >select * from clienti;
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
G811 POMEZIA RM
M297 FIUMICINO RM
F205 MILANO MI
G686 PIOLTELLO MI
E415 LAINATE MI
F839 NAPOLI NA
G964 POZZUOLI NA
G902 PORTICI NA
82
WTO >select * from ordini
6.3 SCOTT/TIGER
Il database Oracle fornisce per default alcuni schemi d’esempio
muniti di alcune tabelle già popolate. Questi schemi sono utili per i
corsi di formazione. Il più famoso di questi schemi è senz’altro SCOTT
(password tiger), presente fin dalle primissime versioni di Oracle.
L’utente SCOTT inizialmente ha l’account bloccato. Tentando di
connettersi a questo schema da SQL*Plus si otterrà un errore.
WTO> conn scott/tiger
ERROR:
ORA-28000: the account is locked
Utente modificato.
83
Cambio password per scott
Nuova password: *****
Ridigitare la nuova password: *****
Password cambiata
Connesso.
TABLE_NAME TABLE_TYPE
------------------------------ -----------
BONUS TABLE
DEPT TABLE
EMP TABLE
SALGRADE TABLE
14 rows selected.
84
Le tabelle di SCOTT saranno di tanto in tanto utilizzate negli
esempi di questo manuale.
85
7 SQL
86
modifiche apportate ai dati o annullarle completamente o
parzialmente. Talvolta queste istruzioni sono considerate parte
delle istruzioni DML.
L’istruzione SELECT, che consente di leggere i dati dal
database ma non di modificarli, viene normalmente fatta rientrare nel
linguaggio DML sebbene forse sarebbe più corretto considerarla in un
insieme a se stante.
Nello spirito “introduttivo” di questo corso non si analizzeranno i
comandi SQL nel dettaglio di tutte le opzioni. Ci si limiterà agli aspetti
principali utilizzati nella maggior parte dei casi. Per un’analisi
dettagliata dei comandi con riferimento a tutte le opzioni si rimanda
alle 1508 pagine del manuale “SQL Language Reference” scaricabile
liberamente, come tutta la documentazione del db, dal sito
tahiti.oracle.com.
Negli esempi capiterà talvolta di utilizzare comandi che ancora
non sono stati descritti. Si è ricorso a questa soluzione quando si
voleva subito spiegare un concetto con un esempio senza rinviare ad
un momento successivo. In tutti questi casi i comandi utilizzati “in
anticipo” sono molto intuitivi e comunque commentati.
Tutti i comandi SQL sono case-insensitive, dunque possono
essere scritti indifferentemente in maiuscolo, in minuscolo o con una
qualunque mescolanza di caratteri minuscoli e maiuscoli. Le uniche
parti delle istruzioni che debbono essere considerate case-sensitive
sono quelle che appaiono racchiuse da apici singoli o doppi, ad
esempio le due seguenti
‘Contenuto Case-Sensitive’
“Contenuto Case-Sensitive”
87
I dati si dividono essenzialmente in quattro macro categorie:
• stringhe alfanumeriche, ad esempio nomi e cognomi,
descrizioni, note;
• numeri, ad esempio importi, quantità;
• date ed orari;
• dati binari, ad esempio foto, video, audio.
Anche i tipi di dato forniti da Oracle ricalcano più o meno questa
suddivisione. Li analizzeremo nei prossimi paragrafi.
88
invece si tentasse di inserire più di N caratteri Oracle solleverebbe un
errore.
In Oracle esiste anche il tipo di dati VARCHAR(N). Attualmente
questo tipo si comporta esattamente nello stesso modo del
VARCHAR2(N) ma Oracle ne sconsiglia l’utilizzo perché in future
versioni del DB potrebbe applicare a questo tipo di dato una diversa
logica di confronto tra stringhe rispetto a quella applicata per il tipo
VARCHAR2. Se si definisce un campo di una tabella utilizzando il tipo
VARCHAR Oracle lo converte automaticamente in VARCHAR2.
WTO> create table A (X VARCHAR(3));
Tabella creata.
WTO> DESC A
Nome Nullo? Tipo
----------------------------------------- -------- ------------
X VARCHAR2(3)
89
7.1.2 Numeri
Il tipo di dato principalmente utilizzato in Oracle per la
conservazione dei dati numerici è NUMBER(P,S). P, la precisione, è
un numero che varia da 1 a 38 e rappresenta il numero di cifre
significative complessive, includendo sia la parte intera che la parte
decimale del numero. S, la scala, è un numero che varia da -84 a 127
ed indica la massima cifra frazionaria da gestire. Se S assume un
valore negativo si intende che non si vogliono gestire cifre frazionarie
né le S cifre intere meno significative. Ad esempio un campo definito
NUMBER(5,-2) potrà contenere al massimo cinque cifre significative e
non conterrà né cifre frazionarie né unità né decine. Sarà quindi un
numero sempre arrotondato alle centinaia: 1234500, 400, 98700…
Precisione e scala non devono essere obbligatoriamente
indicate. In particolare scrivere NUMBER(P) equivale a scrivere
NUMBER(P,0) e rappresenta un numero intero di massimo P cifre. Se
non si specificano né P né S si ottiene un numero a virgola mobile (
10.5) (tutti i NUMBER(P,S) visti finora sono a virgola fissa) che
rappresenta il numero avente la massima precisione e scala gestito in
Oracle.
Nella tabella seguente sono elencati alcuni esempi di NUMBER
con i relativi valori positivi minimi e massimi.
90
rappresenta la precisione delle frazioni di secondo da gestire. Un
campo TIMESTAMP(2) contiene ad esempio anno, mese, giorno, ora,
minuti, secondi, decimi e centesimi di secondo.
Nel caso in cui si inserisca una data, o timestamp, priva della
componente orario, questa verrà comunque valorizzata utilizzando
come default la mezzanotte esatta. Sia in un campo DATE che
TIMESTAMP è anche possibile omettere la parte relativa al giorno.
Inserendo solo l’orario, ad esempio “15:10:20”, Oracle inserirà la data
di sistema (oggi) all’orario specificato.
7.2.1 CREATE
Il comando CREATE si utilizza per definire nuovi oggetti di
database. Il comando ovviamente assume una forma diversa per ogni
tipo di oggetto da creare. Per ogni tipo di oggetto preso in
considerazione nel capitolo 5 sarà fornito un esempio e saranno
discusse le possibili varianti.
Quando si crea un oggetto è sempre possibile definire lo
schema in cui si desidera che l’oggetto sia inserito anteponendo il
nome dello schema al nome dell’oggetto e separando i due nomi con
un punto.
91
Ad esempio in fase di creazione di una tabella l’istruzione
comincerà con
Create table <nome schema>.<nome tabella> (
Tabella creata.
Tabella creata.
92
WTO >create table "TEST" (a number);
Tabella creata.
93
con il valore indicato in fase di creazione. L’argomento sarà
approfondito nell’ambito del comando INSERT.
In molti database è possibile indicare che una colonna deve
essere automaticamente popolata con una sequenza numerica. In
Oracle ciò non è possibile. Esistono, come già detto, appositi oggetti di
database che nascono per generare sequenze numeriche, ma queste
sequenze non possono essere associate esplicitamente in fase di
creazione della tabella ad una colonna. Tale associazione deve essere
fatta via programma, in PL/SQL o in qualunque altro programma che
possa effettuare operazioni SQL. Ci torneremo quando tratteremo le
sequenze ed i trigger PL/SQL.
Nel comando di creazione della tabella è possibile specificare i
constraint che si intendono definire sui dati. Nell’esempio che segue
viene creata una tabella TEST con le colonne A, B, C, D. La colonna A
è definita come primary key, la colonna B come unique key, la colonna
C come foreign key verso la colonna C di un’altra tabella TEST2, sulla
colonna D viene definito un check constraint: la colonna ammetterà
solo valori maggiori di 3.
WTO >create table test (
2 a number primary key,
3 b number unique,
4 c number references test2(c),
5 d number check (d>3)
6 );
Tabella creata.
94
DESC che chiude l’esempio mostra la struttura della tabella
TEST_COPY.
WTO >create table test (
2 A number not null,
3 B varchar2(30),
4 C date
5 );
Tabella creata.
Tabella creata.
Tabella creata.
95
Creazione di un indice
La forma più semplice dell’istruzione di creazione di un indice è
la seguente:
create index <nome schema>.<nome indice> ON <nome tabella>(
<nome colonna>,
<nome colonna>,
...
<nome colonna>
);
Indice creato.
Indice creato.
Indice creato.
Indice creato.
96
Creazione di una IOT
Una IOT, come già detto, è una ordinaria tabella relazionale che
viene archiviata in memoria sotto forma di indice, cioè in una struttura
di tipo B-Tree. Per creare una IOT si utilizza l’istruzione CREATE
TABLE seguita dalla clausola ORGANIZATION INDEX.
Una IOT non può esistere se sulla tabella non è definita la
primary key. Nell’esempio che segue si cerca dapprima di creare una
IOT senza primary key e poi si modifica l’istruzione aggiungendo la
definizione del constraint.
WTO >create table test_iot (
2 A number not null,
3 B varchar2(30),
4 C date
5 )
6 organization index;
organization index
*
ERRORE alla riga 6:
ORA-25175: no PRIMARY KEY constraint
found
Tabella creata.
97
Nell’esempio seguente viene definito il cluster
COMUNI_PROVINCE avente come chiave di clusterizzazione il codice
della provincia e come spazio riservato ad ogni valore della chiave
(clausola SIZE) due blocchi di database (16384 byte nel caso
specifico):
WTO >create cluster COMUNI_PROVINCE (
2 cod_provincia char(2)
3 ) SIZE 16384;
Creato cluster.
Indice creato.
Tabella creata.
Tabella creata.
Creato cluster.
98
WTO >create table COMUNI (
2 cod_comune char(4) not null primary key,
3 descrizione varchar2(100),
4 cod_provincia char(2)
5 ) cluster COMUNI_PROVINCE (cod_provincia);
Tabella creata.
Tabella creata.
99
A questo punto se un utente esegue il comando
WTO >select *
2 from clienti_roma;
NOME COGNOME
---------- ----------
MARCO ROSSI
GIOVANNI BIANCHI
NOME COGNOME
---------- ----------
MARCO ROSSI
GIOVANNI BIANCHI
Vista creata.
100
WTO >create view clienti_roma as
2 select nome, cognome
3 from clienti
4 where comune='H501';
create view clienti_roma as
*
ERRORE alla riga 1:
ORA-00955: name is already used by an
existing object
Vista creata.
NOME COGNOME
---------- ----------
MARCO ROSSI
GIOVANNI BIANCHI
101
WTO >update clienti
2 set nome='MASSIMO'
3 where cognome='ROSSI';
Aggiornata 1 riga.
NOME COGNOME
---------- ----------
MASSIMO ROSSI
GIOVANNI BIANCHI
NOME COGNOME
---------- ----------
MARCO ROSSI
GIOVANNI BIANCHI
NOME COGNOME
---------- ----------
MASSIMO ROSSI
GIOVANNI BIANCHI
102
Se ad esempio START WITH è pari ad oggi alle 18.30 e NEXT
è pari ad oggi alle 19.00, allora l’aggiornamento avverrà alle 18.30, poi
alle 19.00 e via così ogni mezz’ora.
Se si omette la clausola START WITH il primo aggiornamento
coincide con l’istante di creazione della vista.
Se si omette la clausola NEXT ci sarà un solo aggiornamento.
Se si omette del tutto la clausola REFRESH la vista non sarà
mai aggiornata automaticamente.
Nell’esempio seguente viene creata una vista materializzata che
ha come START WITH l’istante di creazione più un minuto e come
NEXT l’istante di creazione più tre minuti. Di conseguenza la vista
materializzata viene aggiornata dopo un minuto dalla creazione e poi
ogni due minuti.
La sintassi con cui si esprime l’istante di creazione della vista fa
uso della funzione SYSDATE che ritorna l’istante corrente (al
secondo). A questa vengono aggiunti i minuti come frazioni di giorni
1/(24*60) è pari alla 1440esima parte di un giorno, cioè un minuto.
WTO >create materialized view clienti_roma_mat
2 refresh start with sysdate+1/(24*60)
3 next sysdate+3/(24*60)
4 as
5 select nome, cognome
6 from clienti
7 where comune='H501';
NOME COGNOME
---------- ----------
MARCO ROSSI
GIOVANNI BIANCHI
Aggiornata 1 riga.
WTO >commit;
Commit completato.
103
WTO >select * from clienti_roma_mat;
NOME COGNOME
---------- ----------
MARCO ROSSI
GIOVANNI BIANCHI
NOME COGNOME
---------- ----------
MASSIMO ROSSI
GIOVANNI BIANCHI
Aggiornata 1 riga.
NOME COGNOME
---------- ----------
MASSIMO ROSSI
GIOVANNI BIANCHI
Commit completato.
NOME COGNOME
---------- ----------
MASS ROSSI
GIOVANNI BIANCHI
104
Dunque le viste materializzate acquisiscono solo le modifiche
confermate con un comando di COMMIT. Di questo comando
parleremo diffusamente nella sezione dedicata ai comandi TCL.
Creazione di una sequenza
Una sequenza è un oggetto che fornisce un progressivo
numerico ad ogni chiamata. È usata normalmente per creare
automaticamente i valori della chiave in tabelle che non hanno una
chiave primaria naturale. Ad esempio il codice cliente potrebbe essere
una colonna di questo tipo.
Il modo più semplice per creare la sequenza è
CREATE SEQUENCE <nome schema>.<nome sequenza>;
Sequenza creata.
NEXTVAL
----------
1
NEXTVAL
----------
2
NEXTVAL
----------
3
CURRVAL
----------
3
105
NEXTVAL. Aprendo una nuova sessione di SQL*Plus, ed utilizzando
lo stesso comando si ottiene:
WTO >select seq_test.currval from dual;
select seq_test.currval from dual
*
ERRORE alla riga 1:
ORA-08002: sequence SEQ_TEST.CURRVAL is not yet defined in this
session
Sequenza creata.
NEXTVAL
----------
2
NEXTVAL
----------
4
NEXTVAL
----------
6
--ALTRE CHIAMATE PER PORTARE LA SEQUENZA A 46 SONO STATE OMESSE
WTO >select seq_test_pari.nextval from dual
NEXTVAL
----------
48
WTO >;
NEXTVAL
----------
50
106
WTO >select seq_test_pari.nextval from dual;
NEXTVAL
----------
1
NEXTVAL
----------
3
Sequenza creata.
NEXTVAL
----------
2
NEXTVAL
----------
4
-- DOPO ALTRE CHIAMATE:
WTO > select seq_test_pari.nextval from dual
NEXTVAL
----------
48
NEXTVAL
----------
50
NEXTVAL
----------
2
107
NEXTVAL
----------
4
Sequenza creata.
NEXTVAL
----------
30
NEXTVAL
----------
29
NEXTVAL
----------
28
NEXTVAL
----------
1
NEXTVAL
----------
0
NEXTVAL
----------
-1
NEXTVAL
----------
-2
108
Creazione di un sinonimo
Un sinonimo è solo un nome alternativo che scegliamo di
utilizzare per un oggetto del db. La sintassi è banale
CREATE SYNONYM <nome schema>.<nome sinonimo>
FOR <nome schema>.<nome oggetto>;
Sinonimo creato.
WTO >desc c
Nome Nullo? Tipo
----------------------- -------- ---------------
COD_CLIENTE NOT NULL NUMBER(8)
NOME NOT NULL VARCHAR2(30)
COGNOME NOT NULL VARCHAR2(30)
COD_FISC CHAR(16)
INDIRIZZO NOT NULL VARCHAR2(30)
CAP NOT NULL CHAR(5)
COMUNE NOT NULL CHAR(4)
Sinonimo creato.
WTO >desc c2
Nome Nullo? Tipo
----------------- -------- ------------
COD_CLIENTE NOT NULL NUMBER(8)
NOME NOT NULL VARCHAR2(30)
COGNOME NOT NULL VARCHAR2(30)
COD_FISC CHAR(16)
INDIRIZZO NOT NULL VARCHAR2(30)
109
CAP NOT NULL CHAR(5)
COMUNE NOT NULL CHAR(4)
WTO >desc c2
Nome Nullo? Tipo
----------------- -------- ------------
COD_CLIENTE NOT NULL NUMBER(8)
NOME NOT NULL VARCHAR2(30)
COGNOME NOT NULL VARCHAR2(30)
COD_FISC CHAR(16)
INDIRIZZO NOT NULL VARCHAR2(30)
CAP NOT NULL CHAR(5)
COMUNE NOT NULL CHAR(4)
110
(mediante la stringa di connessione altro_db) sia utente e password da
usare per la connessione.
Per leggere la tabella DEPT che si trova sullo schema SCOTT
del database remoto dobbiamo solo aggiungere @scott_remoto dopo
il nome della tabella:
WTO >select * from dept@scott_remoto;
7.2.2 ALTER
Il comando ALTER consente di modificare alcuni dei parametri
degli oggetti già creati senza modificarne il contenuto. Nel seguito
sono discusse ed esemplificate alcune delle attività per cui, più
comunemente, si ricorre al comando ALTER.
Aggiungere, modificare, rinominare, eliminare le colonne di
una tabella
Per aggiungere colonne ad una tabella si utilizza il comando
ALTER TABLE <nome schema>.<nome tabella> ADD (
<nome colonna> <tipo colonna> <null/not null>,
<nome colonna> <tipo colonna> <null/not null>,
...
<nome colonna> <tipo colonna> <null/not null>
);
Tabella creata.
111
WTO >desc test
Nome Nullo? Tipo
----------------- -------- ------------
A NOT NULL NUMBER
B VARCHAR2(30)
C DATE
Tabella modificata.
Tabella modificata.
112
vuota oppure tutti i record presenti in tabella non hanno valorizzata la
colonna che si intende modificare.
Se si modifica solo la dimensione massima del campo senza
cambiarne il tipo, ad esempio trasformando un VARCHAR2(30) in
VARCHAR2(10) o viceversa, si ottiene errore solo quando la
dimensione viene diminuita ed esistono nella colonna che si sta
modificando dati che eccedono la nuova lunghezza. Ad esempio
ipotizziamo che nella colonna B della tabella TEST la stringa più lunga
presente sia lunga due caratteri. Possiamo tranquillamente modificare
la colonna di VARCHAR2(30) a VARCHAR2(10), ma se cerchiamo di
modificare la colonna a VARCHAR2(1) otteniamo un errore.
WTO >alter table test modify b varchar2(10);
Tabella modificata.
Ad esempio:
WTO >desc test
Nome Nullo? Tipo
----------------- -------- ------------
A NOT NULL NUMBER
B VARCHAR2(10)
C NOT NULL NUMBER
D NUMBER
E DATE
Tabella modificata.
113
È possibile eliminare una colonna mediante la clausola DROP:
ALTER TABLE <nome schema>.<nome tabella> DROP COLUMN <nome
colonna>
Ad esempio:
WTO >alter table test drop column d;
Tabella modificata.
Tabella modificata.
Tabella modificata.
114
prima della modifica, ed il comando INSERT…SELECT, per
ripristinare i dati dopo la modifica.
--IL COMANDO SEGUENTE CREA UNA NUOVA TABELLA IDENTICA A TEST
WTO >create table test_pre_modifica as select * from test;
Tabella creata.
Tabella modificata.
Tabella modificata.
Tabella troncata.
Creata 1 riga.
115
dove il tipo constraint può essere PRIMARY KEY, FOREIGN
KEY, UNIQUE oppure CHECK e la specifica constraint dipende dal
tipo come segue:
Tipo constraint Specifica constraint
(
PRIMARY KEY <nome colonna>,
<nome colonna>,
...
<nome colonna>
)
(
FOREIGN KEY <nome colonna>,
<nome colonna>,
...
<nome colonna>
) REFERENCES <nome tabella> (
<nome colonna>,
<nome colonna>,
...
<nome colonna>
)
(
UNIQUE <nome colonna>,
<nome colonna>,
...
<nome colonna>
)
(<regola>)
CHECK
Quattro esempi per chiarire:
Aggiunta della PRIMARY KEY
WTO >ALTER TABLE TEST ADD
2 CONSTRAINT TEST_PK PRIMARY KEY (NUOVA_A);
Tabella modificata.
Tabella creata.
Tabella modificata.
116
WTO >alter table test add constraint test_uk
2 unique (x);
Tabella modificata.
Tabella modificata.
Tabella creata.
Tabella modificata.
Creata 1 riga.
117
--DISABILITAZIONE DELLA PRIMARY KEY
WTO >alter table test_dup modify constraint x_pk disable;
Tabella modificata.
Creata 1 riga.
X
----------
1
1
Eliminata 1 riga.
X
----------
1
Tabella modificata.
Ad esempio
WTO >alter table TEST_DUP rename constraint X_PK to NUOVO_X_PK;
Tabella modificata.
118
Per eliminare un constraint basta utilizzare il comando
ALTER TABLE <nome schema>.<nome tabella>
DROP CONSTRAINT <nome constraint>
Ad esempio
WTO >alter table TEST_DUP drop constraint NUOVO_X_PK;
Tabella modificata.
Creato cluster.
Creato cluster.
Modificato cluster.
Modificare un indice
Nessuno dei parametri degli indici analizzati in precedenza può
essere modificato con l’ALTER INDEX. Il comando seguente può
essere utilizzato per rinominare l’indice.
WTO >create index test_idx on test (c,x);
Indice creato.
Modificato indice.
119
Modificare una IOT
Una IOT è una normale tabella ed in quanto tale le si applicano
tutte le opzioni di ALTER già viste sopra. L’organizzazione di una
tabella non può essere modificata con il comando ALTER. Non è
possibile passare da una tabella classica ad una IOT o viceversa con
questo comando. Per realizzare tale attività si può utilizzare il package
di sistema DBMS_REDEFINITION. Si tratta di un’attività di livello
avanzato che non sarà approfondita in questo corso.
Modificare una vista
Nessuno dei parametri delle viste analizzati in precedenza può
essere modificato con il comando ALTER VIEW.
Per ridefinire una vista si utilizza la parola chiave OR REPLACE
dopo la CREATE nel comando di creazione come già visto sopra.
Modificare una vista materializzata
La clausola REFRESH può essere modificata a piacere per
cambiare la configurazione dell’aggiornamento automatico. Ad
esempio l’istruzione seguente imposta l’aggiornamento automatico in
modo che sia eseguito ogni giorno a mezzanotte.
WTO >alter materialized view clienti_roma_mat
2 refresh start with trunc(sysdate+1)
3 next trunc(sysdate+2);
Modificare un sinonimo
Non esiste il comando ALTER SYNONYM, si utilizza la clausola
OR REPLACE dopo la CREATE.
Modificare una sequenza
Il comando ALTER SEQUENCE (senza parole chiave
aggiuntive) consente di modificare tutti i parametri già descritti in fase
di creazione delle sequenze ad eccezione del valore di partenza, che
era stato definito mediante la clausola START WITH. Ad esempio
nell’istruzione che segue si modifica la sequenza SEQ_TEST,
originariamente impostata per fornire un progressivo numerico
crescente senza buchi, per ottenere un progressivo di numeri che si
incrementi di tre unità ad ogni chiamata.
WTO >select seq_test.nextval from dual;
NEXTVAL
----------
5
120
WTO >select seq_test.nextval from dual;
NEXTVAL
----------
6
Sequenza modificata.
NEXTVAL
----------
9
7.2.3 RENAME
Il comando RANAME consente di rinominare un oggetto di
database. Questo comando si applica solo a tabelle, viste, sequenze e
sinonimi privati. La sintassi è banale.
RENAME <nome schema>.<vecchio nome> to <nome schema>.<nuovo nome>
Tabella rinominata.
--UNA VISTA
WTO >rename clienti_roma to new_clienti_roma;
Tabella rinominata.
--UNA SEQUENZA
WTO >rename seq_test to seq_test_new;
Tabella rinominata.
Tabella rinominata.
121
Il messaggio di feedback è fuorviante perché parla sempre di
“tabella rinominata” anche quando l’oggetto rinominato non è una
tabella.
7.2.4 DROP
Il comando generico per eliminare un oggetto di database è il
seguente:
DROP <tipo oggetto> <nome schema>.<nome oggetto>
Tabella eliminata.
--UN INDICE
WTO >drop index TEST_IDX;
Indice eliminato.
--UNA VISTA
WTO >drop view CLIENTI_ROMA;
Vista eliminata.
Eliminato cluster.
--UNA IOT
WTO >drop table TEST_IOT;
Tabella eliminata.
Sinonimo eliminato.
Sinonimo eliminato.
122
In aggiunta a questo comando generale, valido per tutti gli
oggetti del database, si possono utilizzare alcune clausole specifiche
per alcuni tipi di oggetto.
Eliminazione di una tabella referenziata in una foreign key
Nel caso di eliminazione di una tabella, può succedere che essa
sia referenziata da un constraint di foreign key definito su un’altra
tabella. Negli esempi precedenti si era definita, ad esempio, una
foreign key tra la tabella TEST e la tabella TEST2.
In questa situazione non è possibile eliminare la tabella TEST2
con il comando standard.
WTO >drop table test2;
drop table test2
*
ERRORE alla riga 1:
ORA-02449: unique/primary keys in table referenced by foreign
keys
Tabella eliminata.
Tabella modificata.
Tabella eliminata.
123
Per superare quest’ostacolo è possibile utilizzare la clausola
INCLUDING TABLES
WTO >drop cluster COMUNI_PROVINCE including tables;
Eliminato cluster.
Tabella eliminata.
Tabella eliminata.
Eliminato cluster.
7.2.5 TRUNCATE
Il comando TRUNCATE consente di eliminare in modo rapido
l’intero contenuto di una tabella o di un cluster di tabelle senza
modificarne la struttura. Il comando non è annullabile ma è
automaticamente confermato. Il comando TRUNCATE è generalmente
più rapido del comando DML DELETE che sarà illustrato più avanti.
Troncamento di una tabella
L’istruzione di troncamento di una tabella è
TRUNCATE TABLE <nome schema>.<nome tabella>
Ad esempio:
WTO >truncate table test;
Tabella troncata.
124
Creato cluster.
Eliminato cluster.
Creato cluster.
Cluster troncato.
7.2.6 PURGE
Quando si elimina un oggetto segment Oracle lo conserva nel
cestino, come quando si cancella un file in windows.
L’oggetto viene rinominato con un nome di sistema e
conservato.
L’effetto delle cancellazioni eseguite negli esempi precedenti è il
seguente:
WTO >select * from cat;
TABLE_NAME TABLE_TYPE
------------------------------ -----------
TEST_COPY TABLE
COMUNI TABLE
SEQ_TEST_PARI SEQUENCE
X SEQUENCE
SEQ_DESC SEQUENCE
CLIENTI TABLE
SEQ_CLIENTI SEQUENCE
PRODOTTI TABLE
ORDINI TABLE
SEQ_ORDINI SEQUENCE
PRODOTTI_ORDINATI TABLE
FATTURE TABLE
SEQ_FATTURE SEQUENCE
TEST_PRE_MODIFICA TABLE
TEST_DUP TABLE
123TesTVirgo@à++ TABLE
TEST TABLE
125
NEW_CLIENTI_ROMA VIEW
SEQ_TEST_NEW SEQUENCE
BIN$HzDBX3YoS3m9RX5c+xg2/w==$0 TABLE
BIN$H30bMxKuQput74HcB1GXcA==$0 TABLE
BIN$d8Hp3XYlS1G0xuBp0rYIhA==$0 TABLE
BIN$Sw7y9bgbQLSkwfEM+SLAnA==$0 TABLE
Selezionate 23 righe.
Svuotata tabella.
Svuotato indice.
126
WTO >purge recyclebin;
Svuotato cestino.
Tabella creata.
--INSERIMENTO DI UN RECORD
WTO >insert into TEST_FLASHBACK values (123);
Creata 1 riga.
A
----------
123
Tabella eliminata.
Completato flashback.
127
--VISUALIZZAZIONE DEI DATI IN ESSA CONTENUTI
WTO >select * from TEST_FLASHBACK;
A
----------
123
128
possibile arrivare ai secondi ed anche scendere fino al dettaglio delle
frazioni di secondo
TIMESTAMP'2011-01-28 23:59:59'
TIMESTAMP'2011-01-28 23:59:59.999'
7.3.2 INSERT
Il comando INSERT consente di inserire record in una tabella. Il
comando può essere utilizzato nella forma più semplice seguente:
INSERT INTO <nome schema>.<nome tabella>
VALUES (<valore>, <valore>, ..., <valore>);
Tabella eliminata.
Tabella creata.
Creata 1 riga.
A B C
---------- --------- ------------------------------
123,4 13-GEN-11 Stringa d'esempio
129
WTO >insert into test values (123.4, date'2011-01-13');
insert into test values (123.4, date'2011-01-13')
*
ERRORE alla riga 1:
ORA-00947: not enough values
Creata 1 riga.
A B C
---------- --------- ------------------------------
123,4 13-GEN-11 Stringa d'esempio
123,4 13-GEN-11
Creata 1 riga.
A B C
---------- --------- ------------------------------
123,4 13-GEN-11 Stringa d'esempio
123,4 13-GEN-11
0 Test
Creata 1 riga.
A B C
---------- --------- ------------------------------
123,4 13-GEN-11 Stringa d'esempio
123,4 13-GEN-11
0 Test
Test con campi nulli
130
Nell’esempio presedente, sebbene tutte le tre colonne fossero
inserite nella clausola INTO, si è scelto di valorizzare la sola colonna
C.
Inserimento dati e valori di default
Vediamo cosa succede quando una colonna è munita di valore
di default. Definiamo la tabella TEST_DEFAULT avente due colonne,
A è un numero, B è una stringa con valore di default.
WTO >create table test_default (
2 a number,
3 b varchar2(30) default 'Valore di default'
4 );
Tabella creata.
Creata 1 riga.
A B
---------- ------------------------------
1 Test
Creata 1 riga.
A B
---------- ------------------------------
1 Test
2 Valore di default
Creata 1 riga.
131
WTO >select * from test_default;
A B
---------- ------------------------------
1 Test
2 Valore di default
3
Creata 1 riga.
A B
---------- ------------------------------
1 Test
2 Valore di default
3
4 Valore di default
Tabella creata.
Create 4 righe.
132
WTO >select * from test2;
D E F
--------- ---------- ------------------------------
13-GEN-11 123,4 Stringa d'esempio
13-GEN-11 123,4
0 Test
Test con campi nulli
Create 4 righe.
D E F
--------- ---------- ------------------------------
13-GEN-11 123,4 Stringa d'esempio
13-GEN-11 123,4
0 Test
Test con campi nulli
13-GEN-11 123,4 Stringa d'esempio
13-GEN-11 123,4
0 Test
Test con campi nulli
Selezionate 8 righe.
Tabella creata.
133
WTO >insert all
2 into test2(d, e, f)
3 values (b, a, c)
4 into test3(g, h, i)
5 values (a, b, c)
6 select * from test;
Create 8 righe.
D E F
--------- ---------- ------------------------------
13-GEN-11 123,4 Stringa d'esempio
13-GEN-11 123,4
0 Test
Test con campi nulli
13-GEN-11 123,4 Stringa d'esempio
13-GEN-11 123,4
0 Test
Test con campi nulli
13-GEN-11 123,4 Stringa d'esempio
13-GEN-11 123,4
0 Test
Test con campi nulli
Selezionate 12 righe.
G H I
---------- --------- ------------------------------
123,4 13-GEN-11 Stringa d'esempio
123,4 13-GEN-11
0 Test
Test con campi nulli
Eliminate 4 righe.
134
WTO >select * from test;
A B C
---------- --------- ------------------------------
123,4 13-GEN-11 Stringa d'esempio
123,4 13-GEN-11
0 Test
Test con campi nulli
Create 8 righe.
G H I
---------- --------- ------------------------------
124,4 14-GEN-11 Stringa d'esempio
124,4 14-GEN-11
1 Test
Test con campi nulli
125,4 15-GEN-11 Stringa d'esempio
125,4 15-GEN-11
2 Test
Test con campi nulli
Selezionate 8 righe.
135
WTO >create table persone (
2 nome varchar2(30),
3 data_nascita date,
4 sesso char(1)
5 );
Tabella creata.
Creata 1 riga.
Creata 1 riga.
Creata 1 riga.
Creata 1 riga.
NOME DATA_NASC S
---------- --------- -
Massimo 01-NOV-71 M
Laura 09-MAR-00 F
Marco 23-AGO-90 M
Clara 18-APR-95 F
Vista creata.
NOME DATA_NASC S
---------- --------- -
Massimo 01-NOV-71 M
Marco 23-AGO-90 M
Creata 1 riga.
136
WTO >select * from maschi;
NOME DATA_NASC S
---------- --------- -
Massimo 01-NOV-71 M
Marco 23-AGO-90 M
NOME DATA_NASC S
---------- --------- -
Massimo 01-NOV-71 M
Laura 09-MAR-00 F
Marco 23-AGO-90 M
Clara 18-APR-95 F
Veronica 26-FEB-80 F
Vista creata.
Creata 1 riga.
NOME DATA_NASC S
---------- --------- -
Massimo 01-NOV-71 M
Laura 09-MAR-00 F
Marco 23-AGO-90 M
Clara 18-APR-95 F
Veronica 26-FEB-80 F
Vittoria 10-MAG-87 F
Selezionate 6 righe.
137
7.3.3 UPDATE
Il comando UPDATE consente di modificare i dati presenti in
una tabella. La sintassi del comando è la seguente:
UPDATE <nome schema>.<nome tabella>
SET <nome colonna> = <valore>,
<nome colonna> = <valore>,
...
<nome colonna> = <valore>
WHERE <condizione>
;
NOME DATA_NASC S
---------- --------- -
Massimo 01-NOV-71 M
Laura 09-MAR-00 F
Marco 23-AGO-90 M
Clara 18-APR-95 F
Veronica 26-FEB-80 F
Vittoria 10-MAG-87 F
Selezionate 6 righe.
Aggiornate 6 righe.
NOME DATA_NASC S
---------- --------- -
Massimo 12-GEN-00 M
Laura 12-GEN-00 F
Marco 12-GEN-00 M
Clara 12-GEN-00 F
Veronica 12-GEN-00 F
Vittoria 12-GEN-00 F
Selezionate 6 righe.
138
Nell’esempio seguente solo la riga relativa alla persona avente
NOME=’Vittoria’ viene aggiornata.
WTO >update persone
2 set data_nascita = date'1987-05-10'
3 where nome='Vittoria';
Aggiornata 1 riga.
NOME DATA_NASC S
---------- --------- -
Massimo 12-GEN-00 M
Laura 12-GEN-00 F
Marco 12-GEN-00 M
Clara 12-GEN-00 F
Veronica 12-GEN-00 F
Vittoria 10-MAG-87 F
Selezionate 6 righe.
Aggiornate 6 righe.
NOME DATA_NASC S
---------- --------- -
Massimo 13-GEN-00 M
Laura 13-GEN-00 F
Marco 13-GEN-00 M
Clara 13-GEN-00 F
Veronica 13-GEN-00 F
Vittoria 11-MAG-87 F
Selezionate 6 righe.
139
7.3.4 DELETE
Il comando per cancellare righe da una tabella assume la forma
DELETE FROM <nome schema>.<nome tabella>
WHERE <condizione>
;
NOME DATA_NASC S
---------- --------- -
Massimo 13-GEN-00 M
Laura 13-GEN-00 F
Marco 13-GEN-00 M
Clara 13-GEN-00 F
Veronica 13-GEN-00 F
Vittoria 11-MAG-87 F
Selezionate 6 righe.
Eliminate 2 righe.
NOME DATA_NASC S
---------- --------- -
Laura 13-GEN-00 F
Clara 13-GEN-00 F
Veronica 13-GEN-00 F
Vittoria 11-MAG-87 F
Eliminate 4 righe.
140
7.3.5 MERGE
L’istruzione MERGE consente di eseguire in un unico comando
un UPDATE ed un INSERT. È possibile infatti specificare una
condizione e, a seconda del fatto che questa condizione sia vera o
falsa, inserire un nuovo record in tabella oppure aggiornarne uno già
esistente.
Come esempio d’uso si ipotizzi di avere la necessità di dover
aggiornare una tabella periodicamente leggendo i dati da un'altra
tabella.
Si ipotizzi anche che i dati nella tabella sorgente siano completi,
cioè che ci siano sia i record che abbiamo già caricato con inserimenti
precedenti (e magari ora vanno aggiornati) sia nuovi record.
L'approccio più semplice è sicuramente quello di svuotare la
tabella ad ogni aggiornamento e riempirla rileggendo tutti i dati. Non
sempre, però, è l'approccio migliore. Se invece vogliamo inserire i
nuovi record ed aggiornare soltanto quelli già presenti possiamo
utilizzare l'istruzione MERGE, che in pratica racchiude la possibilità di
eseguire, a seconda dei record, un INSERT oppure un UPDATE.
Vediamo un esempio. Nello schema SCOTT ipotizziamo di
dover aggiornare la lista dei dipendenti (una nuova tabella MYEMP
strutturalmente identica alla EMP) leggendo la tabella EMP.
WTO> select * from emp;
Selezionate 14 righe.
141
7782 CLARK MANAGER 7839 09-GIU-81 1000 10
7788 SCOTT ANALYST 7566 09-DIC-82 3000 20
7839 KING PRESIDENT 17-NOV-81 5000 10
7844 TURNER SALESMAN 7698 08-SET-81 1500 0 30
7876 ADAMS CLERK 7788 12-GEN-83 1100 20
7900 JAMES CLERK 7698 03-DIC-81 950 30
7902 FORD ANALYST 7566 03-DIC-81 3000 20
7934 MILLER CLERK 7782 23-GEN-82 1300 10
Selezionate 11 righe.
14 di righe unite.
Selezionate 14 righe.
142
• MERGE INTO MYEMP
indica la tabella da aggiornare
• USING EMP
indica la tabella da cui leggere.
• ON (emp.empno=myemp.empno)
è la condizione da controllare, se questa è vera si effettuerà un
UPDATE, altrimenti un INSERT.
• WHEN MATCHED THEN
quando la condizione di cui sopra è vera...
• UPDATE SET etc...
esegui questo UPDATE.
• WHEN NOT MATCHED THEN
altrimenti...
• INSERT (empno, ename, job, mgr, hiredate, sal, comm, deptno)
etc..
esegui questo INSERT.
C'è qualche clausola della MERGE che non è stata utilizzata
nell'esempio.
All'UPDATE può essere aggiunta una clausola WHERE per
aggiornare solo determinati record. Se, per esempio, vogliamo
aggiornare il record solo se lo stipendio nuovo è maggiore di quello
vecchio faremo:
MERGE into MYEMP
USING EMP
ON (emp.empno=myemp.empno)
WHEN MATCHED THEN
UPDATE SET
ename = emp.ename,
job = emp.job,
mgr = emp.mgr,
hiredate = emp.hiredate,
sal = emp.sal,
comm = emp.comm,
deptno = emp.deptno
WHERE sal < emp.sal
WHEN NOT MATCHED THEN
INSERT (empno, ename, job, mgr, hiredate, sal, comm, deptno)
VALUES (emp.empno, emp.ename, emp.job, emp.mgr, emp.hiredate,
emp.sal, emp.comm, emp.deptno)
;
143
C'è poi la possibilità di specificare una clausola DELETE
nell'UPDATE.
Serve per cancellare dalla tabella di destinazione i record che,
dopo l'UPDATE, verificano una certa condizione.
Se per esempio vogliamo rifare l'aggiornamento di prima ma
cancellare da MYEMP i dipendenti che, dopo l'aggiornamento, hanno
stipendio minore di 1000 facciamo:
WTO> MERGE into MYEMP
2 USING EMP
3 ON (emp.empno=myemp.empno)
4 WHEN MATCHED THEN
5 UPDATE SET
6 ename = emp.ename,
7 job = emp.job,
8 mgr = emp.mgr,
9 hiredate = emp.hiredate,
10 sal = emp.sal,
11 comm = emp.comm,
12 deptno = emp.deptno
13 DELETE WHERE sal < 1000
14 WHEN NOT MATCHED THEN
15 INSERT (empno, ename, job, mgr, hiredate, sal, comm, deptno)
16 VALUES (emp.empno,emp.ename, emp.job, emp.mgr, emp.hiredate,
17 emp.sal, emp.comm, emp.deptno)
18 ;
14 di righe unite.
Selezionate 13 righe.
144
Attenzione: non è stata cancellata la riga di SMITH che, pur
avendo uno stipendio di 800, non era presente nella tabella MYEMP
prima della MERGE e dunque è entrato con l'INSERT, non con
l'UPDATE. La DELETE si applica solo ai record che sono stati
aggiornati, non a tutti i record della tabella.
Anche la INSERT può avere una clausola WHERE per inserire
solo i record che verificano una certa condizione, ma non può avere
una clausola DELETE.
Ad esempio per inserire in MYEMP solo i dipendenti con
stipendio maggiore di 1000:
WTO> MERGE into MYEMP
2 USING EMP
3 ON (emp.empno=myemp.empno)
4 WHEN MATCHED THEN
5 UPDATE SET
6 ename = emp.ename,
7 job = emp.job,
8 mgr = emp.mgr,
9 hiredate = emp.hiredate,
10 sal = emp.sal,
11 comm = emp.comm,
12 deptno = emp.deptno
13 WHEN NOT MATCHED THEN
14 INSERT (empno, ename, job, mgr, hiredate, sal, comm, deptno)
15 VALUES (emp.empno,emp.ename, emp.job, emp.mgr, emp.hiredate,
16 emp.sal, emp.comm, emp.deptno)
17 WHERE emp.sal>1000
18 ;
13 di righe unite.
Selezionate 13 righe.
145
referenziare colonne della tabella che si sta aggiornando, ma solo
colonne della tabella sorgente.
Table created.
146
WTO> alter table emp2 modify (
2 COMM not null,
3 ENAME varchar2(5));
Table altered.
147
WTO> select length(ename), count(0)
2 from emp
3 group by length(ename);
LENGTH(ENAME) COUNT(0)
------------- ----------
6 3
5 8
4 3
COUNT(*) COUNT(COMM)
---------- -----------
14 4
COUNT(0)
----------
12
148
WTO> desc DBMS_ERRLOG
PROCEDURE CREATE_ERROR_LOG
Argument Name Type In/Out Default?
------------------------------ ----------- ------ --------
DML_TABLE_NAME VARCHAR2 IN
ERR_LOG_TABLE_NAME VARCHAR2 IN DEFAULT
ERR_LOG_TABLE_OWNER VARCHAR2 IN DEFAULT
ERR_LOG_TABLE_SPACE VARCHAR2 IN DEFAULT
SKIP_UNSUPPORTED BOOLEAN IN DEFAULT
149
WTO> insert into emp2
2 select * from emp
3 log errors reject limit 20
4 ;
2 rows created.
COUNT(0)
----------
12
ORA_ERR_MESG$
------------------------------------------------------------
ORA-12899: value too large for column "SCOTT"."EMP2"."ENAME"
ORA-01400: cannot insert NULL into ("SCOTT"."EMP2"."COMM")
ORA-01400: cannot insert NULL into ("SCOTT"."EMP2"."COMM")
ORA-01400: cannot insert NULL into ("SCOTT"."EMP2"."COMM")
ORA-01400: cannot insert NULL into ("SCOTT"."EMP2"."COMM")
ORA-12899: value too large for column "SCOTT"."EMP2"."ENAME"
ORA-01400: cannot insert NULL into ("SCOTT"."EMP2"."COMM")
ORA-01400: cannot insert NULL into ("SCOTT"."EMP2"."COMM")
ORA-01400: cannot insert NULL into ("SCOTT"."EMP2"."COMM")
ORA-12899: value too large for column "SCOTT"."EMP2"."ENAME"
ORA-01400: cannot insert NULL into ("SCOTT"."EMP2"."COMM")
ORA-01400: cannot insert NULL into ("SCOTT"."EMP2"."COMM")
12 rows selected.
150
• ORA_ERR_OPTYP$: il tipo di operazione (insert (I), update (U),
delete (D))
• ORA_ERR_TAG$: un tag che opzionalmente è possibile indicare
nella clausola LOG ERRORS per identificare meglio i record nella
tabella degli errori
Per ogni record rigettato c'è un solo errore, anche su quelli che
avrebbero avuto più di un motivo per essere scartati.
Per valorizzare il campo ORA_ERR_TAG$ si fa come segue:
WTO> insert into emp2
2 select * from emp
3 log errors ('ins2') reject limit 20;
2 rows created.
ORA_ERR_TAG$ COUNT(0)
------------------------------ ----------
12
ins2 12
2 rows created.
ORA_ERR_TAG$ COUNT(0)
------------------------------ ----------
12
2010-02-05 18:54:31 12
ins2 12
151
WTO> select EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO
2 from ERR$_EMP2
3 where ORA_ERR_TAG$ = 'ins2';
12 rows selected.
Selezionate 8 righe.
152
Il comando base può essere complicato con una serie molto
ampia di clausole aggiuntive. Nel seguito saranno illustrate le
principali.
7.4.1 Proiezioni
Partendo dal comando base sopra illustrato, il primo
raffinamento dell’istruzione è la scelta delle colonne da visualizzare.
Tale query, detta anche proiezione della tabella, si realizza
specificando le colonne che si desiderano estrarre, separate da
virgola.
Nell’esempio seguente si estraggono solo le colonne
COGNOME ed INDIRIZZO della tabella CLIENTI.
WTO >select cognome, indirizzo from clienti;
COGNOME INDIRIZZO
-------- ----------------------
ROSSI VIA LAURENTINA, 700
BIANCHI VIA OSTIENSE, 850
VERDI VIA DEL MARE, 8
NERI VIA TORINO, 30
COLOMBO PIAZZA DUOMO, 1
ESPOSITO VIA CARACCIOLO, 100
RUSSO VIA GIULIO CESARE, 119
AMATO VIA NAPOLI, 234
Selezionate 8 righe.
C I
------------------------------ -----------------------------
ROSSI VIA LAURENTINA, 700
BIANCHI VIA OSTIENSE, 850
VERDI VIA DEL MARE, 8
NERI VIA TORINO, 30
COLOMBO PIAZZA DUOMO, 1
ESPOSITO VIA CARACCIOLO, 100
RUSSO VIA GIULIO CESARE, 119
AMATO VIA NAPOLI, 234
Selezionate 8 righe.
153
WTO >select cognome c, indirizzo i
2 from clienti;
C I
------------------------------ -----------------------------
ROSSI VIA LAURENTINA, 700
BIANCHI VIA OSTIENSE, 850
VERDI VIA DEL MARE, 8
NERI VIA TORINO, 30
COLOMBO PIAZZA DUOMO, 1
ESPOSITO VIA CARACCIOLO, 100
RUSSO VIA GIULIO CESARE, 119
AMATO VIA NAPOLI, 234
Selezionate 8 righe.
SYSDATE
---------
02-FEB-11
02-FEB-11
02-FEB-11
02-FEB-11
02-FEB-11
02-FEB-11
02-FEB-11
02-FEB-11
Selezionate 8 righe.
3*5
----------
15
15
15
15
15
15
15
15
Selezionate 8 righe.
154
WTO >select 'Una stringa fissa' from clienti;
'UNASTRINGAFISSA'
-----------------
Una stringa fissa
Una stringa fissa
Una stringa fissa
Una stringa fissa
Una stringa fissa
Una stringa fissa
Una stringa fissa
Una stringa fissa
Selezionate 8 righe.
Selezionate 8 righe.
Selezionate 8 righe.
155
Ovviamente, poiché nel comando non si è specificato quanti
record si desiderano estrarre dalla tabella CLIENTI, viene estratta una
riga per ogni record presente in tabella. Se la tabella è vuota non viene
estratto nulla
WTO >delete test;
Eliminate 4 righe.
Sequenza creata.
NEXTVAL
----------
1
2
3
4
5
6
7
8
Selezionate 8 righe.
CURRVAL
----------
8
8
8
8
8
8
8
8
Selezionate 8 righe.
156
Per ovviare a questo problema Oracle mette a disposizione una
tabella d’appoggio: DUAL. La tabella DUAL, creata per default nello
schema SYS ed accessibile da tutti gli utenti, contiene una sola
colonna (DUMMY) ed una sola riga. L’unico dato della tabella è una
stringa ‘X’.
WTO >desc dual
Nome Nullo? Tipo
----------------- -------- ------------
DUMMY VARCHAR2(1)
D
-
X
157
Due pseudo colonne sono state già descritte quando si è parlato
delle sequenze:
• CURRVAL (ritorna il valore corrente della sequenza)
• NEXTVAL (incrementa la sequenza ritornando il nuovo
valore)
Le pseudo colonne
• COLUMN_VALUE
• XMLDATA
Saranno introdotte quando si discuterà delle funzionalità per la
gestione dei dati XML.
La pseudocolonna ROWID indica la collocazione fisica nei
datafile di ciascun record presente nel database.
Si guardi ad esempio la query seguente:
WTO >select cognome, rowid
2 from clienti;
COGNOME ROWID
-------- ------------------
ROSSI AAAlsNAAEAAA7gkAAA
BIANCHI AAAlsNAAEAAA7gkAAB
VERDI AAAlsNAAEAAA7gkAAC
NERI AAAlsNAAEAAA7gkAAD
COLOMBO AAAlsNAAEAAA7gkAAE
ESPOSITO AAAlsNAAEAAA7gkAAF
RUSSO AAAlsNAAEAAA7gkAAG
AMATO AAAlsNAAEAAA7gkAAH
Selezionate 8 righe.
158
che cambia di record in record è ovviamente la posizione nel blocco:
ROSSI è il primo record nel blocco (AAA in base 64 con la codifica
definita equivale a zero in base 10), BIANCHI ha indice uno (AAB) ed
è quindi il secondo record e così via.
Per ogni riga estratta da una query, la pseudocolonna
ROWNUM restituisce un numero che indica l'ordine in cui Oracle
seleziona la riga dalla tabella. La prima riga selezionata ha ROWNUM
uguale a 1, la seconda 2 e così via.
WTO >select cognome, rownum
2 from clienti;
COGNOME ROWNUM
-------- ----------
ROSSI 1
BIANCHI 2
VERDI 3
NERI 4
COLOMBO 5
ESPOSITO 6
RUSSO 7
AMATO 8
Selezionate 8 righe.
7.4.4 Selezioni
Per estrarre solo una parte delle righe presenti in tabella si
utilizza la clausola WHERE.
SELECT <elenco colonne>
FROM <nome schema>.<nome oggetto>
WHERE <condizione>;
159
• Operatori aritmetici
• Operatori logici
• Pseudo colonne
La condizione più semplice assume la forma
<nome colonna> <operatore di confronto> <valore>
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
G811 POMEZIA RM
M297 FIUMICINO RM
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
F205 MILANO MI
G686 PIOLTELLO MI
E415 LAINATE MI
F839 NAPOLI NA
G964 POZZUOLI NA
G902 PORTICI NA
Selezionate 6 righe.
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
F205 MILANO MI
G686 PIOLTELLO MI
E415 LAINATE MI
F839 NAPOLI NA
G964 POZZUOLI NA
G902 PORTICI NA
Selezionate 6 righe.
160
WTO >select * from comuni
2 where provincia^='RM';
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
F205 MILANO MI
G686 PIOLTELLO MI
E415 LAINATE MI
F839 NAPOLI NA
G964 POZZUOLI NA
G902 PORTICI NA
Selezionate 6 righe.
Selezionate 8 righe.
161
Questo vuol dire che in presenza di valori NULL non vale la
regola intuitiva che estraendo i record che verificano la condizione:
<nome colonna> = <valore>
Selezionate 8 righe.
162
WTO >select * from clienti
2 where cod_fisc is not null;
163
WTO >select * from fatture
2 where importo<=500;
164
Per osservare questo comportamento cambiamo
momentaneamente il formato di visualizzazione di default delle date
con il comando
WTO >ALTER SESSION SET NLS_DATE_FORMAT='dd/mm/yyyy hh24:mi:ss';
Modificata sessione.
Aggiornate 5 righe.
165
A questo punto le stesse istruzioni precedenti danno risultati ben
diversi.
WTO >select * from fatture
2 where DATA_FATTURA > date'2010-12-01';
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
G811 POMEZIA RM
G686 PIOLTELLO MI
F839 NAPOLI NA
G964 POZZUOLI NA
G902 PORTICI NA
Selezionate 6 righe.
166
WTO >select * from comuni
2 where des_comune>='MILANO';
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
G811 POMEZIA RM
F205 MILANO MI
G686 PIOLTELLO MI
F839 NAPOLI NA
G964 POZZUOLI NA
G902 PORTICI NA
Selezionate 7 righe.
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
M297 FIUMICINO RM
E415 LAINATE MI
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
M297 FIUMICINO RM
F205 MILANO MI
E415 LAINATE MI
167
L’operatore reciproco di BETWEEN è NOT BETWEEN. Esso
estrae i record che si trovano al di fuori dell’intervallo.
WTO >select * from fatture
2 where importo not between 400 and 500;
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
G811 POMEZIA RM
M297 FIUMICINO RM
F205 MILANO MI
G686 PIOLTELLO MI
E415 LAINATE MI
Selezionate 6 righe.
168
WTO >select * from comuni
2 where provincia not in ('RM','MI');
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
F839 NAPOLI NA
G964 POZZUOLI NA
G902 PORTICI NA
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
G811 POMEZIA RM
G686 PIOLTELLO MI
G964 POZZUOLI NA
G902 PORTICI NA
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
G811 POMEZIA RM
169
9 Il carattere _ che significa “qualunque singolo carattere”.
Scrivere dunque il modello ‘P%’ vuol dire che il nome del
comune deve cominciare con P e dopo può avere qualunque stringa di
caratteri.
Scrivere il modello ‘__M%’ vuol dire che il nome può cominciare
con qualunque carattere, deve avere al secondo posto qualunque
carattere, al terzo posto una M e poi una qualunque stringa di caratteri.
Continuando con questa logica vediamo alcuni esempi.
Tutti i comuni che finiscono per O
WTO >select * from comuni
2 where des_comune like '%O';
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
M297 FIUMICINO RM
F205 MILANO MI
G686 PIOLTELLO MI
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
G686 PIOLTELLO MI
F839 NAPOLI NA
G964 POZZUOLI NA
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
M297 FIUMICINO RM
F205 MILANO MI
E415 LAINATE MI
F839 NAPOLI NA
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
G686 PIOLTELLO MI
G964 POZZUOLI NA
170
L’operatore reciproco di LIKE è NOT LIKE ed estrae tutti i dati
che non rispettano il modello.
WTO >select * from comuni
2 where des_comune not like 'P%';
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
M297 FIUMICINO RM
F205 MILANO MI
E415 LAINATE MI
F839 NAPOLI NA
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
M297 FIUMICINO RM
F205 MILANO MI
G686 PIOLTELLO MI
E415 LAINATE MI
F839 NAPOLI NA
G964 POZZUOLI NA
G902 PORTICI NA
Selezionate 7 righe.
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
G811 POMEZIA RM
E415 LAINATE MI
F839 NAPOLI NA
G964 POZZUOLI NA
G902 PORTICI NA
Selezionate 6 righe.
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
G811 POMEZIA RM
M297 FIUMICINO RM
F205 MILANO MI
E415 LAINATE MI
G902 PORTICI NA
Selezionate 6 righe.
171
WTO >select * from comuni
2 where des_comune not like '%N%';
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
G811 POMEZIA RM
G686 PIOLTELLO MI
G964 POZZUOLI NA
G902 PORTICI NA
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
G811 POMEZIA RM
M297 FIUMICINO RM
F205 MILANO MI
E415 LAINATE MI
F839 NAPOLI NA
G902 PORTICI NA
Selezionate 7 righe.
Selezionate 9 righe.
172
L’insieme di tutti i comuni il cui nome comincia con la lettera P è
identificato dalla condizione
DES_COMUNE LIKE 'P%'
Eseguendo la query:
WTO >select * from comuni
2 where des_comune like 'P%';
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
G811 POMEZIA RM
G686 PIOLTELLO MI
G964 POZZUOLI NA
G902 PORTICI NA
Eseguendo la query:
WTO >select * from comuni
2 where provincia = 'NA';
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
F839 NAPOLI NA
G964 POZZUOLI NA
G902 PORTICI NA
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
G964 POZZUOLI NA
G902 PORTICI NA
• Disgiunzione (OR)
La disgiunzione di due condizioni consente di estrarre tutti i
record che rispettano o l’una o l’altra oppure entrambe le condizioni. È
equivalente all’operazione insiemistica di UNIONE.
Nell’esempio precedente la disgiunzione delle due condizioni
estrae tutti i comuni che si trovano in provincia di Napoli oppure
iniziano per P (o che verificano entrambe le condizioni).
173
WTO >select * from comuni
2 where provincia = 'NA'
3 or des_comune like 'P%';
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
G811 POMEZIA RM
G686 PIOLTELLO MI
F839 NAPOLI NA
G964 POZZUOLI NA
G902 PORTICI NA
• Negazione (NOT)
La negazione di una condizione consente di estrarre tutti i
record che non rispettano la condizione data. È equivalente
all’operazione insiemistica di COMPLEMENTO.
Nell’esempio precedente possiamo estrarre tutti i comuni che
non si trovano in provincia di Napoli con la condizione
WTO >select * from comuni
2 where not provincia = 'NA';
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
G811 POMEZIA RM
M297 FIUMICINO RM
F205 MILANO MI
G686 PIOLTELLO MI
E415 LAINATE MI
Selezionate 6 righe.
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
G811 POMEZIA RM
M297 FIUMICINO RM
F205 MILANO MI
G686 PIOLTELLO MI
E415 LAINATE MI
Selezionate 6 righe.
174
WTO >select * from clienti
2 where cod_fisc like 'R%';
e
NOT (<prima condizione> OR <seconda condizione>)
È equivalente a
NOT <prima condizione> AND NOT <seconda condizione>
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
G811 POMEZIA RM
M297 FIUMICINO RM
F205 MILANO MI
G686 PIOLTELLO MI
E415 LAINATE MI
F839 NAPOLI NA
Selezionate 7 righe.
175
WTO >select * from comuni
2 where not provincia = 'NA' or not des_comune like 'P%';
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
G811 POMEZIA RM
M297 FIUMICINO RM
F205 MILANO MI
G686 PIOLTELLO MI
E415 LAINATE MI
F839 NAPOLI NA
Selezionate 7 righe.
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
G811 POMEZIA RM
M297 FIUMICINO RM
F205 MILANO MI
G686 PIOLTELLO MI
E415 LAINATE MI
F839 NAPOLI NA
Selezionate 7 righe.
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
M297 FIUMICINO RM
F205 MILANO MI
E415 LAINATE MI
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
M297 FIUMICINO RM
F205 MILANO MI
E415 LAINATE MI
176
WTO >select * from comuni
2 where provincia != 'NA' and des_comune not like 'P%';
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
H501 ROMA RM
M297 FIUMICINO RM
F205 MILANO MI
E415 LAINATE MI
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
G686 PIOLTELLO MI
F839 NAPOLI NA
G964 POZZUOLI NA
G902 PORTICI NA
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
G686 PIOLTELLO MI
F839 NAPOLI NA
G964 POZZUOLI NA
G902 PORTICI NA
177
WTO >select * from comuni
2 where (provincia='NA'
3 or provincia='MI')
4 and des_comune like 'P%';
COD_ DES_COMUNE PR
---- -------------------------------------------------- --
G686 PIOLTELLO MI
G964 POZZUOLI NA
G902 PORTICI NA
7.4.5 Ordinamento
Per ordinare i record estratti da una query è necessario
aggiungere una clausola ORDER BY. In assenza di tale clausola
Oracle non garantisce alcun tipo di ordinamento. A prima vista può
sembrare che, in assenza della clausola ORDER BY, le righe della
tabella siano restituite nello stesso ordine in cui sono state inserite.
Non è vero.
La forma più semplice della clausola ORDER BY prevede la
seguente sintassi:
ORDER BY <discriminante>, <discriminante>, ..., <discriminante>
178
WTO >select * from clienti
2 order by nome;
Selezionate 8 righe.
Selezionate 8 righe.
COGNOME INDIRIZZO
-------- ----------------------
COLOMBO PIAZZA DUOMO, 1
NERI VIA TORINO, 30
ESPOSITO VIA CARACCIOLO, 100
RUSSO VIA GIULIO CESARE, 119
VERDI VIA DEL MARE, 8
AMATO VIA NAPOLI, 234
BIANCHI VIA OSTIENSE, 850
ROSSI VIA LAURENTINA, 700
Selezionate 8 righe.
179
Come detto la discriminante può essere anche ottenuta
manipolando le colonne, nell’esempio seguente si ordina sulla somma
di CAP e codice cliente.
WTO >select cognome, indirizzo
2 from clienti
3 order by cap+cod_cliente;
COGNOME INDIRIZZO
-------- ----------------------
VERDI VIA DEL MARE, 8
ROSSI VIA LAURENTINA, 700
BIANCHI VIA OSTIENSE, 850
COLOMBO PIAZZA DUOMO, 1
NERI VIA TORINO, 30
AMATO VIA NAPOLI, 234
ESPOSITO VIA CARACCIOLO, 100
RUSSO VIA GIULIO CESARE, 119
Selezionate 8 righe.
Selezionate 8 righe.
N C INDIRIZZO
-------------------------- ----------------------- ----------------------
VINCENZO AMATO VIA NAPOLI, 234
GIOVANNI BIANCHI VIA OSTIENSE, 850
AMBROGIO COLOMBO PIAZZA DUOMO, 1
GENNARO ESPOSITO VIA CARACCIOLO, 100
LUCA NERI VIA TORINO, 30
MARCO ROSSI VIA LAURENTINA, 700
PASQUALE RUSSO VIA GIULIO CESARE, 119
MATTEO VERDI VIA DEL MARE, 8
Selezionate 8 righe.
180
Per ogni discriminante può opzionalmente essere indicato se
l’ordinamento deve avvenire in direzione crescente o decrescente
aggiungendo la parola chiave ASC o DESC. Per default l’ordinamento
è crescente.
Nell’esempio seguente si ordina in senso decrescente sul
cognome.
WTO >select * from clienti
2 order by cognome desc;
Selezionate 8 righe.
Selezionate 8 righe.
181
WTO >select * from clienti
2 order by cod_fisc;
Selezionate 8 righe.
Selezionate 8 righe.
7.4.6 Raggruppamenti
Alcune volte è necessario estrarre le righe da una tabella
raggruppandole secondo un determinato criterio.
Ipotizziamo ad esempio di voler conoscere tutti i comuni in cui è
residente almeno uno dei nostri clienti, banalmente la prima query che
viene in mente è
WTO >select comune from clienti;
COMU
----
H501
H501
G811
F205
F205
F839
F839
G964
Selezionate 8 righe.
182
questi vengono estratti più volte. Abbiamo quindi bisogno di
raggruppare il risultato per comune.
Il modo più semplice per estrarre una sola riga per ogni comune
è utilizzare la clausola DISTINCT.
WTO >select distinct comune from clienti;
COMU
----
F205
F839
G811
G964
H501
DATA_FATT IMPORTO
--------- ----------
01-OTT-10 300
20-OTT-10 700
01-DIC-10 500
01-FEB-11 1000
183
Questa clausola si aggiunge alla fine dell’istruzione, dopo la
WHERE, e richiede di specificare l’elenco delle colonne su cui si
intende raggruppare. Segue quindi la sintassi
SELECT <colonna>, <colonna>,...<colonna>
FROM <nome tabella>
WHERE <condizione>
GROUP BY <colonna>, <colonna>,...<colonna>
COMU
----
F205
F839
G811
G964
H501
SUM(IMPORTO)
------------
3000
184
WTO >select data_fattura, sum(importo)
2 from fatture
3 group by data_fattura;
DATA_FATT SUM(IMPORTO)
--------- ------------
01-OTT-10 300
20-OTT-10 700
01-DIC-10 1000
01-FEB-11 1000
SUM(IMPORTO)
------------
300
700
1000
1000
MIN(IMPORTO)
------------
300
NUM_ORDINE MIN(IMPORTO)
---------- ------------
1 300
2 700
3 1000
5 500
• MAX
Estrae il valore massimo di un dato presente nelle righe che
compongono il gruppo. Si comporta come la MIN.
185
WTO >select max(importo)
2 from fatture;
MAX(IMPORTO)
------------
1000
NUM_ORDINE MAX(IMPORTO)
---------- ------------
1 500
2 700
3 1000
5 500
• AVG
Calcola il valore medio di un dato presente nelle righe che
compongono il gruppo.
WTO >select avg(importo)
2 from fatture;
AVG(IMPORTO)
------------
600
NUM_ORDINE AVG(IMPORTO)
---------- ------------
1 400
2 700
3 1000
5 500
• VARIANCE
Calcola la varianza di un dato presente nelle righe che
compongono il gruppo.
186
WTO >select variance(importo)
2 from fatture;
VARIANCE(IMPORTO)
-----------------
70000
NUM_ORDINE VARIANCE(IMPORTO)
---------- -----------------
1 20000
2 0
3 0
5 0
• STDDEV
Calcola la deviazione standard di un dato presente nelle righe
che compongono il gruppo
STDDEV(IMPORTO)
---------------
264,575131
NUM_ORDINE STDDEV(IMPORTO)
---------- ---------------
1 141,421356
2 0
3 0
5 0
• COUNT
Conta le righe presenti nel gruppo. È l’unica funzione di gruppo
che può prendere in input un singolo dato oppure il carattere jolly *.
Se la funzione COUNT riceve in input un dato conta solo le
righe che hanno quel dato valorizzato, non NULL. Se invece riceve in
input * conta tutte le righe presenti nel gruppo a prescindere dal fatto
che i valori siano NULL oppure no.
Per contare tutte le righe presenti in una tabella si utilizza
dunque la sintassi
SELECT COUNT(*) FROM <nome tabella>
187
WTO >select count(*) from clienti;
COUNT(*)
----------
8
COUNT(NOME)
-----------
8
COUNT(COD_FISC)
---------------
5
NUM_ORDINE COUNT(*)
---------- ----------
1 2
2 1
3 1
5 1
188
gruppo ci possono essere nomi differenti ed Oracle non saprebbe
quale estrarre.
In una query in cui è presente la clausola GROUP BY Oracle
segue il seguente flusso. Innanzi tutto applica la clausola WHERE e
scarta le righe che non la rispettano.
Successivamente raggruppa le righe rispetto alle colonne
indicate nella clausola GROUP BY e calcola le funzioni di gruppo. In
questo modo ottiene le righe accorpate.
A questo punto potrebbe essere necessario escludere alcune
righe accorpate. Ciò non può essere fatto con la clausola WHERE
poiché, come detto, questa viene applicata prima del raggruppamento.
Per questa esigenza esiste la clausola HAVING. Questa
clausola accetta una condizione che viene applicata alle righe
accorpate al fine di escludere quelle che non la verificano.
Ipotizziamo ad esempio di voler estrarre le fatture accorpate per
numero ordine escludendo gli ordini che hanno meno di due fatture.
Innanzi tutto è necessario utilizzare una clausola GROUP BY
per numero ordine, successivamente bisogna escludere con una
HAVING le righe che hanno COUNT(*) minore di due.
WTO >select num_ordine, sum(importo), count(*)
2 from fatture
3 group by num_ordine
4 having count(*)>=2;
189
condizione utilizzata nella clausola WHERE. Ad esempio potremmo
estratte tutti gli ordini che hanno almeno due fatture oppure una sola
fattura di importo almeno 700 euro col comando:
WTO >select num_ordine, sum(importo), count(*)
2 from fatture
3 group by num_ordine
4 having count(*)>=2
5 or (count(*)=1 and sum(importo) >=700);
Selezionate 9 righe.
190
dipartimento-impiego, ma volendo visualizzare totali parziali per
dipartimento si può scrivere:
WTO> select deptno, job,sum(sal)
2 from emp
3 group by deptno,rollup (job);
Selezionate 12 righe.
invece di
JOB
191
WTO> select deptno, nvl(job,' Tot. '||deptno) job,sum(sal)
2 from emp
3 group by deptno,rollup (job);
Selezionate 12 righe.
Dove
nvl(job,' Tot. '||deptno)
Selezionate 13 righe.
192
WTO> select deptno, nvl(job,' Tot. '||
2 nvl(to_char(deptno),'Complessivo')) job,sum(sal)
3 from emp
4 group by rollup (deptno,job);
Selezionate 13 righe.
COGNOME ROWNUM
-------- ----------
ROSSI 1
BIANCHI 2
VERDI 3
NERI 4
COLOMBO 5
ESPOSITO 6
RUSSO 7
AMATO 8
Selezionate 8 righe.
193
WTO >select cognome, rownum
2 from clienti
3 where rownum<4;
COGNOME ROWNUM
-------- ----------
ROSSI 1
BIANCHI 2
VERDI 3
COGNOME ROWNUM
-------- ----------
BIANCHI 2
ROSSI 1
VERDI 3
194
record che assume anch'esso ROWNUM=1 (visto che il precedente è
stato scartato) e dunque anch'esso viene scartato. Così via per tutti i
restanti record.
Hanno invece senso le seguenti:
WTO >select cognome, rownum
2 from clienti
3 where rownum=1;
COGNOME ROWNUM
-------- ----------
ROSSI 1
COGNOME ROWNUM
-------- ----------
ROSSI 1
BIANCHI 2
VERDI 3
COGNOME ROWNUM
-------- ----------
ROSSI 1
BIANCHI 2
VERDI 3
NERI 4
195
vogliamo il nome dei dipendente, nella seconda gli stipendi dei
dipendenti appartenenti al dipartimento 10, nella terza di quelli
appartenenti al dipartimento 20 e nella quarta lo stipendio dei
dipendenti del dipartimento 30. Fino ad Oracle 10g avremmo risolto
con una UNION:
SQL> select ename, sal d10, null d20, null d30
2 from emp where deptno=10
3 union
4 select ename, null d10, sal d20, null d30
5 from emp where deptno=20
6 union
7 select ename, null d10, null d20, sal d30
8 from emp where deptno=30;
196
Ovviamente una volta ottenuto questo risultato di partenza
possiamo complicare a piacere le cose per derivare altri risultati.
Alcuni esempi:
La somma degli stipendi per dipartimento (con i risultati in
colonne distinte):
SQL> select sum(d10),sum(d20),sum(d30) from emp
2 pivot (sum(sal) for deptno
3 in (10 as D10, 20 as d20, 30 as d30));
197
WTO> select *
2 from vendite
3 UNPIVOT
4 (importo for anno in (
5 anno_2008 as '2008',
6 anno_2009 as '2009',
7 anno_2010 as '2010')
8 );
9 rows selected.
1 row updated.
198
WTO> select *
2 from vendite
3 UNPIVOT
4 (importo for anno in (
5 anno_2008 as 2008,
6 anno_2009 as 2009,
7 anno_2010 as 2010)
8 );
8 rows selected.
9 rows selected.
199
WTO> select deptno, min(anno), max(anno), avg(importo)
2 from vendite
3 UNPIVOT INCLUDE NULLS
4 (importo for anno in (
5 anno_2008 as 2008,
6 anno_2009 as 2009,
7 anno_2010 as 2010)
8 )
9 group by deptno;
200
7782 CLARK 7839
7788 SCOTT 7566
7839 KING
7844 TURNER 7698
7876 ADAMS 7788
7900 JAMES 7698
7902 FORD 7566
7934 MILLER 7782
201
Detto questo possiamo leggere il nostro albero nel risultato della
query appena eseguita: KING è padre di tutti ed ha livello 1.
Sotto di lui ci sono tre dipendenti a livello 2 (JONES,BLAKE e
CLARK). Di seguito tutti gli altri.
Come viene realizzata la gerarchia? Prima di tutto si leggono i
record. Poi si determinano le radici applicando la clausola START
WITH. Successivamente a partire da ogni radice si determinano i figli
di primo livello applicando la CONNECT BY e così via per ogni figlio.
Per migliorare un po' la visualizzazione dell'output possiamo
ricorrere all’utilizzo della LPAD:
WTO> select empno,lpad(' ',level*3,' ')||ename nome,
2 mgr, prior ename, level
3 from emp
4 connect by prior empno = mgr
5 start with mgr is null;
202
WTO> select empno,lpad(' ',level*3,' ')||ename nome,
2 mgr, prior ename, level
3 from emp
4 connect by prior empno = mgr
5 start with mgr is null
6 order siblings by ename;
Abbiamo detto che KING è figlio di SMITH, che però a sua volta
è pronipote di KING. Che succede, dunque, se rieseguiamo la query
precedente?
WTO> select empno,lpad(' ',level*3,' ')||ename nome,
2 mgr, prior ename, level
3 from emp
4 connect by prior empno = mgr
5 start with mgr is null
6 order siblings by ename;
Certo, perché non c'è nessun record con MGR NULL. Allora
cambiamo la START WITH come segue:
WTO> select empno,lpad(' ',level*3,' ')||ename nome,
2 mgr, prior ename, level
3 from emp
4 connect by prior empno = mgr
5 start with empno=7839;
ERROR:
ORA-01436: CONNECT BY in loop sui dati utente
203
Ecco il loop, è impossibile creare la gerarchia.
Ma Oracle ci ha messo un pezza: la clausola NOCYCLE
WTO> select empno,lpad(' ',level*3,' ')||ename nome,
2 mgr, prior ename, level
3 from emp
4 connect by nocycle prior empno = mgr
5 start with empno=7839;
Selezionate 14 righe.
204
WTO> select empno,lpad(' ',level*3,' ')||ename nome,
2 mgr, prior ename, connect_by_iscycle ciclo
3 from emp
4 connect by nocycle prior empno = mgr
5 start with empno=7839;
Selezionate 14 righe.
Aggiornata 1 riga.
205
Otteniamo quanto segue:
WTO> select empno,lpad(' ',level*3,' ')||ename nome,
2 connect_by_root ename boss
3 from emp
4 connect by prior empno = mgr
5 start with mgr is null;
206
C'è ancora una pseudo colonna molto interessante,
CONNECT_BY_ISLEAF.
Ci dice se un record è foglia della gerarchia oppure no:
WTO> select empno,lpad(' ',level*3,' ')||ename nome,
2 connect_by_isleaf foglia
3 from emp
4 connect by prior empno = mgr
5 start with mgr is null;
LEVEL
----------
1
2
3
4
5
6
7
8
9
10
Selezionate 10 righe.
207
SELECT deptno, ltrim(SYS_CONNECT_BY_PATH(ename, ','),',') enames
FROM (select deptno, ename, rank()
over(partition by deptno order by rownum) num from emp)
where connect_by_isleaf=1
START WITH num=1
CONNECT BY PRIOR num+1 = num and prior deptno=deptno;
DEPTNO ENAMES
---------- ----------------------------------------
10 CLARK,KING,MILLER
20 SMITH,JONES,SCOTT,ADAMS,FORD
30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES
STR
-------------------------
prae
epar
arpe
rpae
paer
pare
repa
reap
prea
earp
aepr
aerp
pera
erpa
arep
erap
aper
pear
epra
eapr
rpea
rape
raep
apre
Selezionate 24 righe.
208
In questo caso la parola era "pera".
La query prima estrae tutte le lettere della parola data
assegnando un codice univoco ad ognuna:
WTO> with t as
2 (select 'pera' name from dual)
3 select substr(name,level,1) c, name,
4 level*1000+ascii(substr(name,level,1)) cod
5 from t
6 connect by level<=length(name);
C NAME COD
- ---- ----------
p pera 1112
e pera 2101
r pera 3114
a pera 4097
209
insert into bilancio_familiare values
(DATE '2009-12-01', 'D', 'CASA', 'RATA MUTUO',500);
insert into bilancio_familiare values
(DATE '2009-12-10', 'D', 'PERSONALI', 'VACANZA',1000);
insert into bilancio_familiare values
(DATE '2009-12-10', 'D', 'AUTO', 'PIENO',60);
insert into bilancio_familiare values
(DATE '2009-12-20', 'D', 'AUTO', 'PIENO',60);
insert into bilancio_familiare values
(DATE '2009-12-30', 'D', 'AUTO', 'PIENO',60);
insert into bilancio_familiare values
(DATE '2010-01-10', 'D', 'AUTO', 'PIENO',60);
insert into bilancio_familiare values
(DATE '2010-01-20', 'D', 'AUTO', 'PIENO',60);
insert into bilancio_familiare values
(DATE '2010-01-30', 'D', 'AUTO', 'PIENO',60);
insert into bilancio_familiare values
(DATE '2010-01-01', 'D', 'CASA', 'RATA MUTUO',510);
insert into bilancio_familiare values
(DATE '2009-12-15', 'A', 'STIPENDI', '13esima',2000);
insert into bilancio_familiare values
(DATE '2009-12-27', 'A', 'STIPENDI', 'Dicembre',2200);
insert into bilancio_familiare values
(DATE '2010-01-27', 'A', 'STIPENDI', 'Gennaio',2400);
insert into bilancio_familiare values
(DATE '2009-12-15', 'D', 'REGALI', 'Natale',800);
insert into bilancio_familiare values
(DATE '2009-12-25', 'A', 'REGALI', 'Natale',200);
14 rows selected.
210
WTO> select to_char(data,'yyyy') anno, segno,
2 categoria, sum(importo) importo
3 from bilancio_familiare
4 group by to_char(data,'yyyy'), segno, categoria
5 ;
Selezionate 9 righe.
211
Detto questo, da questo momento in poi si utilizzerà il termine
cella per indicare uno specifico valore di IMPORTO individuato da
ANNO, SEGNO e CATEGORIA.
Si può pensare ad una cartella Excel in cui per ogni anno c’è un
foglio, ed in ogni foglio si utilizzano segno e categoria come coordinate
(righe e colonne) con cui individuare le singole celle.
Il contenuto della singola cella è un valore di importo.
Calcolo vettoriale simbolico
Questo nome roboante rappresenta la capacità di popolare celle
(o vettori di celle) con formule che si basano su altre celle.
Vediamo un semplice esempio.
Vogliamo aggiungere nuove celle al foglio per calcolare
1) il "TOTALE CASA E AUTO" facendo la somma delle celle che
hanno segno D e categoria AUTO e CASA
2) il "TOTALE AVERE" facendo la somma delle celle che hanno
segno A e categoria STIPENDI e REGALI
WTO> select anno, segno, categoria, importo
2 from (select to_char(data,'yyyy') anno, segno,
3 categoria, sum(importo) importo
4 from bilancio_familiare
5 group by to_char(data,'yyyy'), segno, categoria)
6 model
7 PARTITION BY (anno) DIMENSION BY (segno, categoria)
8 MEASURES (importo)
9 RULES
10 (importo['D', 'TOTALE CASA e AUTO'] =
11 importo['D', 'CASA'] + importo['D', 'AUTO'],
12 importo['A', 'TOTALE AVERE'] =
13 importo['A', 'STIPENDI'] + importo['A', 'REGALI']
14 )
15 ORDER BY anno, segno,categoria;
13 rows selected.
212
Come si vede, nella clausola MODEL sono state prima di tutto
definite PARTIZIONI, DIMENSIONI e MISURE.
Poi si è passati alle regole, indicando che l'importo in Dare con
categoria TOTALE CASA E AUTO è dato dalla somma degli importi
Dare aventi categorie CASA e AUTO.
Con la stessa logica è stato calcolato il TOTALE AVERE.
Notiamo però che il TOTALE AVERE del 2010 ha importo nullo
perché per il 2010 manca la cella (A, REGALI). Come al solito
sommando quindi questo valore NULL al valore della cella (A,
STIPENDI) si ottiene NULL.
Per ovviare al problema si può banalmente utilizzare la funzione
NVL:
WTO> select anno, segno, categoria, importo
2 from (select to_char(data,'yyyy') anno, segno,
3 categoria, sum(importo) importo
4 from bilancio_familiare
5 group by to_char(data,'yyyy'), segno, categoria)
6 model
7 PARTITION BY (anno) DIMENSION BY (segno, categoria)
8 MEASURES (importo)
9 RULES
10 (importo['D', 'TOTALE CASA e AUTO'] =
11 importo['D', 'CASA'] + importo['D', 'AUTO'],
12 importo['A', 'TOTALE AVERE'] =
13 nvl(importo['A', 'STIPENDI'],0) +
14 nvl(importo['A', 'REGALI'],0)
15 )
16 ORDER BY anno, segno,categoria;
13 rows selected.
213
WTO> select anno, segno, categoria, importo
2 from (select to_char(data,'yyyy') anno, segno,
3 categoria, sum(importo) importo
4 from bilancio_familiare
5 group by to_char(data,'yyyy'), segno, categoria)
6 model
7 PARTITION BY (anno) DIMENSION BY (segno, categoria)
8 MEASURES (importo)
9 RULES
10 (importo['D', 'TOTALE CASA e AUTO'] =
11 importo['D', 'CASA'] + importo['D', 'AUTO'],
12 importo['A', 'TOTALE AVERE'] =
13 nvl(importo['A', 'STIPENDI'],0)
14 + nvl(importo['A', 'REGALI'],0),
15 importo['D', 'TOTALE DARE'] =
16 sum(importo)['D',categoria like '%']
17 )
18 ORDER BY anno, segno,categoria;
15 rows selected.
214
Aggiorna le celle già presenti ed aggiunge nuove celle su un
numero limitato di regole
• UPSERT ALL
Aggiorna le celle già presenti ed aggiunge nuove celle su un
numero più ampio di regole
Si può specificare una modalità di calcolo per ogni regola ed il
default è UPSERT.
Vediamo un esempio.
Tutte le celle che abbiamo calcolato finora sono celle aggiunte,
essendo totali. Poiché non abbiamo mai specificato la modalità di
calcolo essa vale UPSERT e le celle sono sempre state create.
Utilizziamo invece la modalità UPDATE per il calcolo del
TOTALE DARE.
WTO> select anno, segno, categoria, importo
2 from (select to_char(data,'yyyy') anno, segno,
3 categoria, sum(importo) importo
4 from bilancio_familiare
5 group by to_char(data,'yyyy'), segno, categoria)
6 model
7 PARTITION BY (anno) DIMENSION BY (segno, categoria)
8 MEASURES (importo)
9 RULES
10 (importo['D', 'TOTALE CASA e AUTO'] =
11 importo['D', 'CASA'] + importo['D', 'AUTO'],
12 importo['A', 'TOTALE AVERE'] =
13 nvl(importo['A', 'STIPENDI'],0)
14 + nvl(importo['A', 'REGALI'],0),
15 UPDATE importo['D', 'TOTALE DARE'] =
16 sum(importo)['D',categoria like '%']
17 )
18 ORDER BY anno, segno,categoria;
13 rows selected.
215
Rispetto a prima abbiamo perso due righe. I due totali dare per il
2009 ed il 2010.
Siccome la cella (D, TOTALE DARE) non esiste al momento
dell'estrazione, la regola in modalità UPDATE non l'aggiunge.
Se invece proviamo ad inserire in tabella un record fatto così:
insert into bilancio_familiare values
(DATE '2009-12-25', 'D', 'TOTALE DARE', null,1000);
14 rows selected.
216
Ci sono regole, infatti, su cui UPSERT non aggiunge le celle.
Un esempio è il seguente:
WTO> select anno, segno, categoria, importo
2 from (select to_char(data,'yyyy') anno, segno,
3 categoria, sum(importo) importo
4 from bilancio_familiare
5 group by to_char(data,'yyyy'), segno, categoria)
6 model
7 PARTITION BY (anno) DIMENSION BY (segno, categoria)
8 MEASURES (importo)
9 RULES
10 (importo[segno='A','TOTALE']=
11 sum(importo)['A',categoria like '%'])
12 ORDER BY anno, segno,categoria;
Selezionate 9 righe.
Selezionate 11 righe.
217
UPSERT non aggiunge celle quando le dimensioni sono
specificate con una condizione (SEGNO='A') anziché con una costante
('A').
UPSERT ALL invece aggiunge le celle anche in questo caso.
La wildcard ANY
Quest'ultimo esempio ci ricorda che dovevamo trovare un modo
più elegante per indicare "tutti i valori di categoria".
Lo possiamo fare con la parola chiave ANY:
WTO> select anno, segno, categoria, importo
2 from (select to_char(data,'yyyy') anno, segno,
3 categoria, sum(importo) importo
4 from bilancio_familiare
5 group by to_char(data,'yyyy'), segno, categoria)
6 model
7 PARTITION BY (anno) DIMENSION BY (segno, categoria)
8 MEASURES (importo)
9 RULES
10 (importo['D', 'TOTALE CASA e AUTO'] =
11 importo['D', 'CASA'] + importo['D', 'AUTO'],
12 importo['A', 'TOTALE AVERE'] =
13 nvl(importo['A', 'STIPENDI'],0)
.14 + nvl(importo['A', 'REGALI'],0),
15 UPDATE importo['D', 'TOTALE DARE'] =
16 sum(importo)['D',ANY]
17 )
18 ORDER BY anno, segno,categoria;
14 rows selected.
218
Se la si utilizza a destra nella regola non ci sono dubbi. Se la si
utilizza a sinistra, invece, come si fa a destra della regola a sapere
quale valore della dimensione si sta lavorando?
Facciamo un esempio:
WTO> select anno, segno, categoria, importo
2 from (select to_char(data,'yyyy') anno, segno,
3 categoria, sum(importo) importo
4 from bilancio_familiare
5 group by to_char(data,'yyyy'), segno, categoria)
6 model
7 PARTITION BY (anno) DIMENSION BY (segno, categoria)
8 MEASURES (importo)
9 RULES
10 (importo['A', 'TOTALE AVERE'] = sum(importo)['A',ANY],
11 importo['D', 'TOTALE DARE'] = sum(importo)['D',ANY])
12 ORDER BY anno, segno,categoria;
13 rows selected.
219
ANNO S CATEGORIA IMPORTO
---- - -------------------- ----------
2009 A REGALI 200
2009 A STIPENDI 4200
2009 A TOTALE 4400
2009 D AUTO 180
2009 D CASA 500
2009 D PERSONALI 1000
2009 D REGALI 800
2009 D TOTALE 2480
2010 A STIPENDI 2400
2010 A TOTALE 2400
2010 D AUTO 180
2010 D CASA 510
2010 D TOTALE 690
Selezionate 13 righe.
220
WTO >Select nome||cognome from clienti;
NOME||COGNOME
--------------------------------------------
MARCOROSSI
GIOVANNIBIANCHI
MATTEOVERDI
LUCANERI
AMBROGIOCOLOMBO
GENNAROESPOSITO
PASQUALERUSSO
VINCENZOAMATO
Selezionate 8 righe.
NOME||''||COGNOME
-----------------------------------------------
MARCO ROSSI
GIOVANNI BIANCHI
MATTEO VERDI
LUCA NERI
AMBROGIO COLOMBO
GENNARO ESPOSITO
PASQUALE RUSSO
VINCENZO AMATO
Selezionate 8 righe.
NOME||''||COD_FISC
------------------------------------
MARCO RSSMRC70R20H501X
GIOVANNI
MATTEO VRDMTT69S02H501X
LUCA NRILCU77A22F205X
AMBROGIO
GENNARO SPSGNN71B10F839X
PASQUALE RSSPSQ70C14F839X
VINCENZO
Selezionate 8 righe.
221
WTO >select data_fattura||'-'||importo
2 from fatture;
DATA_FATTURA||'-'||IMPORTO
---------------------------------------------
01-OTT-10-300
01-DIC-10-500
20-OTT-10-700
01-FEB-11-1000
01-DIC-10-500
3+5
----------
8
• Sottrazione (–)
WTO >select importo, importo-100
2 from fatture;
IMPORTO IMPORTO-100
---------- -----------
300 200
500 400
700 600
1000 900
500 400
• Moltiplicazione (*)
WTO >select importo, importo*0.20 iva
2 from fatture;
IMPORTO IVA
---------- ----------
300 60
500 100
700 140
1000 200
500 100
• Divisione (/)
WTO >select importo, importo/2
2 from fatture;
IMPORTO IMPORTO/2
---------- ----------
300 150
500 250
700 350
1000 500
500 250
222
Gli operatori aritmetici rispettano le consuete regole di
precedenza, moltiplicazioni e divisioni hanno la precedenza rispetto ad
addizioni e sottrazioni. Per forzare la precedenza è possibile fare uso
di parentesi tonde.
WTO >select 3*(5-2)/4+1 from dual;
3*(5-2)/4+1
-----------
3,25
3*5-2/4+1
----------
15,5
• CONCAT
La funzione CONCAT è equivalente all’operatore di
concatenazione.
WTO >select concat(nome,cognome) from clienti;
CONCAT(NOME,COGNOME)
------------------------------------------------------------
MARCOROSSI
GIOVANNIBIANCHI
MATTEOVERDI
LUCANERI
AMBROGIOCOLOMBO
GENNAROESPOSITO
PASQUALERUSSO
VINCENZOAMATO
Selezionate 8 righe.
223
WTO >select concat(nome,concat(' ',cognome)) from clienti;
CONCAT(NOME,CONCAT('',COGNOME))
-------------------------------------------------------------
MARCO ROSSI
GIOVANNI BIANCHI
MATTEO VERDI
LUCA NERI
AMBROGIO COLOMBO
GENNARO ESPOSITO
PASQUALE RUSSO
VINCENZO AMATO
Selezionate 8 righe.
• SUBSTR
La funzione SUBSTR estrae una sottostringa da una stringa
data. La funzione riceve in input tre parametri: la stringa di partenza; la
posizione, all’interno della stringa di partenza, a partire dalla quale
bisogna estrarre i caratteri; la lunghezza della stringa da estrarre.
Ipotizziamo ad esempio di voler estrarre i primi tre caratteri del
nome dei clienti. La posizione di partenza è uno, la lunghezza della
stringa è tre.
WTO >select nome, substr(nome,1,3) from clienti;
NOME SUB
-------- ---
MARCO MAR
GIOVANNI GIO
MATTEO MAT
LUCA LUC
AMBROGIO AMB
GENNARO GEN
PASQUALE PAS
VINCENZO VIN
NOME SUB
-------- ---
MARCO RCO
GIOVANNI OVA
MATTEO TTE
LUCA CA
AMBROGIO BRO
GENNARO NNA
PASQUALE SQU
VINCENZO NCE
224
Se la posizione di partenza è negativa, essa indica una
posizione a partire dall’ultimo carattere della stringa e contando verso
sinistra. Se, ad esempio, si vogliono estrarre gli ultimi tre caratteri del
nome bisognerà partire dalla posizione -3 ed estrarre tre caratteri.
WTO >select nome, substr(nome,-3,3) from clienti;
NOME SUB
-------- ---
MARCO RCO
GIOVANNI NNI
MATTEO TEO
LUCA UCA
AMBROGIO GIO
GENNARO ARO
PASQUALE ALE
VINCENZO NZO
Selezionate 8 righe.
NOME S
-------- -
MARCO
GIOVANNI
MATTEO
LUCA
AMBROGIO
GENNARO
PASQUALE
VINCENZO
Selezionate 8 righe.
NOME SUBSTR(NOME,3)
-------- ----------------------------
MARCO RCO
GIOVANNI OVANNI
MATTEO TTEO
LUCA CA
AMBROGIO BROGIO
GENNARO NNARO
PASQUALE SQUALE
VINCENZO NCENZO
Selezionate 8 righe.
225
E per estrarre gli ultimi tre caratteri scriveremo:
WTO >select nome, substr(nome,-3) from clienti;
NOME SUB
-------- ---
MARCO RCO
GIOVANNI NNI
MATTEO TEO
LUCA UCA
AMBROGIO GIO
GENNARO ARO
PASQUALE ALE
VINCENZO NZO
Selezionate 8 righe.
NOME INSTR(NOME,'A')
-------- ---------------
MARCO 2
GIOVANNI 5
MATTEO 2
LUCA 4
AMBROGIO 1
GENNARO 5
PASQUALE 2
VINCENZO 0
NOME INSTR(NOME,'AR')
-------- ----------------
MARCO 2
GIOVANNI 0
MATTEO 0
LUCA 0
AMBROGIO 0
GENNARO 5
PASQUALE 0
VINCENZO 0
226
Solo MARCO e GENNARO contengono la stringa ‘AR’.
INSTR ammette un terzo parametro, opzionale, mediante il
quale è possibile indicare a partire da quale posizione nella prima
stringa cominciare la ricerca della seconda. Tornando al primo
esempio cerchiamo la lettera ‘A’ nei nomi ma partendo dal terzo
carattere.
WTO >select nome, instr(nome,'A',3) from clienti;
NOME INSTR(NOME,'A',3)
-------- -----------------
MARCO 0
GIOVANNI 5
MATTEO 0
LUCA 4
AMBROGIO 0
GENNARO 5
PASQUALE 6
VINCENZO 0
Selezionate 8 righe.
NOME INSTR(NOME,'A',1,2)
-------- -------------------
MARCO 0
GIOVANNI 0
MATTEO 0
LUCA 0
AMBROGIO 0
GENNARO 0
PASQUALE 6
VINCENZO 0
Selezionate 8 righe.
227
WTO >select nome, length(nome) from clienti;
NOME LENGTH(NOME)
-------- ------------
MARCO 5
GIOVANNI 8
MATTEO 6
LUCA 4
AMBROGIO 8
GENNARO 7
PASQUALE 8
VINCENZO 8
Selezionate 8 righe.
COD_FISC LENGTH(COD_FISC)
---------------- ----------------
RSSMRC70R20H501X 16
VRDMTT69S02H501X 16
NRILCU77A22F205X 16
SPSGNN71B10F839X 16
RSSPSQ70C14F839X 16
Selezionate 8 righe.
LOWER(NOME)
------------------------------
marco
giovanni
matteo
luca
ambrogio
gennaro
pasquale
vincenzo
Selezionate 8 righe.
UPPER('UNASTRINGAMINU
---------------------
UNA STRINGA MINUSCOLA
228
La funzione INITCAP riceve in input una stringa e la restituisce
tutta al minuscolo con le iniziali delle parole maiuscole.
WTO >select initcap(nome) from clienti;
INITCAP(NOME)
------------------------------
Marco
Giovanni
Matteo
Luca
Ambrogio
Gennaro
Pasquale
Vincenzo
INITCAP('UNASTRINGAMI
---------------------
Una Stringa Minuscola
LTRIM('XXX
----------
STRINGAXXX
LTRIM('XYX
----------
STRINGAXXX
LTRIM('STRI
-----------
STRINGA
LTRIM('STRI
-----------
STRINGA
229
La funzione RTRIM si comporta esattamente come la LTRIM ma
alle destra della stringa.
WTO >select rtrim('XXXSTRINGAXXX','X') from dual;
RTRIM('XXX
----------
XXXSTRINGA
RTRIM('XYXYX
------------
XYXYXSTRINGA
RTRIM('STRIN
------------
STRINGA
RTRIM('STRIN
------------
STRINGA
La funzione TRIM elimina gli spazi sia dalla destra che dalla
sinistra di una stringa:
WTO >select trim(' STRINGA ') from dual;
TRIM('S
-------
STRINGA
LTRIM(R
-------
STRINGA
230
• LPAD ed RPAD
La funzione LPAD riceve in input due stringhe ed una
lunghezza. Effettua il riempimento della prima stringa con la seconda
stringa fino a raggiungere la lunghezza data. Ipotizziamo ad esempio
di voler riempire a sinistra i nomi dei clienti con caratteri asterisco fino
ad una lunghezza di dieci caratteri.
WTO >select lpad(nome,10,'*') from clienti;
LPAD(NOME,
----------
*****MARCO
**GIOVANNI
****MATTEO
******LUCA
**AMBROGIO
***GENNARO
**PASQUALE
**VINCENZO
Selezionate 8 righe.
LPAD(NO
-------
**MARCO
GIOVANN
*MATTEO
***LUCA
AMBROGI
GENNARO
PASQUAL
VINCENZ
Selezionate 8 righe.
RPAD(NOME,
----------
MARCO*****
GIOVANNI**
MATTEO****
LUCA******
AMBROGIO**
GENNARO***
PASQUALE**
VINCENZO**
231
WTO >select rpad(nome,7,'*') from clienti;
RPAD(NO
-------
MARCO**
GIOVANN
MATTEO*
LUCA***
AMBROGI
GENNARO
PASQUAL
VINCENZ
Selezionate 8 righe.
• REPLACE
La funzione REPLACE riceve in input tre stringhe ed effettua
una sostituzione, nella prima stringa tutte le occorrenze della seconda
stringa vengono sostituite con occorrenze della terza. Ad esempio se
nel nome dei clienti si intende sostituire le lettere ‘A’ con il carattere
asterisco si scriverà.
WTO >select replace(nome,'A','*') from clienti;
REPLACE(NOME,'A','*')
------------------------------
M*RCO
GIOV*NNI
M*TTEO
LUC*
*MBROGIO
GENN*RO
P*SQU*LE
VINCENZO
Selezionate 8 righe.
REPLACE(NOME,'A','<+>')
----------------------------------------------------------
M<+>RCO
GIOV<+>NNI
M<+>TTEO
LUC<+>
<+>MBROGIO
GENN<+>RO
P<+>SQU<+>LE
VINCENZO
Selezionate 8 righe.
232
• TRANSLATE
La funzione TRANSLATE riceve in input tre stringhe ed effettua
una sostituzione. La prima stringa è quella in cui viene effettuata la
sostituzione. La seconda e la terza stringa rappresentano due alfabeti.
Ogni occorrenza del carattere che si trova al primo posto nel primo
alfabeto sarà sostituito con un’occorrenza del carattere che si trova al
primo posto nel secondo alfabeto e così via. Ad esempio, nei nomi dei
clienti, vogliamo sostituire ogni ‘A’ con un ‘*’, ogni ‘M’ con un ‘-‘ ed ogni
‘R’ con un ‘+’. Scriveremo.
WTO >select nome, translate(nome,'AMR','*-+') from clienti;
NOME TRANSLATE(NOME,'AMR','*-+')
-------- ------------------------------
MARCO -*+CO
GIOVANNI GIOV*NNI
MATTEO -*TTEO
LUCA LUC*
AMBROGIO *-B+OGIO
GENNARO GENN*+O
PASQUALE P*SQU*LE
VINCENZO VINCENZO
Selezionate 8 righe.
TRANSLATE('STRINGA*+-:CON.:/\C
------------------------------
stringa con caratteri speciali
• ASCII e CHR
La funzione ASCII riceve in input un carattere e ne restituisce il
codice ASCII ( 10.4.3) nel set di caratteri del database.
WTO >select ascii('A') from dual;
ASCII('A')
----------
65
233
WTO >select ascii('a') from dual;
ASCII('A')
----------
97
ASCII('{')
----------
123
C C C
- - -
A a {
• ABS
La funzione ABS riceve in input un numero e ne restituisce il
valore assoluto.
WTO >select abs(-3), abs(5) from dual;
ABS(-3) ABS(5)
---------- ----------
3 5
• ROUND e TRUNC
La funzione ROUND riceve in input un numero e lo restituisce
arrotondato alla parte intera. L’arrotondamento aritmetico prevede che
le cifre non significative (i decimali in questo caso) vengano
abbandonate, l’ultima cifra significativa viene aumentata di una unità
se la prima cifra non significativa va da cinque a nove.
WTO >select round(10.3), round(3.5), round(2.4)
2 from dual;
234
WTO >select round(2.345, 3), round(2.345,2), round(2.345,1)
2 from dual;
235
WTO >select trunc(2345,-1), trunc(2345,-2), trunc(2345,-3)
2 from dual;
• CEIL e FLOOR
La funzione CEIL restituisce il più piccolo intero maggiore o
uguale del numero ricevuto in input.
WTO >select ceil(2.6), ceil(3), ceil(3.3)
2 from dual;
• EXP, LN e LOG
La funzione esponenziale EXP restituisce il risultato di una
potenza avente come base il numero di Nepero (detto anche numero
di Eulero 2,718281828459…) e come esponente il numero passato in
input alla funzione.
236
WTO >select exp(1), exp(2)
2 from dual;
EXP(1) EXP(2)
------------ ------------
2,7182818285 7,3890560989
LN(2.7182818285) LN(10)
---------------- ------------
1 2,302585093
LOG(10,10) LOG(2,8)
------------ ------------
1 3
• POWER e SQRT
La funzione potenza POWER riceve in input due numeri.
Restituisce il risultato di una potenza avente come base il primo
parametro e come esponente il secondo.
WTO >select power(2,3), power(10,4)
2 from dual;
POWER(2,3) POWER(10,4)
------------ ------------
8 10000
SQRT(16) SQRT(2)
------------ ------------
4 1,4142135624
• SIGN
La funzione segno SIGN riceve in input un numero e restituisce:
¾ 1 se il numero in input è maggiore di zero,
¾ 0 se il numero in input è zero,
¾ -1 se il numero in input è minore di zero.
237
WTO >select sign(-23), sign(0), sign(3)
2 from dual;
• MOD e REMAINDER
La funzione modulo MOD riceve in input due numeri e
restituisce il resto che si ottiene dividendo il primo parametro per il
secondo.
WTO >select mod(10,3), mod(28,7), mod(17,9)
2 from dual;
MOD(10,7) REMAINDER(10,7)
---------- ---------------
3 3
MOD(10,6) REMAINDER(10,6)
---------- ---------------
4 -2
• TRIGONOMETRICHE
Oracle mette a disposizione alcune funzioni trigonometriche
che ci limiteremo ad elencare: seno (SIN) e seno iperbolico (SINH);
coseno (COS) e coseno iperbolico (COSH); tangente (TAN) e
tangente iperbolica (TANH); arcoseno (ASIN), arco coseno (ACOS),
arcotangente (ATAN) ed arcotangente a due parametri (ATAN2). Le
due funzioni arcotangente sono legate dalla seguente equivalenza:
ATAN2(a,b) = ATAN(a/b).
238
7.5.5 Funzioni sulle date
Oracle per default presenta le date nel formato DD-MON-YY
che significa due cifre per il giorno, tre lettere per il mese e due cifre
per l’anno. Come già detto, però, una data in Oracle contiene sempre
anche ore, minuti e secondi. Il formato di default dunque nasconde
alcune informazioni significative. I formati delle date saranno
approfonditi più avanti, per il momento, per comprendere meglio gli
esempi presenti in questo paragrafo, conviene eseguire la seguente
istruzione SQL:
WTO >alter session set nls_date_format='dd-mm-yyyy hh24:mi:ss';
Modificata sessione.
SYSDATE
-------------------
13-02-2011 17:39:11
SYSTIMESTAMP
------------------------------------
13-FEB-11 17:40:11,641727 +01:00
SYSDATE+1
-------------------
14-02-2011 17:41:59
239
Ovviamente il numero aggiunto o sottratto non deve essere
necessariamente intero. Per togliere un minuto alla data corrente si
può scrivere.
WTO >select sysdate, sysdate-1/(24*60)
2 from dual;
SYSDATE SYSDATE-1/(24*60)
------------------- -------------------
13-02-2011 17:43:23 13-02-2011 17:42:23
ADD_MONTHS(DATE'201
-------------------
13-05-2011 00:00:00
ADD_MONTHS(DATE'201
-------------------
13-10-2010 00:00:00
ADD_MONTHS(DATE'201
-------------------
31-03-2011 00:00:00
ADD_MONTHS(DATE'201
-------------------
30-04-2011 00:00:00
240
WTO >select add_months(date'2011-02-28',-1)
2 from dual;
ADD_MONTHS(DATE'201
-------------------
31-01-2011 00:00:00
ADD_MONTHS(DATE'201
-------------------
31-12-2010 00:00:00
• MONTHS_BETWEEN
La funzione MONTHS_BETWEEN prende in input due date e
ritorna la differenza in mesi tra le due. Il risultato è positivo se la prima
data è maggiore della seconda, altrimenti è negativo.
WTO >select months_between(date'2011-02-28', date'2011-05-07')
2 from dual;
MONTHS_BETWEEN(DATE'2011-02-28',DATE'2011-05-07')
-------------------------------------------------
-2,322580645
MONTHS_BETWEEN(DATE'2011-05-07',DATE'2011-02-28')
-------------------------------------------------
2,3225806452
MONTHS_BETWEEN(DATE'2011-02-28',DATE'2011-03-28')
-------------------------------------------------
-1
MONTHS_BETWEEN(DATE'2011-02-28',DATE'2011-03-30')
-------------------------------------------------
-1,064516129
MONTHS_BETWEEN(DATE'2011-02-28',DATE'2011-03-31')
-------------------------------------------------
-1
241
WTO >select months_between(date'2011-02-28',date'2011-04-01')
2 from dual;
MONTHS_BETWEEN(DATE'2011-02-28',DATE'2011-04-01')
-------------------------------------------------
-1,129032258
• LAST_DAY
La funzione LAST_DAY riceve in input una data e restituisce
l’ultimo giorno del mese in cui cade la data in input. La parte ore-
minuti-secondi resta inalterata.
WTO >select date'2011-03-05', last_day(date'2011-03-05')
2 from dual;
DATE'2011-03-05' LAST_DAY(DATE'2011-
------------------- -------------------
05-03-2011 00:00:00 31-03-2011 00:00:00
SYSDATE LAST_DAY(SYSDATE)
------------------- -------------------
13-02-2011 17:58:00 28-02-2011 17:58:00
• NEXT_DAY
La funzione NEXT_DAY riceve in input una data ed il nome di
un giorno della settimana. Restituisce il primo giorno successivo al
primo parametro che cada nel giorno della settimana indicato come
secondo parametro. Oggi è domenica, chiedendo
NEXT_DAY(SYSDATE, 'MARTEDI')
SYSDATE NEXT_DAY(SYSDATE,'M
------------------- -------------------
13-02-2011 18:00:38 15-02-2011 18:00:38
SYSDATE NEXT_DAY(SYSDATE,'D
------------------- -------------------
13-02-2011 18:09:36 20-02-2011 18:09:36
242
La funzione NEXT_DAY è ovviamente dipendente dalla lingua
della sessione di lavoro. Se, infatti, modifichiamo la lingua della
sessione di lavoro dall’italiano all’inglese
WTO >alter session set nls_language='english';
Session altered.
SYSDATE NEXT_DAY(SYSDATE,'T
------------------- -------------------
13-02-2011 18:12:21 15-02-2011 18:12:21
• ROUND e TRUNC
La funzione ROUND arrotonda una data al giorno più vicino.
WTO >select round(sysdate) from dual;
ROUND(SYSDATE)
-------------------
14-02-2011 00:00:00
SYSDATE ROUND(SYSDATE,'HH')
------------------- -------------------
13-02-2011 18:15:05 13-02-2011 18:00:00
243
WTO >select trunc(sysdate) from dual;
TRUNC(SYSDATE)
-------------------
13-02-2011 00:00:00
SYSDATE TRUNC(SYSDATE,'HH')
------------------- -------------------
13-02-2011 18:19:02 13-02-2011 18:00:00
• CURRENT_DATE e CURRENT_TIMESTAMP
La funzione CURRENT_DATE restituisce la data corrente
applicando il fuso orario della sessione (laddove invece la SYSDATE
applica sempre il fuso orario del database). SYSDATE è diversa da
CURRENT_DATE solo quando il client che instanzia la sessione ed il
server si trovano in due fusi orari differenti. In una situazione standard
(client e server nello stesso fuso orario) si avrà.
WTO >select sysdate, current_date
2 from dual;
SYSDATE CURRENT_DATE
------------------- -------------------
13-02-2011 18:21:04 13-02-2011 18:21:05
Session altered.
SYSDATE CURRENT_DATE
------------------- -------------------
13-02-2011 18:22:21 13-02-2011 09:22:22
Session altered.
SYSTIMESTAMP CURRENT_TIMESTAMP
--------------------------------- ---------------------------------
13-FEB-11 18:26:35,478507 +01:00 13-FEB-11 18:26:35,478528 +01:00
244
WTO >alter session set time_zone='-8:0';
Session altered.
SYSTIMESTAMP CURRENT_TIMESTAMP
--------------------------------- ---------------------------------
13-FEB-11 18:26:46,593435 +01:00 13-FEB-11 09:26:46,593459 -08:00
• EXTRACT
La funzione EXTRACT consente di estrarre da un TIMESTAMP
uno degli elementi che lo compone, ad esempio il mese, il giorno
oppure il minuto. Gli elementi che possono essere estratti da un
TIMESTAMP sono: l’anno (YEAR), il mese (MONTH), il giorno (DAY),
l’ora (HOUR), il minuto (MINUTE), il secondo (SECOND), l’ora del fuso
orario (TIMEZONE_HOUR), il minuto del fuso orario
(TIMEZONE_MINUTE), la regione del fuso orario
(TIMEZONE_REGION), la stringa d’abbreviazione del fuso orario
(TIMEZONE_ABBR).
Se si prova ad applicare la funzione ad una data anziché un
timestamp si ottiene un errore:
WTO >select extract(hour from data_fattura) from fatture;
select extract(hour from data_fattura) from fatture
*
ERRORE alla riga 1:
ORA-30076: invalid extract field for extract source
Tabella creata.
Creata 1 riga.
EXTRACT(HOURFROMCOL)
--------------------
18
245
Estraendo i secondi vengono estratte anche le frazioni di
secondo.
WTO >select extract(second from col),
2 extract(second from systimestamp) from t;
EXTRACT(SECONDFROMCOL) EXTRACT(SECONDFROMSYSTIMESTAMP)
---------------------- -------------------------------
54,280232 1,345504
EXTRACT(HOURFROMTO_TIMESTAMP(DATA_FATTURA))
-------------------------------------------
0
0
0
0
0
EXTRACT(DAYFROMTO_TIMESTAMP(DATA_FATTURA))
------------------------------------------
1
1
20
1
1
EXTRACT(MONTHFROMTO_TIMESTAMP(DATA_FATTURA))
--------------------------------------------
10
12
10
2
12
246
7.5.6 Funzioni di conversione
Le funzioni di conversione consentono di trasformare un tipo di
dato in un altro. Ad esempio una stringa in data oppure un numero in
stringa.
Alcune conversioni di tipo, comunque vengono effettuate
automaticamente da Oracle, si tratta delle cosiddette “conversioni
implicite”. Un esempio di conversione implicita è ciò che avviene
quando si utilizza una stringa tra apici per rappresentare una data.
Nell’esempio che segue si utilizza la funzione LAST_DAY sulla stringa
’12-NOV-10’. Oracle interpreta la stringa come data e determina
l’ultimo giorno di Novembre 2010
WTO >select last_day('12-NOV-10') from dual;
LAST_DAY(
---------
30-NOV-10
'12,3'+5
----------
17,3
TO_CHAR(S
---------
22-FEB-11
247
WTO >select to_char(5.3) from dual;
TO_
---
5,3
TO_CHAR(SY
----------
22/02/2011
TO_CHAR(5.3
-----------
5,30
TO_CHAR(123
-----------
12.345,30
• TO_DATE
La funzione TO_DATE consente di convertire una stringa in
data.
WTO >select to_date('12-nov-10') from dual;
TO_DATE('
---------
12-NOV-10
248
WTO >select to_date('12/11/2010') from dual;
select to_date('12/11/2010') from dual
*
ERRORE alla riga 1:
ORA-01843: not a valid month
TO_DATE('
---------
12-NOV-10
• TO_NUMBER
La funzione TO_NUMBER converte una stringa in numero.
Anche in questo caso è opzionalmente possibile indicare il formato
numerico in cui la stringa è espressa.
WTO >select to_number('123,4') from dual;
TO_NUMBER('123,4')
------------------
123,4
TO_NUMBER('123.123,4','999G999D99')
-----------------------------------
123123,4
• TO_TIMESTAMP
La funzione TO_TIMESTAMP è molto simile alla TO_DATE ma
trasforma una stringa in TIMESTAMP.
WTO >select to_timestamp('12-nov-10 22:10:33,123456')
2 from dual;
TO_TIMESTAMP('12-NOV-1022:10:33,123456')
-----------------------------------------------------------------
12-NOV-10 22:10:33,123456000
249
7.5.7 Formati per la rappresentazione delle date
Un formato di rappresentazione di una data è una stringa con
cui si comunica ad Oracle come deve essere formattata una data. Un
formato di rappresentazione si ottiene combinando in una sola stringa
uno o più codici di formato. Ogni codice di formato consente di
formattare, in un formato specifico, un elemento della data.
Nel seguito vengono presentati i codici di formato utilizzati più di
frequente. Per una lista completa si può fare riferimento al manuale
Oracle SQL Reference.
Il formato ‘d’ stampa il giorno della settimana da uno a sette.
Uno per il lunedì, sette per la domenica. Tale impostazione dipende
dal parametro di sessione NLS_TERRITORY. Per default in una
installazione in italiano tale parametro vale ITALY e dunque il primo
giorno della settimana è lunedì. Se impostiamo tale parametro al
valore AMERICA il primo giorno della settimana sarà la domenica e
dunque il martedì è il terzo giorno della settimana.
WTO >select sysdate from dual;
SYSDATE
---------
22-FEB-11
T
-
2
Modificata sessione.
T
-
3
Modificata sessione.
TO
--
22
250
Il codice di formato ‘ddd’ stampa il giorno dell’anno da uno a
366.
WTO >select to_char(sysdate,'ddd') from dual;
TO_
---
053
TO
--
02
Session altered.
TO_
---
may
Modificata sessione.
TO_
---
Mag
Session altered.
TO_CHAR(D
---------
May
Modificata sessione.
251
WTO >select to_char(date'2010-05-01','month') from dual;
TO_CHAR(D
---------
maggio
T
-
1
TO
--
11
TO_
---
011
TO_C
----
2011
TO
--
10
TO
--
22
252
Il codice ‘mi’ rappresenta i minuti all’interno dell’ora. Da zero a
59.
WTO >select to_char(sysdate,'mi') from dual;
TO
--
42
TO
--
42
TO_CH
-----
81769
TO_CHAR(SYSDATE,'DD
-------------------
22-02-2011 22:43:24
TO_CHAR(SYSDATE,'"OGGIE''IL
---------------------------
oggi e' il 22 di febbraio .
253
7.5.8 Formati per la rappresentazione dei numeri
Come per le date anche i numeri hanno i loro formati di
rappresentazione. La cosa è però più semplice. In una stringa di
formato per i numeri si utilizzano: la lettera “g” per rappresentare il
separatore di migliaia; la lettera “d” per rappresentare il separatore di
decimali; la cifra “9” o la cifra “0” per rappresentare le cifre che
compongono il numero.
WTO >select to_char(123456.234,'9g999g999d999')
2 from dual;
TO_CHAR(123456
--------------
123.456,234
TO_CHAR(123456
--------------
123.456,200
TO_CHAR(123456
--------------
0.123.456,234
TO_CHAR(123456
--------------
0.123.456,200
TO_CHAR
-------
,30
254
WTO >select to_char(0.3,'000d00')
2 from dual;
TO_CHAR
-------
000,30
TO_CHAR
-------
0,30
COD_FISC
----------------
RSSMRC70R20H501X
VRDMTT69S02H501X
NRILCU77A22F205X
SPSGNN71B10F839X
RSSPSQ70C14F839X
Selezionate 8 righe.
NVL(COD_FISC,'VALORENO
----------------------
RSSMRC70R20H501X
VALORE NON DISPONIBILE
VRDMTT69S02H501X
NRILCU77A22F205X
VALORE NON DISPONIBILE
SPSGNN71B10F839X
RSSPSQ70C14F839X
VALORE NON DISPONIBILE
Selezionate 8 righe.
255
La funzione può essere applicata anche a numeri e date.
WTO >create table test (a varchar2(10), b number, c date);
Tabella creata.
Creata 1 riga.
Creata 1 riga.
Creata 1 riga.
Creata 1 riga.
A B C
---------- ---------- ---------
test1 1 22-FEB-11
test2 22-FEB-11
test3 3
test4
A NVL(B,9) NVL(C,SYS
---------- ---------- ---------
test1 1 22-FEB-11
test2 9 22-FEB-11
test3 3 25-FEB-11
test4 9 25-FEB-11
• NVL2
La funzione NVL2 riceve in input tre parametri. Se il primo
parametro è diverso da NULL ritorna il secondo parametro, altrimenti il
terzo.
256
WTO >select nvl2(cod_fisc,'VALORIZZATO','NON VALORIZZATO')
2 from clienti;
NVL2(COD_FISC,'
---------------
VALORIZZATO
NON VALORIZZATO
VALORIZZATO
VALORIZZATO
NON VALORIZZATO
VALORIZZATO
VALORIZZATO
NON VALORIZZATO
Selezionate 8 righe.
NVL2(B,'VALORIZ
---------------
VALORIZZATO
NON VALORIZZATO
VALORIZZATO
NON VALORIZZATO
NVL2(C,'VALORIZ
---------------
VALORIZZATO
VALORIZZATO
NON VALORIZZATO
NON VALORIZZATO
• COALESCE
La funzione COALESCE è una generalizzazione della NVL.
Riceve in input un numero a piacere di espressioni e ritorna la prima
che è diversa da NULL.
WTO >drop table test;
Tabella eliminata.
Tabella creata.
Creata 1 riga.
Creata 1 riga.
257
WTO >insert into test values (null,null,'C','D');
Creata 1 riga.
Creata 1 riga.
Creata 1 riga.
A B C D
---------- ---------- ---------- ----------
A B C D
@ B C D
@ @ C D
@ @ @ D
@ @ @ @
COALESCE(A
----------
A
B
C
D
@
COALESCE(COD_FIS
----------------
RSSMRC70R20H501X
NON VALORIZZATO
VRDMTT69S02H501X
NRILCU77A22F205X
NON VALORIZZATO
SPSGNN71B10F839X
RSSPSQ70C14F839X
NON VALORIZZATO
Selezionate 8 righe.
258
• LNNVL
La funzione LNNVL riceve in input una condizione e ritorna
TRUE quando la condizione è vera oppure quando la condizione a
falsa a causa di valori NULL. Se, ad esempio, cerchiamo tutti i codici
fiscali che cominciano per R otteniamo
WTO >select nome, cod_fisc from clienti;
NOME COD_FISC
-------- ----------------
MARCO RSSMRC70R20H501X
GIOVANNI @
MATTEO VRDMTT69S02H501X
LUCA NRILCU77A22F205X
AMBROGIO @
GENNARO SPSGNN71B10F839X
PASQUALE RSSPSQ70C14F839X
VINCENZO @
Selezionate 8 righe.
NOME
--------
MARCO
PASQUALE
NOME
--------
GIOVANNI
MATTEO
LUCA
AMBROGIO
GENNARO
VINCENZO
Selezionate 6 righe.
259
• NULLIF
La funzione NULLIF riceve in input due parametri e ritorna NULL
quando i due parametri sono uguali.
WTO >select nome, nullif(nome,'MARCO')
2 from clienti;
NOME NULLIF(NOME,'MARCO')
-------- ------------------------------
MARCO @
GIOVANNI GIOVANNI
MATTEO MATTEO
LUCA LUCA
AMBROGIO AMBROGIO
GENNARO GENNARO
PASQUALE PASQUALE
VINCENZO VINCENZO
Selezionate 8 righe.
COD_FISC NULLIF(COD_FISC,
---------------- ----------------
RSSMRC70R20H501X RSSMRC70R20H501X
@ @
VRDMTT69S02H501X VRDMTT69S02H501X
NRILCU77A22F205X NRILCU77A22F205X
@ @
SPSGNN71B10F839X SPSGNN71B10F839X
RSSPSQ70C14F839X RSSPSQ70C14F839X
@ @
Selezionate 8 righe.
260
Successivamente, nella versione 11g, è stata aggiunta anche la
funzione REGEXP_COUNT. In questo paragrafo si vedranno alcuni
esempi d’utilizzo.
• REGEXP_INSTR
La funzione REGEXP_INSTR funziona più o meno come la
INSTR: serve a cercare, all'interno di una stringa data, una sottostringa
che verifica il pattern della regex data in input.
Se non trova nessuna sottostringa che verifica la regex ritorna 0.
Il prototipo della funzione è
REGEXP_INSTR (source_string, pattern, position, occurrence,
return_option, match_parameter)
Dove
• source_string è la stringa in cui cercare;
• pattern è l'espressione regolare;
• position è la posizione a partire dalla quale bisogna cercare;
• occurrence è l'occorrenza richiesta;
• return_option può valere 0 se si desidera la posizione del
primo carattere della sottostringa trovata, 1 se si desidera la
posizione del primo carattere successivo alla sottostringa
trovata;
• match_parameter è un flag che può valere:
9 i (ricerca case-insensitive),
9 c (ricerca case-sensitive),
9 n (il carattere jolly '.' trova anche il ritorno a capo),
9 m (interpreta la stringa in modalità multi-linea)
Solo i primi due parametri sono obbligatori.
Facciamo un esempio: cerchiamo nella stringa 'Questa è una
stringa di prova che mi consente di illustrare le regex in Oracle' la
seconda parola composta di due lettere:
WTO >WITH T AS (
2 SELECT 'Questa è una stringa di prova che mi consente
3 di illustrare le regex in Oracle' str from dual)
4 Select str, REGEXP_INSTR(str,'(^|\ )[[:alpha:]]{2}($|\ )',
5 1, 2)+1 pos
6 FROM t;
STR POS
-------------------------------------------------- ----------
Questa è una stringa di prova che mi consente 35
di illustrare le regex in Oracle
261
• REGEXP_REPLACE
La funzione REGEXP_REPLACE consente di sostituire una
stringa trovata mediante espressione regolare con un'altra stringa
data:
REGEXP_REPLACE(source_string, pattern, replace_string, position,
occurrence, match_parameter)
Dove
• source_string è la stringa in cui effettuare la ricerca e la
sostituzione;
• pattern è l'espressione regolare;
• replace_string è la stringa da sostituire a quanto trovato
mediante il pattern;
• position è la posizione a partire dalla quale bisogna cercare;
• occurrence è l'occorrenza richiesta;
• match_parameter come nella REGEXP_INSTR.
Se quindi vogliamo sostituire, nella stringa dell'esempio
precedente, la seconda parola di due caratteri con una X, dobbiamo
fare:
WTO >WITH T AS (
2 SELECT 'Questa è una stringa di prova che mi consente
3 di illustrare le regex in Oracle' str from dual)
4 Select REGEXP_REPLACE(str,'(^|\ )[[:alpha:]]{2}($|\ )',
5 ' X ',1, 2) newstr
6 FROM t;
NEWSTR
--------------------------------------------------------------
Questa è una stringa di prova che X consente
di illustrare le regex in Oracle
• REGEXP_SUBSTR
La funzione REGEXP_SUBSTR consente di estrarre una
sottostringa:
REGEXP_SUBSTR (source_string , pattern, position, occurrence,
match_parameter)
Dove
• source_string è la stringa in cui effettuare la ricerca e da cui
estrarre la sottostringa;
• pattern è l'espressione regolare;
• position è la posizione a partire dalla quale bisogna cercare;
262
• occurrence è l'occorrenza richiesta;
• match_parameter come nella REGEXP_INSTR.
Quindi per estrarre la seconda parola da due caratteri:
WTO >WITH T AS (
2 SELECT 'Questa è una stringa di prova che mi
3 consente di illustrare le regex in Oracle' str from dual)
4 Select str,
5 trim(REGEXP_SUBSTR(str,'(^|\ )[[:alpha:]]{2}($|\ )'
6 ,1, 2)) substr
7* FROM t
STR SUBSTR
-------------------------------------------------- ------
Questa è una stringa di prova che mi di
consente di illustrare le regex in Oracle
• REGEXP_COUNT
La funzione REGEXP_COUNT ci consente di contare le
occorrenze nella source string che verificano il pattern:
REGEXP_COUNT (source_char , pattern, position, match_param)
Dove
• source_char è la stringa in cui effettuare la ricerca;
• pattern è l'espressione regolare;
• position è la posizione a partire dalla quale bisogna cercare;
• return_option come nella REGEXP_INSTR.
Quindi per ottenere il numero di parole di due caratteri:
WTO> WITH T AS (
2 SELECT 'Questa è una stringa di prova che mi consente
3 di illustrare le regex in Oracle' str from dual)
4 Select str,
5 REGEXP_COUNT(str,'(^|\ )[[:alpha:]]{2}($|\ )',1) count
6 FROM t;
STR COUNT
-------------------------------------------------- ----------
Questa è una stringa di prova che mi consente 4
di illustrare le regex in Oracle
263
STR SUBSTR
-------------------------------------------------- ------
Questa è una stringa di prova che mi consente di
di illustrare le regex in Oracle
• REGEXP_LIKE
La condizione REGEXP_LIKE torna boolean e può essere
utilizzata nella WHERE o nella HAVING di una query:
REGEXP_LIKE (source_string, pattern, match_parameter)
Dove
• source_string è la stringa in cui effettuare la ricerca;
• pattern è l'espressione regolare;
• match_parameter come nella REGEXP_INSTR.
REGEXP_LIKE(str,ptrn,mp) è logicamente equivalente a
REGEXP_INSTR(str,ptrn,1,1,mp)>0
Tiriamo per esempio fuori tutte le stringhe che hanno almeno
una parola da due caratteri:
WTO> WITH T AS (
2 SELECT 'Stringa senza parole di2' str from dual union
3 SELECT 'prima Stringa con parole di 2 caratteri' from dual
4 union
5 SELECT 'seconda Stringa con parole di 2 caratteri' from dual
6 union
7 SELECT 'Altra Stringa senza parole di2' from dual
8 )
9 Select str
10 from t
11 where REGEXP_LIKE(str,'(^|\ )[[:alpha:]]{2}($|\ )');
STR
--------------------------------------------------
prima Stringa con parole di 2 caratteri
seconda Stringa con parole di 2 caratteri
264
WTO> WITH T AS (
2 SELECT 'Stringa senza parole di2' str from dual union
3 SELECT 'prima Stringa con parole di 2 caratteri' from dual
4 union
5 SELECT 'seconda Stringa con parole di 2 caratteri' from dual
6 union
7 SELECT 'Altra Stringa senza parole di2' from dual
8 )
9 Select str
10 from t
11 where REGEXP_INSTR(str,'(^|\ )[[:alpha:]]{2}($|\ )')>0;
STR
--------------------------------------------------
prima Stringa con parole di 2 caratteri
seconda Stringa con parole di 2 caratteri
265
Il dato da controllare è il campo PAGATA. Se questo vale ‘S’
bisogna estrarre ‘PAGATA’, se questo vale ‘N’ bisogna estrarre ‘NON
PAGATA’.
La funzione DECODE consente anche di indicare come ultimo
parametro, alla fine delle coppie valore-risultato, un valore di default da
ritornare nel caso in cui il dato da controllare non sia uguale a nessuno
dei valori. Sempre in riferimento le fatture, ipotizziamo di volere
etichettare la fattura numero uno con ‘PRIMA’, la numero due con
‘SECONDA’ e tutte le altre con ‘SUCCESSIVE’.
WTO >select num_fattura, data_fattura,
2 decode(num_fattura,1,'PRIMA',2,'SECONDA','SUCCESSIVE') posiz
3 from fatture;
NOME CF?
-------- ------
MARCO CON CF
GIOVANNI NO CF
MATTEO CON CF
LUCA CON CF
AMBROGIO NO CF
GENNARO CON CF
PASQUALE CON CF
VINCENZO NO CF
Selezionate 8 righe.
266
WTO >select num_fattura, importo,
2 decode(sign(importo-500),-1,'<500',0,'=500',1,'>500') fascia
3 from fatture;
267
WTO >select num_fattura, data_fattura,
2 CASE pagata when 'S' then 'PAGATA'
3 when 'N' then 'NON PAGATA' END "pagata?"
4 from fatture;
NUM_FATTURA DATA_FATT pagata?
----------- --------- ----------
1 01-OTT-10 PAGATA
2 01-DIC-10 NON PAGATA
3 20-OTT-10 PAGATA
4 01-FEB-11 NON PAGATA
5 01-DIC-10 PAGATA
WTO >select num_fattura, data_fattura,
2 CASE num_fattura when 1 then 'PRIMA'
3 when 2 then 'SECONDA'
4 else 'SUCCESSIVE' end posiz
5 from fatture;
NUM_FATTURA DATA_FATT POSIZ
----------- --------- ----------
1 01-OTT-10 PRIMA
2 01-DIC-10 SECONDA
3 20-OTT-10 SUCCESSIVE
4 01-FEB-11 SUCCESSIVE
5 01-DIC-10 SUCCESSIVE
WTO >select nome,
2 case cod_fisc when null then 'NO CF'
3 else 'CON CF' end "CF?"
4 from clienti;
NOME CF?
-------- ------
MARCO CON CF
GIOVANNI CON CF
MATTEO CON CF
LUCA CON CF
AMBROGIO CON CF
GENNARO CON CF
PASQUALE CON CF
VINCENZO CON CF
Selezionate 8 righe.
268
Come si vede, tre query su quattro hanno dato lo stesso
risultato una, invece, è errata. La struttura CASE utilizzata nella
modalità appena mostrata, non è in grado di gestire i valori nulli. O
meglio li gestisce come UNKNOWN. Di conseguenza nella terza query
la condizione WHEN NULL è sempre falsa.
Per ovviare a questo problema la query può essere riscritta
utilizzando la seconda sintassi ammessa dalla struttura CASE.
WTO >select nome,
2 case when (cod_fisc is null) then 'NO CF'
3 else 'CON CF' end "CF?"
4 from clienti;
NOME CF?
-------- ------
MARCO CON CF
GIOVANNI NO CF
MATTEO CON CF
LUCA CON CF
AMBROGIO NO CF
GENNARO CON CF
PASQUALE CON CF
VINCENZO NO CF
Selezionate 8 righe.
269
WTO >select greatest(-3,5,1,7,2,-4)
2 from dual;
GREATEST(-3,5,1,7,2,-4)
-----------------------
7
GREATEST(
---------
27-FEB-11
GREATE
------
SANDRO
• LEAST
La funzione LEAST è identica alla GREATEST con la differenza
che ritorna il più piccolo dei parametri ricevuti.
WTO >select least(-3,5,1,7,2,-4)
2 from dual;
LEAST(-3,5,1,7,2,-4)
--------------------
-4
LEAST(SYS
---------
22-FEB-11
LEAST('
-------
ALBERTO
270
7.6 JOIN: ricerche da più tabelle
Tutte le query presentate fino a questo punto attingevano ad
una singola tabella per prelevare i dati. Ipotizziamo invece di voler
estrarre, per ogni cliente, nome, cognome, indirizzo, nome del comune
e provincia in cui risiede.
I dati nome e cognome sono nella tabella CLIENTI mentre i
nomi dei comuni e le province sono nella tabella COMUNI. Sarà
dunque necessario accedere contemporaneamente a due tabelle
eseguendo una JOIN.
271
A titolo di esempio estraiamo i campi NOME, COGNOME,
INDIRIZZO e COMUNE dalla tabella CLIENTI insieme ai dati
COD_COMUNE, DES_COMUNE e PROVINCIA della tabella
COMUNI.
WTO >select nome, cognome, indirizzo, comune, cod_comune,
2 des_comune, provincia
3 from clienti, comuni;
272
GENNARO ESPOSITO VIA CARACCIOLO, 100 F839 M297 FIUMICINO RM
GENNARO ESPOSITO VIA CARACCIOLO, 100 F839 F205 MILANO MI
GENNARO ESPOSITO VIA CARACCIOLO, 100 F839 G686 PIOLTELLO MI
GENNARO ESPOSITO VIA CARACCIOLO, 100 F839 E415 LAINATE MI
GENNARO ESPOSITO VIA CARACCIOLO, 100 F839 F839 NAPOLI NA
GENNARO ESPOSITO VIA CARACCIOLO, 100 F839 G964 POZZUOLI NA
GENNARO ESPOSITO VIA CARACCIOLO, 100 F839 G902 PORTICI NA
PASQUALE RUSSO VIA GIULIO CESARE, 119 F839 H501 ROMA RM
PASQUALE RUSSO VIA GIULIO CESARE, 119 F839 G811 POMEZIA RM
PASQUALE RUSSO VIA GIULIO CESARE, 119 F839 M297 FIUMICINO RM
PASQUALE RUSSO VIA GIULIO CESARE, 119 F839 F205 MILANO MI
PASQUALE RUSSO VIA GIULIO CESARE, 119 F839 G686 PIOLTELLO MI
PASQUALE RUSSO VIA GIULIO CESARE, 119 F839 E415 LAINATE MI
PASQUALE RUSSO VIA GIULIO CESARE, 119 F839 F839 NAPOLI NA
PASQUALE RUSSO VIA GIULIO CESARE, 119 F839 G964 POZZUOLI NA
PASQUALE RUSSO VIA GIULIO CESARE, 119 F839 G902 PORTICI NA
VINCENZO AMATO VIA NAPOLI, 234 G964 H501 ROMA RM
VINCENZO AMATO VIA NAPOLI, 234 G964 G811 POMEZIA RM
VINCENZO AMATO VIA NAPOLI, 234 G964 M297 FIUMICINO RM
VINCENZO AMATO VIA NAPOLI, 234 G964 F205 MILANO MI
VINCENZO AMATO VIA NAPOLI, 234 G964 G686 PIOLTELLO MI
VINCENZO AMATO VIA NAPOLI, 234 G964 E415 LAINATE MI
VINCENZO AMATO VIA NAPOLI, 234 G964 F839 NAPOLI NA
VINCENZO AMATO VIA NAPOLI, 234 G964 G964 POZZUOLI NA
VINCENZO AMATO VIA NAPOLI, 234 G964 G902 PORTICI NA
Selezionate 72 righe.
273
WTO >select nome, cognome, indirizzo, comune, cod_comune,
2 des_comune, provincia
3 from clienti, comuni
4 where comune=cod_comune;
Selezionate 8 righe.
Selezionate 8 righe.
274
La query precedente, ad esempio, può essere ristretta ai soli
clienti il cui nome comincia per ‘M’
WTO >select nome, cognome, indirizzo, comune, cod_comune,
2 des_comune, provincia
3 from clienti, comuni
4 where comune=cod_comune
5 and nome like 'M%';
275
7.6.4 Outer join
Come già detto la INNER JOIN causa la perdita dei record di
una tabella quando questi non hanno corrispondenza nell’altra tabella.
Un ulteriore esempio può essere la selezione congiunta di ordini
e fatture.
WTO >select * from ordini;
NUM_ORDINE NUM_FATTURA
---------- -----------
1 1
1 2
2 3
3 4
5 5
276
vogliamo tutti gli ordini anche quelli che non hanno fatture
corrispondenti. Quindi la colonna in cui vogliamo ignorare le mancanze
di corrispondenze è F.NUM_ORDINE. La condizione di JOIN diventa
dunque
O.NUM_ORDINE=F.NUM_ORDINE(+)
NUM_ORDINE NUM_FATTURA
---------- -----------
1 1
1 2
2 3
3 4
5 5
4
Selezionate 6 righe.
NUM_ORDINE NUM_FATTURA
---------- -----------
1 1
1 2
2 3
3 4
5 5
277
WTO> desc emp
Name Null? Type
----------------- -------- ------------
EMPNO NOT NULL NUMBER(4)
ENAME VARCHAR2(10)
JOB VARCHAR2(9)
MGR NUMBER(4)
HIREDATE DATE
SAL NUMBER(7,2)
COMM NUMBER(7,2)
DEPTNO NUMBER(2)
ENAME DEPTNO
---------- ----------
SMITH 20
ALLEN 30
WARD 30
JONES 20
MARTIN 30
BLAKE 30
CLARK 10
SCOTT 20
KING 10
TURNER 30
ADAMS 20
JAMES 30
FORD 20
MILLER 10
14 rows selected.
DEPTNO DNAME
---------- --------------
10 ACCOUNTING
20 RESEARCH
30 SALES
40 OPERATIONS
278
WTO> select ename, e.deptno, d.deptno, dname
2 from emp e, dept d
3 where e.deptno=d.deptno;
14 rows selected.
1 row updated.
13 rows selected.
279
WTO> select ename, e.deptno, d.deptno, dname
2 from emp e, dept d
3 where e.deptno=d.deptno(+);
14 rows selected.
14 rows selected.
280
WTO> select ename, e.deptno, d.deptno, dname
2 from emp e, dept d
3 where e.deptno(+)=d.deptno(+);
where e.deptno(+)=d.deptno(+)
*
ERROR at line 3:
ORA-01468: a predicate may reference only one outer-joined table
15 rows selected.
281
7.7 Gli operatori insiemistici
Abbiamo finora associato molte volte il risultato di una query ad
un insieme di record. I principali operatori insiemistici sono
implementati anche in SQL.
7.7.1 UNION
L’operatore UNION consente di specificare due query distinte ed
ottenere come risultato l’unione insiemistica dei risultati. Le due query
devono ovviamente estrarre lo stesso numero di colonne e le colonne
che si trovano nella stessa posizione nelle diverse query devono
essere dello stesso tipo di dato.
WTO >select nome, cognome from clienti
2 where cod_fisc is null
3 union
4 select nome, cognome from clienti
5 where comune='H501';
NOME COGNOME
------------------------------ ------------------------------
AMBROGIO COLOMBO
GIOVANNI BIANCHI
MARCO ROSSI
VINCENZO AMATO
NOME COGNOME
------------------------------ --------------------
MARCO ROSSI
GIOVANNI BIANCHI
AMBROGIO COLOMBO
VINCENZO AMATO
282
WTO >select nome, cognome from clienti
2 where cod_fisc is null
3 union
4 select nome, cod_cliente from clienti
5 where comune='H501';
select nome, cognome from clienti
*
ERRORE alla riga 1:
ORA-01790: expression must have same datatype as corresponding
expression
NOME COGNOME
------------------------------ ------------------------------
AMBROGIO COLOMBO
GIOVANNI BIANCHI
VIA LAURENTINA, 700 H501
VIA OSTIENSE, 850 H501
VINCENZO AMATO
NOME COGNOME
------------------------------ ---------------------
AMBROGIO COLOMBO
GIOVANNI BIANCHI
VIA LAURENTINA, 700 H501
VIA OSTIENSE, 850 H501
VINCENZO AMATO
283
WTO >select nome, cognome from clienti
2 where cod_fisc is null
3 union
4 select indirizzo, comune
5 from clienti
6 where comune='H501'
7 order by 1;
NOME COGNOME
------------------------------ ---------------------
AMBROGIO COLOMBO
GIOVANNI BIANCHI
VIA LAURENTINA, 700 H501
VIA OSTIENSE, 850 H501
VINCENZO AMATO
NOME
------------------------------
AMBROGIO
GENNARO
GIOVANNI
LUCA
MARCO
MATTEO
PASQUALE
VINCENZO
284
ENAME DEPTNO DEPTNO DNAME
---------- ---------- ---------- --------------
ADAMS 20 20 RESEARCH
ALLEN 30 30 SALES
BLAKE 30 30 SALES
CLARK 10 10 ACCOUNTING
FORD
JAMES 30 30 SALES
JONES 20 20 RESEARCH
KING 10 10 ACCOUNTING
MARTIN 30 30 SALES
MILLER 10 10 ACCOUNTING
SCOTT 20 20 RESEARCH
SMITH 20 20 RESEARCH
TURNER 30 30 SALES
WARD 30 30 SALES
40 OPERATIONS
15 rows selected.
NOME
------------------------------
MARCO
GIOVANNI
MATTEO
LUCA
AMBROGIO
GENNARO
PASQUALE
VINCENZO
MARCO
GIOVANNI
MATTEO
LUCA
AMBROGIO
GENNARO
PASQUALE
VINCENZO
Selezionate 16 righe.
285
WTO> select ename, e.deptno, d.deptno, dname
2 from emp e, dept d
3 where e.deptno=d.deptno(+)
4 union all
5 select ename, e.deptno, d.deptno, dname
6 from emp e, dept d
7 where e.deptno(+)=d.deptno and e.deptno is null;
15 rows selected.
7.7.3 INTERSECT
L’operatore INTERSECT restituisce l’intersezione insiemistica
dei due insiemi di record estratti dalla due singole query.
WTO >select nome, cognome from clienti
2 where cod_fisc is not null;
NOME COGNOME
------------------------------ --------------------
LUCA NERI
MARCO ROSSI
PASQUALE RUSSO
GENNARO ESPOSITO
MATTEO VERDI
NOME COGNOME
------------------------------ --------------------
MARCO ROSSI
GIOVANNI BIANCHI
286
WTO >select nome, cognome from clienti
2 where cod_fisc is not null
3 intersect
4 select nome, cognome from clienti
5 where comune='H501';
NOME COGNOME
------------------------------ --------------------
MARCO ROSSI
7.7.4 MINUS
L’operatore MINUS implementa il complemento insiemistico.
Quando due query vengono messe in relazione con questo operatore
Oracle estrae i record presenti nel risultato della prima query e non
presenti nel risultato della seconda.
WTO >select nome, cognome from clienti
2 where cod_fisc is not null;
NOME COGNOME
------------------------------ ------------------
LUCA NERI
MARCO ROSSI
PASQUALE RUSSO
GENNARO ESPOSITO
MATTEO VERDI
NOME COGNOME
------------------------------ ------------------
MARCO ROSSI
GIOVANNI BIANCHI
WTO >select nome, cognome from clienti where cod_fisc is not null
2 minus
3 select nome, cognome from clienti where comune='H501';
NOME COGNOME
------------------------------ ------------------
GENNARO ESPOSITO
LUCA NERI
MATTEO VERDI
PASQUALE RUSSO
NOME COGNOME
------------------------------ ------------------
GIOVANNI BIANCHI
287
7.8 Ricerche innestate
All’interno di una query possono essere incluse altre espressioni
di query. Queste prendono il nome di subquery.
Una subquery può essere presente praticamente dappertutto,
facciamo qualche esempio. L’importo medio delle fatture si calcola con
la semplice query.
WTO >select avg(importo) from fatture;
AVG(IMPORTO)
------------
600
288
WTO >select num_fattura, importo
2 from fatture
3 where importo>=(select avg(importo) IMPORTO_MEDIO
4 from fatture);
NUM_FATTURA IMPORTO
----------- ----------
3 700
4 1000
NUM_FATTURA IMPORTO
----------- ----------
1 300
2 500
5 500
4 1000
3 700
289
Oracle incorpora alcuni operatori appositamente creati per
gestire il confronto di un singolo valore con i molti record estratti da
una subquery.
• ANY
L’operatore di confronto ANY ritorna VERO se almeno un record
estratto dalla subquery verifica la condizione. Ipotizziamo ad esempio
di volere estrarre tutte le fatture il cui importo è maggiore dell’importo
di almeno uno degli ordini.
WTO >select num_ordine, importo from ordini;
NUM_ORDINE IMPORTO
---------- ----------
1 800
2 700
3 1000
4 1200
5 1700
NUM_FATTURA IMPORTO
----------- ----------
1 300
2 500
3 700
4 1000
5 500
NUM_FATTURA IMPORTO
----------- ----------
4 1000
NUM_FATTURA IMPORTO
----------- ----------
4 1000
3 700
• ALL
L’operatore ALL ritorna VERO se la condizione a cui è applicato
è vera per tutti i record estratti dalla subquery.
Ipotizziamo ad esempio di volere estrarre gli ordini il cui importo
è maggiore dell’importo di tutte le fatture esistenti.
290
WTO >select num_ordine, importo from ordini
2 where importo > ALL (select importo
3 from fatture);
NUM_ORDINE IMPORTO
---------- ----------
4 1200
5 1700
• IN (subquery)
L’operatore IN è del tutto analogo a quello già incontrato in
precedenza. La differenza consiste nel fatto che la lista dei valori è
ottenuta mediante una subquery.
Elenco di tutti i clienti residenti in provincia di Roma.
WTO >select nome, cognome, indirizzo
2 from clienti
3 where comune in (select cod_comune
4 from comuni
5 where provincia='RM');
• EXISTS
L’operatore EXISTS ritorna VERO se la subquery estrae almeno
un record.
Si ipotizzi ad esempio di volere visualizzare nome e cognome
dei clienti che hanno fatto almeno un ordine.
291
WTO >select nome, cognome
2 from clienti c
3 where exists (select * from ordini o
4 where o.cod_cliente = c.cod_cliente);
NOME COGNOME
-------- --------
MARCO ROSSI
GIOVANNI BIANCHI
LUCA NERI
AMBROGIO COLOMBO
PASQUALE RUSSO
NOME COGNOME
-------- --------
MARCO ROSSI
GIOVANNI BIANCHI
LUCA NERI
AMBROGIO COLOMBO
PASQUALE RUSSO
Creata 1 riga.
NOME COGNOME
-------- --------
MARCO ROSSI
MARCO ROSSI
GIOVANNI BIANCHI
LUCA NERI
AMBROGIO COLOMBO
PASQUALE RUSSO
Selezionate 6 righe.
292
WTO >select nome, cognome
2 from clienti c
3 where exists (select * from ordini o
4 where o.cod_cliente = c.cod_cliente);
NOME COGNOME
-------- --------
MARCO ROSSI
GIOVANNI BIANCHI
LUCA NERI
AMBROGIO COLOMBO
PASQUALE RUSSO
NOME COGNOME
-------- --------
GIOVANNI BIANCHI
LUCA NERI
MARCO ROSSI
AMBROGIO COLOMBO
PASQUALE RUSSO
293
Oracle può eseguire dapprima la subquery
WTO >select avg(importo) from fatture;
AVG(IMPORTO)
------------
600
NOME MAX(NUM_ORDINE)
-------- ---------------
AMBROGIO 4
PASQUALE 5
LUCA 3
MARCO 1
GIOVANNI 2
294
WTO >select nome,
2 (select max(num_ordine) from ordini o
3 where o.cod_cliente=c.cod_cliente) max_num_ord
4 from clienti c;
NOME MAX_NUM_ORD
-------- -----------
MARCO 1
GIOVANNI 2
MATTEO
LUCA 3
AMBROGIO 4
GENNARO
PASQUALE 5
VINCENZO
Selezionate 8 righe.
NOME MAX(NUM_ORDINE)
-------- ---------------
MATTEO
AMBROGIO 4
PASQUALE 5
LUCA 3
VINCENZO
MARCO 1
GIOVANNI 2
GENNARO
Selezionate 8 righe.
295
una subquery correlata è molto maggiore del costo di una subquery
scorrelata di pari complessità.
Nella clausola WITH possono essere definite più viste una dietro
l’altra, basta separale con una virgola.
Complichiamo, ad esempio, la query precedente aggiungendo
anche l’importo minimo degli ordini.
296
WTO >with media as
2 (select avg(importo) IMPORTO_MEDIO from fatture),
3 minimo_ordine as
4 (select min(importo) MIN_ORDINE from ordini)
5 select num_fattura, importo, IMPORTO_MEDIO, MIN_ORDINE
6 from fatture, media, minimo_ordine;
7.9.2 COMMIT
Per default ogni transazione legge solo i dati esplicitamente
confermati da altre transazioni ed i dati modificati, anche se ancora
non confermati, nella propria transazione.
Facciamo un esempio. Apriamo due sessioni di SQL*plus
utilizzando lo stesso utente. Negli esempi seguenti ho utilizzato il
prompt “WT1” per indicare la prima sessione ed il prompt “WT2” per
indicare la seconda sessione.
La sessione 1 crea una tabella.
WT1 >create table test_tr (a number, b varchar2(10));
Tabella creata.
297
WT2 >desc test_tr
Nome Nullo? Tipo
----------------- -------- ------------
A NUMBER
B VARCHAR2(10)
Creata 1 riga.
A B
---------- ----------
1 ins wt1
Commit completato.
A B
---------- ----------
1 ins wt1
Creata 1 riga.
Tabella creata.
298
WT2 >select * from test_tr;
A B
---------- ----------
1 ins wt1
2 ins wt1
7.9.3 ROLLBACK
Il comando ROLLBACK consente di annullare tutte le modifiche
apportate durante la transazione.
WT1 >insert into test_tr values (3,'ins wt1');
Creata 1 riga.
Creata 1 riga.
Creata 1 riga.
A B
---------- ----------
1 ins wt1
2 ins wt1
3 ins wt1
4 ins wt1
X
----------
100
WT1 >rollback;
Rollback completato.
A B
---------- ----------
1 ins wt1
2 ins wt1
299
7.9.4 SAVEPOINT
Il comando SAVEPOINT consente di inserire dei segnaposto
all’interno della transazione in modo che sia possibile, mediante una
opzione del comando ROLLBACK, annullare parzialmente le modifiche
fatte durante la transazione.
Nell’esempio seguente vengono definiti alcuni savepoint
all’interno della transazione e poi si annullano alcuni comandi eseguiti.
Innanzitutto si inserisce una riga in TEST_TR e si crea un
savepoint.
WT1 >insert into test_tr values (3,'ins wt1');
Creata 1 riga.
Creato savepoint.
Creata 1 riga.
Creato savepoint.
Creata 1 riga.
A B
---------- ----------
1 ins wt1
2 ins wt1
3 ins wt1
4 ins wt1
X
----------
200
300
WT1 >rollback to rec_200;
Rollback completato.
A B
---------- ----------
1 ins wt1
2 ins wt1
3 ins wt1
X
----------
200
Rollback completato.
A B
---------- ----------
1 ins wt1
2 ins wt1
3 ins wt1
Commit completato.
A B
---------- ----------
1 ins wt1
2 ins wt1
3 ins wt1
301
Un ulteriore ROLLBACK non produrrà risultato, ormai la
sessione è stata confermata.
WT1 >rollback;
Rollback completato.
A B
---------- ----------
1 ins wt1
2 ins wt1
3 ins wt1
Impostata transazione.
A B
---------- ----------
1 ins wt1
2 ins wt1
3 ins wt1
Creata 1 riga.
302
WT2 >commit;
Commit completato.
A B
---------- ----------
1 ins wt1
2 ins wt1
3 ins wt1
Rollback completato.
A B
---------- ----------
1 ins wt1
2 ins wt1
3 ins wt1
4 ins wt2
Impostata transazione.
303
Per comprendere meglio facciamo un esempio. La sessione 1
aggiorna il primo record di TEST_TR.
WT1 >select * from test_tr;
A B
---------- ----------
1 ins wt1
2 ins wt1
3 ins wt1
4 ins wt2
Aggiornata 1 riga.
A B
---------- ----------
1 agg
2 ins wt1
3 ins wt1
4 ins wt2
Commit completato.
Aggiornata 1 riga.
A B
---------- ----------
10 agg
2 ins wt1
3 ins wt1
4 ins wt2
WT2 >commit;
Commit completato.
304
L’altro livello di isolamento che si può utilizzare è
SERIALIZABLE. Questo livello di isolamento prevede che una
transazione non possa vedere e modificare record modificati e
committati in un’altra transazione se tali modifiche erano state
apportate ma non confermate all’istante dell’apertura della transazione
serializzabile. Ci vuole un esempio.
Ipotizziamo che la sessione 2 aggiorni un record in TEST_TR.
WT2 >select * from test_tr;
A B
---------- ----------
10 agg
2 ins wt1
3 ins wt1
4 ins wt2
Aggiornata 1 riga.
A B
---------- ----------
10 agg
2 agg2
3 ins wt1
4 ins wt2
Impostata transazione.
A B
---------- ----------
10 agg
2 ins wt1
3 ins wt1
4 ins wt2
Commit completato.
305
A questo punto in una normale transazione READ COMMITTED
la sessione 1 vedrebbe le modifiche apportate dalla sessione 2 e
potrebbe a sua volta aggiornare il medesimo record.
In una transazione SERIALIZABLE, invece, ciò non è possibile.
WT1 >update test_tr set a=20 where a=2;
update test_tr set a=20 where a=2
*
ERRORE alla riga 1:
ORA-08177: can't serialize access for this transaction
A B
---------- ----------
10 agg
2 ins wt1
3 ins wt1
4 ins wt2
Tabella/e bloccata/e.
306
WT1 >roll;
Completato rollback.
La sessione 2 va avanti.
WT2 >update test_tr
2 set b='agg3'
3 where a=3;
Aggiornata 1 riga.
A B
---------- ----------
10 agg
2 agg2
3 agg3
4 ins wt2
WT2 >commit;
Commit completato.
Aggiornata 1 riga.
307
Facciamo un esempio.
La sessione 1 acquisisce un lock sulla riga numero tre della
tabella TEST_TR.
WT1 >update test_tr set b='x' where a=3;
Aggiornata 1 riga.
Aggiornata 1 riga.
Rollback completato.
Aggiornata 1 riga.
WT2 >commit;
Commit completato.
308
7.10 Comandi DCL
Ogni utente ha normalmente diritto di accedere esclusivamente
agli oggetti definiti nel proprio schema. Utenti particolarmente
privilegiati, poi, hanno diritto di accesso a tutti gli oggetti presenti nel
database. In questo paragrafo, senza entrare nel merito di attività di
amministrazione, descriveremo i due comandi SQL che consentono di
concedere e rimuovere un privilegio di accesso ad un utente.
Per gli esempi lavoreremo questa volta con due utenti distinti:
SCOTT e WTO_ESEMPIO. Per distinguere le due sessioni
WTO_ESEMPIO avrà come prompt la stringa “WTO” mentre SCOTT
avrà “SCO”.
7.10.1 GRANT
Il comando GRANT consente di concedere ad un utente il
privilegio di eseguire una determinata azione su un oggetto.
La sintassi del comando è
GRANT <privilegio> ON <oggetto> TO <utente>
Concessione riuscita.
ENAME JOB
---------- ---------
SMITH CLERK
ALLEN SALESMAN
WARD SALESMAN
JONES MANAGER
MARTIN SALESMAN
BLAKE MANAGER
CLARK MANAGER
SCOTT ANALYST
KING PRESIDENT
TURNER SALESMAN
ADAMS CLERK
JAMES CLERK
FORD ANALYST
MILLER CLERK
Selezionate 14 righe.
309
Ricordandosi che il nome della tabella deve essere qualificata
con il nome dello schema perché non si trova nello schema di
WTO_ESEMPIO.
WTO_ESEMPIO può eseguire solo la SELECT sulla tabella. Se
prova ad eseguire una diversa azione per cui non ha ricevuto il
privilegio otterrà un errore.
WTO >update scott.emp set ename='FORD2' where ename='FORD';
update scott.emp set ename='FORD2' where ename='FORD'
*
ERRORE alla riga 1:
ORA-01031: insufficient privileges
Concessione riuscita.
7.10.2 REVOKE
La revoca di uno o più privilegi precedentemente concessi ad un
utente si realizza con il comando REVOKE. La sintassi è
REVOKE <privilegio> ON <oggetto> FROM <utente>
Revoca riuscita.
7.10.3 RUOLI
Può essere utile accorpare in un unico contenitore un insieme di
privilegi in modo da poterli concedere o revocare tutti insieme. Un
contenitore di privilegi si chiama RUOLO.
310
La creazione del ruolo si realizza col comando CREATE ROLE.
L’associazione al ruolo dei privilegi si esegue mediante il comando
GRANT.
Ad esempio ipotizziamo che l’utente SCOTT voglia creare un
ruolo RUOLO_TEST che includa i privilegi di SELECT ed UPDATE su
EMP ed il privilegio di SELECT su DEPT.
SCO >create role ruolo_test;
Ruolo creato.
Concessione riuscita.
Concessione riuscita.
Concessione riuscita.
Revoca riuscita.
311
8 PL/SQL
312
8.1 Blocchi PL/SQL anonimi
Un programma PL/SQL può essere eseguito una tantum
lanciandolo da SQL*Plus o SQL Developer oppure memorizzato nel
database per essere eseguito più volte. Nel primo caso si parla di
blocco PL/SQL anonimo.
BEGIN
EXCEPTION
END;
313
Il comando SQL*Plus set serverout on serve ad abilitare la
visualizzazione dei messaggi stampati dai programmi PL/SQL. Non è
un comando SQL ed è necessario solo quando si lavora da SQL*Plus.
Se l’avessimo omesso il programma sarebbe stato eseguito
correttamente ma non avrebbe stampato il messaggio.
Il comando
dbms_output.put_line('Hello World!');
314
Figura 8-2 Visualizzazione dell’output in SQL Developer.
In qualunque punto di un programma PL/SQL è possibile
inserire un commento che sarà ignorato da Oracle in due modi:
• Includendo il commento tra i caratteri /* e */, in tal caso il
commento può proseguire anche su più righe.
• Inserendo il commento dopo un doppio trattino --, in tal caso
il commento continua fino a fine riga.
Ad esempio:
DECLARE
A VARCHAR2(3); --Commento che continua fino a fine linea
BEGIN
/*commento che continua anche
Sulle righe successive
A quella in cui è iniziato*/
dbms_output.put_line('Hello World!');
END;
315
8.3 Dichiarazione di costanti e variabili
8.3.1 Tipi di dato
I tipi di dato elementari utilizzabili in PL/SQL sono grossomodo
gli stessi che si utilizzano in SQL. Le differenze degne di nota sono le
seguenti:
• il tipo BOOLEAN, esistente solo in PL/SQL. Questo tipo
consente di definire variabili booleane che possono
assumere i valori TRUE o FALSE;
• i tipi VARCHAR2, CHAR, RAW possono avere lunghezza
fino a 32676 byte;
• i tipi CLOB e BLOB possono contenere fino a 128TB di dati.
In aggiunta ai tipi di dato elementari, il programmatore può
definire tipi di dato personalizzati. Questi saranno trattati più avanti in
questo capitolo.
316
DECLARE
Variabile_stringa VARCHAR2(100) := 'Esempio';
Variabile_numero NUMBER(10,4) := 1234.9872;
Variabile_booleana BOOLEAN := TRUE;
Variabile_data DATE;
BEGIN
317
8.4 Costrutti di base
Si è già accennato al fatto che il PL/SQL nasce come
estensione procedurale dell’SQL. Per realizzare tale estensione sono
state introdotte le strutture di controllo che vengono descritte nei
prossimi paragrafi.
Tutti i costrutti descritti possono essere utilizzati nell’area delle
istruzioni operative e nell’area di gestione degli errori, non nell’area di
dichiarazione delle variabili.
318
Come esempio nel seguente programma sono dichiarate due
variabili numeriche A e B. Alle variabili vengono assegnati due valori
predefiniti. Nel corpo del programma se A è maggiore di B si stampa
“A maggiore di B”, se A è minore di B si stampa “A minore di B”
altrimenti si stampa “A e B sono uguali”.
WTO >declare
2 a number := 3;
3 b number := 2;
4 begin
5 if a>b then
6 dbms_output.put_line('A maggiore di B');
7 elsif a<b then
8 dbms_output.put_line('A minore di B');
9 else
10 dbms_output.put_line('A e B sono uguali');
11 end if;
12 end;
13 /
A maggiore di B
Procedura PL/SQL completata correttamente.
Se si utilizzano diversi valori di A e B si ottengono tutti i possibili
output:
WTO >declare
2 a number := 3;
3 b number := 4;
4 begin
5 if a>b then
6 dbms_output.put_line('A maggiore di B');
7 elsif a<b then
8 dbms_output.put_line('A minore di B');
9 else
10 dbms_output.put_line('A e B sono uguali');
11 end if;
12 end;
13 /
A minore di B
Procedura PL/SQL completata correttamente.
WTO >declare
2 a number := 3;
3 b number := 3;
4 begin
5 if a>b then
6 dbms_output.put_line('A maggiore di B');
7 elsif a<b then
8 dbms_output.put_line('A minore di B');
9 else
10 dbms_output.put_line('A e B sono uguali');
11 end if;
12 end;
13 /
A e B sono uguali
Procedura PL/SQL completata correttamente.
319
In questi esempi è stato ignorato il caso in cui almeno una tra A
e B sia NULL. In tal caso valgono tutte le considerazioni fatte per
l’SQL: si possono utilizzare le stesse funzioni per la gestione dei valori
nulli e gli operatori di confronto IS NULL ed IS NOT NULL.
La struttura condizionale CASE serve a decodificare un valore.
È simile alla CASE di primo tipo vista in SQL:
DECLARE
<nome variabile> <tipo>;
BEGIN
<nome variabile> :=
CASE <dato da controllare>
WHEN <valore1> THEN
<valore di ritorno1>
WHEN <valore2> THEN
<valore di ritorno2>
...ALTRI BLOCCHI WHEN...
ELSE
<valore di ritornoN>
END;
END;
320
WTO >declare
2 voto number := 6;
3 giudizio varchar2(20);
4 begin
5 giudizio := case voto
6 when 10 then 'Eccellente'
7 when 9 then 'Ottimo'
8 when 8 then 'Distinto'
9 when 7 then 'Buono'
10 when 6 then 'Sufficiente'
11 when 5 then 'Mediocre'
12 else 'Insufficiente'
13 end;
14 dbms_output.put_line('Giudizio:'||giudizio);
15 end;
16 /
Giudizio:Sufficiente
WTO >declare
2 voto number := 3;
3 giudizio varchar2(20);
4 begin
5 giudizio := case voto
6 when 10 then 'Eccellente'
7 when 9 then 'Ottimo'
8 when 8 then 'Distinto'
9 when 7 then 'Buono'
10 when 6 then 'Sufficiente'
11 when 5 then 'Mediocre'
12 else 'Insufficiente'
13 end;
14 dbms_output.put_line('Giudizio:'||giudizio);
15 end;
16 /
Giudizio:Insufficiente
321
LOOP
<istruzioni1>
EXIT WHEN <condizione>
<istruzioni2>
END LOOP;
SYSDATE
---------
10-MAR-11
322
Se il programma non stampa nulla bisogna ricordarsi di attivare
la visualizzazione dell’output.
WTO >SET SEVEROUT ON
323
WTO >begin
2 for g in REVERSE 1..extract(day from systimestamp) loop
3 dbms_output.put_line('giorno '||g);
4 end loop;
5 end;
6 /
giorno 10
giorno 9
giorno 8
giorno 7
giorno 6
giorno 5
giorno 4
giorno 3
giorno 2
giorno 1
324
8.4.4 Strutture Sequenziali
La struttura GOTO consente di far saltare il flusso del
programma ad un’istruzione precedentemente contrassegnata con
un’etichetta.
La struttura assume la sintassi
GOTO <etichetta>
<istruzioni1>
<<<etichetta>>>
<istruzioni2>
325
Quindi Oracle non entra nell’IF e non effettua il salto. Viene
eseguito l’azzeramento della variabile OGGI che, dunque, alla
seconda stampa vale zero.
La struttura GOTO è diffusamente considerata il “nemico
pubblico numero uno” della buona programmazione. In effetti, questa
struttura invita il programmatore a fare salti incontrollati e se ne può
tranquillamente fare a meno. È però importante sottolineare che si può
facilmente scrivere un buon programma usando le GOTO e si può
altrettanto facilmente scrivere un pessimo programma senza utilizzare
questa struttura.
L’istruzione NULL non esegue nessuna operazione. In alcuni
casi Oracle richiede necessariamente che in una struttura ci sia
un’istruzione. Ad esempio non è possibile scrivere un’IF senza
istruzioni al proprio interno:
WTO >begin
2 if 1>2 then
3 end if;
4 end;
5 /
end if;
*
ERRORE alla riga 3:
ORA-06550: line 3, column 2:
PLS-00103: Encountered the symbol "END" when expecting one of the
following:
begin case declare exit for goto if loop mod null pragma
raise return select update while with <an identifier>
<a double-quoted delimited-identifier> <a bind variable> <<
close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe
326
8.5 Utilizzo dell’SQL
Il PL/SQL nasce come estensione procedurale dell’SQL. Fin qui
ne sono stati velocemente descritti gli aspetti procedurali, adesso ne
vengono introdotte le caratteristiche tipicamente SQL.
COUNT(*)
----------
8
327
WTO >declare
2 num_f number;
3 max_f number;
4 avg_f number;
5 begin
6 select count(*), max(importo), avg(importo)
7 into num_f, max_f, avg_f
8 from fatture;
9
10 dbms_output.put_line('Ci sono '||num_f||' fatture.');
11 dbms_output.put_line('La più alta è di '||max_f||' euro');
12 dbms_output.put_line('La media è di '||avg_f||' euro');
13
14 end;
15 /
Ci sono 5 fatture.
La più alta è di 1000 euro
La media è di 600 euro
328
WTO >declare
2 s varchar2(100):='create table abcd (a number)';
3 begin
4 execute immediate s;
5 end;
6 /
329
WTO >declare
2 num_r number;
3 s varchar2(100):=
4 'select count(*) from clienti where comune=:c';
5 begin
6 execute immediate s into num_r using 'H501';
7 dbms_output.put_line('Ho '||num_r||' clienti a ROMA.');
8 execute immediate s into num_r using 'F839';
9 dbms_output.put_line('Ho '||num_r||' clienti a NAPOLI.');
10 end;
11 /
Ho 2 clienti a ROMA.
Ho 2 clienti a NAPOLI.
330
WTO >declare
2 r number;
3 s_cli varchar2(100):= 'select count(*) from clienti';
4 s_fat varchar2(100):= 'select count(*) from fatture';
5 begin
6 if extract(day from systimestamp)>10 then
7 execute immediate s_cli into r;
8 else
9 execute immediate s_fat into r;
10 end if;
11 dbms_output.put_line('Ci sono '||r||' righe in tabella.');
12 end;
13 /
Ci sono 8 righe in tabella.
Tabella rinominata.
331
WTO >declare
2 r number;
3 s_cli varchar2(100):= 'select count(*) from clienti';
4 s_fat varchar2(100):= 'select count(*) from fatture';
5 begin
6 if extract(day from systimestamp)>10 then
7 execute immediate s_cli into r;
8 else
9 execute immediate s_fat into r;
10 end if;
11 dbms_output.put_line('Ci sono '||r||' righe in tabella.');
12 end;
13 /
Ci sono 8 righe in tabella.
8.5.3 CURSORI
Gli esempi di query SQL visti finora, sia nella modalità statica
che nella modalità dinamica, estraggono un singolo record dal
database. Nel caso in cui la query estragga un insieme di record la
clausola INTO genera un errore. Chiaramente è spesso necessario
leggere molti record dal DB, per gestire quest’esigenza il PL/SQL
mette a disposizione i cursori. Un cursore non è altro che un nome
simbolico assegnato ad una query. Il cursore si dichiara nella sezione
DECLARE e poi si utilizza (eseguendo effettivamente la query e
scorrendo le righe estratte) nella sezione delle istruzioni operative.
Lo scorrimento di un cursore richiede un LOOP, non essendo
noto a priori quanti record ci saranno nel cursore.
Nell’esempio seguente viene dichiarato un cursore che estrae
nome, cognome e comune dei clienti, il cursore viene poi aperto (cioè
viene eseguita la query), scorso ed infine chiuso. Solo i clienti residenti
a Napoli o Roma vengono stampati a video.
332
WTO >declare
2 v_nome clienti.nome%type;
3 v_cogn clienti.cognome%type;
4 v_com clienti.comune%type;
5 cursor c is
6 select nome, cognome, comune
7 from clienti;
8 begin
9 open c;
10 loop
11 fetch c into v_nome, v_cogn, v_com;
12 exit when c%notfound;
13 if v_com in ('H501','F839') then
14 dbms_output.put_line('Cliente '||v_nome||' '||v_cogn);
15 end if;
16 end loop;
17 close c;
18 end;
19 /
Cliente MARCO ROSSI
Cliente GIOVANNI BIANCHI
Cliente GENNARO ESPOSITO
Cliente PASQUALE RUSSO
333
WTO >declare
2 cursor c is select sysdate from dual;
3 begin
4 if 1>2 then
5 open c;
6 end if;
7 close c;
8 end;
9 /
declare
*
ERRORE alla riga 1:
ORA-01001: invalid cursor
ORA-06512: at line 7
334
WTO >declare
2 v_nome clienti.nome%type;
3 v_cogn clienti.cognome%type;
4 v_com clienti.comune%type;
5 cursor c is
6 select nome, cognome, comune
7 from clienti;
8 begin
9 open c;
10 loop
11 fetch c into v_nome, v_cogn, v_com;
12 exit when c%notfound;
13 dbms_output.put_line('Letta la riga '||c%rowcount);
14 if v_com in ('H501','F839') then
15 dbms_output.put_line('Cliente '||v_nome||' '||v_cogn);
16 end if;
17 end loop;
18 close c;
19 end;
20 /
Letta la riga 1
Cliente MARCO ROSSI
Letta la riga 2
Cliente GIOVANNI BIANCHI
Letta la riga 3
Letta la riga 4
Letta la riga 5
Letta la riga 6
Cliente GENNARO ESPOSITO
Letta la riga 7
Cliente PASQUALE RUSSO
Letta la riga 8
335
WTO >declare
2 cursor c is
3 select nome, cognome, comune
4 from clienti;
5 begin
6 for r in c loop
7 dbms_output.put_line('Letta la riga '||c%rowcount);
8 if r.comune in ('H501','F839') then
9 dbms_output.put_line('Cliente '||r.nome||' '||r.cognome);
10 end if;
11 end loop;
12 end;
13 /
Letta la riga 1
Cliente MARCO ROSSI
Letta la riga 2
Cliente GIOVANNI BIANCHI
Letta la riga 3
Letta la riga 4
Letta la riga 5
Letta la riga 6
Cliente GENNARO ESPOSITO
Letta la riga 7
Cliente PASQUALE RUSSO
Letta la riga 8
336
8.6 Gestione delle eccezioni
8.6.1 Blocco Exception
Se nel corso del programma si verifica un errore Oracle passa il
controllo alla parte di codice presente dopo la parola chiave
EXCEPTION. Poiché tale parte del blocco è facoltativa, in sua
assenza Oracle passa il controllo al programma che ha chiamato il
blocco PL/SQL segnalando l’errore. Facciamo un esempio banale.
WTO >set serverout on
WTO >declare
2 a number;
3 begin
4 a := 1/0;
5 dbms_output.put_line('A='||a);
6 end;
7 /
declare
*
ERRORE alla riga 1:
ORA-01476: divisor is equal to zero
ORA-06512: at line 4
337
controllo non torna al punto dove l’errore si era verificato, ma continua
con il resto del blocco (in questo caso non c’è nient’altro dopo l’errore).
L’eccezione ZERO_DIVIDE è un’eccezione predefinita, ne
esistono altre che saranno descritte nel prossimo paragrafo.
In uno stesso blocco possono essere gestite più eccezioni.
WTO >declare
2 a number;
3 begin
4 a := 1/0;
5 dbms_output.put_line('A='||a);
6 exception
7 when zero_divide then
8 dbms_output.put_line('Divisione per zero non ammessa.');
9 when no_data_found then
10 dbms_output.put_line('Dati non trovati.');
11 end;
12 /
Divisione per zero non ammessa.
338
Gestendo l’eccezione possiamo evitare che il programma vada
in errore e dare un messaggio all’utente.
WTO >begin
2 update clienti
3 set cod_cliente=7
4 where cod_cliente=8;
5 exception
6 when dup_val_on_index then
7 dbms_output.put_line('Valore duplicato, non aggiorno.');
8 end;
9 /
Valore duplicato, non aggiorno.
INVALID_NUMBER
Si verifica nel caso in cui viene effettuata una conversione di
una stringa a numero e la stringa non contiene un numero valido.
Nell’esempio seguente si cerca di convertire il codice fiscale dei
clienti in numero.
WTO >declare
2 a number;
3 begin
4 select to_number(cod_fisc)
5 into a
6 from clienti;
7 end;
8 /
declare
*
ERRORE alla riga 1:
ORA-01722: invalid number
ORA-06512: at line 4
WTO >declare
2 a number;
3 begin
4 select to_number(cod_fisc)
5 into a
6 from clienti;
7 exception
8 when invalid_number then
9 dbms_output.put_line('Numero non valido.');
10 end;
11 /
Numero non valido.
NO_DATA_FOUND
Si verifica quando una SELECT..INTO non estrae nessun
record.
339
WTO >declare
2 a number;
3 begin
4 select cod_cliente
5 into a
6 from clienti
7 where nome='ASTOLFO';
8 end;
9 /
declare
*
ERRORE alla riga 1:
ORA-01403: no data found
ORA-06512: at line 4
WTO >declare
2 a number;
3 begin
4 select cod_cliente
5 into a
6 from clienti
7 where nome='ASTOLFO';
8 exception
9 when no_data_found then
10 dbms_output.put_line('Cliente non trovato.');
11 end;
12 /
Cliente non trovato.
TOO_MANY_ROWS
Si verifica quando una SELECT..INTO estrae più di un record.
WTO >declare
2 a number;
3 begin
4 select cod_cliente
5 into a
6 from clienti
7 where comune='H501';
8 end;
9 /
declare
*
ERRORE alla riga 1:
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at line 4
340
WTO >declare
2 a number;
3 begin
4 select cod_cliente
5 into a
6 from clienti
7 where comune='H501';
8 exception
9 when too_many_rows then
10 dbms_output.put_line('Più di un cliente estratto.');
11 end;
12 /
Più di un cliente estratto.
VALUE_ERROR
Si verifica quando si mette in una variabile un valore non valido.
Nell’esempio seguente si cerca di assegnare il numero 5000 ad una
variabile definita NUMBER(3).
WTO >declare
2 a number(3);
3 begin
4 a:= 5000;
5 end;
6 /
declare
*
ERRORE alla riga 1:
ORA-06502: PL/SQL: numeric or value error: number precision too
large
ORA-06512: at line 4
WTO >declare
2 a number(3);
3 begin
4 a:= 5000;
5 exception
6 when value_error then
7 dbms_output.put_line('Valore non valido.');
8 end;
9 /
Valore non valido.
ZERO_DIVIDE
Si verifica quando si cerca di effettuare una divisione per zero.
Alcuni esempi sono stati mostrati nel paragrafo precedente.
OTHERS
È un’eccezione generica che si utilizza con il significato
“qualunque altro errore si verifichi”. Nell’esempio seguente il
programma gestisce gli errori NO_DATA_FOUND e
341
TOO_MANY_ROWS ma quello che si verifica è INVALID_NUMBER
che non è esplicitamente gestito. Il flusso finisce dunque nel WHEN
OTHERS dove c’è un messaggio generico.
WTO >declare
2 a number;
3 begin
4 select to_number(cod_fisc)
5 into a
6 from clienti
7 where cod_cliente=1;
8 exception
9 when no_data_found then
10 dbms_output.put_line('Cliente non trovato.');
11 when too_many_rows then
12 dbms_output.put_line('Più di un cliente estratto.');
13 when others then
14 dbms_output.put_line('Errore generico.');
15 end;
16 /
Errore generico.
342
WTO >declare
2 a number;
3 begin
4 select to_number(cod_fisc)
5 into a
6 from clienti
7 where cod_cliente=1;
8 exception
9 when no_data_found then
10 dbms_output.put_line('Cliente non trovato.');
11 when too_many_rows then
12 dbms_output.put_line('Più di un cliente estratto.');
13 when others then
14 dbms_output.put_line('Errore generico.');
15 dbms_output.put_line(sqlerrm);
16 end;
17 /
Errore generico.
ORA-01722: invalid number
343
WTO >begin
2 raise_application_error(-20123,'Errore utente.');
3 end;
4 /
begin
*
ERRORE alla riga 1:
ORA-20123: Errore utente.
ORA-06512: at line 2
344
Se il programma gira prima del 16 del mese il risultato sarà
WTO >declare
2 dopo_il_15 exception;
3 begin
4 if extract(day from systimestamp)>10 then
5 raise dopo_il_15;
6 else
7 dbms_output.put_line('Prima del 16');
8 end if;
9 exception
10 when dopo_il_15 then
11 dbms_output.put_line('Errore: dopo il 15 del mese!');
12 end;
13 /
Prima del 16
345
La clausola WHEN OTHERS THEN NULL mette a tacere
qualunque errore, rendendo impossibile (in casi più complessi di quello
presentato) l’analisi di ciò che è successo nel programma.
346
WTO >declare
2 a number(3,2);
3 begin
4 for i in 1..20 loop
5 Begin
6 dbms_output.put_line('I='||i);
7 a:=1/(i-7);
8 dbms_output.put_line('A='||a);
9 exception
10 when zero_divide then
11 dbms_output.put_line('Divisione per zero!');
12 end;
13 end loop;
14 end;
15 /
I=1
A=-,17
I=2
A=-,2
I=3
A=-,25
I=4
A=-,33
I=5
A=-,5
I=6
A=-1
I=7
Divisione per zero!
I=8
A=1
I=9
A=,5
I=10
A=,33
I=11
A=,25
I=12
A=,2
I=13
A=,17
I=14
A=,14
I=15
A=,13
I=16
A=,11
I=17
A=,1
I=18
A=,09
I=19
A=,08
I=20
A=,08
347
Alla settima iterazione il programma divide per zero, il controllo
passa al gestore dell’eccezione WHEN ZERO_DIVIDE e stampa
“Divisione per zero!”. Poi il blocco finisce ma siamo ancora dentro il
ciclo che continua fino alla fine.
348
WTO >declare
2 a number;
3 begin
4 begin
5 select 1 into a
6 from dual where 1=2;
7 dbms_output.put_line('Dopo l''errore');
8 exception
9 when no_data_found then
10 dbms_output.put_line('Gestore interno');
11 raise;
12 end;
13 dbms_output.put_line('Siamo nel blocco esterno');
14 exception
15 when no_data_found then
16 dbms_output.put_line('Gestore esterno');
17 end;
18 /
Gestore interno
Gestore esterno
349
8.7 Tipi di dato complessi
8.7.1 Record
Un record è un tipo di dati complesso costituito da un numero
determinato di elementi. Una variabile di tipo record è particolarmente
indicata per contenere i dati delle diverse colonne di una riga estratta
dal database con una SELECT.
La dichiarazione di un tipo di dati complesso record segue la
seguente sintassi.
TYPE <nome tipo> IS RECORD (
<nome colonna1> <tipo colonna1>,
<nome colonna2> <tipo colonna2>,
...
<nome colonnaN> <tipo colonnaN>
)
350
WTO >declare
2 type t_numeri is record (
3 num_f number,
4 max_f number,
5 avg_f number);
6 r t_numeri;
7 begin
8 select count(*), max(importo), avg(importo)
9 into r
10 from fatture;
11
12 dbms_output.put_line('Ci sono '||r.num_f||' fatture.');
13 dbms_output.put_line('La più alta è di '||r.max_f||' euro');
14 dbms_output.put_line('La media è di '||r.avg_f||' euro');
15
16 end;
17 /
Ci sono 5 fatture.
La più alta è di 1000 euro
La media è di 600 euro
351
L’attributo %ROWTYPE può essere utilizzato anche per
ottenere un record identico al risultato di un cursore.
Nell’esempio seguente si dichiarano un cursore ed un record
strutturalmente identico al record estratto dal cursore.
Successivamente il cursore viene aperto, viene eseguita la FETCH
della prima riga nella variabile di tipo record e vengono stampati i tre
campi che compongono la variabile.
WTO >declare
2 cursor c is
3 select nome, cognome, cod_fisc from clienti;
4 r c%rowtype;
5 begin
6 open c;
7 fetch c into r;
8 dbms_output.put_line('Nome='||r.nome);
9 dbms_output.put_line('Cognome='||r.cognome);
10 dbms_output.put_line('Cod fisc='||r.cod_fisc);
11 close c;
12 end;
13 /
Nome=MARCO
Cognome=ROSSI
Cod fisc=RSSMRC70R20H501X
352
8.7.2 Array associativi (PL/SQL Tables)
Un array associativo è un tipo di dati che consente di gestire
una lista di coppie (chiave, valore). Gli array associativi erano
inizialmente chiamati PL/SQL Tables e sono ancora spesso
riconosciuti con questo nome.
La chiave di un array associativo può essere un numero intero
oppure una stringa, Oracle conserva gli elementi nell’array associativo
in ordine di chiave, non in ordine di inserimento.
Nell’esempio seguente si definisce, si popola e poi si stampa un
array associativo avente come chiave il codice cliente e come valore il
cognome del cliente.
WTO >declare
2 type t_clienti is table of varchar2(30)
3 index by binary_integer;
4 cli t_clienti;
5 i number;
6 begin
7 for r in (select cod_cliente, nome from clienti) loop
8 cli(r.cod_cliente) := r.nome;
9 end loop;
10
11 i := cli.first;
12 loop
13 dbms_output.put_line('Il cliente '||i||' è '||cli(i));
14 exit when i=cli.last;
15 i:=cli.next(i);
16 end loop;
17 end;
18 /
Il cliente 1 è MARCO
Il cliente 2 è GIOVANNI
Il cliente 3 è MATTEO
Il cliente 4 è LUCA
Il cliente 5 è AMBROGIO
Il cliente 6 è GENNARO
Il cliente 7 è PASQUALE
Il cliente 8 è VINCENZO
353
La sintassi utilizzata per il CURSOR FOR LOOP evita al
programmatore di esplicitare la dichiarazione del cursore. Il cursore è
implicitamente definito nel FOR scrivendone la SELECT tra parentesi.
Il popolamento della lista CLI è fatto utilizzando un LOOP sugli
elementi estratti dalla query. Per ogni record si definisce un nuovo
elemento della lista avente come indice R.COD_CLIENTE e come
valore R.NOME.
La stampa di tutti i valori dell’array si effettua mediante un ciclo
che fa uso di tre funzioni di base che tutti gli array associativi mettono
a disposizione.
CLI.FIRST restituisce la chiave di valore più basso presente
nella lista.
CLI.NEXT(I) restituisce la chiave successiva a quella che riceve
in input.
CLI.LAST restituisce la chiave di valore più alto presente nella
lista.
Il valore di un array associativo può essere un record. In tal caso
l’array è una vera e propria tabella in memoria.
Nell’esempio seguente si ripete quanto fatto nell’ultimo esempio
ma si mette nell’array l’intero contenuto della tabella clienti.
WTO >declare
2 type t_clienti is table of clienti%rowtype
3 index by binary_integer;
4 cli t_clienti;
5 i number;
6 begin
7 for r in (select * from clienti) loop
8 cli(r.cod_cliente) := r;
9 end loop;
10
11 i := cli.first;
12 loop
13 dbms_output.put_line('Il cliente '||i||' è '||cli(i).nome);
14 dbms_output.put_line('il suo cf è '||cli(i).cod_fisc);
15 dbms_output.put_line('----------------------------------');
16 exit when i=cli.last;
17 i:=cli.next(i);
18 end loop;
19 end;
20 /
354
Il cliente 1 è MARCO
il suo cf è RSSMRC70R20H501X
----------------------------------
Il cliente 2 è GIOVANNI
il suo cf è
----------------------------------
Il cliente 3 è MATTEO
il suo cf è VRDMTT69S02H501X
----------------------------------
Il cliente 4 è LUCA
il suo cf è NRILCU77A22F205X
----------------------------------
Il cliente 5 è AMBROGIO
il suo cf è
----------------------------------
Il cliente 6 è GENNARO
il suo cf è SPSGNN71B10F839X
----------------------------------
Il cliente 7 è PASQUALE
il suo cf è RSSPSQ70C14F839X
----------------------------------
Il cliente 8 è VINCENZO
il suo cf è
----------------------------------
355
ANNO 2000 2001 2002 2003 2004 …
CITTA
Roma 13.000 11.300 12.500 14.400 16.000
Napoli 5.400 6.500 6.200 5.800 6.100
Milano 11.300 13.100 12.600 15.700 14.700
Firenze 2.350 2.680 2.200 2.920 2.810
Bologna 1.350 1.470 1.840 1.930 1.150
…
Nel programma si dichiara prima un array associativo avente
come chiave una stringa (il nome della città) e come valore un numero
(il numero di nuovi residenti).
Successivamente si dichiara un array associativo avente come
chiave un intero (l’anno) e come valore l’array associativo precedente.
A questo punto utilizzando un doppio indice è possibile
specificare il numero di nuovi residenti in una determinata città per un
determinato anno.
WTO >declare
2 type t_citta is table of number(8)
3 index by varchar2(30);
4 type t_anni is table of t_citta
5 index by binary_integer;
6 res t_anni;
7 begin
8 res(2000)('ROMA') := 13000;
9 res(2001)('ROMA') := 11300;
10 -- tutti gli altri elementi
11 -- omessi per brevità
12 -- stampiamo solo i nuovi residenti di Roma nel 2000
13 dbms_output.put_line('Roma nel 2000:'||res(2000)('ROMA'));
14 end;
15 /
Roma nel 2000:13000
356
8.7.3 Varray
Un varray, o array a dimensione variabile, è una lista di coppie
(chiave, valore) avente un numero di elementi massimo definito ed un
indice sempre numerico.
Il massimo numero di coppie presenti nell’array si definisce in
fase di dichiarazione utilizzando la sintassi seguente:
TYPE <nome tipo> is VARRAY(<dimensione massima>) of <tipo>
357
Rispetto agli array associativi, i varray hanno le seguenti
caratteristiche:
• Hanno un numero massimo di elementi prefissato.
• Hanno per forza indice numerico.
• Devono obbligatoriamente essere inizializzati.
• Possono essere utilizzati come colonna in una tabella di
database.
L’ultima caratteristica è, di fatto, l’unica che può far decidere di
utilizzare un VARRAY al posto di un array associativo.
358
Successivamente la nested table viene estesa di un elemento
con il metodo EXTEND. Tale metodo aggiunge un elemento vuoto in
ultima posizione. Per valorizzare l’elemento nuovo appena aggiunto si
utilizza la solita sintassi di assegnazione, l’unica novità è che l’indice
del nuovo elemento è determinato dal metodo COUNT che restituisce
il numero totale di elementi presenti nella nested table.
Rispetto agli array associativi, le nested table hanno le seguenti
caratteristiche:
• Hanno per forza indice numerico.
• Devono obbligatoriamente essere inizializzate.
• Possono essere utilizzati come colonna in una tabella di
database.
Anche in questo caso, l’ultima caratteristica è, di fatto, l’unica
che può far decidere di utilizzare una nested table al posto di un array
associativo.
359
Il cliente 1 è MARCO
il suo cf è RSSMRC70R20H501X
----------------------------------
Il cliente 2 è GIOVANNI
il suo cf è
----------------------------------
Il cliente 3 è MATTEO
il suo cf è VRDMTT69S02H501X
----------------------------------
Il cliente 4 è LUCA
il suo cf è NRILCU77A22F205X
----------------------------------
Il cliente 5 è AMBROGIO
il suo cf è
----------------------------------
Il cliente 6 è GENNARO
il suo cf è SPSGNN71B10F839X
----------------------------------
Il cliente 7 è PASQUALE
il suo cf è RSSPSQ70C14F839X
----------------------------------
Il cliente 8 è VINCENZO
il suo cf è
----------------------------------
360
WTO >declare
2 type t_citta is table of varchar2(30);
3 c t_citta := t_citta('ROMA','NAPOLI','MILANO', 'FIRENZE');
4 begin
5 for i in c.first..c.last loop
6 dbms_output.put_line('Città '||i||': '||c(i));
7 end loop;
8 dbms_output.put_line('Elimino il terzo elemento');
9 c.delete(3);
10 for i in c.first..c.last loop
11 dbms_output.put_line('Città '||i||': '||c(i));
12 end loop;
13 end;
14 /
Città 1: ROMA
Città 2: NAPOLI
Città 3: MILANO
Città 4: FIRENZE
Elimino il terzo elemento
Città 1: ROMA
Città 2: NAPOLI
declare
*
ERRORE alla riga 1:
ORA-01403: no data found
ORA-06512: at line 11
361
Città 1: ROMA
Città 2: NAPOLI
Città 3: MILANO
Città 4: FIRENZE
Elimino il terzo elemento
Città 1: ROMA
Città 2: NAPOLI
Città 4: FIRENZE
362
TRIM
Riceve in input un numero intero ed elimina dalla coda della
collection un numero di elementi pari al numero ricevuto in input.
WTO >declare
2 type t_citta is table of varchar2(30);
3 c t_citta := t_citta('ROMA','NAPOLI','MILANO', 'FIRENZE');
4 i number;
5 begin
6 i := c.first;
7 loop
8 dbms_output.put_line('Città '||i||': '||c(i));
9 exit when i = c.last;
10 i := c.next(i);
11 end loop;
12 dbms_output.put_line('Elimino gli ultimi due elementi.');
13 c.trim(2);
14 i := c.first;
15 loop
16 dbms_output.put_line('Città '||i||': '||c(i));
17 exit when i = c.last;
18 i := c.next(i);
19 end loop;
20 end;
21 /
Città 1: ROMA
Città 2: NAPOLI
Città 3: MILANO
Città 4: FIRENZE
Elimino gli ultimi due elementi.
Città 1: ROMA
Città 2: NAPOLI
363
EXTEND
Il metodo EXTEND aggiunge un elemento in coda alla
collection.
WTO >declare
2 type t_citta is table of varchar2(30);
3 c t_citta := t_citta('ROMA','NAPOLI','MILANO', 'FIRENZE');
4 i number;
5 begin
6 i := c.first;
7 loop
8 dbms_output.put_line('Città '||i||': '||c(i));
9 exit when i = c.last;
10 i := c.next(i);
11 end loop;
12 dbms_output.put_line('Aggiungo un elemento vuoto');
13 c.extend;
14 i := c.first;
15 loop
16 dbms_output.put_line('Città '||i||': '||c(i));
17 exit when i = c.last;
18 i := c.next(i);
19 end loop;
20 end;
21 /
Città 1: ROMA
Città 2: NAPOLI
Città 3: MILANO
Città 4: FIRENZE
Aggiungo un elemento vuoto
Città 1: ROMA
Città 2: NAPOLI
Città 3: MILANO
Città 4: FIRENZE
Città 5:
364
WTO >declare
2 type t_citta is table of varchar2(30);
3 c t_citta := t_citta('ROMA','NAPOLI','MILANO', 'FIRENZE');
4 i number;
5 begin
6 i := c.first;
7 loop
8 dbms_output.put_line('Città '||i||': '||c(i));
9 exit when i = c.last;
10 i := c.next(i);
11 end loop;
12 dbms_output.put_line('Aggiungo tre elementi vuoti.');
13 c.extend(3);
14 i := c.first;
15 loop
16 dbms_output.put_line('Città '||i||': '||c(i));
17 exit when i = c.last;
18 i := c.next(i);
19 end loop;
20 end;
21 /
Città 1: ROMA
Città 2: NAPOLI
Città 3: MILANO
Città 4: FIRENZE
Aggiungo tre elementi vuoti.
Città 1: ROMA
Città 2: NAPOLI
Città 3: MILANO
Città 4: FIRENZE
Città 5:
Città 6:
Città 7:
365
EXISTS
Ritorna TRUE o FALSE in funzione del fatto che un elemento
con un determinato indice esiste o meno nella collezione.
WTO >declare
2 type t_citta is table of varchar2(30);
3 c t_citta := t_citta('ROMA','NAPOLI','MILANO', 'FIRENZE');
4 i number;
5 begin
6 i := c.first;
7 loop
8 dbms_output.put_line('Città '||i||': '||c(i));
9 exit when i = c.last;
10 i := c.next(i);
11 end loop;
12 dbms_output.put_line('Esiste l''elemento 3?');
13 if c.exists(3) then
14 dbms_output.put_line('Sì, esiste.');
15 else
16 dbms_output.put_line('No, non esiste.');
17 end if;
18 dbms_output.put_line('Esiste l''elemento 5?');
19 if c.exists(5) then
20 dbms_output.put_line('Sì, esiste.');
21 else
22 dbms_output.put_line('No, non esiste.');
23 end if;
24 end;
25 /
Città 1: ROMA
Città 2: NAPOLI
Città 3: MILANO
Città 4: FIRENZE
Esiste l'elemento 3?
Sì, esiste.
Esiste l'elemento 5?
No, non esiste.
366
FIRST
Il metodo FIRST ritorna il primo indice della collezione.
WTO >declare
2 type t_citta is table of varchar2(30);
3 c t_citta := t_citta('ROMA','NAPOLI','MILANO', 'FIRENZE');
4 i number;
5 begin
6 i := c.first;
7 loop
8 dbms_output.put_line('Città '||i||': '||c(i));
9 exit when i = c.last;
10 i := c.next(i);
11 end loop;
12 dbms_output.put_line('Primo indice='||c.first);
13 end;
14 /
Città 1: ROMA
Città 2: NAPOLI
Città 3: MILANO
Città 4: FIRENZE
Primo indice=1
LAST
Il metodo LAST ritorna l’ultimo indice della collezione.
WTO >declare
2 type t_citta is table of varchar2(30);
3 c t_citta := t_citta('ROMA','NAPOLI','MILANO', 'FIRENZE');
4 i number;
5 begin
6 i := c.first;
7 loop
8 dbms_output.put_line('Città '||i||': '||c(i));
9 exit when i = c.last;
10 i := c.next(i);
11 end loop;
12 dbms_output.put_line('Ultimo indice='||c.last);
13 end;
14 /
Città 1: ROMA
Città 2: NAPOLI
Città 3: MILANO
Città 4: FIRENZE
Ultimo indice=4
367
COUNT
Il metodo COUNT ritorna il numero totale di elementi presenti
nella collezione.
WTO >declare
2 type t_citta is table of varchar2(30);
3 c t_citta := t_citta('ROMA','NAPOLI','MILANO', 'FIRENZE');
4 i number;
5 begin
6 i := c.first;
7 loop
8 dbms_output.put_line('Città '||i||': '||c(i));
9 exit when i = c.last;
10 i := c.next(i);
11 end loop;
12 dbms_output.put_line('Numero di elementi='||c.count);
13 end;
14 /
Città 1: ROMA
Città 2: NAPOLI
Città 3: MILANO
Città 4: FIRENZE
Numero di elementi=4
LIMIT
Il metodo LIMIT restituisce il numero massimo di elementi che la
collezione può contenere. Restituisce NULL se la collezione non ha un
numero massimo di elementi.
In un array associativo ed in una nested table sarà sempre
NULL.
WTO >declare
2 type t_citta is table of varchar2(30);
3 c t_citta := t_citta('ROMA','NAPOLI','MILANO', 'FIRENZE');
4 i number;
5 begin
6 i := c.first;
7 loop
8 dbms_output.put_line('Città '||i||': '||c(i));
9 exit when i = c.last;
10 i := c.next(i);
11 end loop;
12 dbms_output.put_line('Numero massimo di
elementi='||c.limit);
13 end;
14 /
Città 1: ROMA
Città 2: NAPOLI
Città 3: MILANO
Città 4: FIRENZE
Numero massimo di elementi=
368
In un varray sarà valorizzato.
WTO >declare
2 type t_giorni is varray(12) of number(2);
3 g t_giorni := t_giorni(31,28,31,30,31,30,31,31,30,31,30,31);
4 begin
5 dbms_output.put_line('Numero di elementi='||g.count);
6 dbms_output.put_line('Numero max di elementi='||g.limit);
7 g.trim(5);
8 dbms_output.put_line('Numero di elementi='||g.count);
9 dbms_output.put_line('Numero max di elementi='||g.limit);
10 end;
11 /
Numero di elementi=12
Numero max di elementi=12
Numero di elementi=7
Numero max di elementi=12
PRIOR
Il metodo PRIOR restituisce l’indice precedente rispetto a quello
che riceve in input.
WTO >declare
2 type t_citta is table of varchar2(30);
3 c t_citta := t_citta('ROMA','NAPOLI','MILANO', 'FIRENZE');
4 i number;
5 begin
6 i := c.first;
7 loop
8 dbms_output.put_line('Città '||i||': '||c(i));
9 exit when i = c.last;
10 i := c.next(i);
11 end loop;
12 dbms_output.put_line('Precedente di 3='||c.prior(3));
13 c.delete(2);
14 dbms_output.put_line('Precedente di 3='||c.prior(3));
15 end;
16 /
Città 1: ROMA
Città 2: NAPOLI
Città 3: MILANO
Città 4: FIRENZE
Precedente di 3=2
Precedente di 3=1
369
NEXT
Il metodo NEXT restituisce l’indice successivo rispetto a quello
ricevuto in input.
WTO >declare
2 type t_citta is table of varchar2(30);
3 c t_citta := t_citta('ROMA','NAPOLI','MILANO', 'FIRENZE');
4 i number;
5 begin
6 i := c.first;
7 loop
8 dbms_output.put_line('Città '||i||': '||c(i));
9 exit when i = c.last;
10 i := c.next(i);
11 end loop;
12 dbms_output.put_line('Successivo di 2='||c.next(2));
13 end;
14 /
Città 1: ROMA
Città 2: NAPOLI
Città 3: MILANO
Città 4: FIRENZE
Successivo di 2=3
370
8.8 Oggetti PL/SQL nel DB
Fino a questo punto si sono visti solo programmi PL/SQL
anonimi, scritti direttamente a linea di comando ed eseguiti. Il
database in questo caso non memorizza in alcun modo lo script
eseguito che deve essere salvato nel file system, se serve, a cura
dell’utente.
In questo capitolo, invece, vengono trattati i programmi PL/SQL
che si salvano nel database con un nome e possono essere eseguiti a
richiesta di un utente oppure automaticamente quando si verifica un
determinato evento.
8.8.1 Procedure
Una procedura PL/SQL (in inglese Stored Procedure) è un
programma PL/SQL memorizzato nel database che accetta un
determinato numero di parametri di input/output ed esegue un’attività
nel database senza ritornare un valore al programma che l’ha
chiamato.
La sintassi per la definizione di una procedura è la seguente:
CREATE OR REPLACE PROCEDURE <nome procedura>
(<nome parametro1> <modo parametro1> <tipo parametro1>,
<nome parametro2> <modo parametro2> <tipo parametro2>,
...
<nome parametroN> <modo parametroN> <tipo parametroN>) IS
<dichiarazione delle variabili>
BEGIN
<corpo PL/SQL della procedura>
END;
371
o OUT per i parametri di output, questi non possono
essere valorizzati dal programma chiamante e
nella procedura possono essere modificati per
essere restituiti valorizzati al chiamante.
o IN OUT per i parametri di input/output, questi
possono essere sia valorizzati dal chiamante che
modificati all’interno della procedura per essere
restituiti al chiamante.
• Il tipo di dato del parametro, sono validi tutti i tipi PL/SQL
(anche quelli definiti dall’utente). Si specifica solo il tipo di
dato e non la lunghezza, per una stringa, ad esempio, si
indica VARCHAR2, non VARCHAR(30).
La definizione della procedura fino alla parola chiave IS è detta
FIRMA della procedura.
Come primo esempio si mostra una procedura che non accetta
alcun parametro e compie, come unica operazione, l’aumento del 10%
di tutti gli importi delle fatture.
WTO >create or replace procedure aumenta is
2 begin
3 update fatture
4 set importo = importo*1.1;
5 end;
6 /
Procedura creata.
372
Nell’esempio seguente la stessa procedura appena presentata è
creata con errori perché è stato utilizzato un nome di tabella
inesistente.
WTO >create or replace procedure aumenta is
2 begin
3 update fatturA
4 set importo = importo*1.1;
5 end;
6 /
LINE/COL ERROR
-------- ---------------------------------------------------
3/3 PL/SQL: SQL Statement ignored
3/10 PL/SQL: ORA-00942: table or view does not exist
Il log degli errori letto dal basso verso l’alto mostra che a linea 3
della procedura è stato utilizzata una tabella di database inesistente e
per questo motivo l’intera istruzione a linea 3 è stata ignorata.
Corretto l’errore creiamo nuovamente la procedura e cerchiamo
di eseguirla.
WTO >create or replace procedure aumenta is
2 begin
3 update fatture
4 set importo = importo*1.1;
5 end;
6 /
Procedura creata.
373
WTO >select * from fatture;
Rollback completato.
Procedura creata.
374
La procedura riceve in input il parametro PCT di tipo numerico.
Tale parametro è utilizzato nell’UPDATE per come percentuale
d’incremento.
Per eseguire la procedura procediamo come prima, ma
passiamo il parametro. Aumento del 20%:
WTO >exec aumenta(20)
Annullo le modifiche.
WTO >rollback;
Rollback completato.
375
Dopo avere cambiato la procedura, non possiamo più chiamarla
senza parametri.
WTO >exec aumenta
BEGIN aumenta; END;
*
ERRORE alla riga 1:
ORA-06550: line 1, column 7:
PLS-00306: wrong number or types of arguments in call to
'AUMENTA'
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored
Procedura creata.
376
E dopo chiamiamola passando 20 come percentuale d’aumento
WTO >exec aumenta(20)
WTO >rollback;
Rollback completato.
Procedura creata.
377
La procedura, rispetto alla versione precedente, presenta un
comando SQL che legge il massimo importo dalle fatture e lo scrive
nel parametro di output.
Per lanciare una procedura ci sono sempre due modi. Si può
scrivere un semplice PL/SQL in cui si dichiara una variabile per ogni
parametro di output. Nella chiamata alla procedura i parametri di input
possono essere passati come costanti, mentre per i parametri di
output (o di input/output) è necessario specificare una variabile in cui
la procedura scriverà il risultato.
WTO >set serverout on
WTO >declare
2 m number;
3 begin
4 aumenta(10,m);
5 dbms_output.put_line('Massimo importo='||m);
6 end;
7 /
Massimo importo=1100
MAXIMP
----------
1210
378
SQL*Plus ci offre, infatti, la possibilità di dichiarare delle aree di
memoria che restano valide fino a quando si esce da SQL*Plus.
Il comando SQL*Plus VAR dichiara una di queste aree di
memoria, nel caso specifico si chiama MAXIMP ed è un numero.
A questo punto è possibile utilizzare il comando EXEC
passando come secondo parametro la variabile MAXIMP, si usano i
due punti per far capire a SQL*Plus che si tratta di una variabile.
Il comando SQL*Plus PRINT stampa il contenuto della variabile,
mostrando che essa è stata correttamente valorizzata con il massimo
importo delle fatture.
Ripuliamo i dati prima di andare avanti.
WTO >rollback;
Rollback completato.
8.8.2 Funzioni
Una funzione PL/SQL si comporta come una procedura con la
sola differenza che deve restituire un valore al termine dell’esecuzione.
La parola chiave RETURN consente, ad inizio funzione, di dichiarare
che tipo di dato sarà ritornato e, nel corpo della funzione, di
determinare il valore da restituire.
La sintassi di una funzione si presenta come segue:
CREATE OR REPLACE FUNCTION <nome funzione>
(<nome parametro1> <modo parametro1> <tipo parametro1>,
<nome parametro2> <modo parametro2> <tipo parametro2>,
...
<nome parametroN> <modo parametroN> <tipo parametroN>)
RETURN <tipo> IS
<dichiarazione delle variabili>
BEGIN
<corpo PL/SQL della funzione>
RETURN <valore>;
END;
379
Una funzione non deve obbligatoriamente accettare parametri,
nel caso in cui non ci siano parametri la sintassi si riduce a
CREATE OR REPLACE FUNCTION <nome funzione>
RETURN <tipo> IS
<dichiarazione delle variabili>
BEGIN
<corpo PL/SQL della funzione>
RETURN <valore>;
END;
LINE/COL ERROR
-------- --------------------------------------------------------
1/20 PLS-00103: Encountered the symbol "IS" when expecting
one of the following:
( return compress compiled wrapped
Funzione creata.
380
La clausola RETURN nel corpo della funzione può essere
presente anche più di una volta.
WTO >create or replace function test_func (
2 mypar in number) return number is
3 begin
4 if mypar >100 then
5 return mypar;
6 else
7 return mypar*10;
8 end if;
9 end;
10 /
Funzione creata.
TEST_FUNC(200) TEST_FUNC(15)
-------------- -------------
200 150
Funzione creata.
TEST_FUNC(200) TEST_FUNC(15)
-------------- -------------
200 150
381
2 mypar in number) return number is
3 ret_val number(3);
4 begin
5 if mypar >100 then
6 ret_val := mypar;
7 else
8 ret_val := mypar*10;
9 end if;
10
11 return ret_val;
12 exception
13 when value_error then
14 dbms_output.put_line('Errore di valore.');
15 end;
16 /
Funzione creata.
TEST_FUNC(200) TEST_FUNC(15)
-------------- -------------
200 150
Errore di valore.
382
WTO >create or replace function test_func (
2 mypar in number) return number is
3 ret_val number(3);
4 begin
5 if mypar >100 then
6 ret_val := mypar;
7 else
8 ret_val := mypar*10;
9 end if;
10
11 return ret_val;
12 exception
13 when value_error then
14 dbms_output.put_line('Errore di valore.');
15 return null;
16 end;
17 /
Funzione creata.
TEST_FUNC(1000)
---------------
Errore di valore.
Funzione creata.
TEST_FUNC(200) TEST_FUNC(15)
-------------- -------------
200 150
383
WTO >select test_func(1000)
2 from dual;
TEST_FUNC(1000)
---------------
Errore di valore.
Funzione creata.
384
WTO >create or replace function test_func (
2 mypar in number) return number is
3 ret_val number(3);
4 begin
5 if mypar >100 then
6 ret_val := mypar;
7 else
8 ret_val := mypar*10;
9 end if;
10
11 return ret_val;
12 exception
13 when value_error then
14 dbms_output.put_line('Errore di valore.');
15 return null;
16 end;
17 /
Funzione creata.
RET_VAL
----------
500
8.8.3 Package
Un package PL/SQL è un oggetto virtuale che racchiude un
insieme di procedure e funzioni. Ci sono vari buoni motivi per
accorpare le procedure e funzioni in package anziché tenerle slegate
tra loro:
385
• Modularità dell’applicazione: le procedure e funzioni
logicamente collegate tra loro sono conservate nello stesso
package e viste come un tutt’uno. Ciò agevola un buon
disegno architetturale della soluzione consentendo di avere
pochi pacchetti di programmi anziché molti programmi
sciolti.
• Possibilità di nascondere le funzionalità di servizio: un
package è composto di due parti, la specification ed il body.
Solo le procedure, funzioni e variabili incluse nella
specification possono essere accedute dagli altri programmi
PL/SQL presenti nel DB; le procedure, funzioni e variabili
presenti solo nel body sono “private” ed accessibili solo dagli
altri programmi del package.
• Variabili di sessione: una variabile definita nel package al di
fuori di tutte le procedure e funzioni è istanziata la prima
volta che il package viene richiamato e resta valida fino a
fine sessione. È dunque possibile utilizzare delle variabili
che sopravvivono alla fine del programma e possono essere
utilizzate da altri programmi che gireranno in seguito nella
stessa sessione.
La sintassi per la creazione di un package, come detto, è tale da
definire due oggetti separati: la specification ed il body.
Per la specification:
CREATE OR REPLACE PACKAGE <nome package> IS
PROCEDURE/FUNCTION <nome procedura o funzione >
<parametri> <eventuale clausola return>;
PROCEDURE/FUNCTION <nome procedura o funzione >
<parametri> <eventuale clausola return>;
..
PROCEDURE/FUNCTION <nome procedura o funzione >
<parametri> <eventuale clausola return>;
<dichiarazione delle variabili di package>
END;
386
Per il body
CREATE OR REPLACE PACKAGE BODY <nome package> IS
<dichiarazione delle variabili private di package>
PROCEDURE/FUNCTION <nome procedura o funzione >
<parametri> <eventuale clausola return> IS
<variabili di procedura o funzione>
BEGIN
<corpo della procedura o funzione>
END;
Procedura eliminata.
Funzione eliminata.
Package creato.
387
2
3 procedure aumenta (pct in number default 10,
4 max_imp out number) is
5 begin
6 update fatture
7 set importo=importo*(1+pct/100);
8
9 select max(importo)
10 into max_imp
11 from fatture;
12 end;
13
14 function test_func (mypar in number) return number is
15 ret_val number(3);
16 begin
17 if mypar >100 then
18 ret_val := mypar;
19 else
20 ret_val := mypar*10;
21 end if;
22
23 return ret_val;
24 exception
25 when value_error then
26 dbms_output.put_line('Errore di valore.');
27 return null;
28 end;
29
30 end;
31 /
WTO >print m
M
----------
1200
388
WTO >select * from fatture;
TEST_PKG.TEST_FUNC(15)
----------------------
150
WTO >rollback;
Rollback completato.
Package creato.
WTO >print m
M
----------
1200
389
WTO >create or replace package body test_pkg is
2
3 function test_func (mypar in number) return number is
4 ret_val number(3);
5 begin
6 if mypar >100 then
7 ret_val := mypar;
8 else
9 ret_val := mypar*10;
10 end if;
11
12 return ret_val;
13 exception
14 when value_error then
15 dbms_output.put_line('Errore di valore.');
16 return null;
17 end;
18
19 procedure aumenta (pct in number default 10,
20 max_imp out number) is
21 begin
22 update fatture
23 set importo=importo*(1+pct/100);
24
25 max_imp := test_func(10);
26
27 end;
28
29
30 end;
31 /
WTO >print m
M
----------
100
390
WTO >create or replace package body test_pkg is
2
3 procedure aumenta (pct in number default 10,
4 max_imp out number) is
5 begin
6 update fatture
7 set importo=importo*(1+pct/100);
8
9 max_imp := test_func(10);
10
11 end;
12
13 function test_func (mypar in number) return number is
14 ret_val number(3);
15 begin
16 if mypar >100 then
17 ret_val := mypar;
18 else
19 ret_val := mypar*10;
20 end if;
21
22 return ret_val;
23 exception
24 when value_error then
25 dbms_output.put_line('Errore di valore.');
26 return null;
27 end;
28
29 end;
30 /
LINE/COL ERROR
-------- --------------------------------------------------------
9/3 PL/SQL: Statement ignored
9/14 PLS-00313: 'TEST_FUNC' not declared in this scope
391
WTO >create or replace package body test_pkg is
2
3 VAR_BODY number;
4
5 function test_func (mypar in number) return number is
6 ret_val number(3);
7 begin
8 if mypar >100 then
9 ret_val := mypar;
10 else
11 ret_val := mypar*10;
12 end if;
13
14 return ret_val;
15 exception
16 when value_error then
17 dbms_output.put_line('Errore di valore.');
18 return null;
19 end;
20
21 procedure aumenta (pct in number default 10,
22 max_imp out number) is
23 begin
24 update fatture
25 set importo=importo*(1+pct/100);
26
27 max_imp := test_func(10);
28
29 end;
30
31 end;
32 /
392
WTO >exec :m := test_pkg.var_spec
WTO >print m
M
----------
100
393
WTO >create or replace package body test_pkg is
2
3 VAR_BODY number;
4
5 function test_func (mypar in number) return number is
6 ret_val number(3);
7 begin
8 if mypar >100 then
9 ret_val := mypar;
10 else
11 ret_val := mypar*10;
12 end if;
13
14 return ret_val;
15 exception
16 when value_error then
17 dbms_output.put_line('Errore di valore.');
18 return null;
19 end;
20
21 procedure aumenta (pct in number default 10,
22 max_imp out number) is
23 begin
24 update fatture
25 set importo=importo*(1+pct/100);
26
27 max_imp := test_func(10);
28
29 end;
30
31 --Quello che segue è il codice d'inizializzazione.
32 begin
33 var_spec := 1234;
34 var_body := 4321;
35 end;
36 /
WTO >print m
M
----------
1234
394
8.8.4 Database Trigger
Un database trigger è un programma PL/SQL salvato nel
database che scatta automaticamente quando si verifica un
determinato evento. I trigger si dividono in due categorie:
I system trigger sono collegati ad eventi DDL (ad esempio
DROP, CREATE, ALTER) oppure ad eventi amministrativi (ad
esempio STARTUP, SHUTDOWN) che avvengono in un determinato
schema oppure sull’intero database. Sono utilizzati prevalentemente
per attività di amministrazione del database e per questo motivo non
saranno approfonditi in questo manuale.
I DML trigger sono collegati ad eventi di tipo DML (INSERT,
UPDATE o DELETE) su una specifica tabella o vista. Nel seguito si
approfondiranno solo i trigger di questa categoria.
Come detto, l’evento che può far scattare un trigger è
l’esecuzione di un comando DML su una tabella (o vista). Il trigger può
essere configurato per scattare prima o dopo l’esecuzione del
comando DML.
La sintassi minimale per la definizione di un trigger è
CREATE OR REPLACE TRIGGER <nome trigger>
<BEFORE/AFTER> <INSERT/UPDATE/DELETE> ON <nome tabella>
DECLARE
<eventuali vairabili e costanti>
BEGIN
<codice PL/SQL del trigger>
END;
Trigger creato.
Aggiornate 8 righe.
WTO >rollback;
Completato rollback.
395
È possibile definire un EACH ROW trigger che viene eseguito
una volta per ogni record coinvolto nel comando DML che lo fa
scattare. Se il trigger è AFTER DELETE e scatta per una DELETE che
cancella cento record, lo statement trigger scatterebbe una sola volta
mentre l’EACH ROW trigger scatta cento volte.
Per definire un EACH ROW trigger basta aggiungere la clausola
FOR EACH ROW.
WTO >create or replace trigger TEST_UPD_CLI
2 AFTER UPDATE ON CLIENTI
3 FOR EACH ROW
4 begin
5 dbms_output.put_line('Trigger eseguito!');
6 end;
7 /
Trigger creato.
Aggiornate 8 righe.
WTO >rollback;
Completato rollback.
Trigger creato.
396
WTO >update clienti set nome='pippo';
Aggiornate 8 righe.
WTO >update clienti set nome='Pippo';
Trigger eseguito!
Trigger eseguito!
Trigger eseguito!
Trigger eseguito!
Trigger eseguito!
Trigger eseguito!
Trigger eseguito!
Trigger eseguito!
Aggiornate 8 righe.
WTO >rollback;
Rollback completato.
397
Da notare il fatto che prima di OLD e NEW è richiesto il
carattere due punti nel corpo del trigger, ma non nella clausola WHEN.
In un trigger di UPDATE sono disponibili sia i valori OLD che i
NEW, in un trigger di INSERT sono disponibili solo i NEW mentre gli
OLD sono tutti NULL, viceversa in un trigger di DELETE sono
disponibili solo gli OLD mentre i NEW sono tutti NULL.
CLAUSOLA REFERENCING
Nell’ipotesi che nel database esista una tabella di nome OLD
oppure NEW è possibile, per evitare fraintendimenti, utilizzare la
clausola REFERENCING per sostituire i prefissi OLD e NEW con
prefissi a piacere scelti dal programmatore.
WTO >create or replace trigger TEST_UPD_CLI
2 AFTER UPDATE ON CLIENTI
3 REFERENCING OLD as VECCHIO NEW as NUOVO
4 FOR EACH ROW
5 begin
6 dbms_output.put_line('Cliente '||:vecchio.cod_cliente||
7 ' vecchio nome '||:vecchio.nome||' nuovo '||:nuovo.nome);
8 end;
9 /
Trigger creato.
Aggiornate 8 righe.
WTO >rollback;
Rollback completato.
398
WTO >create or replace trigger TEST_UPD_CLI
2 AFTER UPDATE OF NOME, COGNOME ON CLIENTI
3 FOR EACH ROW
4 begin
5 dbms_output.put_line('Cliente '||:old.cod_cliente||
6 ' vecchio nome '||:old.nome||' nuovo '||:new.nome);
7 end;
8 /
Trigger creato.
Aggiornate 8 righe.
Aggiornate 8 righe.
WTO >rollback;
Rollback completato.
Trigger creato.
399
WTO >update clienti set nome='pippo';
Cliente 1 vecchio nome MARCO nuovo pippo
Cliente 2 vecchio nome GIOVANNI nuovo pippo
Cliente 3 vecchio nome MATTEO nuovo pippo
Cliente 4 vecchio nome LUCA nuovo pippo
Cliente 5 vecchio nome AMBROGIO nuovo pippo
Cliente 6 vecchio nome GENNARO nuovo pippo
Cliente 7 vecchio nome PASQUALE nuovo pippo
Cliente 8 vecchio nome VINCENZO nuovo pippo
Aggiornate 8 righe.
Creata 1 riga.
WTO >rollback;
Rollback completato.
Trigger creato.
Aggiornate 8 righe.
400
WTO >insert into clienti values (
2 9,'pippo','pluto',null,'indirizzo','00100','H501');
Inserimento nuovo cliente
Creata 1 riga.
WTO >rollback;
Rollback completato.
DISABILITAZIONE ED ABILITAZIONE
Un trigger non può essere eseguito esplicitamente, va in
esecuzione solo quando si verifica l’evento a cui è collegato. È però
possibile disabilitare un trigger per evitare che scatti. La disabilitazione
di un trigger si esegue con il comando
WTO >alter trigger TEST_UPD_CLI disable;
Trigger modificato.
Aggiornate 8 righe.
WTO >rollback;
Rollback completato.
Trigger modificato.
Aggiornate 8 righe.
WTO >rollback;
Rollback completato.
DIMENSIONE DI UN TRIGGER
La massima dimensione del codice di un trigger è 32Kb, è
vivamente consigliato, però, non superare le sessanta linee di codice.
Se il codice supera le sessanta linee di codice è consigliabile mettere
401
una parte del codice in una stored procedure oppure in una funzione di
database.
ORDINE DI ESECUZIONE
Su una tabella possono essere definiti più trigger. In questo
caso l’ordine di esecuzione è il seguente:
• Before statement
• Before each row
• After each row
• After statement
Nell’esempio seguente si definiscono quattro trigger di UPDATE
sulla tabella COMUNI e si fanno scattare tutti con un’unica istruzione
per verificare l’ordine di esecuzione.
WTO >create trigger com_b_u_s
2 before update on comuni
3 begin
4 dbms_output.put_line('Before update statement');
5 end;
6 /
Trigger creato.
Trigger creato.
Trigger creato.
Trigger creato.
402
WTO >update comuni set des_comune='nuova descrizione';
Before update statement
Before update row
After update row
Before update row
After update row
Before update row
After update row
Before update row
After update row
Before update row
After update row
Before update row
After update row
Before update row
After update row
Before update row
After update row
Before update row
After update row
After update statement
Aggiornate 9 righe.
WTO >rollback;
Rollback completato.
UTILIZZI FREQUENTI
Gli utilizzi più frequenti dei database trigger sono:
• Valorizzazione automatica di campi
• Controlli di validità complessi
• Log delle operazioni DML
Vediamo nel seguito un esempio per ciascuna di queste
casistiche.
Cominciamo dalla valorizzazione automatica di colonne. In caso
di inserimento in tabella CLIENTI vogliamo che il programmatore non
debba preoccuparsi di calcolare il corretto codice cliente da utilizzare.
Il trigger seguente valorizza automaticamente il codice cliente
utilizzando la sequenza SEQ_CLIENTE.
403
WTO >create or replace trigger clienti_bir
2 before insert on clienti
3 for each row
4 begin
5 select seq_clienti.nextval
6 into :new.cod_cliente
7 from dual;
8 end;
9 /
Trigger creato.
Creata 1 riga.
Selezionate 9 righe.
WTO >rollback;
Rollback completato.
404
WTO >create or replace trigger clienti_bir
2 before insert on clienti
3 for each row
4 declare
5 des comuni.des_comune%type;
6 begin
7 select des_comune
8 into des
9 from comuni
10 where cod_comune = :new.comune;
11
12 if des not like 'R%' then
13 RAISE_APPLICATION_ERROR(-20001,des||' Non inizia per R.');
14 else
15 select seq_clienti.nextval
16 into :new.cod_cliente
17 from dual;
18 end if;
19 end;
20 /
Trigger creato.
Creata 1 riga.
WTO >rollback;
Rollback completato.
Tabella creata.
405
Poi creiamo il trigger.
WTO >create or replace trigger clienti_a_iud
2 after insert or update or delete on clienti
3 for each row
4 declare
5 evento varchar2(20);
6 begin
7 if inserting then
8 evento := 'Inserimento';
9 elsif updating then
10 evento := 'Aggiornamento';
11 else
12 evento := 'Cancellazione';
13 end if;
14
15 insert into log_clienti values
16 (sysdate, nvl(:old.cod_cliente,:new.cod_cliente),evento);
17 end;
18 /
Trigger creato.
Creata 1 riga.
Aggiornata 1 riga.
Aggiornata 1 riga.
CURRVAL
----------
26
Eliminata 1 riga.
406
WTO >select * from log_clienti;
WTO >rollback;
Rollback completato.
MUTATING TABLE
Un trigger FOR EACH ROW scatta durante l’esecuzione di un
comando DML. Durante il trigger, dunque, la tabella è in fase di
cambiamento (mutating in inglese) e non può essere letta o modificata.
Facciamo un esempio. Aggiungiamo al trigger dell’esempio
precedente una banale query che conta il numero di record presenti in
CLIENTI.
WTO >create or replace trigger clienti_a_iud
2 after insert or update or delete on clienti
3 for each row
4 declare
5 evento varchar2(20);
6 n number;
7 begin
8 if inserting then
9 evento := 'Inserimento';
10 elsif updating then
11 evento := 'Aggiornamento';
12 else
13 evento := 'Cancellazione';
14 end if;
15
16 insert into log_clienti values
17 (sysdate, nvl(:old.cod_cliente,:new.cod_cliente),evento);
18
19 select count(*) into n
20 from clienti;
21 end;
22 /
Trigger creato.
407
WTO >update clienti
2 set nome='pippo';
update clienti
*
ERRORE alla riga 1:
ORA-04091: table WTO_ESEMPIO.CLIENTI is mutating,
trigger/function may not see it
ORA-06512: at "WTO_ESEMPIO.CLIENTI_A_IUD", line 16
ORA-04088: error during execution of trigger
'WTO_ESEMPIO.CLIENTI_A_IUD'
Trigger creato.
Aggiornate 8 righe.
WTO >rollback;
Rollback completato.
408
WTO >select * from cli_com;
Selezionate 8 righe.
Aggiornata 1 riga.
WTO >rollback;
Rollback completato.
Trigger creato.
Creata 1 riga.
409
fatto qualunque cosa nel DB, in questo caso non ha fatto null’altro che
visualizzare il messaggio “Tentato inserimento in CLI_COM”.
410
WTO >select name, type, REFERENCED_NAME, REFERENCED_TYPE
2 from user_dependencies
3 ;
411
Le righe aventi REFERENCED_TYPE = ‘NON-EXISTENT’
possono essere ignorate. Sono dei record di servizio che Oracle
aggiunge quando si crea una dipendenza da un sinonimo pubblico
(DBMS_OUTPUT in questo caso).
La vista USER_DEPENDENCIES contiene solo le dipendenze
dirette ma, ovviamente, il database è spesso ricco di dipendenze
indirette. Costruiamo un esempio.
La procedura P1 richiama la funzione F1. La funzione F1
richiama la procedura P2. La procedura P2 utilizza la tabella CLIENTI.
WTO >create or replace procedure P2 is
2 a number;
3 begin
4 select count(*) into a from clienti;
5 end;
6 /
Procedure created.
Function created.
Procedure created.
9 rows selected.
412
Il package SYS_STUB_FOR_PURITY_ANALYSIS, come dice il
nome, è un oggetto di sistema utilizzato per la verifica di “purezza”
delle procedure e funzioni. Di quest’argomento si parlerà più avanti nel
paragrafo dedicato alla PRAGMA RESTRICT_REFERENCES.
Dalla query non emerge, ad esempio, che la procedura P1
dipende sia dalla tabella CLIENTI che dalla procedura P2.
Per vedere le dipendenze in maniera gerarchica si può utilizzare
una CONNECT BY.
WTO >select lpad(' ',level-1,' ')||REFERENCED_NAME REF_NAME,
2 REFERENCED_TYPE
3 from user_dependencies
4 connect by prior REFERENCED_NAME = name
5 and prior REFERENCED_TYPE = type
6 start with name='P1';
REF_NAME REFERENCED_TYPE
---------------------------------------- ---------------
STANDARD PACKAGE
SYS_STUB_FOR_PURITY_ANALYSIS PACKAGE
F1 FUNCTION
STANDARD PACKAGE
SYS_STUB_FOR_PURITY_ANALYSIS PACKAGE
P2 PROCEDURE
STANDARD PACKAGE
SYS_STUB_FOR_PURITY_ANALYSIS PACKAGE
CLIENTI TABLE
9 rows selected.
413
WTO >select lpad(level||')',level+1,' ')||REFERENCED_NAME
2 REF_NAME, REFERENCED_TYPE
3 from user_dependencies
4 connect by prior REFERENCED_NAME = name
5 and prior REFERENCED_TYPE = type
6 start with name='P1';
REF_NAME REFERENCED_TYPE
---------------------------------------- ---------------
1)STANDARD PACKAGE
1)SYS_STUB_FOR_PURITY_ANALYSIS PACKAGE
1)F1 FUNCTION
2)STANDARD PACKAGE
2)SYS_STUB_FOR_PURITY_ANALYSIS PACKAGE
2)P2 PROCEDURE
3)STANDARD PACKAGE
3)SYS_STUB_FOR_PURITY_ANALYSIS PACKAGE
3)CLIENTI TABLE
9 rows selected.
REFERENCED_NAME REFERENCED_TYPE
---------------------------- ---------------
F1 FUNCTION
P2 PROCEDURE
SYS_STUB_FOR_PURITY_ANALYSIS PACKAGE
STANDARD PACKAGE
CLIENTI TABLE
Table created.
1 row created.
414
WTO >commit;
Commit complete.
Function created.
LEGGI
---------
1
Grant succeeded.
LEGGI
---------
1
Table created.
415
WTO >insert into test values (2);
1 row created.
WTO >commit;
Commit complete.
LEGGI
---------
1
Function created.
LEGGI
---------
1
416
WTO >conn scott/tiger
Connected.
WTO >select wto_esempio.leggi from dual;
LEGGI
---------
2
7 rows selected.
TEXT
-------------------------------
function leggi return number
authid current_user is
a number;
begin
select a into a from test;
return a;
end;
7 rows selected.
417
Il codice dei trigger non è conservato in questa vista ma nella
vista USER_TRIGGERS.
WTO >desc user_triggers
Name Null? Type
------------------------------- -------- ----
TRIGGER_NAME VARCHAR2(30)
TRIGGER_TYPE VARCHAR2(16)
TRIGGERING_EVENT VARCHAR2(227)
TABLE_OWNER VARCHAR2(30)
BASE_OBJECT_TYPE VARCHAR2(16)
TABLE_NAME VARCHAR2(30)
COLUMN_NAME VARCHAR2(4000)
REFERENCING_NAMES VARCHAR2(128)
WHEN_CLAUSE VARCHAR2(4000)
STATUS VARCHAR2(8)
DESCRIPTION VARCHAR2(4000)
ACTION_TYPE VARCHAR2(11)
TRIGGER_BODY LONG
TRIGGER_BODY
-------------------------------------------
declare
n number;
begin
select count(*) into n
from clienti;
end;
418
Figura 8-3 Uno script PL/SQL in chiaro ed offuscato.
Il file leggi_wrap.sql è uno script sql valido e perfettamente
equivalente al leggi.sql che possiamo eseguire da SQL*Plus
WTO >@leggi_wrap
Function created.
LEGGI
---------
1
419
WTO >select text
2 from user_source
3 where name='LEGGI'
4 order by line;
TEXT
-----------------------------------------------------------------
function leggi wrapped
a000000
b2
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
8
72 a2
pxahtprUcMdJlRqLRR04tdlJsjMwg2JHf8tGfHSiszsQGL6A5S85S6BX8Quxw+eozXMeYw6S
lbf58TzmmO6/h7GyFxr9Vv700SoL64hiVd5SXQ6EB3exbVlxPTYE5MJExXdRJJM0u+dKFii8
cqNG35xyLMvHpAM=
420
8.9 Argomenti avanzati di PL/SQL
In questo paragrafo sono trattati alcuni argomenti avanzati di
PL/SQL già pubblicati sul blog http://oracleitalia.wordpress.com e
riadattati per la pubblicazione in questo manuale.
Questi argomenti sono stati riportati qui per due motivi: perché
forniscono uno spunto per la soluzione di alcuni problemi frequenti di
programmazione (invio email, cifratura, gestione dei campi BLOB) e
perché sono validi esempi di quanto i package forniti da Oracle
aumentino le potenzialità del PL/SQL.
Il lettore principiante può tranquillamente fermarsi qui e ritornare
su questi argomenti quando avrà acquisito una maggiore padronanza
dei tanti strumenti fino ad ora introdotti.
421
create or replace procedure insert_blob(in_id in number,
in_filename in varchar2) is
myfile BFILE := BFILENAME('FILE_DIR',in_filename);
myblob BLOB;
lun_file BINARY_INTEGER;
begin
dbms_lob.fileopen(myfile, dbms_lob.file_readonly);
lun_file := dbms_lob.getlength(myfile);
dbms_lob.createtemporary(myblob,false);
dbms_lob.loadfromfile(myblob, myfile, lun_file);
dbms_lob.fileclose(myfile);
end;
/
422
A questo punto il più è fatto, resta solo da inserire i dati (blob
finito compreso) in tabella e chiudere il BFILE (se ce ne
dimenticassimo verrebbe comunque chiuso a fine sessione).
Eseguiamo il nostro programma per caricare una canzone degli
U2 in tabella:
WTO>exec insert_blob(1,'WithOrWithoutYou.mp3')
ID NAME CONTENT
--- ---------------------- --------------------------------------
1 WithOrWithoutYou.mp3 49443303000000000F7647454F42000006A600
000062696E61727900005265616C4A756B6562
6F783A4D6574616461746100524A4D44000000
010000068000000000000000000000001D0000
00220000
Prova del nove: cancello il file dal file system e scrivo una
procedura per tirarlo fuori dal DB.
create or replace procedure extract_blob(in_id in number) is
myblob BLOB;
lun_file BINARY_INTEGER;
myname myfiles.name%type;
myfile utl_file.file_type;
appo_raw RAW(32760);
buffer_length number := 32760;
wrote_length number := 0;
begin
select content, name, dbms_lob.getlength(content)
into myblob, myname, lun_file
from myfiles
where id=in_id;
423
Successivamente si apre in modalità scrittura (wb) un file in
d:\media col nome letto in tabella. L'ultimo parametro specifica la
massima lunghezza delle singole linee, il massimo per questo
parametro è 32767.
Il processo di scrittura nel file è il seguente: UTL_FILE ci
consente di scrivere solo RAW lunghe al massimo 32767 byte.
Dobbiamo quindi scrivere un pezzetto da 32760 (ho troncato alla
decina di byte per fare conto pari) alla volta.
Mettiamo dunque in piedi un WHILE che esce quando la
lunghezza scritta è uguale a quella letta dal DB.
Ad ogni iterazione del ciclo leggiamo un pezzetto da 32760 (o
meno se è l'ultimo pezzetto) dal blob e lo scarichiamo nel file di output.
Alla fine chiudiamo il file.
Per estrarre, dunque, la canzone dal DB eseguo
WTO> exec extract_blob(1)
424
che lo utilizza. Ovviamente si assume che il lettore abbia una discreta
conoscenza dei fondamenti di crittografia.
Le funzionalità implementate in DBMS_CRYPTO sono:
Algoritmi di Crittografia:
Oltre ai già citati DES, Triple DES (in questa versione anche a
due ed a tre chiavi) ed AES c'è anche l'RC4.
Funzioni di Hashing:
MD4, MD5, e SHA-1.
Funzioni per la generazione di MAC code:
MD5 e SHA-1.
Funzioni per la generazione di valori pseudo-casuali:
È possibile generare un valore pseudo-casuale di tipo
BINARY_INTEGER, NUMBER o RAW.
Prima di passare ad un esempio di procedura per la
cifratura/decifratura si sottolinea che il package DBMS_CRYPTO
contiene due procedure (ENCRYPT e DECRYPT) che gestiscono
cifratura e decifratura di campi di tipo LOB, sia BLOB che CLOB, e due
funzioni (sempre ENCRYPT e DECRYPT) che gestiscono cifratura e
decifratura di campi RAW. I campi VARCHAR2 devono essere
convertiti in RAW e nel set di caratteri AL32UTF8 prima di essere
cifrati.
Cominciamo con una funzione per la cifratura di una stringa
VARCHAR2 che ritorna la stringa cifrata, ovviamente in un campo
RAW, e riceve in input la stringa da cifrare, la chiave di cifratura ed un
flag che indica l'algoritmo da utilizzare: A=AES con chiave a 32 byte,
D=3DES con chiave a 24 byte.
425
CREATE OR REPLACE function CIFRA(in_str in VARCHAR2,
chiave in varchar2,
Algoritmo in char)
return RAW is
raw_da_cifrare RAW(2048) :=
UTL_RAW.CAST_TO_RAW(CONVERT(in_str,'AL32UTF8','WE8MSWIN1252'));
raw_chiave RAW(128) :=
UTL_RAW.CAST_TO_RAW(CONVERT(chiave,'AL32UTF8','WE8MSWIN1252'));
alg PLS_INTEGER;
BEGIN
if Algoritmo = 'D' then
alg := DBMS_CRYPTO.DES3_CBC_PKCS5;
elsif Algoritmo = 'A' then
alg := DBMS_CRYPTO.ENCRYPT_AES256 +
DBMS_CRYPTO.CHAIN_CBC +
DBMS_CRYPTO.PAD_PKCS5;
else
RAISE_APPLICATION_ERROR(-20001,
'Algoritmo non previsto, scegliere D o A!');
end if;
return dbms_crypto.Encrypt(raw_da_cifrare, alg, raw_chiave);
END;
/
426
• Chiave di cifratura
Anche la chiave di cifratura deve essere convertita in RAW e nel
set di caratteri AL32UTF8. Bisogna fare attenzione alla lunghezza
della chiave.
La lunghezza minima dipende dall'algoritmo utilizzato, nel nostro
caso è 24 byte per il 3DES e 32 per l'AES.
• Vettore di inizializzazione per cifrari a blocchi (opzionale)
Nel nostro esempio non lo passiamo.
Ed ecco come cifriamo un testo utilizzando la funzione appena
definita:
WTO> select cifra('Questo è un testo molto lungo da
2 cifrare, contiene anche ritorni a capo etc...',
3 'MIACHIAVE012345678901234','D') cifrato
4 from dual;
CIFRATO
----------------------------------------
812DAC49DBEBD0619CEC10E4341FDA9DCC435007
1EB29B3B0F62AA8D95B1AA5996BA75ABB1E4AB91
FEA914480892D248A5D92E5642559135CD15DD50
6D878D5E6B17355800729310C0675B33B7546C12
UTL_RAW.CAST_TO_RAW(CONVERT(chiave,'AL32UTF8','WE8MSWIN1252'));
raw_decifrata RAW(2048);
alg PLS_INTEGER;
BEGIN
if Algoritmo = 'D' then
alg := DBMS_CRYPTO.DES3_CBC_PKCS5;
elsif Algoritmo = 'A' then
alg := DBMS_CRYPTO.ENCRYPT_AES256 +
DBMS_CRYPTO.CHAIN_CBC +
DBMS_CRYPTO.PAD_PKCS5;
else
RAISE_APPLICATION_ERROR(-20001,
'Algoritmo non previsto, scegliere D o A!');
end if;
427
Dove banalmente rispetto a prima abbiamo chiamato la funzione
per decifrare e poi abbiamo riconvertito la RAW ottenuta in
VARCHAR2 nel nostro set di caratteri WE8MSWIN1252.
Facciamo la prova del nove, proviamo a decifrare la stringa che
prima avevamo cifrato:
WTO> select decifra(hextoraw('DC84F4C1CA98FC27208C3A501
3F0A88720DCCE5E5C1E250ADD38352404F3CB73E6C4A97DE64
E999997B43F5531EAE824D6A2EA7AC5EE78BF5DBC3CCB86CC1
31B89CBADD7295D71DBE57B7CDC1DFE3787'),
'MIACHIAVE012345678901234','D') decifrato
2* from dual
DECIFRATO
--------------------------------------------------
Questo è un testo molto lungo da
cifrare, contiene anche ritorni a capo etc...
CIFRATO
----------------------------------------
84FB4135CD3FAF07AF7B729ED2A0FD7BF306A5AA
6923B39E81D27CA693D5E343
DECIFRATO
--------------------------------------------------
Testo da cifrare con AES
428
Quindi bisogna prima di tutto disporre di una tabella che
contenga i BLOB da cifrare e cifrati. Io ho una tabella MYFILES che
contiene una canzone degli U2 ed un episodio di FlashForward:
WTO> select * from myfiles
ID NAME CONTENT
---------- -------------------- --------------------
1 WithOrWithoutYou.mp3 49443303000000000F76
47454F42000006A60000
0062696E617279000052
65616C4A756B65626F78
3A4D6574616461746100
524A4D44000000010000
06800000000000000000
0000001D000000220000
2 FlashForward.avi 5249464632B9DF154156
49204C4953547E220000
6864726C617669683800
0000409C000000000000
000000001001000072ED
00000000000002000000
00000000700200006001
00000000000000000000
429
create or replace function cifra_file(in_id in number,
chiave in varchar2,
Algoritmo in char)
return number is
blob_da_cifrare BLOB;
blob_cifrato BLOB;
raw_chiave RAW(128) :=
UTL_RAW.CAST_TO_RAW(CONVERT(chiave,'AL32UTF8','WE8MSWIN1252'));
alg PLS_INTEGER;
out_id number;
begin
out_id := fileid.nextval;
DBMS_CRYPTO.Encrypt(blob_cifrato,blob_da_cifrare,alg,raw_chiave);
return out_id;
end;
/
430
WTO> exec dbms_output.put_line(cifra_file(1,
'MIACHIAVE012345678901234','D'))
3
ID NAME CONTENT
---------- -------------------- --------------------
1 WithOrWithoutYou.mp3 49443303000000000F76
47454F42000006A60000
0062696E617279000052
65616C4A756B65626F78
3A4D6574616461746100
524A4D44000000010000
06800000000000000000
0000001D000000220000
2 FlashForward.avi 5249464632B9DF154156
49204C4953547E220000
6864726C617669683800
0000409C000000000000
000000001001000072ED
00000000000002000000
00000000700200006001
00000000000000000000
3 WithOrWithoutYou.mp3 9243E49958296FFC341B
- cifrato 1120021A70A6006A285A
CE89D6F62EA04187E59B
6523DC6336B9FDDDC820
37DDCF8FDCA6FF1E0681
507A44F117F94F2DE517
8298B6F39FE6FA710003
8BE3C3F80F43AABF0200
431
create or replace function decifra_file(in_id in number,
chiave in varchar2,
Algoritmo in char)
return number is
blob_da_decifrare BLOB;
blob_decifrato BLOB;
raw_chiave RAW(128) :=
UTL_RAW.CAST_TO_RAW(CONVERT(chiave,'AL32UTF8','WE8MSWIN1252'));
alg PLS_INTEGER;
out_id number;
begin
out_id := fileid.nextval;
DBMS_CRYPTO.Decrypt(blob_decifrato,blob_da_decifrare,alg,raw_chia
ve);
return out_id;
end;
/
432
WTO> select * from myfiles;
ID NAME CONTENT
---------- -------------------- --------------------
1 WithOrWithoutYou.mp3 49443303000000000F76
47454F42000006A60000
0062696E617279000052
65616C4A756B65626F78
3A4D6574616461746100
524A4D44000000010000
06800000000000000000
0000001D000000220000
2 FlashForward.avi 5249464632B9DF154156
49204C4953547E220000
6864726C617669683800
0000409C000000000000
000000001001000072ED
00000000000002000000
00000000700200006001
00000000000000000000
3 WithOrWithoutYou.mp3 9243E49958296FFC341B
- cifrato 1120021A70A6006A285A
CE89D6F62EA04187E59B
6523DC6336B9FDDDC820
37DDCF8FDCA6FF1E0681
507A44F117F94F2DE517
8298B6F39FE6FA710003
8BE3C3F80F43AABF0200
4 WithOrWithoutYou.mp3 49443303000000000F76
- cifrato - decifra 47454F42000006A60000
to 0062696E617279000052
65616C4A756B65626F78
3A4D6574616461746100
524A4D44000000010000
06800000000000000000
0000001D000000220000
433
CREATE OR REPLACE PROCEDURE SENDMAIL
(dest in varchar2,msg in varchar2) is
mailhost VARCHAR2(10) := 'localhost';
sender VARCHAR2(20) := '[email protected]';
mail_conn utl_smtp.connection;
r utl_smtp.replies;
BEGIN
mail_conn := utl_smtp.open_connection(mailhost, 25);
r:=utl_smtp.ehlo(mail_conn, mailhost);
for i in r.first..r.last loop
dbms_output.put_line('helo code='||r(i).code||
' text='||r(i).text);
end loop;
utl_smtp.mail(mail_conn, sender);
utl_smtp.rcpt(mail_conn, dest);
utl_smtp.open_data(mail_conn);
utl_smtp.write_data(mail_conn, 'From: '||sender||
chr(13)|| CHR(10));
utl_smtp.write_data(mail_conn, 'Subject: '||
'messaggio di test'||chr(13)|| CHR(10));
utl_smtp.write_data(mail_conn, 'To: '||dest||
chr(13)|| CHR(10));
utl_smtp.write_data(mail_conn, chr(13)|| CHR(10));
utl_smtp.write_data(mail_conn, msg||chr(13)|| CHR(10));
utl_smtp.close_data(mail_conn);
utl_smtp.quit(mail_conn);
END;
/
434
Che è identico al precedente ma non ha output.
Dopo avere fatto la conoscenza col server utilizziamo il
comando MAIL per avviare il processo di invio di una email.
Il successivo comando RCPT seleziona la mailbox in cui si
desidera depositare il messaggio. Questo comando può essere
ripetuto più volte se i destinatari del messaggio sono più di uno.
Il comando OPEN_DATA apre la session di scrittura del corpo
dell'email.
Dopo la OPEN_DATA è possibile eseguire un numero a piacere
di comandi WRITE_DATA chiusi dal comando COLSE_DATA.
Nel nostro caso passiamo con write successive ciò che sarà
visualizzato nei campi: From, Subject e To dell'email.
La riga vuota successiva serve a chiudere l'header dell'email ed
a passare al corpo effettivo.
Anche nel corpo potremmo scrivere un numero a piacere di
righe, noi accettiamo il testo come parametro di input.
Come detto completiamo l'invio con il comando CLOSE_DATA e
salutiamo il server con il comando QUIT.
Vediamo un esempio d’esecuzione della procedura:
WTO> exec SENDMAIL('[email protected]','Caro amico ti scrivo...')
BEGIN SENDMAIL('[email protected]','Caro amico ti scrivo...'); END;
*
ERRORE alla riga 1:
ORA-24247: accesso alla rete negato dalla lista di controllo
dell'accesso (ACL)
ORA-06512: a "SYS.UTL_TCP", line 17
ORA-06512: a "SYS.UTL_TCP", line 246
ORA-06512: a "SYS.UTL_SMTP", line 115
ORA-06512: a "SYS.UTL_SMTP", line 138
ORA-06512: a "WTO_ESEMPIO.SENDMAIL", line 7
ORA-06512: a line 1
435
BEGIN
DBMS_NETWORK_ACL_ADMIN.CREATE_ACL (
acl => 'SMTP_ACL.xml',
description => 'ACL for SMTP',
principal => 'WTO_ESEMPIO',
is_grant => TRUE,
privilege => 'connect',
start_date => null,
end_date => null);
END;
/
BEGIN
DBMS_NETWORK_ACL_ADMIN.ASSIGN_ACL (
acl => 'SMTP_ACL.xml',
host => 'localhost',
lower_port => 25,
upper_port => 25);
END;
/
436
WTO> select table_name from tabs;
TABLE_NAME
------------------------------
BONUS
DEPT
EMP
SALGRADE
DBMS_METADATA.GET_DDL('TABLE','EMP')
---------------------------------------------------------------
CREATE TABLE "SCOTT"."EMP"
( "EMPNO" NUMBER(4,0) NOT NULL ENABLE,
"ENAME" VARCHAR2(10),
"JOB" VARCHAR2(9),
"MGR" NUMBER(4,0),
"HIREDATE" DATE,
"SAL" NUMBER(7,2),
"COMM" NUMBER(7,2),
"DEPTNO" NUMBER(2,0)
) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS
LOGGING
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS
2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT)
TABLESPACE "USERS"
Tabella modificata.
Tabella modificata.
Tabella modificata.
437
WTO> select dbms_metadata.get_ddl('TABLE','EMP') from dual;
DBMS_METADATA.GET_DDL('TABLE','EMP')
-----------------------------------------------------------------
DBMS_METADATA.GET_DDL('PROCEDURE','INSERT_BLOB')
-----------------------------------------------------------------
CREATE OR REPLACE PROCEDURE "WTO_ESEMPIO"."INSERT_BLOB" (in_id
in number, in_filename
in varchar2) is
myfile BFILE := BFILENAME('FILE_DIR',in_filename);
myblob BLOB;
lun_file BINARY_INTEGER;
begin
dbms_lob.fileopen(myfile, dbms_lob.file_readonly);
lun_file := dbms_lob.getlength(myfile);
dbms_lob.createtemporary(myblob,false);
dbms_lob.loadfromfile(myblob, myfile, lun_file);
438
Alcuni oggetti di database, però, non hanno un nome ma sono
strettamente dipendenti da un altro oggetto.
Ad esempio una grant concessa su una tabella:
WTO> grant select on emp to system;
DBMS_METADATA.GET_DEPENDENT_DDL('OBJECT_GRANT','EMP')
---------------------------------------------------------------
GRANT SELECT ON "SCOTT"."EMP" TO "SYSTEM"
DBMS_METADATA.GET_XML('TABLE','EMP')
----------------------------------------------------------------
<?xml version="1.0"?><ROWSET><ROW>
<TABLE_T>
<VERS_MAJOR>1</VERS_MAJOR>
<VERS_MINOR>2 </VERS_MINOR>
<OBJ_NUM>69585</OBJ_NUM>
<SCHEMA_OBJ>
<OBJ_NUM>69585</OBJ_NUM>
<DATAOBJ_NUM>69585</DATAOBJ_NUM>
<OWNER_NUM>81</OWNER_NUM>
<OWNER_NAME>SCOTT</OWNER_NAME>
<NAME>EMP</NAME>
... (omissis) qui in mezzo c'erano circa altre 400 linee ...
</COL>
</TGT_COL_LIST_ITEM>
</TGT_COL_LIST>
</CON2_LIST_ITEM>
</CON2_LIST>
<REFPAR_LEVEL>0</REFPAR_LEVEL>
</TABLE_T>
</ROW></ROWSET>
439
WTO>select dbms_metadata.get_dependent_xml('OBJECT_GRANT','EMP')
2 from dual;
DBMS_METADATA.GET_DEPENDENT_XML('OBJECT_GRANT','EMP')
---------------------------------------------------------------
<?xml version="1.0"?><ROWSET><ROW>
<OBJGRANT_T>
<VERS_MAJOR>1</VERS_MAJOR>
<VERS_MINOR>2 </VERS_MINOR>
<OBJ_NUM>69585</OBJ_NUM>
<BASE_OBJ>
<OBJ_NUM>69585</OBJ_NUM>
<DATAOBJ_NUM>69585</DATAOBJ_NUM>
<OWNER_NUM>81</OWNER_NUM>
<OWNER_NAME>SCOTT</OWNER_NAME>
<NAME>EMP</NAME>
<NAMESPACE>1</NAMESPACE>
<TYPE_NUM>2</TYPE_NUM>
<TYPE_NAME>TABLE</TYPE_NAME>
<CTIME>2009-11-01 00:54:56</CTIME>
<MTIME>2009-12-26 00:29:16</MTIME>
<STIME>2009-11-01 00:54:56</STIME>
<STATUS>1</STATUS>
<FLAGS>0</FLAGS>
<SPARE1>6</SPARE1>
<SPARE2>1</SPARE2>
<SPARE3>81</SPARE3>
</BASE_OBJ>
<GRANTOR>SCOTT</GRANTOR>
<GRANTEE>SYSTEM</GRANTEE>
<PRIVNAME>SELECT</PRIVNAME>
<SEQUENCE>33681</SEQUENCE>
<WGO>0</WGO>
</OBJGRANT_T>
</ROW></ROWSET>
440
Infine si può lavorare anche in modalità opposta, cioè partire da
un XML (magari estratto da un altro DB), trasformarlo in DDL e creare
quindi l'oggetto nel DB.
Queste attività possono essere eseguite utilizzando le funzioni:
OPENW, ADD_TRANSFORM, SET_TRANSFORM_PARAM,
SET_REMAP_PARAM, SET_PARSE_ITEM, CONVERT, PUT, e
CLOSE.
Per tutti i dettagli fare riferimento al manuale appena citato.
441
• gestione catene di job
Per organizzare i job in workflow complessi
• gestione credenziali
Per definire e conservare nel DB le credenziali di accesso a
sistemi remoti che i job devono utilizzare
• Funzioni di servizio
Leggere file, scrivere file, ripulire il log etc...
Gestione job, programmi e schedulazioni è ovviamente la
categoria principale, ed è quella che approfondiremo.
La procedura più utilizzata è CREATE_JOB che consente di
creare un JOB mediante una delle sei modalità ammesse.
Facciamo un esempio utilizzando una schedulazione ed un
"named program" già definiti.
Prerequisito per l'utilizzo di questa procedura è avere già
definito precedentemente
• Una schedulazione, magari utilizzata per un altro job,
salvata nello schedulatore assegnandogli un nome
mediante la procedura CREATE_SCHEDULE.
• Un "named program", cioè un'istruzione da eseguire,
anch'essa già salvata nello schedulatore assegnandogli un
nome mediante la procedura CREATE_PROGRAM.
Vogliamo definire un job che ogni minuto raddoppia il valore
presente in un campo di DB.
Da SQL*Plus eseguiamo:
-- CREO LA TABELLA CHE UTILIZZERO' PER IL MIO TEST
create table test_sched(col number);
442
--CREO LA SCHEDULAZIONE, Si chiama OGNIMINUTO,
--comincia nell'istante in cui la creo,
--scatta ogni minuto e finisce dopo 24 ore.
EXEC DBMS_SCHEDULER.CREATE_SCHEDULE
('OGNIMINUTO',systimestamp,'FREQ=MINUTELY',systimestamp+1);
ORA COL
-------- ----------
01:02:26 1
ORA COL
-------- ----------
01:02:27 2
443
• Creare un job partendo da un "named program" ed un
evento scatenante.
Non entriamo nel dettaglio di tutte perché sono abbastanza
intuitive. Basta dire che quando non ho un "named program" oppure
una schedulazione già definite posso comunque creare il job passando
direttamente l'istruzione da eseguire ed il tempo di esecuzione.
L'unica cosa su cui ci si sofferma è la possibilità di definire una
schedulazione "ad evento". Il verificarsi di un evento è associato alla
presenza di un determinato messaggio in una coda Oracle Streams
Advanced Queuing che è il sistema di message queuing di Oracle.
Se dunque vogliamo definire un job che parte alla ricezione di
un file nel sistema operativo, dobbiamo innanzitutto avere
un'applicazione che all'arrivo del file inserisca un opportuno messaggio
in una coda, dopodiché potremo intercettare quest'evento dallo
scheduler ed utilizzarlo come "triggering event" del nostro job.
Ovviamente un job definito può essere modificato, disabilitato o
eliminato in qualunque momento.
Le procedure da utilizzare sono: SET_ATTRIBUTE, DISABLE e
DROP_JOB.
Se, ad esempio, vogliamo modificare il tempo d'esecuzione del
nostro job, passando da una volta al minuto ad una volta all'ora
possiamo eseguire
--CREO LA NUOVA SCHEDULAZIONE, Si chiama OGNIORA,
--comincia nell'istante in cui la creo,
--scatta ogni ora a partire dalla prossima ora esatta
--e finisce dopo 24 ore.
EXEC DBMS_SCHEDULER.CREATE_SCHEDULE ('OGNIORA',
trunc(systimestamp,'HH24')+1/24, 'FREQ=HOURLY',
trunc(systimestamp,'HH24')+25/24);
444
In questa tabella c’è un record per ogni esecuzione del job e, se
si sta verificando un errore, si trovano codice e messaggio d'errore
nella colonna ADDITIONAL_INFO.
La query
select * from user_scheduler_jobs
Function created.
P
--------------------------------------------------------------
oracle 10 (10)
445
CREATE or replace function p return varchar2 is
BEGIN
return 'oracle 10 ('||DBMS_DB_VERSION.VERSION||')';
END p;
/
TEXT
-----------------------------------------------------------------
function p return varchar2 is
BEGIN
$IF DBMS_DB_VERSION.VERSION < 11 $THEN
return 'oracle 10 ('||DBMS_DB_VERSION.VERSION||')';
$ELSE
return 'da 11 in poi ('||DBMS_DB_VERSION.VERSION||')';
$END
END p;
8 rows selected.
CERCA('ABRACADABRAABBIAMOMESSOLASTRINGAAB','AB')
------------------------------------------------
4
446
Ma in Oracle11g abbiamo la funzione REGEXP_COUNT che
consente di ottenere lo stesso risultato in modo più semplice:
WTO> create or replace function cerca (txt varchar2,
2 str varchar2)
3 return number is
4 begin
5 return regexp_count(testo,str);
6 end;
7 /
Funzione creata.
CERCA('ABRACADABRAABBIAMOMESSOLASTRINGAAB','AB')
------------------------------------------------
4
Function created.
CERCA('ABRACADABRAABBIAMOMESSOLASTRINGAAB','AB')
------------------------------------------------
4
BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.2.0 - Prod
447
WTO> create or replace function cerca (txt varchar2,
2 str varchar2)
3 return number is
4 begin
5 $IF DBMS_DB_VERSION.VERSION < 11 $THEN
6 return (length(txt)-length(replace(txt,str)))/length(str);
7 $ELSE
8 return regexp_count(txt,str);
9 $END
10 end;
11 /
Funzione creata.
CERCA('ABRACADABRAABBIAMOMESSOLASTRINGAAB','AB')
------------------------------------------------
4
BANNER
--------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 -
Production
448
dove la variabile debug potrebbe essere definita in un package
in questo modo
create or replace package vars is
debug number(1) := 1;
end;
/
Package created.
Procedure created.
449
8.9.7 Le direttive di compilazione PRAGMA
Come abbiamo visto nel paragrafo precedente a partire dalla
versione 10 del DB Oracle ha introdotto nel PL/SQL la possibilità di
effettuare compilazione condizionale.
Esistono però altre direttive di compilazione presenti in PL/SQL
da molto prima, le PRAGMA.
In questo paragrafo analizzeremo una per una le cinque
PRAGMA presenti in Oracle11g. Le prime 4 esistono almeno dalla
versione 8i mentre l'ultima è stata introdotta proprio nella versione 11g
del PL/SQL.
Iniziamo con la PRAGMA EXCEPTION_INIT. Questa direttiva
consente di associare un codice d'errore ORA ad una user defined
exception.
Fatta l'associazione sarà possibile gestire nel codice l'eccezione
come se fosse un'eccezione predefinita (ad esempio
NO_DATA_FOUND o TOO_MANY_ROWS).
Vediamo un esempio.
Immaginiamo di dover creare un funzione che riceve in input
una stringa e la trasforma in data assumendo come formato 'YYYY-
MM-DD':
WTO> create or replace function stringa2data
2 (str in varchar2) return date is
3 retDate date;
4 begin
5 retDate := to_date(str,'yyyy-mm-dd');
6 return retDate;
7 end;
8 /
Funzione creata.
STRINGA2D
---------
31-GEN-10
450
Come abbiamo visto nell'esempio, se la stringa in input non è
valida rispetto al formato otteniamo l'errore ORA-1841.
Ci proponiamo di gestire quest'errore con l'aiuto della direttiva
PRAGMA EXCEPTION_INIT:
WTO> create or replace function stringa2data (str in varchar2)
2 return date is
3 retDate date;
4 data_non_corretta exception;
5 PRAGMA EXCEPTION_INIT(data_non_corretta,-1841);
6 begin
7 retDate := to_date(str,'yyyy-mm-dd');
8 return retDate;
9 exception
10 when data_non_corretta then
11 dbms_output.put_line('Attenzione: la stringa '||str||
12 ' non può essere convertita in data!');
13 return null;
14 end;
15 /
Funzione creata.
STRINGA2D
---------
451
WTO> create or replace package pack is
2 function a return number;
3 end;
4 /
Package creato.
A
----------
2
WTO> select pack.a from dual;
select pack.a from dual
*
ERRORE alla riga 1:
ORA-14551: impossibile eseguire un'operazione DML all'interno di
una query
ORA-06512: a "WTO_ESEMPIO.PACK", line 4
Package creato.
452
A questo punto se un programmatore, non sapendo che la
funzione deve essere usata in SQL, cerca di aggiungere nel codice di
A un update sul db:
WTO> create or replace package body pack is
2 function a return number is
3 begin
4 update emp set empno=0 where 1=2;
5 return 2;
6 end;
7 end;
8 /
LINE/COL ERROR
-------- ------------------------------------------------------
2/1 PLS-00452: Il sottoprogramma 'A' viola il pragma
associato
Package creato.
453
WTO> exec dbms_output.put_line('Var='||pack.var);
Var=1
Package creato.
WTO> begin
2 pack.var := 1;
3 dbms_output.put_line('Var='||pack.var);
4 end;
5 /
Var=1
SUM(SAL)
----------
29025
454
WTO> create or replace function getsal return number is
2 s number;
3 begin
4 select sum(sal) into s from emp;
5 return s;
6 end;
7 /
Funzione creata.
Funzione creata.
Aggiornate 14 righe.
455
Ad esempio se il codice è
declare
totale number;
begin
totale := calcola_nominali + calcola_interessi;
end;
return s;
end;
return s;
end;
select sum(interessi)
into v_calcola_interessi
from operazioni;
456
declare
totale number;
begin
PRAGMA INLINE(calcola_nominali,'NO');
totale := calcola_nominali + calcola_interessi;
end;
457
8.9.8 PL/SQL Trace
Fin da Oracle8i è disponibile una preziosa utility che consente
di mettere sotto trace una sessione di lavoro PL/SQL. Si tratta del
package DBMS_TRACE.
Il package offre le seguenti funzionalità:
CLEAR_PLSQL_TRACE
Procedura che consente di stoppare una sessione di tracing.
GET_PLSQL_TRACE_LEVEL
Funzione che restituisce il livello di tracing corrente.
PLSQL_TRACE_VERSION
Procedura che restituisce la versione del package
DBMS_TRACE.
SET_PLSQL_TRACE
Procedura che avvia una sessione di tracing impostando il livello
di tracing.
Per fare un semplice test creiamo una semplice funzione
ricorsiva ed una procedura che la chiama:
WTO> create or replace function f1(p number) return number is
2 a number;
3 begin
4 select 1/p into a from dual;
5 return f1(p-1);
6 exception
7 when others then
8 return 0;
9 end;
10 /
Function created.
Procedure created.
458
La procedura innesca la funzione passando il valore 3 come
parametro.
Innanzi tutto dobbiamo dire che il package DBMS_TRACE
scrive in un paio di tabelle del database che devono essere create
lanciando (da SYS) lo script tracetab.sql che si trova nella directory
RDBMS/ADMIN sotto la Oracle Home del server.
Una volta lanciato lo script e create le tabelle
WTO> desc sys.plsql_trace_runs
Nome Nullo? Tipo
----------------------- -------- ----------------
RUNID NOT NULL NUMBER
RUN_DATE DATE
RUN_OWNER VARCHAR2(31)
RUN_COMMENT VARCHAR2(2047)
RUN_COMMENT1 VARCHAR2(2047)
RUN_END DATE
RUN_FLAGS VARCHAR2(2047)
RELATED_RUN NUMBER
RUN_SYSTEM_INFO VARCHAR2(2047)
SPARE1 VARCHAR2(256)
459
Possiamo attivare la trace:
WTO> begin
2 DBMS_TRACE.SET_PLSQL_TRACE (
3 DBMS_TRACE.TRACE_ALL_CALLS+
4 DBMS_TRACE.TRACE_ALL_EXCEPTIONS+
5 DBMS_TRACE.TRACE_ALL_SQL);
6 end;
7 /
GET_PLSQL_TRACE_LEVEL
---------------------
37
WTO> print v1
V1
----------
1
WTO> print v2
V2
----------
0
460
Tornando al nostro esempio, siamo pronti per eseguire p1 con
la trace attiva:
WTO> exec p1
RUNID COUNT(0)
---------- ----------
1 7
2 7
3 52
4 7
5 24
Selezionate 24 righe.
461
Come si vede ci sono alcuni eventi di sistema (la cui tracciatura
può essere soppressa utilizzando la costante
NO_TRACE_ADMINISTRATIVE) e molti eventi applicativi che erano
stati richiesti: le query eseguite, le eccezioni sollevate e gestite, le
chiamate tra programmi.
Un altro utile campo è il CALLSTACK, valorizzato solo su alcuni
eventi particolari:
WTO> select event_seq, CALLSTACK
2 from sys.plsql_trace_events
3 where runid=5;
EVENT_SEQ CALLSTACK
---------- ------------------------------------------------------
1
2 ----- PL/SQL Call Stack -----
object line object
handle number name
1CDCDB88 21 package body SYS.DBMS_TRACE
1CDCDB88 75 package body SYS.DBMS_TRACE
1CDCDB88 80 package body SYS.DBMS_TRACE
1CDCFD7C 2 anonymous block
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 ----- PL/SQL Call Stack -----
object line object
handle number name
1CF6A894 4 function WTO_ESEMPIO.F1
1CF6A894 5 function WTO_ESEMPIO.F1
1CF6A894 5 function WTO_ESEMPIO.F1
1CF6A894 5 function WTO_ESEMPIO.F1
1CDB5A58 4 procedure WTO_ESEMPIO.P1
1CDC5888 1 anonymous block
462
19
20
21
22
23
24
Selezionate 24 righe.
463
8.9.9 PL/SQL Profiling
Il profiling è l'attività di monitoraggio dei tempi d'esecuzione dei
singoli step di programma.
Quest'attività può essere eseguita con l'ausilio del package
DBMS_PROFILER fino a Oracle10g e con il nuovo package
DBMS_HPROF in Oracle11g.
DBMS_PROFILER scrive i risultati del profiling in tabelle di
database che devono essere create lanciando lo script proftab.sql (si
trova nella directory RDBMS/ADMIN sotto la Oracle Home del server).
Una volta lanciato lo script saranno presenti nel DB le seguenti
tre tabelle:
Sessioni di profiling attivate:
WTO> desc plsql_profiler_runs
Name Null? Type
----------------------- -------- ----------------
RUNID NOT NULL NUMBER
RELATED_RUN NUMBER
RUN_OWNER VARCHAR2(32)
RUN_DATE DATE
RUN_COMMENT VARCHAR2(2047)
RUN_TOTAL_TIME NUMBER
RUN_SYSTEM_INFO VARCHAR2(2047)
RUN_COMMENT1 VARCHAR2(2047)
SPARE1 VARCHAR2(256)
464
Tempi di esecuzione per i singoli elementi delle unità di codice:
WTO> desc plsql_profiler_data
Name Null? Type
----------------------- -------- ----------------
RUNID NOT NULL NUMBER
UNIT_NUMBER NOT NULL NUMBER
LINE# NOT NULL NUMBER
TOTAL_OCCUR NUMBER
TOTAL_TIME NUMBER
MIN_TIME NUMBER
MAX_TIME NUMBER
SPARE1 NUMBER
SPARE2 NUMBER
SPARE3 NUMBER
SPARE4 NUMBER
START_PROFILER
--------------
0
465
E poi lanciamo la procedura P1 definita nel paragrafo
precedente:
WTO> exec p1
466
Ci sono anche le unità di codice esaminate:
WTO> select RUNID, UNIT_NUMBER, UNIT_TYPE, UNIT_OWNER, UNIT_NAME
2 from plsql_profiler_units;
9 rows selected.
16 rows selected.
467
WTO> desc dbmshp_runs
Name Null? Type
----------------------- -------- ----------------
RUNID NOT NULL NUMBER
RUN_TIMESTAMP TIMESTAMP(6)
TOTAL_ELAPSED_TIME NUMBER(38)
RUN_COMMENT VARCHAR2(2047)
468
WTO> var id number;
WTO> exec :id := DBMS_HPROF.ANALYZE('TEST_DIR','prof_test.txt');
11 rows selected.
Singole chiamate
(PARENTSYMID è il codice del chiamante, CHILDSYMID è il
codice del chiamato). I tempi qui sono tutti in microsecondi.
469
WTO> select * from dbmshp_parent_child_info;
13 rows selected.
470
9 XML DB
Tabella creata.
471
WTO >insert into myxml values (
2 xmltype('<emps>
3 <employee>
4 <EMPNO>7369</EMPNO>
5 <ENAME>SMITH</ENAME>
6 <JOB>CLERK</JOB>
7 <SAL>800</SAL>
8 </employee>
9 <employee>
10 <EMPNO>7499</EMPNO>
11 <ENAME>ALLEN</ENAME>
12 <JOB>SALESMAN</JOB>
13 <SAL>1600</SAL>
14 </employee>
15 <employee>
16 <EMPNO>7521</EMPNO>
17 <ENAME>WARD</ENAME>
18 <JOB>SALESMAN</JOB>
19 <SAL>1250</SAL>
20 </employee>
21 </emps>')
22 );
Creata 1 riga.
472
WTO> select xmlelement('NOME',ename) from emp;
XMLELEMENT('NOME',ENAME)
-----------------------------------------------
<NOME>SMITH</NOME>
<NOME>ALLEN</NOME>
<NOME>WARD</NOME>
<NOME>JONES</NOME>
<NOME>MARTIN</NOME>
<NOME>BLAKE</NOME>
<NOME>CLARK</NOME>
<NOME>SCOTT</NOME>
<NOME>KING</NOME>
<NOME>TURNER</NOME>
<NOME>ADAMS</NOME>
<NOME>JAMES</NOME>
<NOME>FORD</NOME>
<NOME>MILLER</NOME>
Selezionate 14 righe.
473
WTO> select xmlelement('DIPENDENTE',xmlelement('NOME',ename)
2 ,xmlelement('STIPENDIO',sal)) C
3 from emp;
C
---------------------------------------------------------------
<DIPENDENTE><NOME>SMITH</NOME>
<STIPENDIO>800</STIPENDIO></DIPENDENTE>
<DIPENDENTE><NOME>ALLEN</NOME>
<STIPENDIO>1600</STIPENDIO></DIPENDENTE>
<DIPENDENTE><NOME>WARD</NOME>
<STIPENDIO>1250</STIPENDIO></DIPENDENTE>
<DIPENDENTE><NOME>JONES</NOME>
<STIPENDIO>2975</STIPENDIO></DIPENDENTE>
<DIPENDENTE><NOME>MARTIN</NOME>
<STIPENDIO>1250</STIPENDIO></DIPENDENTE>
<DIPENDENTE><NOME>BLAKE</NOME>
<STIPENDIO>2850</STIPENDIO></DIPENDENTE>
<DIPENDENTE><NOME>CLARK</NOME>
<STIPENDIO>2450</STIPENDIO></DIPENDENTE>
<DIPENDENTE><NOME>SCOTT</NOME>
<STIPENDIO>3000</STIPENDIO></DIPENDENTE>
<DIPENDENTE><NOME>KING</NOME>
<STIPENDIO>5000</STIPENDIO></DIPENDENTE>
<DIPENDENTE><NOME>TURNER</NOME>
<STIPENDIO>1500</STIPENDIO></DIPENDENTE>
<DIPENDENTE><NOME>ADAMS</NOME>
<STIPENDIO>1100</STIPENDIO></DIPENDENTE>
<DIPENDENTE><NOME>JAMES</NOME>
<STIPENDIO>950</STIPENDIO></DIPENDENTE>
<DIPENDENTE><NOME>FORD</NOME>
<STIPENDIO>3000</STIPENDIO></DIPENDENTE>
<DIPENDENTE><NOME>MILLER</NOME>
<STIPENDIO>1300</STIPENDIO></DIPENDENTE>
Selezionate 14 righe.
474
WTO> select xmlelement(evalname ename,sal) from emp;
XMLELEMENT(EVALNAMEENAME,SAL)
-------------------------------------------------------
<SMITH>800</SMITH>
<ALLEN>1600</ALLEN>
<WARD>1250</WARD>
<JONES>2975</JONES>
<MARTIN>1250</MARTIN>
<BLAKE>2850</BLAKE>
<CLARK>2450</CLARK>
<SCOTT>3000</SCOTT>
<KING>5000</KING>
<TURNER>1500</TURNER>
<ADAMS>1100</ADAMS>
<JAMES>950</JAMES>
<FORD>3000</FORD>
<MILLER>1300</MILLER>
Selezionate 14 righe.
DIP
-------------------------------------------------------------
<DIPENDENTE><NOME>SMITH</NOME><STIPENDIO>800</STIPENDIO>
<IMPIEGO>CLERK</IMPIEGO><CAPO>7902</CAPO></DIPENDENTE>
<DIPENDENTE><NOME>ALLEN</NOME><STIPENDIO>1600</STIPENDIO>
<IMPIEGO>SALESMAN</IMPIEGO><CAPO>7698</CAPO></DIPENDENTE>
<DIPENDENTE><NOME>WARD</NOME><STIPENDIO>1250</STIPENDIO>
<IMPIEGO>SALESMAN</IMPIEGO><CAPO>7698</CAPO></DIPENDENTE>
<DIPENDENTE><NOME>JONES</NOME><STIPENDIO>2975</STIPENDIO>
<IMPIEGO>MANAGER</IMPIEGO><CAPO>7839</CAPO></DIPENDENTE>
<DIPENDENTE><NOME>MARTIN</NOME><STIPENDIO>1250</STIPENDIO>
<IMPIEGO>SALESMAN</IMPIEGO><CAPO>7698</CAPO></DIPENDENTE>
<DIPENDENTE><NOME>BLAKE</NOME><STIPENDIO>2850</STIPENDIO>
<IMPIEGO>MANAGER</IMPIEGO><CAPO>7839</CAPO></DIPENDENTE>
<DIPENDENTE><NOME>CLARK</NOME><STIPENDIO>2450</STIPENDIO>
<IMPIEGO>MANAGER</IMPIEGO><CAPO>7839</CAPO></DIPENDENTE>
<DIPENDENTE><NOME>SCOTT</NOME><STIPENDIO>3000</STIPENDIO>
<IMPIEGO>ANALYST</IMPIEGO><CAPO>7566</CAPO></DIPENDENTE>
<DIPENDENTE><NOME>KING</NOME><STIPENDIO>5000</STIPENDIO>
<IMPIEGO>PRESIDENT</IMPIEGO></DIPENDENTE>
<DIPENDENTE><NOME>TURNER</NOME><STIPENDIO>1500</STIPENDIO>
<IMPIEGO>SALESMAN</IMPIEGO><CAPO>7698</CAPO></DIPENDENTE>
<DIPENDENTE><NOME>ADAMS</NOME><STIPENDIO>1100</STIPENDIO>
<IMPIEGO>CLERK</IMPIEGO><CAPO>7788</CAPO></DIPENDENTE>
<DIPENDENTE><NOME>JAMES</NOME><STIPENDIO>950</STIPENDIO>
<IMPIEGO>CLERK</IMPIEGO><CAPO>7698</CAPO></DIPENDENTE>
<DIPENDENTE><NOME>FORD</NOME><STIPENDIO>3000</STIPENDIO>
<IMPIEGO>ANALYST</IMPIEGO><CAPO>7566</CAPO></DIPENDENTE>
475
<DIPENDENTE><NOME>MILLER</NOME><STIPENDIO>1300</STIPENDIO>
<IMPIEGO>CLERK</IMPIEGO><CAPO>7782</CAPO></DIPENDENTE>
Selezionate 14 righe.
ELEMENTI
-----------------------------------------------------------
<SMITH>SMITH</SMITH><MGR7369>7902</MGR7369>
<ALLEN>ALLEN</ALLEN><MGR7499>7698</MGR7499>
<WARD>WARD</WARD><MGR7521>7698</MGR7521>
<JONES>JONES</JONES><MGR7566>7839</MGR7566>
<MARTIN>MARTIN</MARTIN><MGR7654>7698</MGR7654>
<BLAKE>BLAKE</BLAKE><MGR7698>7839</MGR7698>
<CLARK>CLARK</CLARK><MGR7782>7839</MGR7782>
<SCOTT>SCOTT</SCOTT><MGR7788>7566</MGR7788>
<KING>KING</KING>
<TURNER>TURNER</TURNER><MGR7844>7698</MGR7844>
<ADAMS>ADAMS</ADAMS><MGR7876>7788</MGR7876>
<JAMES>JAMES</JAMES><MGR7900>7698</MGR7900>
<FORD>FORD</FORD><MGR7902>7566</MGR7902>
<MILLER>MILLER</MILLER><MGR7934>7782</MGR7934>
Selezionate 14 righe.
476
select xmlelement('DIPENDENTE',
xmlattributes(deptno as 'dip',hiredate as 'data'),
xmlforest(ename as 'NOME', sal as 'STIPENDIO',
job as 'IMPIEGO', mgr as 'CAPO')) dip from emp
DIP
-------------------------------------------------------------
<DIPENDENTE dip='20' data='1980-12-
17'><NOME>SMITH</NOME><STIPENDIO>800</STIPENDIO>
<IMPIEGO>CLERK</IMPIEGO><CAPO>7902</CAPO></DIPENDENTE>
<DIPENDENTE dip='30' data='1981-02-
20'><NOME>ALLEN</NOME><STIPENDIO>1600</STIPENDIO>
<IMPIEGO>SALESMAN</IMPIEGO><CAPO>7698</CAPO></DIPENDENTE>
<DIPENDENTE dip='30' data='1981-02-
22'><NOME>WARD</NOME><STIPENDIO>1250</STIPENDIO>
<IMPIEGO>SALESMAN</IMPIEGO><CAPO>7698</CAPO></DIPENDENTE>
<DIPENDENTE dip='20' data='1981-04-
02'><NOME>JONES</NOME><STIPENDIO>2975</STIPENDIO>
<IMPIEGO>MANAGER</IMPIEGO><CAPO>7839</CAPO></DIPENDENTE>
<DIPENDENTE dip='30' data='1981-09-
28'><NOME>MARTIN</NOME><STIPENDIO>1250</STIPENDIO>
<IMPIEGO>SALESMAN</IMPIEGO><CAPO>7698</CAPO></DIPENDENTE>
<DIPENDENTE dip='30' data='1981-05-
01'><NOME>BLAKE</NOME><STIPENDIO>2850</STIPENDIO>
<IMPIEGO>MANAGER</IMPIEGO><CAPO>7839</CAPO></DIPENDENTE>
<DIPENDENTE dip='10' data='1981-06-
09'><NOME>CLARK</NOME><STIPENDIO>2450</STIPENDIO>
<IMPIEGO>MANAGER</IMPIEGO><CAPO>7839</CAPO></DIPENDENTE>
<DIPENDENTE dip='20' data='1982-12-
09'><NOME>SCOTT</NOME><STIPENDIO>3000</STIPENDIO>
<IMPIEGO>ANALYST</IMPIEGO><CAPO>7566</CAPO></DIPENDENTE>
<DIPENDENTE dip='10' data='1981-11-
17'><NOME>KING</NOME><STIPENDIO>5000</STIPENDIO>
<IMPIEGO>PRESIDENT</IMPIEGO></DIPENDENTE>
<DIPENDENTE dip='30' data='1981-09-
08'><NOME>TURNER</NOME><STIPENDIO>1500</STIPENDIO>
<IMPIEGO>SALESMAN</IMPIEGO><CAPO>7698</CAPO></DIPENDENTE>
<DIPENDENTE dip='20' data='1983-01-
12'><NOME>ADAMS</NOME><STIPENDIO>1100</STIPENDIO>
<IMPIEGO>CLERK</IMPIEGO><CAPO>7788</CAPO></DIPENDENTE>
<DIPENDENTE dip='30' data='1981-12-
03'><NOME>JAMES</NOME><STIPENDIO>950</STIPENDIO>
<IMPIEGO>CLERK</IMPIEGO><CAPO>7698</CAPO></DIPENDENTE>
<DIPENDENTE dip='20' data='1981-12-
03'><NOME>FORD</NOME><STIPENDIO>3000</STIPENDIO>
<IMPIEGO>ANALYST</IMPIEGO><CAPO>7566</CAPO></DIPENDENTE>
<DIPENDENTE dip='10' data='1982-01-
23'><NOME>MILLER</NOME><STIPENDIO>1300</STIPENDIO>
<IMPIEGO>CLERK</IMPIEGO><CAPO>7782</CAPO></DIPENDENTE>
Selezionate 14 righe.
477
A questo punto siamo soddisfatti di come abbiamo visualizzato
le singole righe per ogni dipendente ma vogliamo fare un altro passo:
metterle tutte insieme in un solo documento XML che abbia come
elemento radice DIPENDENTI:
select xmlelement('DIPENDENTI',xmlagg(xmlelement('DIPENDENTE',
xmlattributes(deptno as 'dip',hiredate as 'data'),
xmlforest(ename as 'NOME', sal as 'STIPENDIO',
job as 'IMPIEGO', mgr as 'CAPO')))) mydoc
from emp;
MYDOC
------------------------------------------------------------
<DIPENDENTI><DIPENDENTE dip='20' data='1980-12-17'><NOME>SMI
TH</NOME><STIPENDIO>800</STIPENDIO><IMPIEGO>CLERK</IMPIEGO><
CAPO>7902</CAPO></DIPENDENTE><DIPENDENTE dip='30' data='1981
-02-20'><NOME>ALLEN</NOME><STIPENDIO>1600</STIPENDIO><IMPIEG
O>SALESMAN</IMPIEGO><CAPO>7698</CAPO></DIPENDENTE><DIPENDENT
E dip='30' data='1981-02-22'><NOME>WARD</NOME><STIPENDIO>125
0</STIPENDIO><IMPIEGO>SALESMAN</IMPIEGO><CAPO>7698</CAPO></D
IPENDENTE><DIPENDENTE dip='20' data='1981-04-02'><NOME>JONES
</NOME><STIPENDIO>2975</STIPENDIO><IMPIEGO>MANAGER</IMPIEGO>
<CAPO>7839</CAPO></DIPENDENTE><DIPENDENTE dip='30' data='198
1-09-28'><NOME>MARTIN</NOME><STIPENDIO>1250</STIPENDIO><IMPI
EGO>SALESMAN</IMPIEGO><CAPO>7698</CAPO></DIPENDENTE><DIPENDE
NTE dip='30' data='1981-05-01'><NOME>BLAKE</NOME><STIPENDIO>
2850</STIPENDIO><IMPIEGO>MANAGER</IMPIEGO><CAPO>7839</CAPO><
/DIPENDENTE><DIPENDENTE dip='10' data='1981-06-09'><NOME>CLA
RK</NOME><STIPENDIO>2450</STIPENDIO><IMPIEGO>MANAGER</IMPIEG
O><CAPO>7839</CAPO></DIPENDENTE><DIPENDENTE dip='20' data='1
982-12-09'><NOME>SCOTT</NOME><STIPENDIO>3000</STIPENDIO><IMP
IEGO>ANALYST</IMPIEGO><CAPO>7566</CAPO></DIPENDENTE><DIPENDE
NTE dip='10' data='1981-11-17'><NOME>KING</NOME><STIPENDIO>5
000</STIPENDIO><IMPIEGO>PRESIDENT</IMPIEGO></DIPENDENTE><DIP
ENDENTE dip='30' data='1981-09-08'><NOME>TURNER</NOME><STIPE
NDIO>1500</STIPENDIO><IMPIEGO>SALESMAN</IMPIEGO><CAPO>7698</
CAPO></DIPENDENTE><DIPENDENTE dip='20' data='1983-01-12'><NO
ME>ADAMS</NOME><STIPENDIO>1100</STIPENDIO><IMPIEGO>CLERK</IM
PIEGO><CAPO>7788</CAPO></DIPENDENTE><DIPENDENTE dip='30' dat
a='1981-12-03'><NOME>JAMES</NOME><STIPENDIO>950</STIPENDIO><
IMPIEGO>CLERK</IMPIEGO><CAPO>7698</CAPO></DIPENDENTE><DIPEND
ENTE dip='20' data='1981-12-03'><NOME>FORD</NOME><STIPENDIO>
3000</STIPENDIO><IMPIEGO>ANALYST</IMPIEGO><CAPO>7566</CAPO><
/DIPENDENTE><DIPENDENTE dip='10' data='1982-01-23'><NOME>MIL
LER</NOME><STIPENDIO>1300</STIPENDIO><IMPIEGO>CLERK</IMPIEGO
><CAPO>7782</CAPO></DIPENDENTE></DIPENDENTI>
478
comporta un problema, anzi consente di risparmiare spazio e tempo di
elaborazione.
Se però vogliamo proprio vedere il documento ben formattato
possiamo utilizzare la funzione XMLSERIALIZE:
select xmlserialize(document
xmlelement('DIPENDENTI',xmlagg(xmlelement('DIPENDENTE',
xmlattributes(deptno as 'dip',hiredate as 'data'),
xmlforest(ename as 'NOME', sal as 'STIPENDIO',job as 'IMPIEGO',
mgr as 'CAPO'))))) mydoc
from emp;
MYDOC
--------------------------------------------------------------
<DIPENDENTI>
<DIPENDENTE dip='20' data='1980-12-17'>
<NOME>SMITH</NOME>
<STIPENDIO>800</STIPENDIO>
<IMPIEGO>CLERK</IMPIEGO>
<CAPO>7902</CAPO>
</DIPENDENTE>
<DIPENDENTE dip='30' data='1981-02-20'>
<NOME>ALLEN</NOME>
<STIPENDIO>1600</STIPENDIO>
<IMPIEGO>SALESMAN</IMPIEGO>
<CAPO>7698</CAPO>
</DIPENDENTE>
<DIPENDENTE dip='30' data='1981-02-22'>
<NOME>WARD</NOME>
<STIPENDIO>1250</STIPENDIO>
<IMPIEGO>SALESMAN</IMPIEGO>
<CAPO>7698</CAPO>
</DIPENDENTE>
<DIPENDENTE dip='20' data='1981-04-02'>
<NOME>JONES</NOME>
<STIPENDIO>2975</STIPENDIO>
<IMPIEGO>MANAGER</IMPIEGO>
<CAPO>7839</CAPO>
</DIPENDENTE>
<DIPENDENTE dip='30' data='1981-09-28'>
<NOME>MARTIN</NOME>
<STIPENDIO>1250</STIPENDIO>
<IMPIEGO>SALESMAN</IMPIEGO>
<CAPO>7698</CAPO>
</DIPENDENTE>
<DIPENDENTE dip='30' data='1981-05-01'>
<NOME>BLAKE</NOME>
<STIPENDIO>2850</STIPENDIO>
<IMPIEGO>MANAGER</IMPIEGO>
<CAPO>7839</CAPO>
</DIPENDENTE>
<DIPENDENTE dip='10' data='1981-06-09'>
<NOME>CLARK</NOME>
<STIPENDIO>2450</STIPENDIO>
<IMPIEGO>MANAGER</IMPIEGO>
<CAPO>7839</CAPO>
</DIPENDENTE>
<DIPENDENTE dip='20' data='1982-12-09'>
479
<NOME>SCOTT</NOME>
<STIPENDIO>3000</STIPENDIO>
<IMPIEGO>ANALYST</IMPIEGO>
<CAPO>7566</CAPO>
</DIPENDENTE>
<DIPENDENTE dip='10' data='1981-11-17'>
<NOME>KING</NOME>
<STIPENDIO>5000</STIPENDIO>
<IMPIEGO>PRESIDENT</IMPIEGO>
</DIPENDENTE>
<DIPENDENTE dip='30' data='1981-09-08'>
<NOME>TURNER</NOME>
<STIPENDIO>1500</STIPENDIO>
<IMPIEGO>SALESMAN</IMPIEGO>
<CAPO>7698</CAPO>
</DIPENDENTE>
<DIPENDENTE dip='20' data='1983-01-12'>
<NOME>ADAMS</NOME>
<STIPENDIO>1100</STIPENDIO>
<IMPIEGO>CLERK</IMPIEGO>
<CAPO>7788</CAPO>
</DIPENDENTE>
<DIPENDENTE dip='30' data='1981-12-03'>
<NOME>JAMES</NOME>
<STIPENDIO>950</STIPENDIO>
<IMPIEGO>CLERK</IMPIEGO>
<CAPO>7698</CAPO>
</DIPENDENTE>
<DIPENDENTE dip='20' data='1981-12-03'>
<NOME>FORD</NOME>
<STIPENDIO>3000</STIPENDIO>
<IMPIEGO>ANALYST</IMPIEGO>
<CAPO>7566</CAPO>
</DIPENDENTE>
<DIPENDENTE dip='10' data='1982-01-23'>
<NOME>MILLER</NOME>
<STIPENDIO>1300</STIPENDIO>
<IMPIEGO>CLERK</IMPIEGO>
<CAPO>7782</CAPO>
</DIPENDENTE>
</DIPENDENTI>
480
Select xmlserialize(document xmlelement('DIPARTIMENTI',
XMLAGG(xmlelement('DIPARTIMENTO',
xmlelement('CODICE',deptno),
xmlelement('DESCRIZIONE',dname),
xmlelement('DIPENDENTI',
(select XMLAGG(xmlelement('DIPENDENTE',
xmlforest(ename as 'NOME',
sal as 'STIPENDIO')))
from emp where emp.deptno=dept.deptno)
)
)
)
)
) mydoc
from dept;
MYDOC
-------------------------------------------------------------
<DIPARTIMENTI>
<DIPARTIMENTO>
<CODICE>10</CODICE>
<DESCRIZIONE>ACCOUNTING</DESCRIZIONE>
<DIPENDENTI>
<DIPENDENTE>
<NOME>CLARK</NOME>
<STIPENDIO>2450</STIPENDIO>
</DIPENDENTE>
<DIPENDENTE>
<NOME>KING</NOME>
<STIPENDIO>5000</STIPENDIO>
</DIPENDENTE>
<DIPENDENTE>
<NOME>MILLER</NOME>
<STIPENDIO>1300</STIPENDIO>
</DIPENDENTE>
</DIPENDENTI>
</DIPARTIMENTO>
<DIPARTIMENTO>
<CODICE>20</CODICE>
<DESCRIZIONE>RESEARCH</DESCRIZIONE>
<DIPENDENTI>
<DIPENDENTE>
<NOME>SMITH</NOME>
<STIPENDIO>800</STIPENDIO>
</DIPENDENTE>
<DIPENDENTE>
<NOME>JONES</NOME>
<STIPENDIO>2975</STIPENDIO>
</DIPENDENTE>
<DIPENDENTE>
<NOME>SCOTT</NOME>
<STIPENDIO>3000</STIPENDIO>
</DIPENDENTE>
<DIPENDENTE>
<NOME>ADAMS</NOME>
<STIPENDIO>1100</STIPENDIO>
</DIPENDENTE>
<DIPENDENTE>
481
<NOME>FORD</NOME>
<STIPENDIO>3000</STIPENDIO>
</DIPENDENTE>
</DIPENDENTI>
</DIPARTIMENTO>
<DIPARTIMENTO>
<CODICE>30</CODICE>
<DESCRIZIONE>SALES</DESCRIZIONE>
<DIPENDENTI>
<DIPENDENTE>
<NOME>ALLEN</NOME>
<STIPENDIO>1600</STIPENDIO>
</DIPENDENTE>
<DIPENDENTE>
<NOME>WARD</NOME>
<STIPENDIO>1250</STIPENDIO>
</DIPENDENTE>
<DIPENDENTE>
<NOME>MARTIN</NOME>
<STIPENDIO>1250</STIPENDIO>
</DIPENDENTE>
<DIPENDENTE>
<NOME>BLAKE</NOME>
<STIPENDIO>2850</STIPENDIO>
</DIPENDENTE>
<DIPENDENTE>
<NOME>TURNER</NOME>
<STIPENDIO>1500</STIPENDIO>
</DIPENDENTE>
<DIPENDENTE>
<NOME>JAMES</NOME>
<STIPENDIO>950</STIPENDIO>
</DIPENDENTE>
</DIPENDENTI>
</DIPARTIMENTO>
<DIPARTIMENTO>
<CODICE>40</CODICE>
<DESCRIZIONE>OPERATIONS</DESCRIZIONE>
<DIPENDENTI/>
</DIPARTIMENTO>
</DIPARTIMENTI>
482
WTO> select xmlconcat(xmlelement("nome",ename),
2 xmlelement("dipartimento",deptno),
3 xmlelement(stipendio,sal)) myxml
4* from emp
MYXML
------------------------------------------------------------
<nome>SMITH</nome>
<dipartimento>20</dipartimento>
<STIPENDIO>800</STIPENDIO>
<nome>ALLEN</nome>
<dipartimento>30</dipartimento>
<STIPENDIO>1600</STIPENDIO>
<nome>WARD</nome>
<dipartimento>30</dipartimento>
<STIPENDIO>1250</STIPENDIO>
<nome>JONES</nome>
<dipartimento>20</dipartimento>
<STIPENDIO>2975</STIPENDIO>
<nome>MARTIN</nome>
<dipartimento>30</dipartimento>
<STIPENDIO>1250</STIPENDIO>
<nome>BLAKE</nome>
<dipartimento>30</dipartimento>
<STIPENDIO>2850</STIPENDIO>
<nome>CLARK</nome>
<dipartimento>10</dipartimento>
<STIPENDIO>2450</STIPENDIO>
<nome>SCOTT</nome>
<dipartimento>20</dipartimento>
<STIPENDIO>3000</STIPENDIO>
<nome>KING</nome>
<dipartimento>10</dipartimento>
<STIPENDIO>5000</STIPENDIO>
<nome>TURNER</nome>
<dipartimento>30</dipartimento>
<STIPENDIO>1500</STIPENDIO>
<nome>ADAMS</nome>
<dipartimento>20</dipartimento>
<STIPENDIO>1100</STIPENDIO>
<nome>JAMES</nome>
<dipartimento>30</dipartimento>
<STIPENDIO>950</STIPENDIO>
<nome>FORD</nome>
<dipartimento>20</dipartimento>
<STIPENDIO>3000</STIPENDIO>
483
<nome>MILLER</nome>
<dipartimento>10</dipartimento>
<STIPENDIO>1300</STIPENDIO>
Selezionate 14 righe.
XMLPI(NAME"MYINSTR",'VALUE="XY"')
-------------------------------------------------------------
<?myInstr value="xy"?>
484
e dunque per aggiungerla ad un documento:
WTO> select xmlconcat(
2 xmlpi(name "myInstr", 'value="xy"') ,
3 xmlelement("Emps",
4 xmlagg(xmlelement("Emp",
5 xmlforest(ename, deptno, sal)
6 )
7 )
8 )
9 ) mydoc
10 from emp;
MYDOC
----------------------------------------
<?myInstr value="xy"?>
<Emps>
<Emp>
<ENAME>SMITH</ENAME>
<DEPTNO>20</DEPTNO>
<SAL>800</SAL>
</Emp>
<Emp>
<ENAME>ALLEN</ENAME>
<DEPTNO>30</DEPTNO>
<SAL>1600</SAL>
</Emp>
<Emp>
<ENAME>WARD</ENAME>
<DEPTNO>30</DEPTNO>
<SAL>1250</SAL>
</Emp>
<Emp>
<ENAME>JONES</ENAME>
<DEPTNO>20</DEPTNO>
<SAL>2975</SAL>
</Emp>
<Emp>
<ENAME>MARTIN</ENAME>
<DEPTNO>30</DEPTNO>
<SAL>1250</SAL>
</Emp>
<Emp>
<ENAME>BLAKE</ENAME>
<DEPTNO>30</DEPTNO>
<SAL>2850</SAL>
</Emp>
<Emp>
<ENAME>CLARK</ENAME>
<DEPTNO>10</DEPTNO>
<SAL>2450</SAL>
</Emp>
<Emp>
<ENAME>SCOTT</ENAME>
<DEPTNO>20</DEPTNO>
<SAL>3000</SAL>
</Emp>
<Emp>
485
<ENAME>KING</ENAME>
<DEPTNO>10</DEPTNO>
<SAL>5000</SAL>
</Emp>
<Emp>
<ENAME>TURNER</ENAME>
<DEPTNO>30</DEPTNO>
<SAL>1500</SAL>
</Emp>
<Emp>
<ENAME>ADAMS</ENAME>
<DEPTNO>20</DEPTNO>
<SAL>1100</SAL>
</Emp>
<Emp>
<ENAME>JAMES</ENAME>
<DEPTNO>30</DEPTNO>
<SAL>950</SAL>
</Emp>
<Emp>
<ENAME>FORD</ENAME>
<DEPTNO>20</DEPTNO>
<SAL>3000</SAL>
</Emp>
<Emp>
<ENAME>MILLER</ENAME>
<DEPTNO>10</DEPTNO>
<SAL>1300</SAL>
</Emp>
</Emps>
486
WTO> SELECT XMLRoot(xmlelement("Emps",
2 xmlagg(xmlelement("Emp",
3 xmlforest(ename, deptno, sal)
4 )
5 )
6 ),
7 VERSION '1.0',
8 STANDALONE YES) myDoc
9 FROM emp;
MYDOC
-----------------------------------------------------------
<?xml version="1.0" standalone="yes"?>
<Emps>
<Emp>
<ENAME>SMITH</ENAME>
<DEPTNO>20</DEPTNO>
<SAL>800</SAL>
</Emp>
<Emp>
<ENAME>ALLEN</ENAME>
<DEPTNO>30</DEPTNO>
<SAL>1600</SAL>
</Emp>
<Emp>
<ENAME>WARD</ENAME>
<DEPTNO>30</DEPTNO>
<SAL>1250</SAL>
</Emp>
<Emp>
<ENAME>JONES</ENAME>
<DEPTNO>20</DEPTNO>
<SAL>2975</SAL>
</Emp>
<Emp>
<ENAME>MARTIN</ENAME>
<DEPTNO>30</DEPTNO>
<SAL>1250</SAL>
</Emp>
<Emp>
<ENAME>BLAKE</ENAME>
<DEPTNO>30</DEPTNO>
<SAL>2850</SAL>
</Emp>
<Emp>
<ENAME>CLARK</ENAME>
<DEPTNO>10</DEPTNO>
<SAL>2450</SAL>
</Emp>
<Emp>
<ENAME>SCOTT</ENAME>
<DEPTNO>20</DEPTNO>
<SAL>3000</SAL>
</Emp>
<Emp>
<ENAME>KING</ENAME>
<DEPTNO>10</DEPTNO>
<SAL>5000</SAL>
487
</Emp>
<Emp>
<ENAME>TURNER</ENAME>
<DEPTNO>30</DEPTNO>
<SAL>1500</SAL>
</Emp>
<Emp>
<ENAME>ADAMS</ENAME>
<DEPTNO>20</DEPTNO>
<SAL>1100</SAL>
</Emp>
<Emp>
<ENAME>JAMES</ENAME>
<DEPTNO>30</DEPTNO>
<SAL>950</SAL>
</Emp>
<Emp>
<ENAME>FORD</ENAME>
<DEPTNO>20</DEPTNO>
<SAL>3000</SAL>
</Emp>
<Emp>
<ENAME>MILLER</ENAME>
<DEPTNO>10</DEPTNO>
<SAL>1300</SAL>
</Emp>
</Emps>
XMLCOMMENT('QUESTOÈUNCOMMENTO')
--------------------------------------------------------------
<!--questo è un commento-->
XMLCDATA('QUESTAÈUNASEZIONECDATA')
--------------------------------------------------------------
<![CDATA[questa è una sezione cdata]]>
488
Questa coppia può essere formattata in tanti modi diversi,
XMLCOLATTVAL ci consente molto semplicemente di ottenere
frammenti xml come i seguenti:
WTO> select xmlcolattval(ename,deptno,sal) from emp;
XMLCOLATTVAL(ENAME,DEPTNO,SAL)
------------------------------------------------
<column name = "ENAME">SMITH</column>
<column name = "DEPTNO">20</column>
<column name = "SAL">800</column>
489
<column name = "ENAME">MILLER</column>
<column name = "DEPTNO">10</column>
<column name = "SAL">1300</column>
Selezionate 14 righe.
ROWDOC
---------------------------------------------------------------
<?xml version="1.0"?>
<ROW>
<EMPNO>7369</EMPNO><DEPTNO>20</DEPTNO><SAL>800</SAL></ROW>
<?xml version="1.0"?>
<ROW>
<EMPNO>7499</EMPNO><DEPTNO>30</DEPTNO><SAL>1600</SAL></ROW>
<?xml version="1.0"?>
<ROW>
<EMPNO>7521</EMPNO><DEPTNO>30</DEPTNO><SAL>1250</SAL></ROW>
<?xml version="1.0"?>
<ROW>
<EMPNO>7566</EMPNO><DEPTNO>20</DEPTNO><SAL>2975</SAL></ROW>
<?xml version="1.0"?>
<ROW>
<EMPNO>7654</EMPNO><DEPTNO>30</DEPTNO><SAL>1250</SAL></ROW>
<?xml version="1.0"?>
<ROW>
<EMPNO>7698</EMPNO><DEPTNO>30</DEPTNO><SAL>2850</SAL></ROW>
<?xml version="1.0"?>
<ROW>
<EMPNO>7782</EMPNO><DEPTNO>10</DEPTNO><SAL>2450</SAL></ROW>
<?xml version="1.0"?>
<ROW>
<EMPNO>7788</EMPNO><DEPTNO>20</DEPTNO><SAL>3000</SAL></ROW>
<?xml version="1.0"?>
<ROW>
<EMPNO>7839</EMPNO><DEPTNO>10</DEPTNO><SAL>5000</SAL></ROW>
<?xml version="1.0"?>
490
<ROW>
<EMPNO>7844</EMPNO><DEPTNO>30</DEPTNO><SAL>1500</SAL></ROW>
<?xml version="1.0"?>
<ROW>
<EMPNO>7876</EMPNO><DEPTNO>20</DEPTNO><SAL>1100</SAL></ROW>
<?xml version="1.0"?>
<ROW>
<EMPNO>7900</EMPNO><DEPTNO>30</DEPTNO><SAL>950</SAL></ROW>
<?xml version="1.0"?>
<ROW>
<EMPNO>7902</EMPNO><DEPTNO>20</DEPTNO><SAL>3000</SAL></ROW>
<?xml version="1.0"?>
<ROW>
<EMPNO>7934</EMPNO><DEPTNO>10</DEPTNO><SAL>1300</SAL></ROW>
Selezionate 14 righe.
491
WTO> select xmlserialize(document
2 sys_xmlagg(
3 xmlforest(empno,deptno,sal)
4 )
5 ) mydoc
6 from emp;
MYDOC
------------------------------------------------------------
<?xml version="1.0"?>
<ROWSET>
<EMPNO>7369</EMPNO>
<DEPTNO>20</DEPTNO>
<SAL>800</SAL>
<EMPNO>7499</EMPNO>
<DEPTNO>30</DEPTNO>
<SAL>1600</SAL>
<EMPNO>7521</EMPNO>
<DEPTNO>30</DEPTNO>
<SAL>1250</SAL>
<EMPNO>7566</EMPNO>
<DEPTNO>20</DEPTNO>
<SAL>2975</SAL>
<EMPNO>7654</EMPNO>
<DEPTNO>30</DEPTNO>
<SAL>1250</SAL>
<EMPNO>7698</EMPNO>
<DEPTNO>30</DEPTNO>
<SAL>2850</SAL>
<EMPNO>7782</EMPNO>
<DEPTNO>10</DEPTNO>
<SAL>2450</SAL>
<EMPNO>7788</EMPNO>
<DEPTNO>20</DEPTNO>
<SAL>3000</SAL>
<EMPNO>7839</EMPNO>
<DEPTNO>10</DEPTNO>
<SAL>5000</SAL>
<EMPNO>7844</EMPNO>
<DEPTNO>30</DEPTNO>
<SAL>1500</SAL>
<EMPNO>7876</EMPNO>
<DEPTNO>20</DEPTNO>
<SAL>1100</SAL>
<EMPNO>7900</EMPNO>
<DEPTNO>30</DEPTNO>
<SAL>950</SAL>
<EMPNO>7902</EMPNO>
<DEPTNO>20</DEPTNO>
<SAL>3000</SAL>
<EMPNO>7934</EMPNO>
<DEPTNO>10</DEPTNO>
<SAL>1300</SAL>
</ROWSET>
492
Per concludere l'argomento introduciamo il package
DBMS_XMLGEN.
Questo package non è utilizzabile in SQL ma solo in PL/SQL e
fornisce alcune funzionalità molto semplici per la formattazione dei dati
in formato XML.
Un esempio semplice.
WTO> set serverout on
WTO> DECLARE
2 contesto DBMS_XMLGEN.ctxHandle;
3 documento CLOB;
4 BEGIN
5 -- gli dico quale query eseguire per prendere i dati:
6 contesto := DBMS_XMLGEN.newContext('SELECT * FROM emp');
7 -- gli dico che l'elemento radice del documento
8 -- deve essere DIPENDENTI
9 DBMS_XMLGEN.setRowSetTag(contesto, 'DIPENDENTI');
10 -- gli dico che per ogni record di emp voglio
11 --un elemento DIPENDENTE
12 DBMS_XMLGEN.setRowTag(contesto, 'DIPENDENTE');
13 -- costruisco il documento:
14 documento := DBMS_XMLGEN.getXML(contesto);
15 -- stampo a video il documento:
16 DBMS_OUTPUT.PUT_LINE(documento);
17 -- chiudo il contesto:
18 DBMS_XMLGEN.closeContext(contesto);
19 END;
20 /
<?xml version="1.0"?>
<DIPENDENTI>
<DIPENDENTE>
<EMPNO>7369</EMPNO>
<ENAME>SMITH</ENAME>
<JOB>CLERK</JOB>
<MGR>7902</MGR>
<HIREDATE>17-DIC-80</HIREDATE>
<SAL>800</SAL>
<DEPTNO>20</DEPTNO>
</DIPENDENTE>
<DIPENDENTE>
<EMPNO>7499</EMPNO>
<ENAME>ALLEN</ENAME>
<JOB>SALESMAN</JOB>
<MGR>7698</MGR>
<HIREDATE>20-FEB-81</HIREDATE>
<SAL>1600</SAL>
<COMM>300</COMM>
<DEPTNO>30</DEPTNO>
</DIPENDENTE>
<DIPENDENTE>
<EMPNO>7521</EMPNO>
<ENAME>WARD</ENAME>
<JOB>SALESMAN</JOB>
493
<MGR>7698</MGR>
<HIREDATE>22-FEB-81</HIREDATE>
<SAL>1250</SAL>
<COMM>500</COMM>
<DEPTNO>30</DEPTNO>
</DIPENDENTE>
<DIPENDENTE>
<EMPNO>7566</EMPNO>
<ENAME>JONES</ENAME>
<JOB>MANAGER</JOB>
<MGR>7839</MGR>
<HIREDATE>02-APR-81</HIREDATE>
<SAL>2975</SAL>
<DEPTNO>20</DEPTNO>
</DIPENDENTE>
<DIPENDENTE>
<EMPNO>7654</EMPNO>
<ENAME>MARTIN</ENAME>
<JOB>SALESMAN</JOB>
<MGR>7698</MGR>
<HIREDATE>28-SET-81</HIREDATE>
<SAL>1250</SAL>
<COMM>1400</COMM>
<DEPTNO>30</DEPTNO>
</DIPENDENTE>
<DIPENDENTE>
<EMPNO>7698</EMPNO>
<ENAME>BLAKE</ENAME>
<JOB>MANAGER</JOB>
<MGR>7839</MGR>
<HIREDATE>01-MAG-81</HIREDATE>
<SAL>2850</SAL>
<DEPTNO>30</DEPTNO>
</DIPENDENTE>
<DIPENDENTE>
<EMPNO>7782</EMPNO>
<ENAME>CLARK</ENAME>
<JOB>MANAGER</JOB>
<MGR>7839</MGR>
<HIREDATE>09-GIU-81</HIREDATE>
<SAL>2450</SAL>
<DEPTNO>10</DEPTNO>
</DIPENDENTE>
<DIPENDENTE>
<EMPNO>7788</EMPNO>
<ENAME>SCOTT</ENAME>
<JOB>ANALYST</JOB>
<MGR>7566</MGR>
<HIREDATE>09-DIC-82</HIREDATE>
<SAL>3000</SAL>
<DEPTNO>20</DEPTNO>
494
</DIPENDENTE>
<DIPENDENTE>
<EMPNO>7839</EMPNO>
<ENAME>KING</ENAME>
<JOB>PRESIDENT</JOB>
<HIREDATE>17-NOV-81</HIREDATE>
<SAL>5000</SAL>
<DEPTNO>10</DEPTNO>
</DIPENDENTE>
<DIPENDENTE>
<EMPNO>7844</EMPNO>
<ENAME>TURNER</ENAME>
<JOB>SALESMAN</JOB>
<MGR>7698</MGR>
<HIREDATE>08-SET-81</HIREDATE>
<SAL>1500</SAL>
<COMM>0</COMM>
<DEPTNO>30</DEPTNO>
</DIPENDENTE>
<DIPENDENTE>
<EMPNO>7876</EMPNO>
<ENAME>ADAMS</ENAME>
<JOB>CLERK</JOB>
<MGR>7788</MGR>
<HIREDATE>12-GEN-83</HIREDATE>
<SAL>1100</SAL>
<DEPTNO>20</DEPTNO>
</DIPENDENTE>
<DIPENDENTE>
<EMPNO>7900</EMPNO>
<ENAME>JAMES</ENAME>
<JOB>CLERK</JOB>
<MGR>7698</MGR>
<HIREDATE>03-DIC-81</HIREDATE>
<SAL>950</SAL>
<DEPTNO>30</DEPTNO>
</DIPENDENTE>
<DIPENDENTE>
<EMPNO>7902</EMPNO>
<ENAME>FORD</ENAME>
<JOB>ANALYST</JOB>
<MGR>7566</MGR>
<HIREDATE>03-DIC-81</HIREDATE>
<SAL>3000</SAL>
<DEPTNO>20</DEPTNO>
</DIPENDENTE>
<DIPENDENTE>
<EMPNO>7934</EMPNO>
<ENAME>MILLER</ENAME>
<JOB>CLERK</JOB>
<MGR>7782</MGR>
495
<HIREDATE>23-GEN-82</HIREDATE>
<SAL>1300</SAL>
<DEPTNO>10</DEPTNO>
</DIPENDENTE>
</DIPENDENTI>
XMLSERIALIZE(DOCUMENTX)
-------------------------------------------------
<emps>
<employee>
<EMPNO>7369</EMPNO>
<ENAME>SMITH</ENAME>
<JOB>CLERK</JOB>
<SAL>800</SAL>
</employee>
<employee>
<EMPNO>7499</EMPNO>
<ENAME>ALLEN</ENAME>
<JOB>SALESMAN</JOB>
<SAL>1600</SAL>
</employee>
<employee>
<EMPNO>7521</EMPNO>
<ENAME>WARD</ENAME>
<JOB>SALESMAN</JOB>
<SAL>1250</SAL>
</employee>
</emps>
496
Vediamo un esempio:
WTO> update myxml set
2 x=INSERTXMLAFTER(x,
3 '//EMPNO[text()=7521]',
4 xmlelement("newel",'newval')
5 );
1 row updated.
XMLSERIALIZE(DOCUMENTX)
--------------------------------------------------------
<emps>
<employee>
<EMPNO>7369</EMPNO>
<ENAME>SMITH</ENAME>
<JOB>CLERK</JOB>
<SAL>800</SAL>
</employee>
<employee>
<EMPNO>7499</EMPNO>
<ENAME>ALLEN</ENAME>
<JOB>SALESMAN</JOB>
<SAL>1600</SAL>
</employee>
<employee>
<EMPNO>7521</EMPNO>
<newel>newval</newel>
<ENAME>WARD</ENAME>
<JOB>SALESMAN</JOB>
<SAL>1250</SAL>
</employee>
</emps>
497
WTO> update myxml set
2 x=INSERTXMLBEFORE(x,
3 '//EMPNO[text()=7521]',
4 xmlelement("newel",'newval')
5 );
1 row updated.
XMLSERIALIZE(DOCUMENTX)
--------------------------------------------------------
<emps>
<employee>
<EMPNO>7369</EMPNO>
<ENAME>SMITH</ENAME>
<JOB>CLERK</JOB>
<SAL>800</SAL>
</employee>
<employee>
<EMPNO>7499</EMPNO>
<ENAME>ALLEN</ENAME>
<JOB>SALESMAN</JOB>
<SAL>1600</SAL>
</employee>
<employee>
<newel>newval</newel>
<EMPNO>7521</EMPNO>
<ENAME>WARD</ENAME>
<JOB>SALESMAN</JOB>
<SAL>1250</SAL>
</employee>
</emps>
498
WTO> update myxml set
2 x=appendchildxml(x,
3 '//EMPNO[text()=7521]',
4 xmlelement("newel",'newval')
5 );
1 row updated.
XMLSERIALIZE(DOCUMENTX)
-----------------------------------------------------------
<emps>
<employee>
<EMPNO>7369</EMPNO>
<ENAME>SMITH</ENAME>
<JOB>CLERK</JOB>
<SAL>800</SAL>
</employee>
<employee>
<EMPNO>7499</EMPNO>
<ENAME>ALLEN</ENAME>
<JOB>SALESMAN</JOB>
<SAL>1600</SAL>
</employee>
<employee>
<EMPNO>7521<newel>newval</newel>
</EMPNO>
<ENAME>WARD</ENAME>
<JOB>SALESMAN</JOB>
<SAL>1250</SAL>
</employee>
</emps>
499
WTO> update myxml set
2 x=insertchildxml(x,
3 '//EMPNO[text()=7521]',
4 'newel',
5 xmlelement("newel",'newval')
6 );
1 row updated.
XMLSERIALIZE(DOCUMENTX)
---------------------------------------------------------
<emps>
<employee>
<EMPNO>7369</EMPNO>
<ENAME>SMITH</ENAME>
<JOB>CLERK</JOB>
<SAL>800</SAL>
</employee>
<employee>
<EMPNO>7499</EMPNO>
<ENAME>ALLEN</ENAME>
<JOB>SALESMAN</JOB>
<SAL>1600</SAL>
</employee>
<employee>
<EMPNO>7521<newel>newval</newel>
</EMPNO>
<ENAME>WARD</ENAME>
<JOB>SALESMAN</JOB>
<SAL>1250</SAL>
</employee>
</emps>
1 row updated.
500
WTO> select xmlserialize(document x) from myxml;
XMLSERIALIZE(DOCUMENTX)
-----------------------------------------------------------------
<emps>
<employee>
<EMPNO>7369</EMPNO>
<ENAME>SMITH</ENAME>
<JOB>CLERK</JOB>
<SAL>800</SAL>
</employee>
<employee>
<EMPNO>7499</EMPNO>
<ENAME>ALLEN</ENAME>
<JOB>SALESMAN</JOB>
<SAL>2000</SAL>
</employee>
<employee>
<EMPNO>7521</EMPNO>
<ENAME>WARD</ENAME>
<JOB>disoccupato</JOB>
<SAL>1250</SAL>
</employee>
</emps>
1 row updated.
XMLSERIALIZE(DOCUMENTX)
--------------------------------------------------------
<emps>
<employee>
<EMPNO>7369</EMPNO>
<ENAME>SMITH</ENAME>
<JOB>CLERK</JOB>
<SAL>800</SAL>
</employee>
<employee>
<EMPNO>7499</EMPNO>
<ENAME>ALLEN</ENAME>
<JOB>SALESMAN</JOB>
<SAL>2000</SAL>
</employee>
</emps>
501
La funzione DELETEXML riceve in input il documento di
partenza ed un riferimento XPATH, eliminando l'elemento (o gli
elementi) che si trova al riferimento.
EXTRACT(XMLTYPE('<A>2010-03-20</A>'),'//A')
----------------------------------------------------------
<a>2010-03-20</a>
XMLCAST(E
---------
20-MAR-10
Session altered.
XMLCAST(EXTRACT(XML
-------------------
20/03/2010 00:00:00
XMLCAST(EXTRACT(XMLT
--------------------
2010-03-20
502
Si noti che la funzione XMLCAST vuole in input un elemento
XML, se forniamo invece un dato scalare otteniamo un errore:
WTO> select xmlcast('2010-03-20' as date) from dual;
select xmlcast('2010-03-20' as date) from dual
*
ERROR at line 1:
ORA-00932: inconsistent datatypes: expected - got -
XMLSERIALIZE(DOCUMENTX)
----------------------------------------------------------
<emps>
<employee>
<EMPNO>7369</EMPNO>
<ENAME>SMITH</ENAME>
<JOB>CLERK</JOB>
<SAL>800</SAL>
</employee>
<employee>
<EMPNO>7499</EMPNO>
<ENAME>ALLEN</ENAME>
<JOB>SALESMAN</JOB>
<SAL>1600</SAL>
</employee>
<employee>
<EMPNO>7521</EMPNO>
<ENAME>WARD</ENAME>
<JOB>SALESMAN</JOB>
<SAL>1250</SAL>
</employee>
</emps>
NAMES
---------------------------------------------------------------
<ENAME>ALLEN</ENAME><ENAME>WARD</ENAME>
503
WTO> select 1 from myxml
2 where xmlexists(
3 'for $i in //employee
4 where $i/SAL > 1000
5 return $i/ENAME' passing x);
1
----------
1
no rows selected
Exists?
--------------------
TRUE
Exists?
--------------------
FALSE
Exists?
--------------------
TRUE
504
WTO> select case when xmlexists('//ENAME[../SAL>10000]'
2 passing x)
3 then 'TRUE' else 'FALSE' end "Exists?"
4 from myxml;
Exists?
--------------------
FALSE
X Y
------------------------------ ------------------------------
<emps> <emps>
<employee> <employee>
<EMPNO>7369</EMPNO> <EMPNO>7369</EMPNO>
<ENAME>SMITH</ENAME> <ENAME>TOM</ENAME>
<JOB>CLERK</JOB> <JOB>CLERK</JOB>
<SAL>800</SAL> <SAL>800</SAL>
</employee> </employee>
<employee> <employee>
<EMPNO>7499</EMPNO> <EMPNO>7499</EMPNO>
<ENAME>ALLEN</ENAME> <ENAME>ALLEN</ENAME>
<JOB>SALESMAN</JOB> <JOB>SALESMAN</JOB>
<SAL>1600</SAL> <SAL>3000</SAL>
</employee> </employee>
<employee> <employee>
<EMPNO>7521</EMPNO> <EMPNO>7521</EMPNO>
<ENAME>WARD</ENAME> <ENAME>WARD</ENAME>
<JOB>SALESMAN</JOB> <JOB>SALESMAN</JOB>
<SAL>1250</SAL> <SAL>1250</SAL>
</employee> </employee>
</emps> </emps>
505
WTO> select xmldiff(x,y) differenze
2 from myxml;
DIFFERENZE
---------------------------------------------------------
<xd:xdiff xsi:schemaLocation=
"http://xmlns.oracle.com/xdb/xdiff.xsd
http://xmlns.oracle.com/xdb/xdiff.xsd"
xmlns:xd="http://xmlns.oracle.com/xdb/xdiff.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<?oracle-xmldiff operations-in-docorder="true"
output-model="snapshot"
diff-algorithm="global"?>
<xd:update-node xd:node-type="text"
xd:xpath="/emps[1]/employee[1]/ENAME[1]/text()[1]">
<xd:content>TOM</xd:content>
</xd:update-node>
<xd:update-node xd:node-type="text"
xd:xpath="/emps[1]/employee[2]/SAL[1]/text()[1]">
<xd:content>3000</xd:content>
</xd:update-node>
</xd:xdiff>
506
WTO> select xmlserialize(document XMLPATCH(X,XMLDIFF(X,Y)))
2 from myxml;
XMLSERIALIZE(DOCUMENTXMLPATCH(X,XMLDIFF(X,Y)))
-------------------------------------------------------------
<emps>
<employee>
<EMPNO>7369</EMPNO>
<ENAME>TOM</ENAME>
<JOB>CLERK</JOB>
<SAL>800</SAL>
</employee>
<employee>
<EMPNO>7499</EMPNO>
<ENAME>ALLEN</ENAME>
<JOB>SALESMAN</JOB>
<SAL>3000</SAL>
</employee>
<employee>
<EMPNO>7521</EMPNO>
<ENAME>WARD</ENAME>
<JOB>SALESMAN</JOB>
<SAL>1250</SAL>
</employee>
</emps>
507
lo schema o qualunque altra cosa. È solo il nome con cui
referenzieremo il nostro schema d'ora in poi.
Ecco gli altri parametri che si possono utilizzare in fase di
registrazione di uno schema:
• LOCAL specifica se si tratta di uno schema locale o globale
(visibile a tutti gli utenti). Se non si specifica, lo schema viene
registrato come locale.
• GENTYPES Se impostato a TRUE fa sì che Oracle generi
automaticamente tutti i type che servono a gestire i vari frammenti
degli XML che saranno inseriti utilizzando questo schema. Il
default è TRUE.
• GENBEAN Se impostato a TRUE fa sì che Oracle generi
automaticamente dei java bean utilizzabili per gestire i vari
frammenti degli XML che saranno inseriti utilizzando questo
schema. Il default è FALSE.
• GENTABLES Se impostato a TRUE fa sì che Oracle generi
automaticamente tutte le tabelle che servono a gestire i vari
frammenti degli XML che saranno inseriti utilizzando questo
schema. Il default è TRUE.
• FORCE Se impostato a TRUE dice ad Oracle di non generare
errori durante la fase di registrazione dello schema. In caso di
errori lo schema sarà semplicemente marcato come non valido.
Default FALSE.
• OWNER specifica l'utente Oracle in cui creare tutti gli oggetti
suddetti. Per default è l'utente che sta registrando lo schema.
• ENABLEHIERARCHY definisce la modalità in cui chiamare la
procedura DBMS_XDBZ.ENABLE_HIERARCHY durante la
creazione degli oggetti di DB.
• OPTIONS Specifica opzioni aggiuntive.
508
Ecco lo schema che è stato registrato.
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="COMPLETE_NAME_TYPE">
<xs:sequence>
<xs:element name="SURNAME" type="xs:string"/>
<xs:element name="NAME" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:element name="TEAM">
<xs:complexType>
<xs:sequence>
<xs:element name="TRAINER" minOccurs="1" maxOccurs="1">
<xs:complexType>
<xs:sequence>
<xs:element name="TR_COMPL_NAME"
type="COMPLETE_NAME_TYPE"/>
<xs:element name="NATIONALITY" type="xs:string"/>
</xs:sequence>
<xs:attribute name="fifa_id" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:length value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="PLAYERS" minOccurs="1" maxOccurs="1">
<xs:complexType>
<xs:sequence>
<xs:element name="PLAYER" minOccurs="23"
maxOccurs="23">
<xs:complexType>
<xs:sequence>
<xs:element name="COMPL_NAME"
type="COMPLETE_NAME_TYPE"/>
<xs:element name="CLUB" type="xs:string"/>
</xs:sequence>
<xs:attribute name="number" use="required">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="23"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="age" type="xs:integer"
use="required"/>
<xs:attribute name="goalkeeper"
type="xs:string" fixed="YES"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
509
<xs:attribute name="country" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="KOREA"/>
<xs:enumeration value="JAPAN"/>
<xs:enumeration value="CHINA"/>
<xs:enumeration value="SAUDI ARABIA"/>
<xs:enumeration value="SOUTH AFRICA"/>
<xs:enumeration value="CAMEROON"/>
<xs:enumeration value="SENEGAL"/>
<xs:enumeration value="TUNISIA"/>
<xs:enumeration value="NIGERIA"/>
<xs:enumeration value="COSTA RICA"/>
<xs:enumeration value="USA"/>
<xs:enumeration value="MEXICO"/>
<xs:enumeration value="ARGENTINA"/>
<xs:enumeration value="PARAGUAY"/>
<xs:enumeration value="ECUADOR"/>
<xs:enumeration value="BRAZIL"/>
<xs:enumeration value="FRANCE"/>
<xs:enumeration value="POLAND"/>
<xs:enumeration value="SWEDEN"/>
<xs:enumeration value="SPAIN"/>
<xs:enumeration value="RUSSIA"/>
<xs:enumeration value="PORTUGAL"/>
<xs:enumeration value="DENMARK"/>
<xs:enumeration value="CROATIA"/>
<xs:enumeration value="ITALY"/>
<xs:enumeration value="ENGLAND"/>
<xs:enumeration value="SLOVENIA"/>
<xs:enumeration value="TURKEY"/>
<xs:enumeration value="BELGIUM"/>
<xs:enumeration value="GERMANY"/>
<xs:enumeration value="AUSTRALIA"/>
<xs:enumeration value="IRELAND REPUBLIC"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<xs:unique name="NUMBERING">
<xs:selector xpath="./PLAYERS/PLAYER"/>
<xs:field xpath="@number"/>
</xs:unique>
</xs:element>
</xs:schema>
510
Ed ecco gli oggetti di DB che sono stati automaticamente creati:
WTO> select object_name, object_type,
2 to_char(last_ddl_time,'hh24miss')
3 from user_objects
4 where last_ddl_time>trunc(sysdate);
Selezionate 20 righe.
511
Se i file XML arrivano in XMLDB attraverso protocolli, come FTP
o HTTP, che non supportano la specifica della tabella di DB in cui
inserirli, essi vengono automaticamente inseriti in queste tabelle di
default.
L'elenco degli schemi registrati si ottiene dalla vista di dizionario
DBA_XML_SCHEMAS (o ALL_ o USER_):
WTO> select owner, SCHEMA_URL from all_xml_schemas;
OWNER SCHEMA_URL
----------- -----------------------------------------------------
WTO_ESEMPIO http://www.mysite.com/xsd/TestSchema.xsd
XDB http://xmlns.oracle.com/xdb/stats.xsd
XDB http://xmlns.oracle.com/xdb/xdbconfig.xsd
XDB http://xmlns.oracle.com/xs/dataSecurity.xsd
XDB http://xmlns.oracle.com/xs/aclids.xsd
XDB http://xmlns.oracle.com/xdb/XDBSchema.xsd
XDB http://xmlns.oracle.com/xdb/XDBResource.xsd
XDB http://www.w3.org/2001/csx.xml.xsd
XDB http://xmlns.oracle.com/xdb/csx.xmltr.xsd
XDB http://xmlns.oracle.com/xdb/acl.xsd
XDB http://xmlns.oracle.com/xdb/dav.xsd
XDB http://xmlns.oracle.com/xdb/XDBResConfig.xsd
XDB http://xmlns.oracle.com/xdb/XDBStandard.xsd
XDB http://xmlns.oracle.com/xdb/log/xdblog.xsd
XDB http://xmlns.oracle.com/xdb/log/ftplog.xsd
XDB http://xmlns.oracle.com/xdb/log/httplog.xsd
XDB http://www.w3.org/2001/xml.xsd
XDB http://xmlns.oracle.com/xdb/xmltr.xsd
XDB http://xmlns.oracle.com/xdb/XDBFolderListing.xsd
XDB http://www.w3.org/1999/xlink.xsd
XDB http://www.w3.org/1999/csx.xlink.xsd
XDB http://www.w3.org/2001/XInclude.xsd
XDB http://www.w3.org/2001/csx.XInclude.xsd
512
WTO> Insert into TEAMS values (XMLType('
2 <TEAM country="ABCD">
3 <TRAINER fifa_id="ITA1234567">
4 <TR_COMPL_NAME>
5 <SURNAME>Trapattoni</SURNAME>
6 <NAME>Giovanni</NAME>
7 </TR_COMPL_NAME>
8 <NATIONALITY>Italy</NATIONALITY>
9 </TRAINER>
10 <PLAYERS>
11 <PLAYER number="1" age="23" goalkeeper="YES">
12 <COMPL_NAME>
13 <SURNAME>Buffon</SURNAME>
14 <NAME>Gianluigi</NAME>
15 </COMPL_NAME>
16 <CLUB>Juventus</CLUB>
17 </PLAYER>
18 </PLAYERS>
19 </TEAM>
20 '));
Insert into TEAMS values (XMLType('
*
ERRORE alla riga 1:
ORA-31038: Valore enumeration non valido: "ABCD"
513
WTO> Insert into TEAMS values (XMLType('
2 <TEAM country="ITALY">
3 <TRAINER fifa_id="ITA1234567">
4 <TR_COMPL_NAME>
5 <SURNAME>Trapattoni</SURNAME>
6 <NAME>Giovanni</NAME>
7 </TR_COMPL_NAME>
8 <NATIONALITY>Italy</NATIONALITY>
9 </TRAINER>
10 <PLAYERS>
11 <PLAYER number="1" age="23" goalkeeper="YES">
12 <COMPL_NAME>
13 <SURNAME>Buffon</SURNAME>
14 <NAME>Gianluigi</NAME>
15 </COMPL_NAME>
16 <CLUB>Juventus</CLUB>
17 </PLAYER>
18 <PLAYER number="1" age="23" goalkeeper="YES">
19 <COMPL_NAME>
20 <SURNAME>Buffon</SURNAME>
21 <NAME>Gianluigi</NAME>
22 </COMPL_NAME>
23 <CLUB>Juventus</CLUB>
24 </PLAYER>
25 </PLAYERS>
26 </TEAM>
27 '));
Creata 1 riga.
VALIDO
----------
0
0
0
1
0
514
Ecco un esempio di procedura che chiama la validazione su tutti
i record presenti in tabella e stampa l'errore per quelli non validi:
WTO> declare
2 cursor c is
3 select rownum, OBJECT_VALUE from TEAMS;
4 begin
5 for d in c loop
6 begin
7 d.OBJECT_VALUE.schemaValidate();
8 exception
9 when others then
10 dbms_output.put_line('Riga '||d.rownum||':');
11 dbms_output.put_line(sqlerrm);
12 dbms_output.put_line('-----------------------------');
13 end;
14 end loop;
15 end;
16 /
Riga 1:
ORA-31154: documento XML non valido
ORA-19202: Errore durante l'elaborazione XML
LSX-00213: solo 1 occorrenze della parte "PLAYER", il minimo Þ 23
------------------------------
Riga 2:
ORA-31154: documento XML non valido
ORA-19202: Errore durante l'elaborazione XML
LSX-00213: solo 1 occorrenze della parte "PLAYER", il minimo Þ 23
------------------------------
Riga 3:
ORA-31154: documento XML non valido
ORA-19202: Errore durante l'elaborazione XML
LSX-00213: solo 2 occorrenze della parte "PLAYER", il minimo Þ 23
------------------------------
Riga 5:
ORA-31154: documento XML non valido
ORA-19202: Errore durante l'elaborazione XML
LSX-00287: chiave duplicata "1.0"
------------------------------
515
Praticamente copia i documenti XML in una tabella temporanea,
droppa tutti gli oggetti collegati allo schema e lo stesso schema, ri-
registra lo schema (e quindi crea tutti i nuovi oggetti collegati) e
reinserisce tutti i documenti.
Ovviamente i vecchi documenti continueranno ad essere validi
anche rispetto al nuovo schema solo se i due schemi sono
"compatibili".
La procedura non si può definire completamente automatica
perché una serie di oggetti eventualmente presenti sulle vecchie
tabelle devono essere creati a mano sulle nuove (indici, constraint,
trigger).
In Oracle11g è stata introdotta la cosiddetta "In place copy
evolution" ovvero la possibilità di modificare lo schema senza copiare i
documenti in una tabella d'appoggio, droppare la vecchia tabella etc...
Questa procedura, non sempre applicabile, esegue un
aggiornamento al volo senza spostare i documenti, quindi è molto più
rapida della precedente.
Dopo che uno schema è stato creato, registrato, utilizzato e
modificato più volte arriva il momento in cui bisogna cancellarlo.
Per farlo è sufficiente utilizzare la seguente procedura:
WTO> BEGIN
2 DBMS_XMLSCHEMA.deleteSchema(
3 SCHEMAURL => 'http://www.mysite.com/xsd/TestSchema.xsd',
4 DELETE_OPTION => dbms_xmlschema.DELETE_CASCADE_FORCE);
5 END;
6 /
516
9.6 Creare documenti Office con XML e PL/SQL.
A partire dalla versione 2003 di Office, Microsoft ha cominciato a
supportare formati XML per i propri documenti.
Una prima generazione di questi formati si è avuta con
l'introduzione dei Microsoft Office 2003 XML formats in Office 2003,
mentre un consolidamento ed una migliore implementazione sta alla
base di Office 2007 (Office Open XML).
In questo paragrafo si darà un esempio di utilizzo del formato
XML per Microsoft Office 2003 per creare fogli Excel direttamente da
PL/SQL utilizzando le potenzialità di estrazione in formato XML trattate
nei paragrafi precedenti.
Per gli esempi saranno utilizzate le tabelle EMP e DEPT di
SCOTT. Vogliamo creare un cartella Excel 2003 che contenga due
fogli, il primo con i record di EMP, il secondo con i record di DEPT.
Cominciamo ponendoci un primo obbiettivo più semplice,
cerchiamo di creare un unico foglio "Dipendenti" con solo due colonne,
Nome e Stipendio.
Possiamo farlo con un'unica query:
Select
xmlroot(
xmlelement(
"ss:Workbook"
,XMLATTRIBUTES(
'urn:schemas-microsoft-com:office:spreadsheet'
as "xmlns:ss"
),
xmlelement(
"ss:Worksheet",
XMLATTRIBUTES(
'Dipendenti' as "ss:Name"
)
,xmlelement("ss:Table",
xmlelement(
"ss:Row",
xmlelement("ss:Cell",
xmlelement(
"ss:Data",
xmlattributes(
'String' as "ss:Type"
),
'Nome'
)
),
xmlelement("ss:Cell",
xmlelement(
"ss:Data",
xmlattributes(
'String' as "ss:Type"
517
),
'Stipendio'
)
)
),
xmlagg(
xmlelement("ss:Row",
xmlelement("ss:Cell",
xmlelement(
"ss:Data",
xmlattributes(
'String' as "ss:Type"
),
ename )
),
xmlelement("ss:Cell",
xmlelement(
"ss:Data",
xmlattributes(
'Number' as "ss:Type"
),
sal )
)
)
)
)
)
)
, VERSION '1.0'
) MyDoc
from emp;
MYDOC
--------------------------------------------------------------
<?xml version="1.0"?>
<ss:Workbook xmlns:ss="urn:schemas-microsoft-
com:office:spreadsheet">
<ss:Worksheet ss:Name="Dipendenti">
<ss:Table>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">Nome</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">Stipendio</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">SMITH</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">800</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">ALLEN</ss:Data>
518
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">1600</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">WARD</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">1250</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">JONES</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">2975</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">MARTIN</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">1250</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">BLAKE</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">2850</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">CLARK</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">2450</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">SCOTT</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">3000</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">KING</ss:Data>
</ss:Cell>
<ss:Cell>
519
<ss:Data ss:Type="Number">5000</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">TURNER</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">1500</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">ADAMS</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">1100</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">JAMES</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">950</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">FORD</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">3000</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">MILLER</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">1300</ss:Data>
</ss:Cell>
</ss:Row>
</ss:Table>
</ss:Worksheet>
</ss:Workbook>
520
create or replace package x as
function NCell(in_val in number) return XMLType;
function DCell(in_val in Date) return XMLType;
function SCell(in_val in Varchar2) return XMLType;
end;
/
end;
/
521
La query precedente diventa:
Select
xmlroot(
xmlelement(
"ss:Workbook"
,XMLATTRIBUTES(
'urn:schemas-microsoft-com:office:spreadsheet'
as "xmlns:ss"
),
xmlelement(
"ss:Worksheet",
XMLATTRIBUTES('Dipendenti' as "ss:Name")
,xmlelement("ss:Table",
xmlelement(
"ss:Row",
x.SCell('Nome'),
x.SCell('Stipendio')
),
xmlagg(
xmlelement("ss:Row",
x.SCell(ename),
x.NCell(sal)
)
)
)
)
)
, VERSION '1.0'
) MyDoc
from emp;
522
function XRow(in1 in XMLType,
in2 in XMLType default null,
in3 in XMLType default null,
in4 in XMLType default null,
in5 in XMLType default null,
in6 in XMLType default null,
in7 in XMLType default null,
in8 in XMLType default null,
in9 in XMLType default null,
in10 in XMLType default null
) return XMLType is
retVal XMLType;
begin
select XMLELEMENT("ss:Row",
XMLCONCAT(in1,in2,in3,in4,in5,
in6,in7,in8,in9,in10)
)
into retVal
from dual;
return retVal;
end;
Che è equivalente a:
523
Select
xmlroot(
xmlelement(
"ss:Workbook"
,XMLATTRIBUTES(
'urn:schemas-microsoft-com:office:spreadsheet'
as "xmlns:ss"
),
xmlelement(
"ss:Worksheet",
XMLATTRIBUTES('Dipendenti' as "ss:Name")
,xmlelement("ss:Table",
x.XRow(x.SCell('Nome'),
x.SCell('Stipendio')),
(select xmlagg(x.XRow(x.SCell(ename),
x.NCell(sal)))
from emp
)
)
)
)
, VERSION '1.0'
) MyDoc
from dual;
524
)
)
from emp
)
)
)
)
, VERSION '1.0'
) MyDoc
from dual;
MYDOC
-----------------------------------------------------------------
<?xml version="1.0"?>
<ss:Workbook xmlns:ss="urn:schemas-microsoft-
com:office:spreadsheet">
<ss:Worksheet ss:Name="Dipendenti">
<ss:Table>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">Matricola</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">Nome</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">Impiego</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">Manager</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">Assunzione</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">Stipendio</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">Commissioni</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">Dipartimento</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="Number">7369</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">SMITH</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">CLERK</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">7902</ss:Data>
</ss:Cell>
<ss:Cell>
525
<ss:Data ss:Type="Date">1980-12-17</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">800</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number"/>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">20</ss:Data>
</ss:Cell>
</ss:Row>
-- ALTRE 13 RIGHE RIMOSSE --
</ss:Table>
</ss:Worksheet>
</ss:Workbook>
526
Adesso aggiungiamo anche il secondo foglio per DEPT:
Select
xmlroot(
xmlelement(
"ss:Workbook"
,XMLATTRIBUTES(
'urn:schemas-microsoft-com:office:spreadsheet'
as "xmlns:ss"
),
xmlelement(
"ss:Worksheet",
XMLATTRIBUTES('Dipendenti' as "ss:Name")
,xmlelement("ss:Table",
x.XRow(x.SCell('Matricola'),
x.SCell('Nome'),
x.SCell('Impiego'),
x.SCell('Manager'),
x.SCell('Assunzione'),
x.SCell('Stipendio'),
x.SCell('Commissioni'),
x.SCell('Dipartimento')
),
(select xmlagg(x.XRow(x.NCell(empno),
x.SCell(ename),
x.SCell(job),
x.NCell(mgr),
x.DCell(hiredate),
x.NCell(sal),
x.NCell(comm),
x.NCell(deptno)
)
)
from emp
)
)
),
xmlelement(
"ss:Worksheet",
XMLATTRIBUTES('Dipartimenti' as "ss:Name")
,xmlelement("ss:Table",
x.XRow(x.SCell('Codice'),
x.SCell('Nome'),
x.SCell('Ubicazione')
),
(select xmlagg(x.XRow(x.NCell(deptno),
x.SCell(dname),
x.SCell(loc)
)
)
from dept
)
)
)
)
, VERSION '1.0'
) MyDoc
from dual;
527
Che aggiunge a quanto già avevamo visto prima il frammento
seguente:
<ss:Worksheet ss:Name="Dipartimenti">
<ss:Table>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">Codice</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">Nome</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">Ubicazione</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="Number">10</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">ACCOUNTING</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">NEW YORK</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="Number">20</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">RESEARCH</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">DALLAS</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="Number">30</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">SALES</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">CHICAGO</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="Number">40</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">OPERATIONS</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">BOSTON</ss:Data>
</ss:Cell>
528
</ss:Row>
</ss:Table>
</ss:Worksheet>
529
end if;
if formato like '%I%' then
if retVal is not null then
select XMLELEMENT("I",retVal)
into retVal
from dual;
else
select XMLELEMENT("I",in_val)
into retVal
from dual;
end if;
end if;
if formato like '%S%' then
if retVal is not null then
select XMLELEMENT("S",retVal)
into retVal
from dual;
else
select XMLELEMENT("S",in_val)
into retVal
from dual;
end if;
end if;
return retVal;
end;
530
function DCell(in_val in Date, formato in varchar2 default null)
return XMLType is
valForm XMLType;
retval XMLType;
begin
if formato is not null then
valForm := formatta(to_char(in_val,'YYYY-MM-DD'),formato);
select xmlelement("ss:Cell",
xmlelement(
"ss:Data",
xmlattributes(
'DateTime' as "ss:Type"
),
valForm)
)
into retVal
from dual;
else
select xmlelement("ss:Cell",
xmlelement(
"ss:Data",
xmlattributes(
'DateTime' as "ss:Type"
),
in_val)
)
into retVal
from dual;
end if;
return retVal;
end;
531
into retVal
from dual;
end if;
return retVal;
end;
end;
/
532
xmlelement("ss:Column",
xmlattributes('100' as "ss:Width")),
xmlelement("ss:Column",
xmlattributes('100' as "ss:Width")),
xmlelement("ss:Column",
xmlattributes('100' as "ss:Width")),
xmlelement("ss:Column",
xmlattributes('100' as "ss:Width")),
xmlelement("ss:Column",
xmlattributes('100' as "ss:Width")),
xmlelement("ss:Column",
xmlattributes('100' as "ss:Width")),
xmlelement("ss:Column",
xmlattributes('100' as "ss:Width")),
x.XRow(x.SCell('Matricola','B'),
x.SCell('Nome','B'),
x.SCell('Impiego','B'),
x.SCell('Manager','B'),
x.SCell('Assunzione','B'),
x.SCell('Stipendio','B'),
x.SCell('Commissioni','B'),
x.SCell('Dipartimento','B')
),
(select xmlagg(x.XRow(x.NCell(empno),
x.SCell(ename,'I'),
x.SCell(job,'I'),
x.NCell(mgr),
x.DCell(hiredate),
x.NCell(sal),
x.NCell(comm),
x.NCell(deptno)
)
)
from emp
)
)
),
xmlelement(
"ss:Worksheet",
XMLATTRIBUTES('Dipartimenti' as "ss:Name")
,xmlelement("ss:Table",
xmlelement("ss:Column",
xmlattributes('100' as "ss:Width")),
xmlelement("ss:Column",
xmlattributes('100' as "ss:Width")),
xmlelement("ss:Column",
xmlattributes('100' as "ss:Width")),
x.XRow(x.SCell('Codice','B'),
x.SCell('Nome','B'),
x.SCell('Ubicazione','B')
),
(select xmlagg(x.XRow(x.NCell(deptno),
x.SCell(dname),
x.SCell(loc)
)
)
from dept
)
533
)
)
)
, VERSION '1.0'
) MyDoc
from dual;
534
</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">
<B>Dipartimento</B>
</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="Number">7369</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">
<I>SMITH</I>
</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">
<I>CLERK</I>
</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">7902</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="DateTime">1980-12-17</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">800</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number"/>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="Number">20</ss:Data>
</ss:Cell>
</ss:Row>
--Tante altre righe di EMP--
</ss:Table>
</ss:Worksheet>
<ss:Worksheet ss:Name="Dipartimenti">
<ss:Table>
<ss:Column ss:Width="100"/>
<ss:Column ss:Width="100"/>
<ss:Column ss:Width="100"/>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="String">
<B>Codice</B>
</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">
<B>Nome</B>
</ss:Data>
</ss:Cell>
<ss:Cell>
535
<ss:Data ss:Type="String">
<B>Ubicazione</B>
</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="Number">10</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">ACCOUNTING</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">NEW YORK</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="Number">20</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">RESEARCH</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">DALLAS</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="Number">30</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">SALES</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">CHICAGO</ss:Data>
</ss:Cell>
</ss:Row>
<ss:Row>
<ss:Cell>
<ss:Data ss:Type="Number">40</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">OPERATIONS</ss:Data>
</ss:Cell>
<ss:Cell>
<ss:Data ss:Type="String">BOSTON</ss:Data>
</ss:Cell>
</ss:Row>
</ss:Table>
</ss:Worksheet>
</ss:Workbook>
536
Figura 9-1 Il risultato dell’esempio in excel.
Le possibilità di formattazione sono molto più ampie di quelle
che sono state utilizzate, inoltre è possibile aggiungere nel foglio
formule e molto altro.
Per tutte le informazioni di dettaglio è possibile consultare il
reference di Microsoft XML Spreadsheet all’indirizzo
http://msdn.microsoft.com/en-us/library/aa140066(office.10).aspx.
537
10 Prerequisiti
538
la categoria di database che ha riscosso i maggiori successi è quella
dei cosiddetti “database relazionali” definiti da E. F. Codd nel 1970 in
un articolo dal titolo “A Relational Model of Data for Large Shared Data
Banks” (“Un modello relazionale dei dati per grandi banche dati
condivise”, una traduzione in italiano dell’articolo è disponibile
all’indirizzo web http://oracleitalia.wordpress.com/codd/).
Tabella FATTURE
539
Per consentire l’individuazione univoca di ogni singolo
oggetto/riga, solitamente ogni tabella è munita di una chiave primaria
(in inglese primary key). Una chiave primaria non è altro che una
colonna, oppure un insieme di colonne, che non può assumere lo
stesso valore in righe differenti della tabella. Nel nostro esempio la
chiave primaria della tabella CLIENTI è il campo “Codice Cliente”, la
chiave primaria della tabella FATTURE è il numero della fattura. Non ci
possono essere due clienti con lo stesso codice, non ci possono
essere due fatture con lo stesso numero.
Le tabelle possono essere legate tra loro da relazioni, nel nostro
esempio la tabella delle fatture è legata alla tabella dei clienti perché in
ogni fattura è presente il dato “Codice Cliente” mediante il quale è
possibile ricavare, in maniera univoca poiché il Codice Cliente è chiave
primaria nella tabella CLIENTI, le informazioni del cliente a cui è stata
emessa la fattura. La colonna “Codice Cliente” presente nella tabella
FATTURE è detta chiave esterna (foreign key). La chiave esterna così
definita fa riferimento alla chiave primaria definita sulla tabella
CLIENTI. Una chiave esterna e la chiave primaria a cui essa fa
riferimento sono di fatto lo stesso dato, nel nostro esempio il codice del
cliente.
I migliori database relazionali garantiscono automaticamente
l’integrità referenziale, cioè
• non è possibile assegnare un valore specifico ad una chiave
esterna se quello stesso valore non è stato già definito tra i valori
della chiave primaria a cui essa fa riferimento. Non possiamo
assegnare il valore 555555 al codice cliente di una fattura perché
non esiste un cliente in CLIENTI che ha quel codice.
• Non è possibile cancellare una riga da una tabella se ci sono
chiavi esterne in altre tabelle che fanno riferimento a quello
specifico valore. Nel nostro esempio non sarebbe possibile
cancellare i clienti 111111 e 222222 perché essi hanno fatture
collegate, ma sarebbe possibile cancellare il cliente 333333
perché non ha fatture collegate.
10.1.3 DBA
Il DBA (database administrator) è un professionista
dell’informatica che si occupa di installare, aggiornare e gestire uno o
più database. Tutte le grandi organizzazioni hanno alle proprie
dipendenze, direttamente o mediante contratti di consulenza stipulati
con altre aziende, un gruppo di DBA che gestisce i database aziendali.
Si tratta di una figura professionale molto critica visto che il vero
patrimonio di tante grandi aziende sta soprattutto nei dati che esse
gestiscono.
540
10.2 Progettazione di un db relazionale
Il processo di progettazione di un database relazionale passa
solitamente attraverso i seguenti passaggi:
• Definizione del modello concettuale mediante la creazione di un
diagramma entità/relazioni.
• Definizione di un modello logico mediante il processo di
normalizzazione.
• Definizione del modello fisico in funzione dello specifico database
che si intende utilizzare.
10.2.2 Normalizzazione
Completato il modello concettuale bisogna trasformarlo in uno
schema tabellare relazionale. Una prima trasformazione grossolana
può essere effettuata applicando alcune regole fisse allo schema
concettuale, ad esempio:
• Da ogni entità si ottiene una tabella,
• da ogni relazione avente attributi si ottiene una tabella,
• le altre relazioni diventano chiavi esterne.
Si individua, in questo modo, un insieme di tabelle relazionali.
Tali tabelle però potrebbero presentare forti limitazioni oppure delle
ridondanze di dati. Per eliminare le ridondanze si applica il processo di
normalizzazione. Tale processo consiste nella trasformazione
graduale delle tabelle in modo che queste rispettino le regole imposte
dalle forme normali. Tra le molte che sono state enunciate dagli esperti
di analisi dei dati, nel seguito saranno descritte le forme normali che si
considerano indispensabili per ottenere un database a bassa
ridondanza: la prima, la seconda e la terza forma normale.
541
Per ragionare su un esempio concreto si consideri la seguente
tabella:
Tabella CLIENTI – Versione 1
Codice Cognome Indirizzo Comune Prov. Telefono
111111 Rossi Via Rossi, 2 Pomezia RM Casa 0666778899
Cell 3351234567
222222 Verdi Via Verdi, 1 Rho MI Casa 0233445566
Cell 3387654321
333333 Bianchi Via Bianchi, 3 Casoria NA Casa 0819988774
Cell 3491234567
444444 Gialli Via Gialli, 1 Rho MI Cell 3177654321
542
Tabella ORDINI – Versione 1
Num. Cod. data Descr. prodotto Quantità
Ordine Prodotto
111111 XY123 1/9/2010 Bulloni mis. 8 200
111111 AZ321 1/9/2010 Viti da 5mm 100
222222 AS333 3/9/2010 Viti da 7mm 130
333333 AZ321 5/9/2010 Viti da 5mm 230
444444 XY123 7/9/2010 Bulloni mis. 8 150
543
Tabella DETTAGLIO ORDINI
Num. Cod. Quantità
Ordine Prodotto
111111 XY123 200
111111 AZ321 100
222222 AS333 130
333333 AZ321 230
444444 XY123 150
Tabella ORDINI
Num. data
Ordine
111111 1/9/2010
222222 3/9/2010
333333 5/9/2010
444444 7/9/2010
Tabella PRODOTTI
Cod. Descr. prodotto
Prodotto
XY123 Bulloni mis. 8
AZ321 Viti da 5mm
AS333 Viti da 7mm
544
provincia, visto che fissato il comune la provincia si ottiene di
conseguenza. Questa situazione è detta dipendenza transitiva, la
provincia dipende sì dal cliente ma non direttamente, bensì per mezzo
del comune. La tabella non è dunque in terza forma normale. Per
normalizzarla è necessario definire una nuova tabella che abbia come
chiave l’attributo che fa da tramite nella dipendenza transitiva
(nell’esempio il comune) e come attributi tutti gli attributi che erano
transitivamente dipendenti (nell’esempio la provincia)
Tabella CLIENTI – Versione 3
Codice Cognome Indirizzo Codice Tel. Casa Tel. Cell
Comune
111111 Rossi Via Rossi, 2 G811 0666778899 3351234567
222222 Verdi Via Verdi, 1 H264 0233445566 3387654321
333333 Bianchi Via Bianchi, 3 B990 0819988774 3491234567
444444 Gialli Via Gialli, 1 H264 3177654321
Tabella COMUNI
Codice Nome Prov.
Comune Comune
G811 Pomezia RM
H264 Rho MI
B990 Casoria NA
545
10.3 Architettura di un computer
Per installare ed utilizzare un database è necessario avere a
disposizione un calcolatore. Per fare un po’ di prove ed imparare può
essere sufficiente un personal computer come quelli che si trovano in
quasi tutte le case. Per scopi professionali, invece, vengono utilizzati
computer particolarmente potenti detti server. In questo paragrafo
saranno introdotte le strutture di base di un calcolatore, sia esso
personal computer o server.
546
deve essere necessariamente scritto in un linguaggio comprensibile al
processore. Per essere in grado di eseguire le operazioni leggermente
più complesse di quelle elementari, come la gestione delle periferiche,
la gestione dei file e tante altre, il computer deve essere dotato di un
insieme di programmi (Software) di base che prende il nome di
Sistema Operativo.
I sistemi operativi più diffusi sono Microsoft Windows, nelle sue
varie versioni per personal computer o server; Unix, anch’esso in varie
versioni ed utilizzato soprattutto per i server; Linux, una versione per
personal computer di Unix che ultimamente si sta diffondendo anche
nei server.
All’interno del sistema operativo si installano ed utilizzano le
applicazioni, prodotti software utili per la realizzazione di specifiche
attività. Esempi di applicazione sono
• gli strumenti di Office Automation come Microsoft Office ed
OpenOffice, che consentono di scrivere documenti, fogli di
calcolo e presentazioni;
• i browser internet come Internet Explorer, Mozilla Firefox e
Google Chrome che consentono di navigare in internet;
• i database come Oracle, Microsoft SQL Server ed IBM DB2 che
consentono di archiviare dati;
• gli IDE (Integrated Development Environment) come Eclipse o
NetBeans, che consentono di creare software utilizzando uno
dei tanti linguaggi di programmazione esistenti.
547
123 = 1× 26 + 1× 25 + 1× 24 + 1× 23 + 0 × 22 + 1× 21 + 1× 20
Sottintendendo le potenze, come facciamo solitamente nel
sistema decimale, si ottiene la rappresentazione binaria del numero
123:
1111011
Poiché nel sistema binario sono disponibili solo due cifre i
numeri crescono molto rapidamente di lunghezza, ma è solo un
problema di rappresentazione grafica: cinque dita sono sempre
cinque, sia che le si rappresenti con la cifra “5” che con la stringa di
cifre “101”!
Esattamente nello stesso modo si ragiona con il sistema
esadecimale dove le cifre sono ben sedici, da 0 a 9 più le lettere A, B,
C, D, E, F. Ovviamente nel caso del sistema esadecimale i numeri
avranno una rappresentazione più corta di quella decimale. La tabella
seguente rappresenta i numeri da uno a venti in sistema decimale,
binario ed esadecimale. Anche negli altri sistemi di numerazione,
come in quello decimale, gli zeri aggiunti alla sinistra del numero non
ne cambiano il valore.
548
10.4.2 Dal bit al TeraByte
L’unità minima di memorizzazione delle informazioni in un
computer è il bit o binary digit. Si tratta, come dice il suo nome, di una
cifra del sistema binario.
Come detto in precedenza qualunque numero può essere
rappresentato in sistema binario, quindi il fatto di avere a disposizione
solo due cifre non influenza i numeri che possono essere rappresentati
nel computer.
I bit sono raggruppati logicamente in gruppi di 8 per formare i
Byte. Quindi un Byte (abbreviato B) può rappresentare tutti i numeri
interi che vanno da zero “00000000” a 255 “11111111”.
Nei normali sistemi di misura il prefisso kilo indica la
moltiplicazione per mille. Ad esempio un chilogrammo equivale a mille
grammi. In informatica la cosa è un po’ più complicata. Poiché si
ragiona in sistema binario, i moltiplicatori privilegiati non sono le
potenze di dieci (10, 100, 1000) ma le potenze di 2. Per questo motivo
un KiloByte (abbreviato KB) non equivale a 1000 Byte bensì a 1024
Byte che è la potenza di 2 più vicina al numero mille.
Così procedendo 1024 KiloByte formano un MegaByte
(abbreviato MB). 1024 MegaByte formano un GigaByte (GB) e 1024
GigaByte formano un TeraByte (TB). Quindi un TeraByte è formato
dalla bellezza di 8x1024x1024x1024x1024=8.796.093.022.208 cifre
binarie.
549
pochi per i caratteri di molte altre lingue del mondo. Per questo motivo
la codifica ASCII è stata estesa con la codifica UNICODE che è basata
attualmente su 21 cifre binarie e consente di rappresentare circa un
milione di caratteri.
Non tutti i sistemi operativi sono basati sulla codifica
ASCII/Unicode. I mainframe IBM, ad esempio, utilizzano la codifica
EBCDIC, anch’essa basata su otto bit ma associati differentemente ai
caratteri da rappresentare. La stringa di bit “01001101” che in ASCII
rappresenta la lettera “M”, in EBCDIC rappresenta la parentesi tonda
aperta “(“.
550
Accettando l’imprecisione dovuta ad un inevitabile
arrotondamento, anche tutti i numeri reali possono essere espressi in
notazione esponenziale per di più utilizzando un limitato numero di
cifre per rappresentare la mantissa. La rappresentazione a virgola
mobile consiste dunque nel fissare la base ed archiviare per ogni
numero sia la mantissa che l’esponente. Quando il numero dovrà
essere ricostruito a partire da mantissa ed esponente sarà utilizzata la
formula (1).
551
stringa) di dimensione arbitraria e restituisce una stringa di dimensione
predefinita. L’algoritmo di hash deve essere deterministico, cioè
sottoponendo lo stesso input all’algoritmo più volte si deve ottenere
sempre lo stesso output. Poiché la stringa di output ha dimensione
prefissata e solitamente minore dell’input l’algoritmo non può essere
invertibile, cioè più stringhe di input possono generare lo stesso valore
di output e non è possibile, dato un valore di output, determinare la
stringa di input che lo ha determinato.
Una funzione di hash è una funzione, scritta in qualunque
linguaggio di programmazione, che implementa un algoritmo di hash.
Le funzioni di hash sono utilizzate, ad esempio, per generare da
un file di grandi dimensioni una stringa relativamente piccola allo
scopo di garantire l’integrità del file durante una spedizione. Chi invia il
file calcola l’hash prima dell’invio e lo spedisce insieme al file. Chi
riceve il file ricalcola l’hash e, se il file è giunto integro, deve ottenere lo
stesso hash.
Un altro possibile utilizzo è il partizionamento di un insieme di
dati. Se si intende partizionare in cento gruppi un insieme ampio a
piacere di stringhe, queste possono essere tutte sottoposte ad una
funzione di hash che restituisce cento valori differenti. Ogni gruppo
sarà costituito da tutte le stringhe che generano lo stesso valore di
hash.
552
disgiunti. È anche possibile che S e T contengano esattamente gli
stessi elementi, in tal caso la loro intersezione coincide con entrambi
gli insiemi di partenza. Il numero di elementi contenuti nell’intersezione
è sicuramente minore o uguale del numero di elementi contenuti nei
singoli insiemi.
553
Le proposizioni possono combinarsi tra loro mediante alcuni
operatori per formare nuove proposizioni:
554
Per assegnare alla proposizione (a) un valore anche quando
non sono specificate le parentesi si assume la precedenza
dell’operatore AND rispetto all’operatore OR. Di conseguenza la
proposizione (a) è equivalente alla (c) ed è vera.
555
7. La proposizione “s appartiene a T AND NOT s appartiene ad U”,
congiunzione della 1 e della negazione della 2, è vera per tutti e
soli gli elementi dell’insieme differenza T – U.
8. Le leggi di De Morgan possono essere riscritte come segue:
o il complemento rispetto ad S dell’unione di T ed U coincide
con l’intersezione dei complementi.
o Il complemento rispetto ad S dell’intersezione di T ed U
coincide con l’unione dei complementi.
Sia S un insieme. Una proposizione che descrive una
condizione generica vera per alcuni elementi di S e falsa per altri si
dice predicato. Ad esempio se S è l’insieme dei numeri interi ed x è un
elemento di S, sono predicati le seguenti proposizioni: “x è pari”, “x è
maggiore di 10”, “x è il quadrato di un numero intero”. Ogni predicato
descrive completamente un sottoinsieme di S.
Da queste definizioni e dai punti illustrati prima si intuisce che
non c’è alcuna differenza tra applicare i criteri della logica delle
proposizioni ai predicati oppure applicare i criteri della teoria degli
insiemi ai sottoinsiemi di S che i predicati descrivono.
556
Indice Analitico
ASCII · 233
ASCII · 549
% ASIN · 238
Assegnazione · 318
ATAN · 238
%ISOPEN · 333
ATAN2 · 238
%NOTFOUND · 333
AUTHID CURRENT_USER · 416
%ROWCOUNT · 334
AVG · 186
%ROWTYPE · 351
Avviare Oracle · 38
%TYPE · 317
A B
balanced tree · 72
ABS · 234
BEGIN · 313
ACOS · 238
BETWEEN · 167
ADD_MONTHS · 240
binary digit · 549
Aggiornamento di dati XML · 496
bit · 549
albero bilanciato · 72
Bitmap · 72
Algoritmi di hash · 551
BLOB · 91; 421
Algoritmi di ricerca · 551
Blocchi anonimi · 313
Alias di colonna · 153
blocco · 15
ALL · 290
BOOLEAN · 316
ALTER · 111
B-tree · 71
AND · 172
Bulk collect · 359
ANY · 290
BUS · 546
Applicazioni software · 547
Byte · 549
Architettura di un computer · 546
Architettura Hardware · 546
Architettura Software · 546
Archiver · 17 C
ARCn · 17
Aritmetica sulle date · 239 CASE · 267; 320
Array Associativi · 353 CEIL · 236
Arrestare Oracle · 38 Central Processing Unit · 546
557
CHAR · 88 database administrator · 540
check constraint · 70 Database buffer Cache · 14
Check option · 74 Database link · 76
checkpoint · 13; 16 database relazionale · 539
chiave esterna · 69; 540 normalizzazione · 541
chiave primaria · 69; 540 prima forma normale · 542
chiave univoca · 70 progettazione · 541
CHR · 234 seconda forma normale · 542
CKPT · 13 terza forma normale · 544
client · 41 Database Writer · 13
CLOB · 89 datafile · 15
CLOSE · 333 DATE · 90
Cluster di tabelle · 73 Date ed orari · 90
Cluster Index · 73 Dati alfanumerici · 88
COALESCE · 257 Dati binari · 91
Collection · 359 DBA · 540
Colonne di una tabella · 68 DBMS · 538
COLUMN_VALUE · 158; 472 DBMS_CRYPTO · 424
Commenti · 315 DBMS_HPROF · 464
COMMIT · 297 DBMS_LOB · 421
Compilazione condizionale · 445 DBMS_METADATA · 436
CONCAT · 223 DBMS_PROFILER · 464
Concatenazione · 220 DBMS_SCHEDULER · 441
Condizioni · 159 DBMS_TRACE · 458
Congiunzione · 554 DBMS_XMLGEN · 493
CONNECT BY · 200 DBMS_XMLSCHEMA · 507
connect string · 42 DBWn · 13
CONNECT_BY_ISCYCLE · 157 DCL · 309
CONNECT_BY_ISLEAF · 157 DDL · 91
Constraint · 69; 74 Deadlock · 307
control file · 16 DECLARE · 313
Conversioni implicite · 247 DECODE · 265
COS · 238 dedicated server · 42
Costanti · 317 DELETE · 360
COUNT · 368 DELETE · 140
COUNT · 187 DESC · 55
CPU · 546 DESCRIBE · 55
Creare un utente · 49 Dipendenze · 410
CREATE · 91 disco rigido · 546
Crittografia · 424 Disgiunzione · 554
CURRENT_DATE · 244 dispatcher · 42
CURRENT_TIMESTAMP · 244 DISTINCT · 182
CURRVAL · 105; 158 Dizionario dati · 64
CURSOR FOR LOOP · 335 DML · 128
Cursori · 332 DML Trigger · 395
DROP · 122
DUAL · 155
D DUP_VAL_ON_INDEX · 338
data block · 15
database · 12; 538
558
ACOS · 238
E ADD_MONTHS · 240
ASCII · 233
E. F. Codd · 539 ASIN · 238
EBCDIC · 550 ATAN · 238
Eccezioni mute · 345 ATAN2 · 238
Eccezioni predefinite · 338 CEIL · 236
Eccezioni utente · 344 CHR · 234
Email · 433 COALESCE · 257
END · 313 CONCAT · 223
Enterprise Manager · 37 COS · 238
entità · 539 CURRENT_DATE · 244
Espressioni regolari · 260 CURRENT_TIMESTAMP · 244
Estensioni Object-Oriented · 77 DECODE · 265
EXCEPTION · 313; 337 di conversione · 247
EXECUTE IMMEDIATE · 328 EXP · 236
EXISTS · 366 EXTRACT · 245
EXISTS · 291 FLOOR · 236
EXP · 236 GREATEST · 269
EXTEND · 364 INITCAP · 229
extent · 15 INSTR · 226
External Table · 68 LAST_DAY · 242
EXTRACT · 245 LEAST · 270
LENGTH · 227
LN · 237
F LNNVL · 259
LOG · 237
FETCH · 333 LOWER · 228
file di controllo · 16 LPAD · 231
Firma · 372; 380 LTRIM · 229
FIRST · 367 MOD · 238
FLASHBACK TABLE · 127 MONTHS_BETWEEN · 241
FLOOR · 236 NEXT_DAY · 242
FOR · 323 NULLIF · 260
foreign key · 69; 540 NVL · 255
Formati NVL2 · 256
date · 250 POWER · 237
numeri · 254 REGEXP_COUNT · 263
Funzioni · 379 REGEXP_INSTR · 261
Funzioni di gruppo · 184 REGEXP_LIKE · 264
AVG · 186 REGEXP_REPLACE · 262
COUNT · 187 REGEXP_SUBSTR · 262
MAX · 185 REMAINDER · 238
MIN · 185 REPLACE · 232
STDDEV · 187 ROUND · 234; 243
SUM · 184 RPAD · 231
VARIANCE · 186 RTRIM · 230
Funzioni di hash · 551 SIGN · 237
Funzioni PL/SQL · 77 SIN · 238
Funzioni predefinite · 220 SINH · 238
ABS · 234 SQRT · 237
559
SUBSTR · 224
sui numeri · 234
H
sulle date · 239
sulle stringhe · 223 Hard Disk Drive · 546
SYSDATE · 239 Hash · 551
SYSTIMESTAMP · 239 Hash Cluster · 73
TO_CHAR · 247 HAVING · 189
TO_DATE · 248
TO_NUMBER · 249
TO_TIMESTAMP · 249 I
TRANSLATE · 233
TRIM · 230 IDE · 547
TRUNC · 235; 243 IF THEN ELSE · 318
UPPER · 228 IN · 168
Funzioni XML IN (subquery) · 291
APPENDCHILDXML · 498 Indexed Cluster · 73
DELETEXML · 502 Index-Organized Tables · 72
INSERTCHILDXML · 499 Indice Bitmap · 72
INSERTXMLAFTER · 496 Indice B-tree · 71
INSERTXMLBEFORE · 497 Indici · 70
SYS_XMLAGG · 491 INITCAP · 229
SYS_XMLGEN · 490 INNER JOIN · 273
UPDATEXML · 500 INSERT · 129
XMLAGG · 478 Installazione del database · 18
XMLCAST · 502 INSTR · 226
XMLCDATA · 488 Integrated Development
XMLCOLATTVAL · 488 Environment · 547
XMLCOMMENT · 488 integrità referenziale · 69; 540
XMLCONCAT · 482 INTERSECT · 286
XMLDIFF · 505 INVALID_NUMBER · 339
XMLELEMENT · 472 IOT · 72
XMLEXISTS · 503 IS NOT NULL · 161
XMLFOREST · 475 IS NULL · 161
XMLISVALID · 514 istanza · 12; 13
XMLPATCH · 506
XMLPI · 484
XMLROOT · 486
XMLSERIALIZE · 479
J
Java Pool · 14
JOIN · 271
G
GigaByte · 549
GOTO · 325
K
GRANT · 309
GREATEST · 269 KiloByte · 549
GROUP BY · 183
560
L N
Large Pool · 14 Negazione · 554
LAST · 367 Nested Tables · 358
LAST_DAY · 242 NEXT · 370
LEAST · 270 NEXT_DAY · 242
Leggi di De Morgan · 555 NEXTVAL · 105; 158
LENGTH · 227 NO_DATA_FOUND · 339
LEVEL · 157 NOMOUNT · 38
LGWR · 13 Normalizzazione · 541
LIKE · 169 NOT · 174
LIMIT · 368 NOT BETWEEN · 167
Linux · 547 NOT IN · 168
listener · 41 NOT LIKE · 169
Literals · 128 NULL · 326
LN · 237 NULLIF · 260
LNNVL · 259 NUMBER · 90
Lock · 306 Numeri ed importi · 90
LOG · 237 NVL · 255
LOG ERRORS · 146 NVL · 255
log switching · 16; 17 NVL2 · 256
Log Writer · 13
Logica delle proposizioni · 553
LONG · 89 O
LONG RAW · 91
LOOP · 321
Oggetti del DB · 64
LOWER · 228
Oggetti PL/SQL · 76
LPAD · 231
OPEN · 333
LTRIM · 229
OPEN · 38
Operatori
aritmetici · 222
M di concatenazione · 220
di confronto · 160
Materialized views · 75 logici · 172
MAX · 185 Operatori Insiemistici · 282
MegaByte · 549 Operatori Logici · 553
MERGE · 141 congiunzione · 554
Metodi delle collezioni · 360 disgiunzione · 554
MIN · 185 negazione · 554
MINUS · 287 Regole di precedenza · 554
MOD · 238 OR · 173
MODEL · 209 oradim · 37
MONTHS_BETWEEN · 241 ORDER BY · 178
MOUNT · 38 Ordinamento · 178
multiplexed · 17 OTHERS · 341
OUTER JOIN · 276
561
RAISE_APPLICATION_ERROR ·
P 343
RAM · 546
Package · 385 Random Access Memory · 546
Packege PL/SQL · 77 Rappresentazioni numeriche · 550
Partizioni · 68 RAW · 91
Personalizzare SQL*Plus · 51 RDBMS · 539
PGA · 14 Real Application Clusters · 12
PIVOT · 195 RECORD · 350
PL/SQL Tables · 353 recovery · 17
PMON · 13 Redo Log Buffer · 14
POWER · 237 Redo Log file · 16; 17
PRAGMA REGEXP_COUNT · 263
autonomous_transaction · 454 REGEXP_INSTR · 261
exception_init · 450 REGEXP_LIKE · 264
inline · 455 REGEXP_REPLACE · 262
restrict_references · 451 REGEXP_SUBSTR · 262
serially_reusable · 453 relazione · 540
Predicati · 556 REMAINDER · 238
Prima forma normale · 542 RENAME · 121
primary key · 69; 540 REPLACE · 232
PRIOR · 369 Requisiti Hardware · 18
Privilegi · 309 Requisiti Software · 19
Procedure · 371 RETURN · 379
Procedure PL/SQL · 77 REVOKE · 310
Process Monitor · 13 Ricerca Binaria · 551
processi di base · 13 Ricerca Dicotomica · 551
Prodotto cartesiano Ricerca sequenziale · 551
di insiemi · 553 Ricerche innestate · 288
di tabelle · 271 Ricerche su più tabelle · 271
Profiling · 464 ROLLBACK · 299
Progettazione di un db · 541 ROLLUP · 190
Program Global Area · 14 ROUND · 234; 243
Proiezioni · 153 ROWID · 71; 158
proposizione · 553 ROWNUM · 159; 193
Pseudocolonne · 157 RPAD · 231
PURGE · 125 RTRIM · 230
RUOLI · 310
Q
S
Query · 152
Query gerarchiche · 200 SAVEPOINT · 300
Scaricare Oracle · 19
Scheduler · 441
R schema · 40
Seconda forma normale · 542
Raggruppamenti · 182 segment · 16
RAISE · 343; 348 SELECT · 152
SELECT…INTO · 327
Selezioni · 159
562
Sequenze · 75
server · 41
T
servizio client · 41
SET · 55 Tabella ASCII · 549
SET TRANSACTION · 302 Tabelle · 67
SGA · 14 Tabelle Index-Organized · 72
Shared Pool · 14 Table Trigger · 77
shared server · 42 tablespace · 16
SHOW · 54 TCL · 297
Shutdown · 38 Teorema di Bohm-Jacopini · 312
Abort · 39 Teoria degli insiemi · 552
Immediate · 39 Complemento · 553
Normal · 38 Differenza · 553
Transactional · 39 Insieme vuoto · 552
SIGN · 237 Intersezione · 552
SIN · 238 Prodotto cartesiano · 553
SINH · 238 Sottoinsiemi · 552
Sinonimi · 76 Unione · 553
Sistema binario · 547 TeraByte · 549
Sistema esadecimale · 548 Terza forma normale · 544
Sistema operativo · 547 TIMESTAMP · 90
SMON · 13 Tipi di dato SQL · 87
SQL · 86 tnsnames.ora · 42
SQL Developer · 61 TO_CHAR · 247
SQL Dinamico · 328 TO_DATE · 248
SQL Statico · 327 TO_NUMBER · 249
SQL Types · 316 TO_TIMESTAMP · 249
SQL*Plus · 37; 50 TOO_MANY_ROWS · 340
SQLCODE · 342 Trace · 458
SQLERRM · 342 Transazioni · 297
SQRT · 237 TRANSLATE · 233
Startup · 38 Trigger · 395
STDDEV · 187 Trigger PL/SQL · 77
Stored Function · 77 TRIM · 230; 363
Stored Procedure · 77 TRUNC · 235; 243
stringa di connessione · 42 TRUNCATE · 124
Subprogram Inlining · 455
Subquery · 288
correlate · 294 U
Operatori di confronto · 289
scorrelate · 293 UNICODE · 549
Subquery factoring · 296 UNION · 282
SUBSTR · 224 UNION ALL · 285
SUM · 184 unique key · 70
SYS · 40 Unix · 547
SYSDATE · 239 UNPIVOT · 197
SYSTEM · 40 UPDATE · 138
System Global Area · 14 UPPER · 228
System Monitor · 13 USER_SOURCE · 417
System Trigger · 395 utente · 40
SYSTIMESTAMP · 239 UTL_SMTP · 433
563
WHILE · 324
V Windows · 547
WITH · 296
Valori fissi · 128 WRAP · 418
Valori Nulli · 255
VALUE_ERROR · 341
VARCHAR2 · 88
Variabili · 316
X
VARIANCE · 186
Varray · 357 XML DB · 78
Virgola fissa · 550 XML Schema · 507
Virgola mobile · 550 XMLDATA · 158; 472
Viste · 74 XMLType · 471
Viste materializzate · 75
Z
W
ZERO_DIVIDE · 341
WHERE · 159
564
Indice delle figure
565
Figura 2-32 Fine installazione ............................................................. 36
Figura 3-1 Shutdown e startup da Sql*Plus......................................... 39
Figura 3-2 Avvio di Net manager ......................................................... 43
Figura 3-3 La maschera principale di Net Manager ............................ 43
Figura 3-4 Definizione del nome della connect string ......................... 44
Figura 3-5 Scelta del protocollo di rete ................................................ 44
Figura 3-6 Scelta del server e della porta............................................ 45
Figura 3-7 Scelta del nome del database remoto ............................... 45
Figura 3-8 Esecuzione del test ............................................................ 46
Figura 3-9 Errore nel test a causa di utente inesistente ...................... 46
Figura 3-10 Indicazione delle credenziali corrette ............................... 47
Figura 3-11 Test con esito positivo ...................................................... 47
Figura 3-12 Maschera di salvataggio delle modifiche ......................... 48
Figura 3-13 Connessione con Sql*Plus da client remoto. ................... 49
Figura 4-1 Menù contestuale. .............................................................. 51
Figura 4-2 Proprietà di SQL*Plus. ....................................................... 52
Figura 4-3 Proprietà del Layout. .......................................................... 52
Figura 4-4 Modifica del collegamento.................................................. 53
Figura 4-5 Modifica del collegamento.................................................. 53
Figura 4-6 Tutti i parametri di SQL*Plus. ............................................. 54
Figura 4-7 Impostazione della linesize. ............................................... 55
Figura 4-8 Il comando DESC. .............................................................. 56
Figura 4-9 Ritorno a capo durante la scrittura di un comando SQL. ... 57
Figura 4-10 Esecuzione di un comando SQL. ..................................... 57
Figura 4-11 Apertura di afiedit.buf ....................................................... 58
Figura 4-12 Esecuzione del comando SQL modificato. ...................... 58
Figura 4-13 I comandi “i”, “del” e “c”. ................................................... 59
Figura 4-14 I comandi “start” e “@”. .................................................... 60
Figura 4-15 Il comando “set sqlprompt”............................................... 61
Figura 4-16 Pagina principale di SQL Developer. ............................... 62
Figura 4-17 Configurazione delle connessioni. ................................... 62
Figura 4-18 Esecuzione di un’istruzione SQL. .................................... 63
Figura 5-1 Lettura del dizionario da SQL*Plus. ................................... 66
Figura 5-2 Lettura del dizionario da SQL Developer. .......................... 66
Figura 5-3 Caratteristiche principali di una tabella. ............................. 68
Figura 5-4 Struttura di un indice B-tree. .............................................. 71
Figura 8-1 Esecuzione del programma PL/SQL in SQL Developer. . 314
Figura 8-2 Visualizzazione dell’output in SQL Developer. ................ 315
Figura 8-3 Uno script PL/SQL in chiaro ed offuscato. ....................... 419
Figura 9-1 Il risultato dell’esempio in excel........................................ 537
566