F O C U S Potenza e versatilità dei Database Con la versione 9i del database Oracle è possibile leggere un file esterno di dati come se fosse una tabella relazionale. Vediamo come Accedere a dati esterni con query SQL O gni mattina la stessa storia. Da quasi tre anni. Arrivo in ufficio quando i miei colleghi stanno ancora dormendo. Sono il responsabile del “centro elaborazione dati” di un’azienda non molto grande che vende, grazie ad Internet, in tutto il mondo. La mattina arrivo in ufficio e carico nel database Oracle i dati degli ordini ricevuti durante la notte ed il giorno precedente. Le applicazioni per la ricezione degli ordini, il sito Internet e l’applicazione utilizzata dai colleghi del call center, non sono basate sul database Oracle. Scrivono i dati in un file ASCII, io effettuo l’import in una tabella relazionale per consentire al magazzino ed all’ufficio clienti di organizzare le spedizioni. Lo so, è un pessimo esempio d’integrazione d’applicazioni, ma che ci volete fare… quando i sistemi informatici vengono costruiti a pezzi nel corso degli anni, questi sono i risultati. Quando ho raccontato questa storia ad un amico molto esperto di Oracle – pensate che lo chiamano “l’Oracolo”! – mi ha consigliato di passare alla versione 9i del Database e di utilizzare una External Table. Ho fatto come mi aveva consigliato. Se v’interessa quello che ho scoperto continuate a leggere quest’articolo… di Massimo Ruocchio [email protected] È laureato in matematica ed è certificato Oracle Application Developer. Si occupa di analisi, progettazione e sviluppo di applicazioni software Al primo impatto con la definizione sorge subito una perplessità legata alle prestazioni. Più in basso abbiamo dedicato un paragrafo a quest’argomento. Nel prossimo paragrafo cercheremo di familiarizzare con la sintassi che si utilizza per definire una External Table. Nel paragrafo successivo introdurremo un esempio. La sintassi Una External Table si crea con l’istruzione CREATE TABLE. La clausola da utilizzare per indicare che i dati della tabella risiedono esternamente al database è ORGANIZATION EXTERNAL. All’interno di questa clausola bisogna indicare: In generale… Una External Table è una tabella definita nel database ma i cui dati sono conservati in un file ASCII posto fuori del database. La External Table può essere letta esattamente come ogni altra tabella relazionale ma non può essere modificata. La logica è piuttosto semplice. Nella definizione della tabella s’includono (utilizzando la sintassi dei control file di SQL*Loader) le informazioni su come leggere il file che contiene i dati. Quando si esegue un’istruzione SQL che coinvolge la External Table viene eseguito al volo un import dei dati in memoria. I dati memorizzati vengono utilizzati come se fossero provenienti da una tabella memorizzata. Il tipo di dati esterni che si utilizzano (parola chiave TYPE) La directory in cui si trovano i dati esterni (parola chiave DEFAULT DIRECTORY) I parametri con cui è possibile accedere ai dati (parola chiave ACCESS PARAMETERS) La posizione fisica dei dati (parola chiave LOCATION) Vediamo più in dettaglio come valorizzare questi parametri. In teoria i dati da leggere come External Table possono essere conservati in un data source di qualunque tipo. In pratica, per leggere i dati dal data source esterno, e modellarli in memoria in forma rela- 41 CP 116 F O C U S Potenza e versatilità dei Database LISTATO 1 Le prime righe del file comuni.txt CASAPE RM00010B932 GALLICANO NEL LAZIO RM00010D875 MARCELLINA RM00010E924 MONTEFLAVIO RM00010F504 MONTELIBRETTI RM00010F545 MONTORIO ROMANO RM00010F692 MORICONE RM00010F730 POLI RM00010G784 SAN POLO DEI CAVALIERI RM00010I125 SANT’ANGELO ROMANO RM00010I284 MENTANA RM00013F127 MONTECELIO RM00014F466 MONTEROTONDO RM00015F611 NEROLA RM00017F871 PALOMBARA SABINA RM00018G293 TIVOLI RM00019L182 AGOSTA RM00020A084 ARCINAZZO ROMANO RM00020A370 CAMERATA NUOVA RM00020B472 CANTERANO RM00020B635 CERRETO LAZIALE RM00020C518 CERVARA DI ROMA RM00020C543 CICILIANO RM00020C677 CINETO ROMANO RM00020C702 MANDELA RM00020B632 PERCILE RM00020G444 PISONIANO RM00020G704 ROCCA CANTERANO RM00020H387 ROCCAGIOVINE RM00020H411 .................................... zionale, il database ha bisogno di un driver adatto al data source utilizzato. La parola chiave TYPE permette di selezionare il driver da utilizzare. Oracle mette a disposizione un solo driver, quello che consente di leggere i dati immagazzinati in un file ASCII mediante un accesso del tutto simile a quello effettuato da SQL*Loader. Altri driver per accedere a diversi tipi di data source devono essere scritti direttamente da chi intende utilizzarli. Quindi l’unico valore ammesso per TYPE è ORACLE_LOADER, a meno di scriversi un driver da soli per accedere ad altro tipo di dati. Nella specifica della directory da cui leggere i dati non bisogna indicare direttamente il percorso ma un identificatore di directory definito in Oracle. La creazione di un identificatore di directory si realizza con l’istruzione Create directory nomedir as ‘percorso’; 42 CP 116 Una volta creata la directory in Oracle, essa può essere utilizzata come DEFAULT DIRECTORY nella creazione della External Table. Mediante la clausola ACCESS PARAMETERS si specifica la struttura del file ASCII che contiene i dati. La sintassi e le potenzialità sono esattamente le stesse dei control file che si utilizzano per SQL*Loader. È possibile specificare se i record ed i singoli campi sono a lunghezza fissa o terminati da un certo carattere, i tipi ed i formati dei singoli campi, i nomi dei file di Log e dei record scartati o malformati, eccetera. Per una definizione precisa dei parametri d’accesso si può fare riferimento al manuale di SQL*Loader. Mediante LOCATION si definisce il nome del file, o dei file, che contiene i dati. È possibile indicare direttamente il nome del file se esso è contenuto nella directory di default, altrimenti è possibile utilizzare la sintassi DIRECTORY:’FILE’. Questa è la teoria, nel prossimo paragrafo vedremo un esempio pratico. Un esempio Per fare un po’ di pratica creiamo una External Table basata su un file contenente i comuni italiani. Il file si chiama comuni.txt e supponiamo di posizionarlo nella directory c:\documenti\articoli\oraexternal. Nel Listato 1 sono rappresentati i primi record del file. Ogni record è formato dal nome del comune (25 caratteri), la provincia (2), il CAP (5) ed il codice ISTAT (4). I campi non sono delimitati da alcun carattere. Prima di creare la tabella bisogna definire la directory. Eseguiamo quindi l’istruzione Create directory ext_data_dir as ‘c:\documenti\articoli\oraexternal’; Per controllare le directory già esistenti si può effettuare una query dalle tabelle ALL_DIRECTORIES oppure DBA_DIRECTORIES. Ecco l’istruzione per creare la tabella esterna: create table comuni_esterna (comune varchar2(25), provincia char(2), CAP char(5), codice_istat char(4) ) ORGANIZATION EXTERNAL ( TYPE oracle_loader DEFAULT DIRECTORY ext_data_dir ACCESS PARAMETERS ( RECORDS DELIMITED BY NEWLINE LOGFILE ext_data_dir:’comuni_ext.log’ FIELDS ( Comune POSITION(1:25) CHAR(25) NULLIF Comune=BLANKS, Provincia POSITION(26:27) CHAR(2) NULLIF Provincia=BLANKS, CAP POSITION(28:32) CHAR(5) NULLIF CAP=BLANKS, Codice_ISTAT POSITION(33:36) CHAR(4) NULLIF codice_ISTAT=BLANKS ) ) LOCATION (‘comuni.txt’) ) PARALLEL; In accordo con quanto detto nel paragrafo precedente abbiamo prima definito la struttura della tabella e poi, mediante ORGANIZATION EXTERNAL, abbiamo dato le indicazioni su come recuperare i dati. Abbiamo indicato che ogni record occupa una linea del file ed abbiamo definito il nome del file di Log. Per ogni campo abbiamo indicato il tipo di dato, la posizione ed abbiamo indicato di considerare null il campo se nel file sono presenti solo dei blank. Nel Listato 2 si può vedere il risultato della esecuzione del comando e delle prime select effettuate sulla External Table. A questo punto è possibile utilizzare, in sola lettura, la tabella comuni_esterna come una qualunque altra tabella del sistema. Se i campi fossero stati delimitati da virgola, e quindi avessero avuto lunghezza variabile, avremmo potuto sostituire la clausola FIELDS con la seguente FIELDS TERMINATED BY ‘,’ (COMUNE, PROVINCIA, CAP, CODICE_ISTAT) Nel prossimo paragrafo ci occuperemo delle prestazioni delle External Table. LISTATO 2 I test sulle prestazioni hanno dato risultati tranquillizzanti SQL> create table comuni_esterna 2 ( comune provincia char(2), 4 CAP char(5), 5 codice_istat 6 ) 7 Prima di parlare di prestazioni è doverosa una premessa sulla configurazione hardware utilizzata per i test. Tutte le prove sono state eseguite su un Pentium III 600 con 64 Mb di RAM ed OS Windows 98. È una configurazione ridicola, soprattutto se si considera che solo per installare Oracle 9i sono consigliati 96 Mb di RAM. Nonostante la configurazione hardware utilizzata, e nonostante l’impossibilità di indicizzare le External Table, i test sulle prestazioni hanno dato risultati abbastanza incoraggianti. Per migliorare sensibilmente le prestazioni delle query che coinvolgono la tabella COMUNI_ESTERNA, abbiamo aggiunto in fondo all’istruzione di creazione della tabella la parola chiave PARALLEL. Con questa opzione si autorizza il DBMS a caricare i dati in memoria utilizzando più sessioni di lavoro parallele. Attiviamo la visualizzazione dei tempi d’esecuzione delle query mediante il comando SET TIMING ON e facciamo qualche prova. Con i dati contenuti nel file comuni.txt ho creato una tabella classica (utilizzando SQL*Loader) che si chiama comuni ed è identica alla nostra COMUNI_ESTERNA. Cominciamo con le due query più banali: SQL> select count(0) 2 from comuni; COUNT(0) ---------8162 char(4) ORGANIZATION EXTERNAL 8 ( 9 TYPE oracle_loader 10 DEFAULT DIRECTORY ext_data_dir 11 ACCESS PARAMETERS ( 12 RECORDS DELIMITED BY NEWLINE 13 LOGFILE ext_data_dir:’comuni_ext.log’ 14 FIELDS ( 15 Comune 16 17 Provincia 18 CHAR(25) POSITION(26:27) CHAR(2) NULLIF Provincia=BLANKS, 19 CAP POSITION(28:32) CHAR(5) 20 NULLIF CAP=BLANKS, Codice_ISTAT POSITION(33:36) CHAR(4) 22 NULLIF codice_ISTAT=BLANKS 23 ) 24 ) 25 LOCATION (‘comuni.txt’) 26 27 POSITION(1:25) NULLIF Comune=BLANKS, 21 Le prestazioni varchar2(25), 3 ) PARALLEL; Creata tabella. SQL> select count(0) from comuni_esterna 2 ; COUNT(0) ---------8162 SQL> SQL> select * 2 from comuni_esterna 3 where provincia = ‘NA’ 4 and comune like ‘%NAP%’ 5 order by cap; COMUNE PR CAP CODI ------------------------- -- ----- ---CASALNUOVO DI NAPOLI NA 80013 B905 LICIGNANO DI NAPOLI NA 80015 E577 MARANO DI NAPOLI NA 80016 E906 MELITO DI NAPOLI NA 80017 F111 MUGNANO DI NAPOLI NA 80018 F799 NAPOLI NA 80100 F839 Selezionate 6 righe. SQL> Passati: 00:00:00.00 43 CP 116 F O C U S Potenza e versatilità dei Database SQL> select count(0) 2 from comuni_esterna; COUNT(0) ---------8162 Passati: 00:00:00.02 tati catastrofici: per eseguire il conteggio delle righe del prodotto cartesiano il DBMS impiega quasi 7 minuti, come si può vedere qui sotto: SQL> select count(0) 2 from comuni_esterna, 3 comuni_esterna; COUNT(0) ---------66618244 Passiamo ora ad una query un po’ più complicata da eseguire Passati: 00:06:40.09 SQL> select count(0) 2 from comuni 3 where comune like ‘%NAP%’; Per migliorare le prestazioni delle query che coinvolgono tabelle esterne conviene seguire, laddove possibile, i consigli riportati in [1]. COUNT(0) ---------7 Conclusioni Passati: 00:00:01.02 SQL> select count(0) 2 from comuni_esterna 3 where comune like ‘%NAP%’; COUNT(0) ---------7 Passati: 00:00:03.05 Le due query viste sopra coinvolgono comunque un basso numero di record. Per fugare i dubbi residui eseguiamo una query che coinvolge il prodotto cartesiano delle tabelle composto da più di 66 milioni di record: SQL> select count(0) 2 from comuni, comuni; COUNT(0) ---------66618244 Passati: 00:00:58.06 SQL> select count(0) 2 from comuni_esterna, 3 comuni_esterna; COUNT(0) ---------66618244 Le External Table sono uno strumento molto utile introdotto in Oracle9i per leggere direttamente con una istruzione SQL, dati posti in un file fuori dal database. Abbiamo visto come si crea una tabella esterna e ci siamo rassicurati sulle prestazioni che si ottengono. Le External Table nascono fondamentalmente come supporto per l’import dei dati da file esterni. Una volta definita la tabella esterna è possibile usare i dati in essa contenuti per caricare tabelle classiche del database mediante un comando INSERT… SELECT. Ma ci sono anche altre applicazioni per questo strumento. Ad esempio l’utilizzo che ne ha fatto il fantasioso responsabile CED di cui abbiamo parlato all’inizio di quest’articolo. Maggiori dettagli sulle External Table possono essere trovate nella documentazione e nei siti indicati in bibliografia. Entrambi i documenti in bibliografia possono essere trovati sul sito indicato in [3]. Altri articoli sulle nuove funzionalità di Oracle9i saranno pubblicati sui prossimi numeri di Computer Programming. BIBLIOGRAFIA [1] Oracle – “Oracle9i Database Utilities”, Cap. 11 e 12, Oracle Corp., 2001 [2] Oracle – “Oracle9i Database Administrator’s guide”, Cap. 15, Oracle Corp., 2001 Passati: 00:00:48.07 44 CP 116 Sulla lettura dei 66 milioni di record la query sulla tabella esterna è stata addirittura più veloce di quella sulla tabella classica. Se si omette la parola chiave PARALLEL in fondo alla definizione della External Table si ottengono risul- RIFERIMENTI [3] http://technet.oracle.com (Oracle Technology Network)