Sei sulla pagina 1di 283

SQL

E EXCEL
GUIDA ALLA GESTIONE DEI DATI TRA DATABASE E FOGLI DI CALCOLO

Marco Ferrero
Apogeo - IF - Idee editoriali Feltrinelli s.r.l.
Socio Unico Giangiacomo Feltrinelli Editore s.r.l.

ISBN edizione cartacea: 9788850333745

Il presente file pu essere usato esclusivamente per finalit di carattere personale. Tutti i
contenuti sono protetti dalla Legge sul diritto dautore.

Nomi e marchi citati nel testo sono generalmente depositati o registrati dalle rispettive case
produttrici.

Ledizione cartacea in vendita nelle migliori librerie.

Sito web: www.apogeonline.com

Scopri le novit di Apogeo su Facebook

Seguici su Twitter @apogeonline

Collegati con noi su LinkedIn

Rimani aggiornato iscrivendoti alla nostra newsletter


Dedicato a Laura, Marco, Federico, Martina, Lorenzo e alle loro splendide mamme.
Introduzione

Presentazione
Lo strumento di lavoro pi diffuso negli uffici di tutto il mondo un foglio
rettangolare di carta a quadretti, disposto in orizzontale, cio con il lato lungo in basso.
Su questo foglio si scrivono numeri che rappresentano di solito realt economiche:
quantit vendute o prodotte, numero di pezzi in magazzino, costi e prezzi unitari,
associando i numeri a una descrizione, come vediamo qui di seguito.

Gli americani, che hanno un nome per tutte le cose, chiamano questo foglio
spreadsheet, che vuol dire letteralmente foglio dispiegato e se ne servono
egregiamente, con la convinta determinazione che hanno di solito quando lavorano.
Qui da noi non ha un nome ufficiale o comunemente accettato, possiamo chiamarlo
foglio di lavoro, tanto per intenderci. Pur non avendo un nome, comunque usato
parecchio anche in Italia, in tutte le innumerevoli situazioni di lavoro in cui occorre
presentare sinteticamente un insieme di informazioni quantitative.
Le linee verticali del foglio tracciano colonne e quelle orizzontali righe. Nel punto in
cui si intersecano righe e colonne si formano celle, nelle quali si scrivono numeri o
descrizioni, come nellesempio che stiamo vedendo.
Il foglio di lavoro o spreadsheet che dir si voglia indubbiamente un ottimo
strumento per raccogliere dati, ma crea non pochi fastidi quando si sbaglia a inserire un
dato in una cella o quando occorre modificare qualcosa.
Prendiamo lesempio che abbiamo davanti. Se nel Magazz01 la giacenza di rondelle di
diametro 5 passa da 52.000 pezzi a 50.000, il totale in fondo alla riga di queste rondelle
non pi 171.000, ma 169.000. E il totale generale per la colonna del Magazz01 scende a
117.000, mentre si riduce sempre di 2.000 unit la colonna dei totali generali (da
472.000 passa a 470.000).
Per decenni contabili, analisti di gestione, magazzinieri e altri ancora hanno accettato
questo inconveniente in cambio dei grandi vantaggi di chiarezza che offre il foglio di
lavoro. Lunico accorgimento possibile per non soffrire troppo consisteva nellusare
sempre fogli di carta robusta, scrivere i numeri soltanto con la matita e tenere a portata
di mano una gomma per cancellare. Non un caso che negli Stati Uniti, dove si fa un uso
smodato degli spreadsheet, non si trovino in commercio matite di legno senza il
gommino fissato a unestremit.
Tra cronaca e storia
Nel 1978 due giovanotti iscritti al secondo anno del corso per il Master of Business
Administration della Harvard Business School si chiesero se non sarebbe stato possibile
trovare una soluzione non cartacea e non manuale al problema della compilazione dei
fogli di lavoro. Studiare per conseguire lMBA alla Harvard Business School vuol dire
passare intere giornate (e spesso nottate) a riempire decine di fogli di lavoro,
trascrivendovi stati patrimoniali, conti economici, schemi di budget, rendiconti di costi e
ricavi, per analizzare e capire gli aspetti quantitativi della gestione delle imprese.
Da poco pi di un anno era sul mercato un trabiccolo formato da un basamento di
lamiera che conteneva un po di circuiti integrati e nel quale era ricavato
lalloggiamento per una tastiera; sul basamento si appoggiava un monitor
monocromatico (di quelli che si usavano per gli oscilloscopi) e il tutto veniva presentato
col nome di Apple II: era uno dei primissimi personal computer (ancora non avevano
questo nome) nati dalla fertile inventiva di alcuni giovanotti californiani.
I nostri amici, che si chiamavano Dan Bricklin e Bob Frankston, stufi marci di mettere
gi i numeri a matita, di fare i calcoli a mano e di cancellare e ricancellare i dati parziali
per farli quadrare con i totali (o viceversa), decisero di investire la cospicua somma di
1000 dollari e di provare a fare su Apple II un programma che servisse a compilare i
fogli di lavoro.
Dopo pochi mesi, nella primavera del 1979, il risultato tanto atteso prese forma, gli
venne dato il nome di VisiCalc e fu un trionfo. La voce pass fulmineamente di bocca in
bocca (lHarvard Business School una immensa cassa di risonanza), tutti coloro che
avevano a che fare con gli spreadsheet (e negli USA sono legioni) vollero provare se
con VisiCalc si potevano davvero ottenere i risultati sperati (numeri giusti subito, calcoli
e risultati aggiornati automaticamente) e la societ Apple Computer Inc., che aveva fino
ad allora venduto poche decine di esemplari di Apple II, fu subissata di richieste e
divenne ben presto leader di un fiorente mercato in sviluppo.
VisiCalc domin il mercato per qualche anno, poi comparve una societ di software
molto aggressiva, di nome Lotus Development Corp., che svilupp un prodotto
concorrente, predisposto per lavorare su macchine pi potenti, quali il Personal
Computer IBM che nacque nellagosto del 1981.
Il programma creato dalla Lotus per gli spreadsheet si chiamava 1-2-3, per due
ragioni: veniva propagandato con lo slogan facile da usare, come fare 1-2-3... e perch
i suoi creatori erano appassionati di musica (altri prodotti della stessa societ si
chiamarono poi Symphony e Jazz).
Il valzer di Lotus 1-2-3 dur a lungo: il prodotto era molto ben fatto e appagava
completamente tutte le esigenze dei milioni di utenti che usavano un personal computer
soltanto per creare e compilare fogli di lavoro. Il software lavorava nellambiente a
caratteri tipico delle macchine basate sul sistema operativo MS-DOS; pochi fronzoli,
molta sostanza, quello che contava era poter costruire in fretta e bene i fogli di calcolo
ed essere sicuri che i totali per riga e colonna fossero sempre corretti e aggiornati.
Il ruolo di Microsoft
Che cosa faceva, nel frattempo, la Microsoft Corporation? Tramava nellombra,
verrebbe voglia di dire. Non esattamente. Agli inizi degli Ottanta del secolo scorso,
Microsoft era una piccola societ che aveva vinto una lotteria. Per un complesso
concorso di circostanze era proprietaria del sistema operativo MS-DOS che veniva
distribuito insieme con i Personal Computer IBM. Considerato lenorme successo di
vendita di queste macchine e dei milioni di esemplari costruiti dai concorrenti in modo
da dare le stesse identiche prestazioni, la richiesta di copie del sistema operativo MS-
DOS e limpegno per aggiornarlo e potenziarlo assorbivano totalmente le energie della
piccola e grintosa societ di Seattle.
Per un lungo periodo, quindi, Microsoft si tenne ai margini del mercato del software
applicativo, per concentrarsi sul software di sistema, potenziando MS-DOS e preparando
un ambiente di lavoro pi gradevole e pi facile da usare, che sarebbe poi stato
Windows.
La domanda di programmi per gli spreadsheet, per, era talmente vivace che
Microsoft pens di fare comunque qualcosa e mise a punto nel 1985 un suo prodotto, che
chiam orgogliosamente Excel, destinato a lavorare sulla nuova generazione di
macchine create dalla Apple Computer Inc. Queste macchine, che si chiamavano
Macintosh (dal nome di una variet di mele della California), erano strutturalmente
diverse dai PC: invece di lavorare con i caratteri presentavano tutto per mezzo di
immagini (le lettere e i numeri che si vedevano sul monitor erano in realt micro
disegni), usavano il mouse al posto della tastiera per limmissione dei comandi pi
frequenti ed erano in genere molto pi semplici e gradevoli da usare degli austeri
personal computer IBM e derivati.
Excel nacque, quindi, nel contesto grafico dei Macintosh e venne proposto per i PC
soltanto quando su queste macchine si rese disponibile un software, Windows, che
trasformava lambiente di lavoro originario dei PC in un ambiente grafico simile a
quello dei computer della Apple.
Laccoppiata Windows ed Excel mise fuori dal mercato degli spreadsheet il poderoso
concorrente della Lotus (la societ sopravvisse occupandosi di altro) e oggi Excel lo
strumento software pi diffuso per lavorare su fogli di calcolo con un personal
computer dotato del sistema operativo Windows.
I grandi computer e i dati
Il tumultuoso sviluppo dei personal computer negli ultimi cinque anni in tutte le loro
varianti, fino agli attuali smartphone (che sono computer a tutti gli effetti nei quali
integrato un telefono), tende a far dimenticare che i computer non sono nati come
oggetti duso personale, ma come macchine industriali di grandi dimensioni, dette
genericamente mainframe, utilizzate esclusivamente come strumenti per gestire enti e
istituzioni.
La prima di queste macchine si chiamava UNIVAC ed esord sul mercato agli inizi
degli anni Cinquanta del secolo scorso, seguita poco tempo dopo da altre macchine
chiamate 709 e poi 7090 nel decennio successivo e poi ancora negli anni Settanta, Ottanta
e Novanta si diffusero nuovi modelli di mainframe fisicamente pi grandi e pi potenti,
prodotti da numerose societ che operavano nel mercato informatico di allora, fra le
quali primeggiava la International Business Machines meglio nota con la sigla Ibm.
Per ottenere qualcosa di utile da un computer si tratti di un mainframe grande come
una sala da ballo sul quale sono attestate reti di migliaia di computer satelliti e di
terminali o di un semplice tablet tascabile necessario fargli eseguire un programma,
vale a dire un insieme ben organizzato di istruzioni che devono essere scritte a mano da
una persona che conosca quello che viene definito un po pomposamente il linguaggio
di quel computer, cio linsieme delle istruzioni che la macchina in grado di eseguire.
Questo vincolo stato per tutta la storia dellinformatica il pi faticoso e costoso
ostacolo da superare per arrivare a un uso ottimale dei computer: il progresso
vertiginoso nellelettronica raddoppiava ogni 24 mesi la velocit dei circuiti, la parte
tangibile, lhardware, dei computer, mentre lo sviluppo nella realizzazione dei
programmi, della parte non tangibile, il software dei sistemi, restava lontanissimo da
progressi analoghi.
Naturalmente anche nel campo della programmazione si sono fatti notevoli passi
avanti rispetto agli inizi. Agli albori dellinformatica si davano istruzioni ai computer
utilizzando direttamente il cosiddetto emlinguaggio macchina, ovverosia il codice
delle istruzioni elementari predisposte dai progettisti del nucleo logico del computer, la
Central Processing Unit o CPU.
Per rendere meno arduo il lavoro si ricorreva a un linguaggio elementare chiamato
genericamente Assembler, col quale si potevano scrivere le istruzioni usando una
codifica in lettere e numeri. Un programma scritto in Assembler veniva dato in input a
un programma speciale, che traduceva le istruzioni nel codice binario che era lunico
capito dalle CPU.
Per migliorare la produttivit del lavoro di programmazione vennero sviluppati, a
partire dalla fine degli anni Cinquanta del secolo scorso, numerosi linguaggi concepiti
per semplificare ulteriormente la stesura dei programmi. Diversamente dallAssembler, i
nuovi linguaggi di programmazione non contenevano istruzioni che corrispondevano
direttamente con quelle del linguaggio macchina, ma mettevano a disposizione comandi
formulati con parole chiave e operatori aritmetici e logici, molto pi semplici da capire
e da ricordare delle astruse istruzioni dellAssembler.
Le sequenze di caratteri in cui si concretizzavano i programmi scritti usando questi
linguaggi venivano acquisite in input da programmi specializzati (scritti in Assembler)
che traducevano quel flusso di caratteri in istruzioni in linguaggio macchina, che
venivano poi passate per lesecuzione ai computer mainframe.
Due linguaggi di programmazione si affermarono col passare del tempo fra i tanti che
furono sperimentati, chiamati rispettivamente FORTRAN (da FORmula TRANslation)
particolarmente orientato allo sviluppo di programmi per il calcolo scientifico e
COBOL (da COmmon Business Oriented Language), concepito per creare programmi
per la gestione aziendale. La diffusione e il consolidarsi nelluso di questi linguaggi ha
determinato il rigoglioso sviluppo dellinformatica gestionale, che costituisce tuttora la
spina dorsale dei sistemi di gestione delle grandi strutture organizzative (imprese e
istituzioni).
COBOL e FORTRAN sono linguaggi procedurali, vale a dire permettono di
definire puntualmente procedure da seguire per elaborare i dati, per esempio preparare
fatture partendo da bolle di consegna, listini dei prezzi e indirizzi dei clienti o bollettini
di stipendi elaborando dati sugli orari di presenza al lavoro del personale e sulle
retribuzioni associate a quegli orari.
La disponibilit di questi e altri linguaggi procedurali favor enormemente lo
sviluppo dei sistemi informatici per la gestione di strutture organizzative grandi e
piccole, ma il lavoro di sviluppo continuava a restare lento e faticoso, perch
comportava comunque un massiccio impiego di risorse umane.
Per rendere pi spedito e pi efficiente il lavoro intrinsecamente lento della scrittura
dei programmi dei computer si definirono standard di scrittura e si cerc di individuare
elementi ripetitivi nel lavoro di programmazione che potessero essere ricondotti a
blocchi predefiniti di istruzioni, da utilizzare come moduli gi pronti, da inserire nei
nuovi sistemi gestionali detti genericamente applicazioni che si sviluppavano.
Tipicamente, uno dei moduli che fu individuato per primo e si prest meglio di tutti alla
standardizzazione fu lordinamento. In quasi tutte le applicazioni era necessario ordinare
in successione alfabetica o numerica stringhe di caratteri che rappresentavano matricole
di personale o codici di materiali, quindi si crearono e si diffusero con successo
programmi di servizio che ricevevano in input una descrizione schematica dei dati da
ordinare e in base a quella descrizione acquisivano flussi di dati disordinati che
restituivano come sequenze ordinate, pronte per successive elaborazioni di calcolo o di
selezione.
Il successo anche commerciale di questi primi moduli di software utilizzabili con
poche modifiche da chiunque dovesse sviluppare applicazioni che prevedevano di
ordinare sequenze di dati condusse gradualmente a individuare altre caratteristiche delle
applicazioni che si prestassero ad analoghe standardizzazioni.
Alla fine degli anni Sessanta, si individu con chiarezza una struttura portante comune
a tutti i sistemi informatici per la gestione aziendale, che venne chiamata genericamente
base dei dati o data base in inglese. Col senno di poi sembra una banalit, ma si tratt
di una intuizione luminosa che contribu non poco a rendere pi efficiente (e quindi
meno costoso e pi veloce) lo sviluppo delle applicazioni gestionali.
Lidea centrale che qualunque applicazione gestionale fa riferimento a un insieme di
dati, la sua base, che opportuno gestire separatamente, con uno strumento generico che
sia invariante rispetto al contenuto e alla tipologia dei dati, quindi utilizzabile sia da una
banca per i suoi conti correnti sia da una catena di supermercati per i suoi magazzini.
Lidea era quella di separare la gestione dei dati acquisizione e manutenzione dal
loro utilizzo, consentendo a chi sviluppava le applicazioni di occuparsi soltanto
dellutilizzo dei dati e non della loro gestione/amministrazione: un altro importante
passo avanti nella riduzione dei tempi (e quindi dei costi) dello sviluppo delle
applicazioni.
Furono messi a punto svariati sistemi per la gestione delle basi di dati, chiamati
genericamente Data Base Management Systems, in sigla DBMS, che furono promossi
commercialmente con vigore non soltanto dalle societ di sviluppo software che li
avevano creati ma anche e soprattutto dalle imprese che producevano hardware, che
vedevano giustamente nei DBMS un fattore che avrebbe favorito ladozione dei
computer anche in imprese poco propense a farsi carico di notevoli costi per lo sviluppo
del software applicativo.
Nel 1970 un inglese naturalizzato americano studioso di logica e matematica di nome
Edgar F. Codd pubblic nella rivista istituzionale degli informatici USA
(Communications of the Association for Computing Machinery), un articolo intitolato A
Relational Model of Data for Large Shared Data Banks (Un modello relazionale di dati
per grandi banche dati condivise) che segn la nascita formale di una nuova e pi
feconda concezione delle basi di dati, che prese il nome di Relational Data Base
Management System ovvero RDBMS.
Il termine relational sta a indicare lelemento centrale della nuova concezione delle
banche dati, concepite come insiemi di relazioni logiche fra dati elementari univoci che
fisicamente si presentano come tabelle.
Nei dieci anni successivi alla pubblicazione del fondamentale contributo concettuale di
Codd alcune societ che realizzavano software lavorarono allo sviluppo di un
linguaggio di programmazione che consentisse di creare RDBMS e di accedere
agevolmente ai dati che contenevano: il risultato di questo lavoro di ricerca prende il
nome di Structured Query Language, in sigla SQL, vale a dire linguaggio per
interrogazioni strutturate.
Con SQL si creano e si interrogano (emquery in inglese vuol dire domanda,
interrogazione) RDBMS. Ci su cui agisce lo SQL sono tabelle idealmente identiche ai
fogli di lavoro di Excel composte da righe e colonne, secondo regole rigorose che
vedremo in modo approfondito nei prossimi capitoli.
I linguaggi che si sono affermati nel tempo, il FORTRAN e il COBOL, di cui abbiamo
gi parlato, e molti altri ancora, sono accomunati da queste due caratteristiche: sono
linguaggi generalizzati e procedurali.
Un linguaggio generalizzato permette di far fare al computer qualsiasi cosa:
eseguire calcoli matematici, acquisire dati da apparecchiature periferiche (tastiere,
lettori di dischi ottici, schede di memoria) e salvarli su disco in forma di file,
tracciare segni grafici su un monitor, emettere suoni e altro ancora.
Un linguaggio procedurale dotato di istruzioni e comandi con i quali si indica al
computer come deve operare per arrivare al risultato che interessa al
programmatore. Un linguaggio procedurale serve per scrivere procedure, che il
computer esegue con sovrana imperturbabilit.
Lo Structured Query Language, invece, un linguaggio specializzato e non
procedurale.
Specializzato: non si pu usare SQL per fare grafica o per acquisire dati da
trasferire su file, ma lo si usa soltanto per lavorare su tabelle.
Non procedurale: con SQL non si creano procedure, non si spiega al computer
come deve fare, ma gli si danno comandi che descrivono quello che si vuole
ottenere. Lenfasi passa dal come fare al che cosa ottenere.
Naturalmente da qualche parte nel computer ci deve pur essere qualcosa che trasformi
gli imperiosi comandi SQL in miriadi di istruzioni in linguaggio macchina affinch il
computer riesca a dare i risultati che gli si chiedono, ma questo qualcosa un interprete
di SQL, scritto una sola volta per tutte da una squadra di sviluppatori di software di
sistema. Chi vuole usare un database relazionale non deve preoccuparsi di come questo
fatto fisicamente, delle posizioni e dei collegamenti fisici dei record e delle tabelle. Il
modello dei dati e della loro organizzazione invariante rispetto alla loro realt fisica:
con SQL si accede ai dati e li si estrae sempre nello stesso modo, quale che sia la
macchina e il database nel quale i dati risiedono.
Lavorare con SQL ed Excel
Nato nel mondo dei grandi mainframe dove tuttora domina come fondamentale
strumento di lavoro lo Structured Query Language disponibile anche sui personal
computer, nel cui mondo nato Excel, che non un linguaggio di programmazione ma
un ambiente strutturato nel quale collocare dati per memorizzarli ed esaminarne le
caratteristiche e le possibili affinit.
Nei fogli di lavoro Excel i dati si inseriscono per lo pi a mano ma si possono anche
acquisire insiemi di dati gi esistenti come file e inserirli in fogli di lavoro sui quali
eseguire poi calcoli di varia natura confronti, selezioni, ordinamenti e ricavare
illuminanti rappresentazioni grafiche dalle serie di dati contenuti in uno o pi fogli di
lavoro.
In Excel 2016 un foglio di lavoro pu contenere oltre un milione di righe (per
lesattezza 1.048.576) e svilupparsi per 16.384 colonne. Sembrerebbe una capienza pi
che adeguata per acquisire e analizzare grandi insiemi di dati quali quelli che si
generano nei supermercati o nelle banche, ma in realt non sono sufficienti, perch quel
genere di flussi di dati si sviluppa per decine di milioni di record nel giro di poco
tempo, creando depositi sterminati di dati elementari chiamati Big Data che bisogna
affrontare con gli strumenti di gestione tipici dei sistemi per database.
Si pu concepire una comoda divisione del lavoro, quando necessario lavorare sui
Big Data: acquisirli e organizzarli in tabelle con laiuto di SQL e riversarli
selettivamente (ricorrendo alle query di SQL) su un foglio di lavoro Excel nel quale
lavorare di fino usando gli strumenti grafici di Excel per rappresentarli e metterli a
confronto fra loro e ricorrendo agli strumenti di calcolo aritmetico e statistico di Excel
per cercare di penetrare il significato di quelle masse di dati.
In questo libro presentiamo una rassegna completa dello Structured Query Language,
fornendo anche indicazioni su come procurarsene una versione da usare su un personal
computer.
Dopo questa rassegna, una serie di esempi concreti aiuta a capire come acquisire
grandi flussi di dati con SQL e riversarli su fogli di lavoro Excel e quali strumenti
intrinseci di Excel conviene usare caso per caso per analizzare quei dati e ricavarne
opportune rappresentazioni grafiche.

File degli esempi


Allindirizzo http://www.apogeonline.com/libri/9788850333745/scheda i lettori potranno scaricare
liberamente i file dei dati utilizzati per gli esempi mostrati nel corso del testo. Questi file
possono essere utilizzati per fare pratica riproducendo le procedure di lavoro con SQL
ed Excel.
Marco Ferrero
ferrero_m@libero.it

Giugno 2016
Capitolo 1
Gli strumenti di lavoro

Per apprendere le tecniche di analisi dei dati con SQL ed Excel occorrono un certo
numero di dati dimostrativi, una copia installata e funzionante di Excel 2016 e un sistema
per creare e gestire database relazionali utilizzando lo Structured Query Language.
I dati dimostrativi sono a disposizione dei lettori allindirizzo
http://www.apogeonline.com/libri/9788850333745/scheda, il programma Excel 2016 dovrebbe far

parte della dotazione di software del computer col quale si lavora e quanto agli strumenti
per i database ne proponiamo due, disponibili per il download gratuito dal Web e che si
chiamano rispettivamente MySQL e SQL Server. Ai fini dellapprendimento e delle
prove, luno vale laltro: in questo libro presenteremo sempre gli esempi nelle forme
adeguate per poterli provare su entrambi i sistemi.
MySQL
Nato a Helsinki ma cittadino svedese, Ulf Michael Widenius, detto Wolf dagli amici,
un imprenditore e creatore di software che nel 1996 mise a disposizione del pubblico un
sistema che aveva creato per la gestione di database relazionali chiamandolo MySQL dal
nome della sua prima figlia My.
Il sistema MySQL ha avuto uno sviluppo notevole per durata, qualit e successo e
attualmente (anno 2016) viene distribuito in due versioni, per le imprese (a pagamento) e
per la comunit degli studiosi (a titolo gratuito). Per entrambe le versioni esistono
varianti predisposte per operare sotto diversi sistemi operativi, per cui la versione di
MySQL che ci interessa va cercata a partire dal sito web:
http://dev.mysql.com/downloads/windows/

Quando vi si accede, questo sito presenta la schermata che vediamo nella Figura 1.1.

Figura 1.1 La schermata dalla quale si pu attivare il download di MySQL per Windows.

NOT A
Si sarebbe indotti a pronunciare il nome MySQL separando le due parti e sillabando in inglese mai es
chi el, ma gli addetti ai lavori pronunciano mskl, come si apprende dai filmati sullargomento visibili
nel Web.
La versione di questo strumento di lavoro che andremo a utilizzare negli esempi del
libro la 5.6, che si ottiene agevolmente iniziando il processo di download con un clic
sul collegamento ipertestuale MySQL Installer,
In quel contesto possiamo scegliere quale scaricare fra due diversi file di
installazione:
1. il file predisposto per eseguire linstallazione mentre si connessi col Web tramite
una connessione a banda larga: mysql-installer-web-community-5.7.11.0.msi
2. il file previsto per installare MySQL senza essere connessi col Web: mysql-installer-
community 5.7.11.0.msi

Dopo aver scaricato uno o laltro di questi file lo si esegue installando cos una
versione di MySQL prevista per operare su una macchina a 32 bit: una volta installato, il
sistema funziona perfettamente anche su una macchina a 64 bit.
Prima di eseguire il file che si scelto di scaricare opportuno, gi che si collegati
col sito ufficiale di MySQL, individuare e scaricare un altro file da utilizzare per
linstallazione, disponibile in due versioni, per macchine a 32 o a 64 bit, chiamate
rispettivamente:
mysql-workbench-community-6.3.6-winx32.msi
mysql-workbench-community-6.3.6-winx64.msi

Questo file si user dopo aver installato MySQL per ottenere una serie di strumenti
grafici che agevolano linterfaccia dellutente con il sistema MySQL.
Dopo aver scaricato il file mysql-installer nella versione che si preferita lo si esegue
con un doppio clic sul suo nome facendo cos avviare una procedura guidata molto
semplice da seguire, che propone in successione varie opzioni fra le quali intuitivo
scegliere quelle corrette per ottenere una installazione semplice da utilizzare e adeguata
alle finalit didattiche che intendiamo ottenere.
In tempi successivi non sar difficile modificare anche profondamente la versione
installata di MySQL per ottenere risultati professionali molto impegnativi.
Il concetto base che orienta lintero processo di download e poi di installazione che
MySQL un server, vale a dire uno strumento software che agisce come se fosse una
macchina separata da quella con la quale stiamo lavorando: con questo server ci si
connette presentandosi come client per avere un servizio, nel caso specifico la creazione
e la gestione di database.
Per procedere correttamente con linstallazione necessario come prima cosa
accettare le condizioni richieste dalla schermata License Agreement, spuntando con un
clic la casella di controllo I accept the license terms.
La schermata successiva, intitolata Choosing a Setup Type, presenta varie opzioni per
il tipo di impostazione da scegliere: opportuno selezionare lopzione Developer
Default.
Potrebbe successivamente comparire una finestra che chiede di impostare
manualmente determinati requisiti (detti requirements): possiamo ignorare queste
richieste procedendo oltre con un clic sul pulsante Next.
Compare finalmente la schermata che vediamo nella Figura 1.2, che elenca i
componenti detti products che verranno installati, lultimo dei quali chiamato Sample
and Examples e come il suo nome lascia intuire un insieme di esempi e di dati
campione da utilizzare per fini didattici.
Un clic sul pulsante Execute avvia il processo di installazione, che non richiede alcun
intervento da parte delloperatore.
Terminato questo processo, una nuova schermata intitolata Type and Networking
propone una serie di valori predefiniti per le modalit di comunicazione fra server e
client, che si possono lasciare come stanno; in un elenco a discesa chiamato Config Type
scegliamo fra quelle disponibili per definire il tipo di configurazione lopzione
Development Machine, che quella che ci serve: una macchina per creare database e
provare a utilizzarli in un contesto di sviluppo applicativo.
Viene successivamente aperta una finestra Accounts and Roles nella quale dobbiamo
qualificarci come utenti, scegliendo account, ruoli e password relative.
Lorganizzazione di un server di database per sua natura gerarchica e autoritaria, per
cui esiste un unico capo supremo, dal quale promanano le nomine di chi autorizzato ad
accedere ai database e con quali ruoli.
Figura 1.2 La schermata per linstallazione dei prodotti che completano MySQL.

Lautorit suprema si chiama per definizione root e la sua identit va protetta con una
password che dobbiamo scegliere e, come suggerisce la finestra di dialogo, conservare
in un luogo sicuro. opportuno che la password che andiamo a inserire sia lunga
almeno otto caratteri e contenga un assortimento di lettere maiuscole e minuscole, cifre
e caratteri speciali. Dopo che labbiamo scritta interviene un algoritmo interno di
valutazione della sua efficacia, che fa comparire un giudizio scritto in caratteri colorati,
che nel caso migliore Strong.
Nella stessa finestra forti dellautorit che ci viene dallessere per definizione
titolari dellaccount root possiamo creare nuovi account, attribuendo loro un nome in
chiaro, una password e selezionando per ciascuno un certo numero di autorizzazioni
scelte da un elenco chiuso. Vediamo il risultato di questa operazione nella Figura 1.3.
Terminologia
La parola account in inglese vuol dire conto e nel gergo informatico si usa per riferirsi in modo generico a
chiunque sia autorizzato ad accedere a un sistema computerizzato. Il termine nacque agli inizi della
multiutenza sui grandi computer degli anni Settanta del secolo scorso, che erano macchine costosissime:
a ogni utente corrispondeva una posizione contabile, un account, appunto, che serviva per addebitare a
ciascuno i consumi di tempo macchina e loccupazione di spazio sui dischi, cos da ripartire i costi di
gestione in modo proporzionale allutilizzo effettivo da parte dei vari utenti, i quali per similitudine venivano
chiamati anchessi account.

Figura 1.3 Limpostazione degli account root e ausiliari.

Il passo successivo consiste nel configurare MySQL come servizio di Windows,


tramite una nuova finestra chiamata Windows Service.
A questo scopo bisogna assegnare un nome univoco al servizio Windows da usare per
listanza di MySQL che si sta installando. Se si installano pi istanze serviranno
altrettanti nomi univoci.
Attribuito il nome al servizio, mettendo il segno di spunta in unapposita casella di
controllo si pu scegliere se il server MySQL debba essere avviato oppure no
contestualmente allavvio di Windows.
MySQL Server deve operare sotto un determinato account utente, che pu essere
individuato personalizzando un account utente gi disponibile oppure utilizzando
laccount standard del sistema (questa la soluzione pi semplice e anche quella
raccomandata nella finestra di dialogo Windows Service)
Terminate le selezioni nella finestra di dialogo Windows Service, si apre la finestra
Apply Server Configuration, nella quale non c altro da fare che premere il pulsante
Execute per avviare ed eseguire le operazioni di configurazione, che si svolgono in
successione automaticamente; dopo un po la finestra viene sostituita da unaltra con lo
stesso nome ma con segni di spunta verdi associati ai nomi delle singole operazioni per
confermare che andato tutto bene e si pu continuare premendo il pulsante Finish.
Con lesecuzione di questo comando si conclude linstallazione del prodotto MySQL
vero e proprio. opportuno, per, fare un ulteriore passo preliminare, installando anche
linterfaccia grafica per lutente, lanciando il file mysql-workbench-community.msi con un
doppio clic sul suo nome: lesecuzione del tutto automatica e il risultato che si ottiene
si presenta come nella Figura 1.4.

Figura 1.4 MySQL Workbench: linterfaccia utente per lavorare con MySQL.

A questo punto della nostra esposizione possiamo trascurare sia i pulsanti colorati che
si vedono sul lato destro dello schermo, sia i riferimenti che stanno nellangolo in basso
a sinistra: vedremo pi avanti che cosa rappresentano e come si possono utilizzare.
Nellangolo superiore sinistro un riquadro rettangolare ci informa che disponibile
unistanza locale del server di database chiamata MySQL56 che consente laccesso a root
e allhost che lo contiene che si chiama localhost 3306 (se non abbiamo modificato
questi valori durante il processo di installazione).
Un clic su quel riquadro apre finalmente gli strumenti che formano la versione grafica
dellinterfaccia utente di MySQL, chiamata genericamente MySQL Workbench e che si
articola in un numero notevole di finestre e menu.
Se ci si collega al sito ufficiale di MySQL e pi specificamente si attiva questo link:
http://dev.mysql.com/doc/

si accede a una pagina web dalla quale si possono scaricare:


il manuale utente pi aggiornato per la versione di MySQL che abbiamo installato,
un secondo manuale che descrive in tutti i particolari il funzionamento
dellinterfaccia MySQL Workbench e le sue numerosissime finestre e menu.
Non sono letture snelle per ragazzini volonterosi: il manuale completo si sviluppa per
4.480 pagine e quello per linterfaccia di pagine ne contiene 416, i testi sono in inglese e
i file sono in formato PDF, quindi leggibili con qualunque computer.
Possiamo fermarci qui per quanto riguarda lo strumento MySQL: per utilizzarlo
necessario conoscere alcuni concetti fondamentali sui database relazionali e sulle loro
caratteristiche logiche e di funzionamento per arrivare poi a conoscere il lessico e la
sintassi dello Structured Query Language con il quale si creano e si utilizzano database.
Lint erfaccia a riga di comando
I puri di MySQL amano usare una interfaccia molto spartana, basata su comandi immessi da tastiera in
una semplice finestra interattiva dove i caratteri appaiono in bianco su sfondo nero, che si presenta come
nella figura che segue.

Possiamo tranquillamente ignorare le funzionalit offerte da questa austera interfaccia.


SQL Server
Il suo nome ufficiale Microsoft SQL Server ed lantesignano di tutti gli strumenti
per creare e gestire grandi database in una logica client-server,
Passato per numerose versioni nellarco di oltre trentanni di vita, anche SQL Server
disponibile in due varianti, una a pagamento per le imprese e unaltra gratuita per finalit
di studio e di apprendimento.
Per ottenere questo strumento si procede come per MySQL, accedendo al sito web di
Microsoft dove disponibile per il download.
I nomi delle versioni di SQL Server sono stati cambiati molte volte da Microsoft,
iniziando con la classica numerazione a due cifre che storicamente si usava per
distinguere le successive versioni del sistema operativo MS-DOS. In tempi pi recenti si
passati a distinguere le nuove versioni con il nome dellanno nel quale sono state rese
pubbliche: quella pi recente si chiama Microsoft SQL Server 2016 ma nel momento in
cui scriviamo (primavera 2016) ancora in fase di messa a punto finale. Potremo usare
quindi la penultima versione che si chiama Microsoft SQL Server 2014, che
perfettamente adatta ai nostri fini didattici.
SQL Server un prodotto di grande impegno e potenzialmente molto costoso se chi lo
installa unimpresa, ma disponibile in una versione molto snellita e gratuita per chi
non ne fa un uso commerciale. Possiamo quindi ottenere e installare una copia di questo
prodotto accedendo al sito web di Microsoft e cercando il file: SQL Server 2014 Express.
Si viene portati in una pagina nella quale facoltativo dare qualche indicazione su chi
si , compreso un indirizzo e-mail e successivamente si sceglie la versione che interessa
e la lingua dellinterfaccia utente, come possiamo vedere nella Figura 1.5.
Figura 1.5 Le opzioni disponibili per scaricare una versione gratuita di SQL Server.

Per portare a termine questo download bisogna disporre di una buona connessione
Internet, poich il file da ottenere che si chiama SQLEXPRADV_x64_ITA.exe piuttosto
pesante (intorno a 1,2 GB).
Ottenuto il file sul proprio computer basta fare un doppio clic sul suo nome per
avviare una procedura di installazione del tutto automatica, che non richiede
praticamente alcun intervento da parte dellutente (se si accettano le opzioni predefinite:
scelta consigliabile a tutti gli effetti per chi non conosce gi molto bene questo prodotto).
In particolare, raccomandiamo di optare per la verifica dellaccesso a SQL Server
basata su quella predisposta per la macchina Windows con la quale si lavora,
risparmiando cos il fastidio di avere unennesima password da creare e gestire.
Linstallazione procede abbastanza velocemente e si conclude con la comparsa della
finestra di accesso allinterfaccia grafica, che si chiama SQL Server 2014 Management
Studio. Un clic su questa finestra apre la schermata di lavoro, nella quale si pu lavorare
per creare e utilizzare database: come per MySQL, esiste unalternativa a riga di
comando per accedere ai servizi e alle funzionalit di SQL Server, ma non il caso di
parlarne qui.
Se il nostro computer lavora sotto la guida del sistema operativo Windows 10 pu far
comodo disporre le icone delle finestre daccesso ai due sistemi nel modo che vediamo
qui di seguito.
I database dimostrativi
Una volta installato MySQL si pu provare a usarlo subito, aprendo un database creato
a scopo dimostrativo e che si chiama sakila.
Per ottenere questo risultato basta fare un clic sullicona di MySQL Workbench che
stata creata nel menu Start di Windows e nella finestra che si apre un clic sul riquadro in
alto a sinistra avvia la connessione col server, presentando una finestra suddivisa in due
fasce verticali. Quella di sinistra, pi stretta, si chiama Navigator e si articola in due
schede, Management e Schemas. Un clic su Schemas mostra lelenco dei database
disponibili, che a sistema appena installato sono tre: sys, world e sakila. Il primo un
database di sistema ed meglio lasciarlo stare, world un piccolo database dimostrativo
creato da Oracle (grande societ di software per database che di recente ha acquistato
MySQL) e il terzo quello che ci interessa: sakila.
Alla sinistra del nome del database c un pulsante a forma di triangolo che punta
verso destra: un clic su quel pulsante lo trasforma in un triangolo che punta verso il
basso e fa sgranare, subito sotto il nome del database, lelenco delle tabelle che lo
compongono.
Facendo scorrere il puntatore del mouse senza fare clic accanto al nome di una
qualunque tabella si evidenziano tre piccoli pulsanti affiancati a destra del nome della
tabella sulla quale si soffermato il mouse. Il primo di questi pulsanti ha licona di una i
bianca in campo grigio e sta per informazione, il secondo ha licona di una chiave
esagonale e significa struttura e il terzo ha licona di una griglia e indica contenuto
della tabella.
Un clic su ciascuna di queste icone genera nel riquadro pi grande a destra della
sezione Navigator (che si chiama finestra Query) le informazioni corrispondenti.
Vediamo nella Figura 1.6 il risultato che si ottiene premendo il pulsante con licona delle
chiave esagonale in corrispondenza della tabella film.
Figura 1.6 La struttura della tabella film evidenziata nella finestra Query.

Pu essere pi illuminante selezionare il piccolo pulsante con licona della griglia,


che genera nella parte inferiore della finestra Query lelenco dei contenuti della tabella
selezionata .
Il database sakila si articola in 16 tabelle, concepite per rappresentare un ipotetico
negozio per il noleggio di DVD cinematografici. Le tabelle, quindi, contengono un
catalogo dei film disponibili su DVD, un elenco di clienti e varie informazioni di
servizio sui noleggi in corso.
Anche per SQL Server esiste un database dimostrativo, chiamato AdventureWorks2014,
che non viene installato contestualmente alla installazione del server, ma disponibile
come file da scaricare dal sito web di Microsoft dove si trovano le diverse versioni di
SQL Server. Il file da scaricare un file compresso chiamato Adventure Works 2014 Full
Database Backup.zip .
Dopo averlo scaricato bisogna scompattarlo in una cartella qualunque, dove si otterr
un file chiamato AdventureWorks2014.bak .
A questo punto necessario controllare che SQL Server si trovi nel seguente percorso
(se installato in una macchina a 64 bit):
C:\Program Files\Microsoft SQL Server\MSSQL12.SQLEXPRESS

Fatta questa verifica, aprire il programma di scrittura Blocco note di Windows e


trascrivere in un nuovo documento di testo le righe che seguono, assicurandosi di
copiare esattamente ogni singolo carattere:
USE [master]

RESTORE DATABASE AdventureWorks2014


FROM disk= [digitare qui fra virgolette semplici il percorso completo in cui si trova il file
AdventureWorks2014.bak Per esempio 'C:\ESEMPI\ AdventureWorks2014.bak']
WITH MOVE 'AdventureWorks2014_data'
TO 'C:\Program Files\Microsoft SQL server\MSSQL12.SQLEXPRESS\
MSSQL\DATA\AdventureWorks2014.mdf',

MOVE 'AdventureWorks2014_Log'

TO 'C:\Program Files\Microsoft SQL Server\MSSQL12.SQLEXPRESS\


MSSQL\DATA\AdventureWorks2014.ldf'

,REPLACE

Dopo aver scritto e controllato attentamente il testo, salvare il file corrispondente con
un nome qualunque e copiarne il contenuto negli Appunti di Windows.
Aprire SQL Server attivando la connessione predefinita e fare clic sul pulsante Nuova
query nella barra degli strumenti che sta in testa alla finestra.
Accanto alla finestra Esplora oggetti che sta sul lato sinistro si affiancher una finestra
vuota chiamata SQL Query.1sql e in questa finestra bisogna incollare il contenuto degli
Appunti, ottenendo il risultato che si vede nella Figura 1.7.

Figura 1.7 Lo script per installare il database AdventureWorks2014.


Quando il testo entrato nella finestra si fa clic sul pulsante Esegui caratterizzato da
un punto esclamativo rosso che sta nella barra degli strumenti e se tutto va bene dopo
qualche istante il database dimostrativo AdventureWorks2014 installato e il suo nome
compare nella finestra Esplora oggetti ed pronto per essere utilizzato.
Questo database contiene le tabelle per la gestione completa di un business di
produzione e vendita di biciclette.
La finestra Esplora oggetti di Management Studio di SQL Server simile alla finestra
Navigator di MySQL Workbench, ma offre meno possibilit di esplorazione diretta dei
contenuti di un database: soltanto possibile visualizzare lelenco a discesa delle tabelle
che compongono un database e allinterno di questo elenco si possono visualizzare i
nomi dei campi per ciascuna tabella. Per qualunque operazione anche di semplice lettura
dei contenuti di una tabella necessario eseguire espressamente un comando nel
linguaggio SQL, come vedremo nei prossimi capitoli.
I file di lavoro
Per raggiungere i risultati didattici che ci proponiamo con questo libro essenziale
lavorare con esempi reali, quindi con file di dati ricavati da concrete realt sociali ed
economiche.
Abbiamo ottenuto alcuni flussi di dati grezzi da registrazioni di eventi commerciali di
due societ, una che vende articoli di consumo per corrispondenza e laltra che fornisce
servizi di telefonia cellulare.
Per ovvie ragioni di riservatezza abbiamo sostituito ai nomi delle persone e delle
merci e servizi nomi fittizi, mantenendo per la struttura dei dati grezzi a disposizione.
Questi dati sono fisicamente raccolti in file di testo delimitati da tabulazioni e salvati con
lestensione .txt, cosa che li rende facilmente acquisibili in tabelle di database create con
MySQL o con SQL Server. In teoria si potrebbero anche acquisire direttamente in fogli
di lavoro Excel, ma le dimensioni di alcune tabelle superano (deliberatamente) la
capienza in righe di un foglio di lavoro Excel, quindi necessario utilizzare prima un
sistema per i database per acquisire i dati e selezionarli in vari modi cos da trasferirne
opportune sintesi in fogli di lavoro Excel.
Il file di testo chiamato abbonati.txt raccoglie in un unico flusso di oltre cinque milioni
di righe un certo numero di dati riferiti ad abbonati di una societ di telefonia cellulare
privata.
I file ordini.txt, prodotti.txt, righe_ordine.txt e clienti.txt formano un insieme di tabelle
opportunamente coordinate fra loro per la gestione degli acquisti fatti dai clienti della
societ commerciale.
Capitolo 2
Caratteristiche dei database relazionali

Con lo Structured Query Language si creano e si utilizzano database relazionali.


Questo vuol dire che i comandi della componente Data Definition Language (DDL) di
SQL permettono di creare agevolmente tabelle che nel loro insieme formano un database
relazionale, per la struttura e il contenuto delle tabelle devono essere impostati prima di
ricorrere al DDL per crearle e questo un compito separato, che nulla ha a che fare con
la programmazione in SQL.
Affinch un database si possa considerare relazionale, le tabelle (cio le relazioni fra
dati) che lo compongono devono essere costruite nel rispetto di alcuni vincoli, che
vengono genericamente chiamati forme normali (in inglese Normal Forms), per cui le
tabelle che rispettano tali vincoli si dicono normalizzate. In molte realt gestionali si
lavora con tabelle di dati, create con strumenti manuali o informatici, che non rispettano
le forme normali: per poter gestire quei dati con un database relazionale necessario
riorganizzarli in tabelle normalizzate.
La teoria che sta alla base delle forme normali e che orienta le operazioni di
normalizzazione delle tabelle piuttosto ardua e complessa, ma ai fini pratici pu essere
ricondotta a pochi concetti essenziali, che si possono capire bene con alcuni esempi, che
esporremo in questo capitolo.
Righe e colonne
Nella terminologia dei database relazionali una tabella composta da righe, i record, e da colonne, i campi.
In tutto il libro useremo indifferentemente i termini record o riga e campo o colonna per parlare dei due
elementi che costituiscono una tabella..
Univocit e chiavi primarie
Ogni record di una tabella deve essere univoco, vale a dire ciascuna riga deve essere
diversa, almeno in una sua colonna, da tutte le altre. Nella tabella che segue abbiamo due
record perfettamente identici, che creano ambiguit.
Cognome Nome Indirizzo Cit t CAP Provincia
Rossi Giuseppe via Manzoni, 40 Milano 20121 MI
Rossi Anna via Manzoni, 40 Milano 20121 MI
Rossi Giuseppe via Montenapoleone, 12 Milano 20121 MI
Rossi Anna via Manzoni, 40 Milano 20121 MI

Per eliminare lambiguit (e la ridondanza) bisogna togliere dalla tabella il doppione,


ottenendo questo risultato.
Cognome Nome Indirizzo Cit t CAP Provincia
Rossi Giuseppe via Manzoni, 40 Milano 20121 MI
Rossi Anna via Manzoni, 40 Milano 20121 MI
Rossi Giuseppe via Montenapoleone, 12 Milano 20121 MI

I record di una tabella non sono numerati e lordine col quale si susseguono non
noto, quindi per distinguere i singoli record luno dallaltro necessario esaminare il
contenuto dei loro campi. Dato che un record pu contenere decine di campi, per ridurre
il numero delle letture necessarie per distinguere ciascun record opportuno identificare
in una tabella le colonne che potrebbero avere un contenuto diverso in ogni riga. I campi
o colonne che hanno queste caratteristiche si chiamano chiavi candidate e fra queste se
ne sceglie una che assume il ruolo e il nome di chiave primaria. Una tabella pu avere
una sola chiave primaria e la sua presenza garantisce lunivocit dei record; la chiave
primaria pu essere semplice, se formata da una sola colonna, o composta, quando
risulta dalla combinazione di pi colonne (non necessariamente contigue).
Nellesempio che stiamo esaminando, nessuna delle sei colonne pu essere una chiave
candidata, perch ognuna contiene valori ripetuti, quindi lunica possibilit che abbiamo
definire una chiave primaria composta con le colonne Cognome+Nome+Indirizzo.
Che cosa succede, per, se un altro Giuseppe Rossi va ad abitare in via Manzoni, 40 a
Milano? Ci troveremmo con un record ridondante, ma in realt si tratta di una persona
diversa, anche se tutti i campi sono uguali. Quando la natura dei dati raccolti in una
tabella pu creare ambiguit di questo genere, opportuno aggiungere una nuova
colonna alla struttura della tabella, creando con questa una chiave primaria, che potrebbe
essere, per esempio, un codice numerico diverso per ogni record; in questo modo si
garantisce lunivocit di ciascuna riga, anche se vi fossero righe che contengono gli
stessi valori in tutte le altre colonne. Ecco quindi la nostra tabella con una chiave
primaria semplice e diversa per ciascun record.
IDPersona Cognome Nome Indirizzo Cit t CAP Provincia
1 Rossi Giuseppe via Manzoni, 40 Milano 20121 MI
2 Rossi Anna via Manzoni, 40 Milano 20121 MI
3 Rossi Giuseppe via Montenapoleone, 12 Milano 20121 MI
4 Rossi Giuseppe via Manzoni, 40 Milano 20121 MI

In considerazione della sua importanza per garantire lunivocit dei record, anche se
non obbligatorio farlo, sempre opportuno definire una chiave primaria quando si
crea una tabella.
Chiavi esterne e relazioni
Si crea un database di solito per gestire pi tabelle, ciascuna delle quali aggrega dati
relativi ad attributi di entit diverse, ma in un qualche modo correlate: Clienti, Prodotti e
Ordini, per esempio, possono essere le tipiche tabelle che si utilizzano per gestire
lattivit commerciale di unimpresa; in questo caso la correlazione fra Clienti, che
emettono Ordini, i quali si riferiscono a Prodotti.
Gli RDBMS consentono di definire una correlazione fra due tabelle utilizzando la
chiave primaria di una tabella (che la tabella genitore o master) e una colonna,
chiamata chiave esterna, dellaltra (la tabella figlia).
Le relazioni che si stabiliscono in questo modo fra due tabelle possono essere di tre
tipi: uno a uno, uno a molti e molti a molti.

Relazione uno a uno


Due tabelle sono in una relazione uno a uno se, per ciascuna riga della tabella master,
esiste non pi di una sola riga nella tabella figlia.
Nella realt organizzativa difficilmente si danno casi di relazioni uno a uno fra
famiglie di entit, ma nella pratica operativa dei database una relazione di questo tipo
pu essere creata deliberatamente per tenere separati su due tabelle attributi diversi
riferiti alle stesse entit. il caso, per esempio, delle tabelle schematizzate qui di seguito,
che contengono dati clinici sui pazienti e sono in una relazione uno a uno stabilita, in
questo caso, sulla chiave primaria di entrambe le cartelle, che nella seconda funge anche
da chiave esterna. La tabella CartelleCliniche pu essere esaminata e aggiornata con dati
su visite e analisi, senza accedere ai dati che identificano il paziente, conservati nella
tabella Pazienti.
CartelleCliniche Pazienti
CodicePaziente CodicePaziente Chiave primaria
DataNascita Nome
DataRicovero Cognome
Sesso TesseraSanitaria
VES Indirizzo
ECG Citt
PSA Provincia
Bilirubina

Organizzando i dati in questo modo, si possono stabilire per il database regole di


accesso diverse per la tabella CartelleCliniche rispetto alla tabella Pazienti, anche se i
record sono riferiti alle stesse persone.

Relazione uno a molti


Due tabelle sono in una relazione uno a molti quando, per ciascuna riga della prima
tabella, possono esserci zero, uno o molti record nella seconda, ma per ciascun record
della seconda esiste uno e un solo record nella prima.
Diversamente dal caso precedente, quello della relazione uno a uno, la relazione uno a
molti assai frequente nella realt organizzativa, si pu anzi dire che sia la norma, ed
quindi importante capire in che modo si pu stabilire questa relazione fra due tabelle in
un database relazionale. Vediamo un semplice esempio.
Vogliamo completare la tabella Indirizzi che abbiamo visto in precedenza,
aggiungendo un campo per il numero di telefono a quelli gi definiti, cio
IDPersona chiave primaria
Cognome
Nome
Indirizzo
Citt
CAP
Provincia

Ci accorgiamo, per, esaminando la rubrica cartacea dalla quale trascriviamo gli


indirizzi, che alcune persone hanno un solo numero di telefono, altre ne hanno due (fisso
e cellulare, o fisso di casa e fisso dellufficio), altre tre (fisso di casa, fisso dellufficio,
cellulare) e altre ancora arrivano a quattro, aggiungendo agli ultimi tre anche il numero
del telefono fisso della casa al mare o in montagna. Per tener conto di tutte le possibilit
dovremmo aggiungere quattro colonne alla tabella:
TelefonoCasa
TelefonoUfficio
TelefonoCellulare
TelefonoSecondaCasa

ma non sarebbe una soluzione corretta, perch molte righe avrebbero due o tre
colonne vuote e potrebbe poi sempre capitare il caso di una persona che di numeri di
telefono ne ha addirittura cinque o sei.
La soluzione consiste nellaggiungere al database una seconda tabella, che chiamiamo
Telefoni, che pu essere strutturata in questo modo:
CodiceTelefono chiave primaria
Raccordo chiave esterna
NumeroTelefono

Nella tabella Telefoni il campo CodiceTelefono la chiave primaria, mentre il campo


Raccordo la chiave esterna e contiene un valore identico a quello del campo
IDPersona, che la chiave primaria della tabella Indirizzi.
Ogni persona elencata nella tabella Indirizzi pu avere uno o pi numeri di telefono,
registrati nella tabella Telefoni. La corrispondenza fra i record delle due tabelle data dal
fatto che il valore della chiave primaria di Indirizzi viene utilizzato (mettendo lo stesso
valore nel campo Raccordo) per identificare con certezza a chi appartiene ogni singolo
numero di telefono.
Quando due tabelle sono legate da una relazione uno a molti, si collocano in uno
spazio ideale, dove la tabella master sta a sinistra (detto lato uno della relazione) e la
tabella figlia si trova a destra (detto lato molti della relazione).
Non obbligatorio che a tutti i record della tabella master corrisponda un record nella
tabella figlia. In termini concreti, potremmo avere nella nostra tabella Indirizzi nomi di
persone che non hanno telefono (o delle quali non conosciamo il numero di telefono).
invece tassativamente obbligatorio che nella tabella che si trova dal lato molti della
relazione ogni record sia correlato (tramite la sua chiave esterna) a uno e un solo record
della tabella che sta nel lato uno (distinto dalla sua chiave primaria). Nel nostro esempio,
non avrebbe senso avere un record nella tabella Telefoni che non corrisponda a un
record nella tabella Indirizzi.
Lo schema della relazione uno a molti riportato nella Figura 2.1.

Figura 2.1 Una relazione uno a molti.

Relazione molti a molti


Si ha questo tipo di relazione quando si aggregano in tabelle informazioni su realt
che per loro natura sono caratterizzate da una molteplicit di relazioni logiche e
pratiche. il caso, per esempio, dei libri scientifici e dei corsi universitari. Molti
manuali universitari e libri scientifici in genere sono opera di pi autori e gli stessi
autori scrivono anche libri da soli o insieme con altri. Concettualmente simile il caso
degli studenti e dei corsi: ogni studente universitario segue pi corsi, ma non tutti i corsi
sono seguiti dagli stessi studenti.
Per gestire correttamente tabelle che contengono dati di questo tipo titoli e autori,
autori e titoli, oppure studenti e corsi, corsi e studenti che sono correlati con relazioni
molti a molti, necessario scomporre queste relazioni con laiuto di tabelle intermedie,
che fungano da raccordo, in modo da poter rientrare nella casistica delle relazioni uno a
molti. Vediamo un esempio nella Figura 2.2.
In un database utilizzato per gestire una biblioteca abbiamo tre tabelle, Titoli, Autori ed
Editori. La tabella Titoli contiene informazioni sui libri e utilizza come chiave primaria
il codice ISBN, cio lInternational Standard Book Number, che identifica in modo
univoco ciascun libro in circolazione. Questa tabella sta dal lato molti di una relazione
uno a molti con la tabella Editori, dove lo stesso codice ISBN la chiave esterna.
La tabella Autori contiene due sole colonne: ID_Au, la chiave primaria che identifica
univocamente ciascun autore, e Autore, che contiene il nome di ciascun autore.
Dato che fra Autori e Titoli esiste una relazione molti a molti, stata creata una tabella
di raccordo Titolo_Autore, che fa da ponte fra Autori e Titoli, avendo un campo ISBN e
un campo ID_Au per ogni coppia autore-libro.

Figura 2.2 Strutture e relazioni delle tabelle Autori, Titolo_Autore, Titoli ed Editori.

Come si pu vedere dalla figura, che riproduce una schermata da un RDBMS, le


relazioni fra tabelle si rappresentano graficamente come una linea che congiunge la
chiave primaria con la chiave esterna, affiancando il simbolo dellinfinito al nome della
chiave esterna (lato molti) e il numero 1 al nome della chiave primaria (lato uno).
Normalizzazione e forme normali
Le forme normali sono criteri rigorosi, basati sulla teoria degli insiemi e la logica
matematica, per orientare la progettazione delle tabelle di un database relazionale. Gli
studiosi hanno individuato e definito numerose forme normali, che stabiliscono vincoli
per livelli progressivi: una tabella strutturata in modo conforme alla terza forma
normale rispetta anche la prima e la seconda.
Vediamo con un esempio il senso della prima forma normale, in inglese First Formal
Norm, in sigla 1FN. Immaginiamo di aver organizzato una tabella in questo modo:
Nominat ivo Indirizzo
Antonio Rossi via Meravigli, 15 Milano
Pasquale Giordani piazza Garibaldi, 19 Genova
Enrico Ferravilla largo Risorgimento, 54 Torino
Mario Rossi via Uberti, 12 Milano
Anna Giordani piazza Leopardi, 3 Torino
Elena Buscemi corso Pirandello, 12 Agrigento

Le entit rappresentate dalle righe sono persone e le colonne contengono due soli
attributi di ciascuna entit, Nominativo e Indirizzo. Se volessimo disporre le righe di
questa tabella in ordine alfabetico secondo il cognome, non potremmo farlo, perch nel
campo Nominativo il cognome insieme col nome e per di pi viene al secondo posto.
Avremmo difficolt anche a estrarre da una tabella fatta in questo modo le righe delle
persone che abitano in una determinata citt, a Torino, per esempio, o a Milano, perch il
campo Indirizzo mette insieme tutti gli elementi di ogni indirizzo.
Se riorganizziamo i dati in questo modo:
Nome Cognome Indirizzo Cit t
Antonio Rossi via Meravigli, 15 Milano
Pasquale Giordani piazza Garibaldi, 19 Genova
Enrico Ferravilla largo Risorgimento, 54 Torino
Mario Rossi via Uberti, 12 Milano
Anna Giordani piazza Leopardi, 3 Torino
Elena Buscemi corso Pirandello, 12 Agrigento

possiamo ordinare agevolmente le righe in base al cognome, e altrettanto


agevolmente possiamo selezionare soltanto le righe che contengono un determinato
valore, Milano o Torino, per esempio, nella colonna Citt.
Nome Cognome Indirizzo Cit t
Elena Buscemi corso Pirandello, 12 Agrigento
Enrico Ferravilla largo Risorgimento, 54 Torino
Anna Giordani piazza Leopardi, 3 Torino
Pasquale Giordani piazza Garibaldi, 19 Genova
Antonio Rossi via Meravigli, 15 Milano
Mario Rossi via Uberti, 12 Milano

Nome Cognome Indirizzo Cit t


Antonio Rossi via Meravigli, 15 Milano
Mario Rossi via Uberti, 12 Milano
Enrico Ferravilla largo Risorgimento, 54 Torino
Anna Giordani piazza Leopardi, 3 Torino

Cos ristrutturata, la tabella rispetta la 1FN, in base alla quale ogni colonna deve
contenere uno e un solo attributo dellentit rappresentata da ciascuna riga. In altri
termini, il contenuto di ciascun campo deve essere atomico, ovvero non ulteriormente
suddivisibile.
Prendiamo un esempio un po pi complesso. La tabella che segue, creata per
raccogliere dati sulle ore lavorate da ciascun impiegato su vari progetti, non rispetta la
prima forma normale, perch i tre campi Nominativo, Progetto e OreLavorate non sono
atomici.
Mat ricola Nominat ivo Proget t o OreLavorat e
DN-26 Antonio Rossi ALFA-1; ALFA-3; ALFA-4 25; 40; 30
DN-30 Giovanna Trizzi ALFA-1; ALFA-3; ALFA-5 5; 35; 60
DN-35 Marco Esposito ALFA-1; ALFA-2 15; 80
DN-36 Felice Gennari BETA-1 90
DN-40 Elena Bantini BETA-2 75
DN-45 Pasquale Panni ALFA-2; BETA-3 20; 70

Con i dati organizzati in questo modo non possibile distinguere i singoli progetti e
neppure eseguire calcoli sulle ore lavorate per ciascun progetto.
Una riorganizzazione dei dati in forma atomica, quindi nel rispetto della 1NF,
potrebbe essere la seguente:
Mat ricola Cognome Nome P1 OreP1 P2 OreP2 P3 OreP3
DN-26 Rossi Antonio ALFA-1 25 ALFA-3 40 ALFA-4 30
DN-30 Trizzi Giovanna ALFA-1 5 ALFA-3 35 ALFA-5 60
DN-35 Esposito Marco ALFA-1 15 ALFA-2 80
DN-36 Gennari Felice BETA-1 90
DN-40 Bantini Elena BETA-2 75
DN-45 Panni Pasquale ALFA-2 20 BETA-3 70
Questa nuova disposizione dei dati rispetta la 1FN, ma lascia molti campi vuoti;
inoltre, se a un impiegato venisse assegnato un quarto progetto, si dovrebbero
aggiungere altri due campi, che resterebbero vuoti in tutti gli altri record. La situazione
simile a quella che abbiamo visto nel paragrafo Relazione uno a molti, quando
abbiamo provato ad aggiungere i numeri di telefono a una tabella di indirizzi.
Una migliore e pi efficiente organizzazione dei dati richiede, in questo caso, la
creazione di due tabelle, entrambe concepite nel rispetto della prima forma normale,
correlate da una relazione uno a molti. Avremo quindi una tabella Dipendenti, come
questa, nella quale la chiave primaria data dal campo Matricola.
Mat ricola Cognome Nome
DN-26 Rossi Antonio
DN-30 Trizzi Giovanna
DN-35 Esposito Marco
DN-36 Gennari Felice
DN-40 Bantini Elena
DN-45 Panni Pasquale

Una seconda tabella, che possiamo chiamare AvanzamentoProgetti, utilizza il campo


Matricola come chiave esterna, che la correla con la tabella Dipendenti.
Mat ricola Proget t o OreLavorat e
DN-26 ALFA-1 25
DN-26 ALFA-3 40
DN-26 ALFA-4 30
DN-30 ALFA-1 5
DN-30 ALFA-3 35
DN-30 ALFA-5 60
DN-35 ALFA-1 15
DN-35 ALFA-2 80
DN-36 BETA-1 90
DN-40 BETA-2 75
DN-45 ALFA-2 20
DN-45 BETA-3 70

Nella tabella AvanzamentoProgetti si pu utilizzare una chiave primaria composta,


formata dalla coppia di campi Matricola+Progetto.
In sintesi, una tabella rispetta la prima forma normale quando ciascuna colonna
atomica e non vi sono gruppi di colonne ripetute.
Un livello successivo di normalizzazione, la seconda forma normale (Second Normal
Form o 2NF), stabilisce che le colonne di una tabella devono contenere attributi della
sola entit che viene individuata per intero dalla chiave primaria; in altri termini, una
tabella non deve contenere campi con informazioni ridondanti. Supponiamo di aver
bisogno di associare una descrizione a ciascun progetto; potremmo modificare la tabella
AvanzamentoProgetti in questo modo:
Mat ricola Proget t o Descrizione OreLavorat e
DN-26 ALFA-1 Sviluppo sito web 25
DN-26 ALFA-3 Sviluppo grafica 40
DN-26 ALFA-4 Acquisizione immagini fisse 30
DN-30 ALFA-1 Sviluppo sito web 5
DN-30 ALFA-3 Sviluppo grafica 35
DN-30 ALFA-5 Acquisizione immagini dinamiche 60
DN-35 ALFA-1 Sviluppo sito web 15
DN-35 ALFA-2 Nuova interfaccia applicativa 80
DN-36 BETA-1 Documentazione sito web 90
DN-40 BETA-2 Sviluppo XML 75
DN-45 ALFA-2 Nuova interfaccia applicativa 20
DN-45 BETA-3 Ottimizzazione query 70

La tabella cos modificata rispetta la 1FN, ma contiene una colonna ridondante (la
Descrizione) il cui contenuto non dipende dallintera chiave primaria
(Matricola+Progetto), ma dalla sola colonna Progetto. Se la descrizione dei singoli
progetti importante, va gestita con una tabella separata, che potrebbe chiamarsi Progetti
e avere questa struttura:
Proget t o Descrizione
ALFA-1 Sviluppo sito web
ALFA-2 Nuova interfaccia applicativa
ALFA-3 Sviluppo grafica
ALFA-4 Acquisizione immagini fisse
ALFA-5 Acquisizione immagini dinamiche
BETA-1 Documentazione sito web
BETA-2 Sviluppo XML
BETA-3 Ottimizzazione query

La seconda forma normale entra in gioco quando la chiave primaria composta da


pi di una sola colonna. La terza forma normale (Third Normal Form o 3NF) impone un
ulteriore vincolo: nessun attributo pu dipendere da un attributo che non sia la chiave
primaria. Se volessimo associare ai progetti alcune informazioni sui loro responsabili,
laggiunta di un paio di colonne alla tabella Progetti fatta nel modo seguente.
Proget t o Descrizione IDResponsabile Responsabile
ALFA-1 Sviluppo sito web G-1 Bianchini
ALFA-2 Nuova interfaccia applicativa G-2 Gabetti
ALFA-3 Sviluppo grafica G-2 Gabetti
ALFA-4 Acquisizione immagini fisse G-2 Gabetti
ALFA-5 Acquisizione immagini dinamiche G-2 Gabetti
BETA-1 Documentazione sito web G-3 Russo
BETA-2 Sviluppo XML G-1 Bianchini
BETA-3 Ottimizzazione query G-1 Bianchini

violerebbe la 3NF, perch lattributo Responsabile non dipende dalla chiave primaria
Progetto, ma dalla colonna/attributo IDResponsabile. I dati sui responsabili dei progetti
vanno quindi gestiti con una tabella separata, che si colloca dal lato uno di una relazione
uno a molti con la tabella Progetti, in questo modo:
IDResponsabile Responsabile
G-1 Bianchini
G-2 Gabetti
G-3 Russo

La normalizzazione delle tabelle per arrivare al rispetto della terza forma normale
produrrebbe quindi, continuando con lesempio che stiamo illustrando, un database
articolato in quattro tabelle, come si pu vedere dalla Figura 2.3.

Figura 2.3 Linsieme delle tabelle rispetta i vincoli della 3NF.

NOT A
La teoria generale dei database relazionali prende le mosse da un articolo pubblicato da Edgar F. Codd
nella rivista Communications of the ACM, volume 13, numero 6, giugno 1970, intitolato A Relational
Model of Data for Large Shared Data Banks. Labstract dellarticolo molto illuminante, come si pu
vedere qui di seguito, e anticipa la nascita dello Structured Query Language.
Gli utenti futuri di grandi banche di dati non dovranno essere costretti a sapere in che modo i dati sono
rappresentati nella macchina (la loro rappresentazione interna). Un servizio di suggerimenti che fornisca
questo tipo di informazioni non una soluzione soddisfacente. Le attivit deli utenti ai terminali e la
maggior parte dei programmi applicativi non dovrebbero risentire dei cambiamenti delle rappresentazioni
interne e e neppure quando vengono cambiati alcuni aspetti della rappresentazione esterna. Saranno
spesso necessari cambi nella rappresentazione dei dati in conseguenza di cambiamenti nel traffico di
interrogazioni, aggiornamenti e report e della crescita naturale dei tipi di informazioni immagazzinate.
Esistono sistemi per la gestione non inferenziale di dati formattati che forniscono agli utenti file con
strutture ad albero o modelli reticolari dei dati leggermente pi generali. Nella Sezione 1, si discutono le
inadeguatezze di questi sistemi. Vengono introdotti un modello basato su relazioni n_arie, una forma
normale per le operazioni sulle relazioni di una base di dati e il concetto di un sottolinguaggio universale
per i dati. Nella Sezione 2 alcune operazioni sulle relazioni (diverse dallinferenza logica) vengono
discusse e applicate al problema della ridondanza e della coerenza del modello dellutente.
Codd ha continuato a scrivere sullargomento, approfondendo in particolare le forme normali, che con il
suo contributo concettuale e quello di altri studiosi di logica e matematica si sono arricchite di ulteriori
concetti di base, troppo impegnativi per essere descritti in questa sede. I lettori che fossero interessati
allargomento possono leggere con profitto il volume scritto dallo stesso Codd nel 1990 e intitolato The
relational model for database management: version 2 Addison-Wesley Longman Publishing Co., Inc.,
Boston.

Va tenuto presente che le regole previste dalle forme normali sono cumulative:
affinch le tabelle di un database rispettino la 3NF devono soddisfare anche i requisiti
della 2NF e della 1NF.
I teorici dei database relazionali hanno individuato altre forme normali oltre a quelle
che abbiamo illustrato, ma nella prassi comune il rispetto delle prime tre pi che
sufficiente per ottenere un database normalizzato.
Rappresent are le relazioni fra t abelle
Nelle figure precedenti abbiamo utilizzato una semplice ma efficace convenzione grafica per rappresentare
le relazioni fra tabelle in un database: uno spezzone di linea unisce due tabelle collocando la cifra 1
allestremit che tocca la tabella che si trova nel lato uno della relazione e il segno dellinfinito
allestremit che tocca la tabella che sta nel lato molti della medesima relazione.
Questi elementi grafici rappresentano le relazioni fra tabelle presenti nei database di Access, uno strumento
per creare e gestire database relazionali che fa parte del pacchetto Office di Microsoft.
Anche in MySQL e in SQL Server, i due sistemi per la gestione di database che utilizziamo a scopo
dimostrativo in questo libro, disponibile uninterfaccia grafica che rappresenta in modo simbolico le
relazioni fra tabelle, come possiamo vedere nella figura che segue.
Nella progettazione di tabelle e database, laiuto concettuale che deriva dalle forme
normali importante, ma lorientamento principale deve venire da unaccurata analisi
dei risultati che si vogliono ottenere; un database non un fine in s, ma uno strumento
per gestire agevolmente dati che si utilizzano per produrre o vendere merci, per
esempio, o per amministrare personale, impianti, magazzini e risorse in genere: in una
tabella con righe che rappresentano impiegati, una colonna che contiene lattributo Et
pu rispettare tutte e tre le principali forme normali, ma i dati in quella colonna saranno
privi di valore un anno dopo essere stati inseriti; meglio, in questo caso, una colonna
con lattributo DataDiNascita, i cui valori possono essere utilizzati in seguito per
ricavare correttamente, con una semplice operazione aritmetica, let di ciascun
impiegato ogni volta che serve.
Prendendo spunto da questo semplice esempio, bene evitare di inserire colonne che
contengono valori calcolati. In una tabella con situazioni di magazzino come quella che
vediamo qui di seguito.
Codice Descrizione Quant it ValoreUnit ario ValoreTot ale
BZ05 Bullone zincato 5x10 5.000 0,15 750,00
BZ06 Bullone zincato 6x10 6.000 0,16 960,00
BZ08 Bullone zincato 8x10 4.500 0,18 810,00
BZ10 Bullone zincato10x12 2.800 0,20 560,00
BZ12 Bullone zincato 12x18 3.000 0,22 660,00
RP52 Rondella piana 5x2 9.000 0,05 450,00
RP53 Rondella piana 5x3 9.000 0,05 450,00
RP54 Rondella piana 5x4 8.000 0,05 400,00
RP55 Rondella piana 5x5 7.000 0,06 420,00
RP56 Rondella piana 5x6 5.000 0,07 350,00
DZ05 Dado esag zinc 5 5.500 0,15 825,00
DZ06 Dado esag zinc 6 6.500 0,16 1.040,00
DZ08 Dado esag zinc 8 5.000 0,18 900,00
DZ10 Dado esag zinc 10 3.000 0,20 600,00
DZ12 Dado esag zinc 12 3.200 0,22 704,00

la colonna ValoreTotale in contrasto con la terza forma normale, perch il suo


contenuto dipende da quello delle colonne Quantit e ValoreUnitario, e questo gi ne
giustificherebbe leliminazione. Ma a parte questa considerazione formale, la sua
presenza rende la tabella difficile da gestire, perch i contenuti andrebbero ricalcolati
ogni volta che interviene una modifica nella quantit o nel valore unitario: meglio,
quindi, eliminarla del tutto e generare per altra via il valore totale ogni volta che serve;
vedremo pi avanti come si eseguono calcoli di questo tipo con gli strumenti dello
Structured Query Language.
Le interfacce utente
I due sistemi per la gestione di database relazionali che abbiamo presentato allinizio
di questo libro MySQL e SQL Server sono dotati di numerose funzionalit che poco
hanno a che fare con il linguaggio SQL in senso stretto, ma sono concepite per dare
servizi con i quali integrare i database creati con il linguaggio SQL in un contesto di
gestione aziendale che pu essere anche molto complesso, articolandosi eventualmente
in molteplici aggregazioni di server e di client.
La presenza di tutte queste funzionalit per integrare database creati con SQL in un
contesto di sistemi informatici per la gestione aziendale spiega perch le due interfacce
utente, Workbench di MySQL e Management Studio di SQL, contengano un numero cos
elevato di barre degli strumenti, finestre di dialogo e altro ancora.
Tutta questa strumentazione non rilevante ai fini del nostro libro, dedicato com
alluso di SQL in riferimento a grandi basi di dati e allelaborazione con Excel dei
risultati che si possono ottenere con questo linguaggio.
Per evitare confusioni e distrazioni, quindi, conviene impostare il modo in cui si
presentano le due interfacce utente che abbiamo a disposizione procedendo in questo
modo.

MySQL Workbench
Aprire la finestra dellapplicazione da Windows 10, che potrebbe presentarsi articolata
in tre finestre di dialogo sormontate da una barra degli strumenti, come possiamo vedere
nella Figura 2.4.
Figura 2.4 La schermata iniziale di MySQL Workbench presenta tre finestre, una barra dei menu e una barra
degli strumenti.

Notiamo una finestra che si sviluppa in verticale occupando tutta la fascia di sinistra
della schermata e intitolata Navigator; alla destra di Navigator presente, in posizione
centrale, una finestra chiamata Query 1, suddivisa in due sezioni, quella inferiore si
chiama Output; sulla destra, infine, compare una terza finestra intitolata SQLAdditions.
Allestremit destra della barra degli strumenti sono presenti quattro pulsanti di
comando:

Un clic sul primo pulsante, che ha licona di un volante, fa aprire una articolata
finestra di dialogo intitolata Workbench Preferences, che come il suo nome lascia
intuire d la possibilit di intervenire sugli aspetti e il modo di operare dello strumento
che stiamo usando: opportuno chiudere questa finestra accettando tutte le impostazioni
predefinite.
I tre pulsanti di comando che seguono sono dei commutatori: un clic apre (se chiusa)
e chiude (se aperta) la finestra di dialogo che corrisponde allimmagine riportata su
ciascun pulsante.
Il suggerimento che diamo agire su questi pulsanti in modo che compaia soltanto la
finestra chiamata Query 1, suddivisa in due fasce orizzontali, come nella Figura 2.5.

Figura 2.5 La finestra di lavoro preferenziale di MySQL Workbvench.

SQL Server Management Studio


Allapertura questa interfaccia presenta una sola finestra spostata verso il lato sinistro
e chiamata Esplora oggetti, nella quale sono rappresentati i database esistenti, con la
stessa convenzione grafica che si usa in Esplora risorse di Windows per rappresentare le
cartelle in un disco rigido.
Sopra questa finestra campeggiano una barra dei menu e una barra degli strumenti.
Le finestre che compaiono in questa interfaccia si possono chiudere con un clic sul
pulsante contrassegnato da una X allestremit destra del loro titolo.
Conviene chiudere in questo modo la finestra Esplora oggetti e facendo scendere le
opzioni disponibili dal menu Visualizza far aprire la finestra Output, che si andr a
collocare nella parte bassa della finestra principale, occupandola per intero.
Premendo il pulsante di comando Nuova query che si trova nella barra degli strumenti
si ottiene lapertura della finestra di lavoro che ci interessa, ottenendo un risultato che
vediamo nella Figura 2.6.
Figura 2.6 Linterfaccia utente Management Studio di SQL Server pronta per ricevere ed eseguire comandi
SQL.

Dopo aver predisposto nel modo appena descritto le interfacce degli strumenti di
lavoro possiamo procedere adesso a studiare come si usa in concreto lo Structured
Query Language.
Capitolo 3
Database e tabelle

Lo standard dello Structured Query Language non prevede un comando per creare un
database; la circostanza pu sembrare curiosa o strana, ma ha una sua logica: questo
linguaggio uno strumento per creare, gestire e utilizzare tabelle, il database loggetto
software che le contiene e non fa parte delle finalit di SQL definire le caratteristiche e il
funzionamento di questo contenitore. Dato, per, che un database deve pur esistere, tutte
le implementazioni di SQL prevedono almeno un comando per creare un nuovo database
vuoto e qualche altro comando per gestire database esistenti considerati come oggetti
singoli.
Lesecuzione dei comandi per creare database sia in MySQL sia in SQL Server genera
una struttura di dati pi o meno complessa, il cui schema pu essere esaminato
direttamente nella finestra Esplora oggetti del Management Studio di SQL Server e nella
finestra Navigator del Workbench di MySQL dopo aver selezionato in questa la scheda
Schemas. Nella Figura 3.1 vediamo un esempio delle diverse rappresentazioni dei
database nei due sistemi.
Quando si crea un database MySQL in ambiente Windows viene semplicemente creata
una cartella vuota, al cui interno si inseriranno file particolari generati dai comandi per
creare le tabelle; lavorando con SQL Server, il comando di creazione di un database
genera strutture di dati pi complesse di una semplice cartella Windows. comunque
sconsigliabile andare a frugare nel disco di sistema del proprio computer per
individuare ed eventualmente esaminare i file dei database e i loro componenti, perch si
rischierebbe di devastare le strutture dei dati senza ricavarne alcun vantaggio.
Figura 3.1 La rappresentazione grafica delle strutture dei database presenti in MySQL e in SQL Server.
Convenzioni per la descrizione della sintassi
Come tutti i linguaggi di programmazione, lo Structured Query Language ha un
lessico e una sintassi, che consistono luno di un centinaio di parole chiave e laltra di
qualche decina di regole per comporre con tali parole chiave e opportune stringhe di
caratteri le istruzioni per usare i database.
Le parole chiave si possono scrivere indifferentemente in maiuscolo o in minuscolo,
in tutte le implementazioni di SQL: per chiarezza, utilizzeremo le seguenti convenzioni
tipografiche nel descrivere la sintassi:
le parole chiave sono in MAIUSCOLO :
CREATE TABLE

gli elementi sui quali agiscono i comandi, in generale nomi di tabelle e di colonne,
sono scritti in corsivo:
CREATE TABLE nome_tabella

un elemento facoltativo, parola chiave o nome, racchiuso fra parentesi [ quadre ]:


CREATE DATABASE [IF NOT EXISTS] nome_database

quando unistruzione pu utilizzare uno fra pi elementi in alternativa, questi sono


separati da barre | verticali |; quando facoltativo scegliere uno fra gli elementi
disponibili in alternativa, lelenco racchiuso fra parentesi [ quadre ]:
DROP TABLE nome_tabella [RESTRICT | CASCADE]

quando obbligatorio scegliere uno fra pi elementi disponibili in alternativa,


lelenco racchiuso fra parentesi { graffe }:
UPDATE { nome_tabella | nome_vista}

In diverse istruzioni un elemento pu essere ripetuto pi volte; questa caratteristica


viene rappresentata tipograficamente in questo modo:
col1, col2,

Quando in uno schema sintattico sono presenti parentesi tonde e virgole, questi
caratteri fanno parte dellistruzione e quindi vanno scritti come indicato, mentre le
barrette verticali, le parentesi quadre e le graffe non si devono scrivere.
Una istruzione SQL pu essere molto lunga, sviluppandosi su pi righe, che possono
andare a capo in qualunque punto in cui lo schema sintattico prevede uno spazio, mentre
le parole chiave e i nomi degli elementi non si possono dividere con un a capo.
Lo standard SQL prevede che ogni istruzione debba terminare con il carattere punto e
virgola, ma quasi tutte le implementazioni del linguaggio comprese MySQL e SQL
Server hanno uninterfaccia che accetta le istruzioni anche se non sono chiuse da un
punto e virgola.
Dimensioni delle t abelle
Lo standard SQL non impone vincoli sulle dimensioni delle tabelle e sul numero delle loro colonne, ma le
specifiche implementazioni del linguaggio fissano limiti sia al numero di colonne che possono comporre
una riga, sia alle dimensioni delle singole colonne. Nel caso degli strumenti di cui ci occupiamo, i limiti sono
praticamente infiniti, perch le tabelle di questi RDBMS sono semplici file creati nel rispetto dei vincoli del
sistema operativo col quale si sta lavorando.
Nulla impedirebbe, perci, di avere una tabella di un gigabyte, ma una dimensione cos spropositata
creerebbe sicuramente problemi in fase di elaborazione. Inoltre, le informazioni da gestire con un database
vanno sempre organizzate su pi tabelle, ciascuna contenente dati omogenei e che appartengono a una
determinata famiglia: per esempio, una tabella per i clienti, con informazioni su nomi e indirizzi; unaltra per
gli ordini; una diversa tabella per i prodotti che si vendono, con dati su caratteristiche, prezzo e disponibilit
e cos via. Raggruppando i dati in tabelle omogenee e separate, le loro dimensioni dovrebbero risultare
ragionevolmente contenute.
Creare un database
Le istruzioni di SQL si scrivono in una finestra dellinterfaccia utente specializzata per
la composizione delle istruzioni, che chiamata Query in entrambi i sistemi che stiamo
utilizzando; in tale contesto il sistema riconosce le parole chiave quando vengono scritte
e le evidenzia in un colore celeste, lasciando in nero le altre parole che completano
listruzione. Dopo aver scritto il seguente comando:
CREATE DATABASE Prova

diamo lordine di esecuzione, che in SQL Server si attiva premendo il pulsante di


comando Esegui affiancato da un punto esclamativo rosso che si trova nella barra degli
strumenti, mentre in MySQL il pulsante con licona di una saetta subito sotto il bordo
superiore sinistro della finestra Query. Le schermate che otterremo potrebbero
presentarsi come quelle che si vedono nella Figura 3.2.
Figura 3.2 Il comando CREATE DATABASE eseguito in MySQL e in SQL Server.

Nella parte inferiore delle finestre in cui stato eseguito il comando compare una
fascia orizzontale che d informazioni sul risultato del comando appena eseguito. Nel
caso di SQL Server il comando stato eseguito correttamente, mentre in MySQL non
stato possibile creare un nuovo database chiamandolo Prova perch ne esiste gi uno con
quel nome, come spiega con chiarezza il messaggio contenuto nella fascia inferiore
della finestra Query.
Naturalmente se, prima di dare il comando per creare un nuovo database, avessimo
dato unocchiata alle finestre Schemas e Esplora oggetti di Workbench e di Management
Studio avremmo potuto sapere se un database chiamato Prova esisteva oppure no in uno
dei due sistemi.
Si possono avere informazioni sui database esistenti nei due sistemi utilizzando alcuni
comandi che non fanno parte dello standard di SQL. Nel caso di MySQL il comando:
SHOW DATABASES

fa comparire una finestra orizzontale al piede della finestra Query, chiamata Result,
nella quale sono elencati i nomi dei database presenti nel sistema, fra i quali compare
anche il nostro Prova, come possiamo vedere nella Figura 3.3.
Figura 3.3 Lelenco dei database disponibili in MySQL.

Siccome non fanno parte dello standard di SQL, i comandi per visualizzare lelenco
dei database esistenti sono diversi in SQL Server da quello che abbiamo appena visto in
MySQL.
In SQL Server non esiste il comando SHOW, che non fa parte dello standard del
linguaggio SQL. Per avere in SQL Server un risultato simile a quello che abbiamo
ottenuto prima in MySQL possiamo ricorrere a un artificio, dando lordine di elencare i
nomi contenuti in una tabella di sistema che viene utilizzata per gestire i database: questa
tabella si chiama sys.sysdatabases e dopo aver scritto nella finestra Query il comando:
SELECT name FROM sys.sysdatabases

eseguendolo otteniamo il risultato che si vede nella Figura 3.4.


Figura 3.4 Lesecuzione del comando elenca i nomi di tutti i database presenti in SQL Server, compresi quelli di
servizio.

In MySQL si pu facilmente evitare il fastidio di scoprire che il nome di un database


che vogliamo creare esiste gi completando il comando CREATE DATABASE con la clausola IF
NOT EXISTS , in questo modo:
CREATE DATABASE IF NOT EXISTS Prova

Quando viene eseguito, il comando non provoca alcuna segnalazione di errore per
effetto della clausola limitativa.
La sintassi di questo comando molto semplice:
CREATE DATABASE [IF NOT EXISTS] nome_database

Il nome_database pu essere qualunque combinazione di caratteri validi per un nome


di cartella Windows, con esclusione dei caratteri /, \, e . (punto), e una lunghezza
massima di 64 caratteri.
Un comando speculare a quello per creare un database nuovo elimina completamente
un database esistente, cio svuota lintero contenuto della cartella Windows che contiene
il database ed elimina la cartella stessa. La sua sintassi elementare:
DROP DATABASE [IF EXISTS] nome_database

La clausola facoltativa IF EXISTS serve, naturalmente, per prevenire una eventuale


segnalazione di errore. Il comando va dato con cautela, perch la sua esecuzione
irreversibile.
Le due clausole IF NOT EXISTS e IF EXISTS non sono disponibili nella versione SQL Server
del linguaggio SQL.
Per utilizzare un database esistente, bisogna attivarlo, cosa che si ottiene col comando:
USE nome_database
Creare tabelle
Listruzione CREATE TABLE una delle colonne portanti della componente Data Definition
Language di SQL. Lo standard SQL99 per questa istruzione il seguente:
CREATE [GLOBAL TEMPORARY | LOCAL TEMPORARY] TABLE nome_tabella
[ON COMMIT {PRESERVE ROWS | DELETE ROWS}]
(nome_colonna tipo_dati specifiche, [nome_colonna tipo_dati specifiche2,...])
| [LIKE nome_tabella]
| [vincolo_tabella][,...n] ]

Tutte le implementazioni di SQL aggiungono numerose altre opzioni a quelle previste


dallo standard. Qui vedremo nei particolari come si utilizza il comando CREATE TABLE in
MySQL. Per semplicit espositiva procederemo per gradi, presentando prima le
funzionalit di base e in seguito le opzioni pi complesse.
Una tabella deve avere un nome e almeno una colonna, che identificata anchessa
con un nome (diverso da quello della tabella); dato che le colonne sono contenitori per i
dati, per ogni colonna bisogna indicare quale tipo di dato dovr contenere, precisando
anche una o pi specifiche che caratterizzano quel tipo di dato. Quindi, ridotta al suo
schema essenziale, listruzione CREATE TABLE ha questa sintassi:
CREATE TABLE nome_tabella
(nome_colonna tipo_dati specifiche, [nome_colonna tipo_dati specifiche2,...])

Proviamo quindi a creare con MySQL una tabella nel database Prova, che chiameremo
Nomi, articolata su due colonne, Nome e Cognome. Lenunciato necessario per ottenere
questo risultato pu essere il seguente:
CREATE TABLE Nomi
(Nome CHAR (15),
Cognome CHAR (20))

Nellesempio che stiamo vedendo, dopo i due elementi nome_colonna, che valgono
rispettivamente Nome e Cognome, troviamo il tipo_dati, che per entrambi CHAR , una
parola chiave che deriva dallabbreviazione della parola inglese character: con questa
impostazione si indica che la colonna destinata a contenere caratteri di testo (che
saranno considerati come tali anche se fossero numeri).
La specifica, che segue CHAR e va scritta fra parentesi, indica la lunghezza del campo in
caratteri. Il tipo di dato CHAR , infatti, a lunghezza fissa e tale lunghezza va indicata
espressamente nella definizione del tipo di dato. Se la si omette, viene impostata una
lunghezza predefinita.
Eseguiamo lenunciato dalla Query Window e quindi verifichiamo che la tabella sia
stata effettivamente creata eseguendo il comando:
SHOW TABLES
Nella scheda Result compare il nome della sola tabella attualmente presente nel
database Prova (Figura 3.5).

Figura 3.5 Col comando SHOW TABLES si ottiene un elenco delle tabelle presenti nel database attivo.

Il comando SHOW non fa parte dello standard di SQL, ma disponibile con varie forme
in pi implementazioni di SQL. Nel caso di MySQL, pu essere utilizzato, facendolo
seguire da unopportuna parola chiave, per avere un elenco dei database, delle tabelle in
un database, delle colonne di una tabella e cos via. Pu dimostrarsi molto comodo in
situazioni reali, quando si ha bisogno di controllare rapidamente le caratteristiche di un
componente del database, prima di accingersi a creare un enunciato complesso, o
quando si hanno segnalazioni di errore e si vuol controllare di aver utilizzato
correttamente i nomi dei vari elementi.
Possiamo esaminare la struttura della tabella che abbiamo appena creato dando il
comando
DESCRIBE Nomi

Anche il comando DESCRIBE, come SHOW, non incluso nello standard di SQL, ma
disponibile in MySQL per agevolare il controllo delle strutture dei dati. Lesecuzione del
comando visualizza nella scheda Result lo schema che vediamo riprodotto nella Figura
3.6.
Oltre a confermarci quello che gi sappiamo, e cio che la tabella Nomi appena
costruita contiene due campi, Nome e Cognome, entrambi di tipo CHAR e lunghi
rispettivamente 15 e 20 caratteri, il comando DESCRIBE Nomi ci dice che:

nessuno dei due campi un indice (non compare alcuna indicazione nella colonna
Key);
i campi possono contenere valori NULL (YES nella colonna Null);
il valore predefinito (Default) dei campi NULL .

Figura 3.6 Il risultato del comando DESCRIBE Nomi.

Per creare tabelle in un database di SQL Server vale la stessa forma dellistruzione
CREATE TABLE che abbiamo usato in MySQL, quindi, dopo aver attivato col comando USE

Prova il database che ci interessa possiamo scrivere ed eseguire nella finestra Query lo
stesso comando che abbiamo usato in MySQL:
CREATE TABLE Nomi
(Nome CHAR (15),
Cognome CHAR (20))

In SQL Server, per, non sono disponibili i comandi SHOW TABLES e DESCRIBE che abbiamo
visto prima per avere informazioni sulle tabelle e la loro struttura. Si possono utilizzare
in alternativa varie tecniche, ma per il momento accontentiamoci di una molto semplice,
che consiste nel richiamare uno strumento tipico dei sistemi per la gestione di database
relazionali e che si chiama stored procedure: come il suo nome lascia intuire, si tratta
di un documento di testo immagazzinato ovvero salvato da qualche parte nella struttura
del sistema SQL Server e che contiene un certo numero di istruzioni SQL (una
procedura, appunto) predisposte in modo che per eseguirle si deve richiamare il nome di
questo documento facendolo seguire da un parametro. La stored procedure che ci
interessa si chiama sp_columns e per eseguirla basta digitare il suo nome preceduto dal
comando di esecuzione e seguito dal nome delloggetto sul quale deve agire, in questo
modo:
exec sp_columns Nomi

Otterremo subito il risultato che possiamo vedere nella Figura 3.7.

Figura 3.7 Lesecuzione della stored procedure sp_columns visualizza una serie di informazioni sulla tabella
indicata.

NULL
Le colonne di una tabella sono predisposte per ricevere dati di vario tipo (stringhe di caratteri alfabetici o
numerici, numeri interi o decimali, date, ore e altro ancora). Oltre a questi previsto nei database SQL il
dato di tipo NULL (scritto anche Null). Non bisogna farsi trarre in inganno dal nome, pensando che sia uno
spazio vuoto o uno zero. Null vuol dire non definito e quando un campo contiene un valore Null non
possibile elaborare questo contenuto, se non con operazioni logiche per verificare se sia davvero un Null
oppure no. Non neppure pensabile eseguire confronti fra campi con valori NULL, perch ciascun Null
diverso da ogni altro. Se si calcolano valori aggregando pi campi, per esempio si fa una sommatoria o
una media, quelli che contengono valori Null vengono ignorati.
Ma allora, ci si potrebbe chiedere, a che cosa serve e perch esiste? Esiste perch non possibile
generare fisicamente una tabella senza predisporre uno spazio per ciascuna colonna e serve per occupare
lo spazio cos predisposto con un ideale gettone vuoto, in attesa di inserirvi un dato significativo. Se in una
tabella composta da dati sui prodotti un record contiene un valore Null nel campo Prezzo, non vuol dire che
quel prodotto gratis, ma che il suo prezzo non stato ancora definito.

La tabella Nomi che abbiamo appena creato un guscio vuoto, uno schema che pu
contenere due gruppi di caratteri di testo: per lesattezza, 15 per il campo Nome e 20 per
il campo Cognome.
Per inserire dati nella tabella Nomi abbiamo bisogno di unaltra istruzione SQL,
chiamata INSERT, la cui sintassi, nella sua forma pi semplice, :
INSERT nome_tabella VALUES (valore_effettivo, . . .)

dove nome_tabella non ha bisogno di spiegazioni, la clausola VALUES indica che si


intende immettere direttamente i valori nella tabella e valore_effettivo, sono i valori da
scrivere fra parentesi tonde, immediatamente dopo VALUES , uno dopo laltro, separandoli
con virgole.
Trattandosi, nellesempio, di stringhe di caratteri (i campi sono di tipo CHAR ), i valori
vanno scritti fra virgolette doppie o semplici.
Digitiamo quindi questo comando:
INSERT Nomi VALUES ("Antonio", "Rossi")

Lesecuzione del comando provoca soltanto una segnalazione di conferma


dallinterfaccia grafica. Per verificare se effettivamente il primo (e per ora unico) record
della tabella Nomi contiene i dati che abbiamo indicato col comando INSERT, dobbiamo
visualizzare il contenuto della tabella, cosa che si ottiene con il comando SELECT, che il
comando principe di SQL, appartiene allinsieme dei comandi che formano il Data
Manipulation Language (DML) e, come dice il suo nome, serve per selezionare dati da
una tabella. Siccome si utilizza per interrogare un database, un enunciato basato sul
comando SELECT si chiama genericamente query, che in inglese vuol dire interrogazione.
La sintassi base del comando SELECT :
SELECT qualcosa FROM nome_tabella

dove qualcosa una specifica (il nome di un campo, per esempio) di quello che si
vuole estrarre dalla tabella indicata in nome_tabella.
Se si usa un asterisco (*) come valore per qualcosa, si intende tutto il contenuto della
tabella: ed quello che vogliamo ottenere. Digitiamo quindi ed eseguiamo il comando:
SELECT * FROM Nomi
col quale otterremo il risultato che si pu vedere nella Figura 3.8.

Figura 3.8 Il comando SELECT estrae lintero contenuto della tabella Nomi.

Proviamo ora a inserire un nuovo record, ricorrendo allistruzione INSERT formulata


nel modo seguente:
INSERT Nomi
VALUES ("Gianfilippo Aristide Maria", "Di Roncisvalle della Gherardesca")

Anche se abbiamo digitato con cura listruzione senza errori di battitura questa non
viene eseguita e nella fascia inferiore della finestra Query compare la segnalazione di
errore che vediamo riprodotta nella parte superiore della Figura 3.9.
In SQL Server valgono le stesse regole che abbiamo descritto per creare campi e
inserire valori in una tabella MySQL, per cui il tentativo di inserire nei campi stringhe di
lunghezza superiore a quella definita con i parametri CHAR provoca lo stesso errore, che
viene segnalato nella parte inferiore della Figura 3.9.
Figura 3.9 Le due diverse segnalazioni di errore da parte di MySQL e di SQL Server per lo stesso errore.
I tipi di dati per le colonne
Una tabella in MySQL pu contenere un massimo di 4096 colonne, con un ulteriore
limite di 65.535 byte per riga.
Se non si vuole correre il rischio di perdere informazioni quando si inseriscono dati
in una tabella, necessario scegliere con molta attenzione il tipo dei dati.
Un campo destinato a contenere una stringa di caratteri in una tabella SQL pu
contenere da 1 a 255 caratteri. Quando lo si definisce come tipo CHAR , specificando una
lunghezza, questa sar la lunghezza massima effettiva e il campo occuper sempre lo
stesso spazio su disco, anche quando contiene una stringa di lunghezza inferiore. In altri
termini, i campi di tipo CHAR sono a lunghezza fissa.
Esiste un altro tipo di dato per memorizzare stringhe di caratteri, chiamato VARCHAR , che
utilizza meglio lo spazio su disco. Nellassegnare un tipo VARCHAR a un campo, si specifica
una lunghezza, che non viene considerata fissa, ma rappresenta un limite superiore. Cio,
i campi di tipo VARCHAR sono a lunghezza variabile. In pratica, un campo definito come
CHAR(50) occuper sempre 50 caratteri su disco, che ne contenga uno o 50, mentre un
campo VARCHAR(50) impegner su disco soltanto lo spazio effettivamente richiesto dai dati
che contiene, fino al limite superiore di 50 caratteri
Se prevediamo di dover inserire occasionalmente stringhe molto lunghe nei campi
Nome e Cognome della nostra tabella Nomi, utilizzeremo meglio lo spazio sul disco
assegnando una dimensione molto grande, diciamo 50 caratteri, ai campi Nome e
Cognome, ma scegliendo il tipo VARCHAR invece del tipo CHAR : in questo modo, nomi e
cognomi occuperanno ogni volta soltanto lo spazio corrispondente alla loro lunghezza
effettiva, quale che sia.
Come si pu facilmente capire, molto importante definire in modo appropriato il
tipo di dato per ciascuna colonna che si crea in una tabella con listruzione CREATE TABLE: la
Tabella 3.1 riepiloga i tipi di dati disponibili in MySQL e in SQL Server.
Tabella 3.1 I tipi di dati utilizzabili nello Structured Query Language.
MySQL e SQL Server
Nome Funzione
BIGINT Numeri interi compresi tra 263 e + 263 1
BINARY Valori binari con lunghezza fissa fino a 8000 byte.
BIT Numeri interi con valore 1 o 0.
CHAR Stringhe di caratteri non Unicode con lunghezza fissa fino a 8000 caratteri
DATETIME Valori per data e ora compresi tra il 1 gennaio 1753 e il 31 dicembre 9999
DECIMAL Valori numerici con scala e precisione fisse, compresi tra -1038 +1 e 1038 1.
FLOAT Valori numerici a virgola mobile compresi tra 1,79E + 308 e 1,79E + 308.
INT Numeri interi compresi tra 231 e 231 1
SMALLINT Numeri interi compresi tra 215 e 215 1
TEXT Stringhe di caratteri non Unicode con lunghezza variabile fino a 231 1 caratteri

TIMESTAMP
Numero univoco in tutto il database che viene aggiornato ogni volta che si aggiorna una
riga.
TINYINT Numeri interi compresi tra 0 e 255
VARCHAR Stringhe di caratteri non Unicode con lunghezza variabile fino a 8000 caratteri
VARBINARY Valori binari con lunghezza variabile fino a 8.000 byte.

Solo SQL Server
Nome Funzione
IMAGE Valori binari con lunghezza variabile fino a 231 1 byte
MONEY Valori monetari compresi tra 263 e 263 1
SMALLMONEY Valori monetari da 214748.3648 a 214748.3647
NCHAR Stringhe di caratteri Unicode con lunghezza fissa fino ai 4000 caratteri.
NTEXT Stringhe di caratteri Unicode con lunghezza variabile fino a 230 1 caratteri
NUMERIC Equivalente a DECIMAL.
NVARCHAR Stringhe di caratteri Unicode con lunghezza variabile fino a 4000 caratteri.
REAL Valori numerici a virgola mobile compresi tra 3,40E + 38 e 3,40E + 38
SMALLDATETIME Valori per data e ora compresi tra il 1 gennaio 1900 e il 6 giugno 2079
UNIQUEIDENTIFIER Identificatore univoco globale (GUID, Globally Unique IDentifier).

Solo MySQL
Nome Funzione
BLOB Valori binari fino a 65535 caratteri
DATE Valori per date da 1000-01-01 (1 gennaio 1000) a 9999-12-31 (31 dicembre 9999).
DOUBLE Valori numerici a virgola mobile di dimensione doppia rispetto a FLOAT
ENUM Elenco di valori (fino a 65535) che possono essere assegnati separatamente
LONGBLOB Valori binari fino a 4 gigabyte
LONGTEXT Valori di testo fino a 4 gigabyte
MEDIUMBLOB Valori binari fino a 16.777.215 caratteri
MEDIUMTEXT Valori di testo fino a 16.777.215 caratteri
SET Elenco di valori (fino a 64) che possono essere assegnati separatamente
TIME Un valore di tempo (ore, minuti e secondi) che va da 838:59:59 a 838:59:59.
TINYBLOB Valore binario fino a 255 caratteri
TINYTEXT Valore di testo fino a 255 caratteri
YEAR Rappresenta, su quattro cifre, un anno compreso fra 1901 e 2155

Esplora ogget t i
La finestra Esplora oggetti del Management Studio di SQL Server offre un comodo promemoria per
individuare i nomi e le funzioni dei tipi di dati. Si ottiene questo servizio facendo clic sul pulsante che sta a
sinistra del nome di un qualsiasi database, nellelenco che scende si fa la stessa operazione sul pulsante a
fianco di Programmabilit e nellulteriore elenco a discesa che si apre si fa clic sul pulsante Tipi, arrivando
cos a un ulteriore livello nel quale sono incolonnati i nomi delle famiglie dei tipi di dati; un clic sul nome di
una di queste famiglie fa uscire lelenco dei nomi dei tipi corrispondenti: quando si porta il puntatore del
mouse su uno di questi nomi, senza fare clic, dopo un istante compare una concisa descrizione della
funzione di quel tipo di dato.

Per alcuni tipi di dati numerici si devono indicare fra parentesi due parametri, il primo
indica la precisione e il secondo la scala. Per esempio
DECIMAL(5,2)

definisce un numero decimale con precisione 5 e scala 2: precisione indica il


numero di cifre significative che verranno memorizzate per i valori, mentre scala
rappresenta il numero di cifre che vengono dopo la virgola decimale; in una colonna
con tipo dati DECIMAL (5,2) i valori possibili vanno da -99,99 a 999,99 (il segno meno
occupa una posizione fra quelle indicate dal parametro precisione). Il tipo di dati
DECIMAL si utilizza per memorizzare importi in denaro, non suscettibili di arrotondamenti,
mentre i vari tipi a virgola mobile (FLOAT, DOUBLE, DOUBLE PRECISION, REAL ) memorizzano
valori numerici potenzialmente molto grandi o molto piccoli con la tecnica della
notazione scientifica, che introduce sempre qualche tipo di arrotondamento.
I tipi di dati BLOB (da Large Binary Object) si riferiscono a file in formato binario, che
normalmente contengono immagini o brani musicali.
Lo standard SQL prevede un tipo di dati Boolean, per conservare valori logici di tipo
Vero o Falso. Questo tipo di dato non esiste in MySQL n in SQL Server e la sua
funzione si ottiene con il tipo TINYINT.

Not azione scient ifica o a virgola mobile


La notazione scientifica si utilizza nei calcoli di ingegneria, astronomia, fisica e di altre scienze per
rappresentare in forma compatta grandezze molto elevate o molto piccole. La notazione scientifica
rappresenta i numeri mediante lo schema
mmmEeee

dove mmm una mantissa, che esprime la parte significativa del numero, E un simbolo ed eee un
esponente utilizzato per indicare lordine di grandezza del numero.
Per ottenere un numero espresso con questo tipo di notazione si moltiplica la parte a sinistra della E per 10
elevato alla potenza espressa dal numero che sta a destra. Se il numero in notazione scientifica contiene
un segno negativo dopo la E, la trasformazione si effettua dividendo il numero che sta a sinistra della E per
10 elevato al numero che sta a destra. Per esempio, 5,83E+5 uguale a 5,83 moltiplicato per 10 alla 5, cio
5,83 moltiplicato per 100.000 ovvero 583.000. Il numero 3,82E-6 rappresenta 3,82 diviso 1.000.000, cio
0,00000382.
La notazione scientifica detta anche rappresentazione a virgola mobile (in inglese floating point) perch
sposta la posizione della virgola decimale in funzione del valore espresso a destra del simbolo E. La
maggior compattezza che si ottiene con questo tipo di rappresentazione risulta evidente quando si osserva
che con questi nove caratteri 5,83E+130 si definisce il numero 583 seguito da 128 zeri.
Nei calcoli che si eseguono su numeri reali espressi con la virgola mobile, quando i numeri sono di tipo
FLOAT, MySQL e SQL Server tengono conto dei decimali fino alla trentottesima posizione; per i numeri di
tipo DOUBLE le posizioni decimali considerate arrivano fino a 308. Si tratta, come si vede, di limiti
estremamente elevati, che ha senso prendere in considerazione soltanto quando si creano tabelle per
memorizzare dati destinati a calcoli scientifici molto raffinati. Nei database che si utilizzano per le normali
applicazioni gestionali non il caso di utilizzare i tipi di dati FLOAT e DOUBLE, ma quando servono numeri
decimali meglio ricorrere al tipo DECIMAL , che consente di conservare dati in forme decimali prestabilite e
senza arrotondamenti.

Creare una tabella complessa


Lesempio della tabella Nomi che abbiamo visto in precedenza servito giusto per
iniziare a prendere confidenza con il comando CREATE TABLE; nella realt i database SQL
contengono di solito tabelle articolate in pi di due colonne, con vari tipi di dati.
Vediamo, quindi, come si presenta un enunciato SQL complesso, per creare una tabella
significativa. La tabella, che chiameremo Movimenti, conterr dati su operazioni
bancarie.
Nella finestra Query digitiamo il seguente enunciato:
CREATE TABLE Movimenti
(Codice INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
Banca CHAR(8) NULL,
Conto CHAR(5) NULL,
Tipo CHAR(3) NULL,
Num CHAR(3) NULL,
Valuta DATETIME NULL,
Importo DECIMAL(8,2) NOT NULL,
Causale VARCHAR(20) NULL,
Descrizione VARCHAR(50) NULL)

Eseguiamo questo comando nella finestra Query del Workbench di MySQL, e


facciamo la stessa cosa nella finestra Query del Management Studio di SQL Server, dopo
aver cambiato nel modo evidenziato in grassetto la seconda riga dellenunciato:
CREATE TABLE Movimenti
(Codice INT IDENTITY(1,5) PRIMARY KEY,
Banca CHAR(8) NULL,
Conto CHAR(5) NULL,
Tipo CHAR(3) NULL,
Num CHAR(3) NULL,
Valuta DATETIME NULL,
Importo DECIMAL(8, 2) NOT NULL,
Causale VARCHAR(20) NULL,
Descrizione VARCHAR(50) NULL)

quindi verifichiamo la struttura della tabella Movimenti che dovrebbe presentarsi


come nella Figura 3.10.
Figura 3.10 La struttura della tabella Movimenti nei due sistemi.

Lenunciato CREATE TABLE che abbiamo utilizzato ha la stessa struttura logica di quello
usato per creare la tabella Nomi, con alcune differenze di contenuto che hanno il
seguente significato:
sono stati definiti nove campi o colonne, chiamati Codice, Banca, Conto, Tipo, Num,
Valuta, Importo, Causale e Descrizione;
il tipo dati dei campi Tipo e Num CHAR , con una lunghezza fissa di tre caratteri;
i campi Banca, Conto, Causale e Descrizione hanno il tipo dati VARCHAR , con
lunghezze massime variabili caso per caso;
il tipo dati del campo Valuta (che una data, la data di valuta delloperazione
bancaria) DATETIME, per il quale non si richiedono indicazioni di lunghezza;
il tipo dati del campo Importo DECIMAL , le indicazioni fra parentesi si riferiscono, la
prima, alla lunghezza complessiva del campo (8 caratteri) e la seconda al numero
dei decimali (2), per cui il numero massimo che pu essere contenuto nel campo
Importo cos definito 999999.99;
tutti i campi ammettono valori NULL , a eccezione di Importo e Codice;
il valore predefinito (Default) per i campi diversi da Importo NULL , mentre 0.00
per Importo.
Per il campo Codice sono state indicate alcune specifiche addizionali nella versione
per MySQL:
Codice INT NOT NULL PRIMARY KEY AUTO_INCREMENT

che trovano questo riscontro nella descrizione:


Field Type Null Key Default Ext ra
Codice int(11) PRI NULL auto_increment

La parola chiave INT il nome di un tipo di dati, Intero (dallinglese integer). Pur non
avendo espresso una lunghezza nel comando, la verifica segnala che sono stati
predisposti 11 caratteri per questo valore. Infatti, un campo con tipo INT pu contenere un
numero intero nellintervallo fra 2147483648 e +2147483647, che pu occupare,
compreso il segno, esattamente 11 caratteri. Se non si usa il segno, un campo INT pu
contenere un valore che va da 0 a 4294967295.
La clausola NOT NULL specifica che per questo campo non sono ammessi valori NULL ; in
termini operativi, nessun record di questa tabella potr avere un valore che non sia un
numero intero nel campo Codice; per questa ragione la cella Null dello schema della
struttura vuota per la colonna Codice.
La clausola PRIMARY KEY stabilisce che il campo Codice avr funzione di chiave primaria
nella tabella. Abbiamo visto il concetto di chiave primaria nel paragrafo Univocit e
chiavi primarie del Capitolo 1.
Nello schema di struttura riportato nella Figura 3.10, una sigla nella colonna Key
segnala che il campo corrispondente nella tabella una chiave, cio un elemento
utilizzabile per accedere pi agevolmente alla tabella e per gestirla. In questo caso, la
sigla PRI segnala che la colonna Codice la chiave primaria della tabella.
Considerata limportanza che assume la chiave primaria, in tutte le implementazioni di
SQL si ha a disposizione una funzionalit, chiamata caso per caso con nomi diversi, che
consente di generare automaticamente un valore numerico distinto e univoco per la
chiave primaria ogni volta che si inserisce un nuovo record. per ottenere questo
servizio che la definizione del campo Codice viene completata con la clausola
AUTO_INCREMENT, che viene indicata come Extra nella descrizione del campo dopo la

creazione della tabella.


Nella versione su SQL Server, vediamo che la finestra Esplora oggetti rappresenta
correttamente il campo Codice come chiave primaria, associandolo allicona di una
chiave Yale.
Proviamo ora a vedere che cosa accade quando si inserisce un nuovo record nella
tabella Movimenti, utilizzando il comando INSERT, che abbiamo gi incontrato nelle
pagine precedenti.
I valori che vogliamo avere nel nuovo record sono i seguenti:
Codice 1
Banca Popolare
Conto 44053
Tipo ver
Num 000
Valuta 2016/10/16
Importo 1671.9
Causale Locazioni
Descrizione Affitto Gandini

Il comando INSERT corrispondente fatto in questo modo:


INSERT Movimenti
VALUES (1, 'Popolare', '44053', 'ver', '000', '2016/10/16', 1671.9, 'Locazioni', 'Affitto Gandini')

Esistono alcuni vincoli da rispettare per immettere dati in questo modo in una tabella
di un database MySQL:
tutti i dati di tipo CHAR o VARCHAR vanno digitati fra virgolette semplici o doppie;
i valori numerici, siano essi di tipo INT o DECIMAL , si scrivono senza virgolette. Il
separatore dei decimali sempre il punto, anche se si lavora con una versione
italiana di Windows, per la quale il separatore dei decimali la virgola;
i dati di tipo DATETIME vanno immessi fra virgolette semplici e rispettando lo schema
AAAA/MM/GG, cio prima lanno (in quattro cifre), poi il mese e infine il giorno.
Dopo aver eseguito il comando INSERT visualizziamo il contenuto della tabella
Movimenti con il comando SELECT * FROM Movimenti, ottenendo una schermata come quella
che si vede nella Figura 3.11.

Figura 3.11 Il nuovo record nella tabella Movimenti.


I dati si ripresentano nello stesso formato col quale sono stati immessi, con alcune
leggere differenze:
tutti i valori di tipo CHAR e VARCHAR sono memorizzati senza virgolette;
il valore Importo, scritto come 1671.9, stato memorizzato nella forma 1671.90, cio
con due decimali, a causa dellindicazione (8,2) associata al tipo dati DECIMAL
attribuito al campo Importo;
la data Valuta ha subito una trasformazione notevole: immessa come 2016/10/16 sta
nel record della tabella senza virgolette e nella forma 2016-10-16 00:00:00. Le tre
coppie di zeri che seguono la data rappresentano i valori hh:mm:ss, cio lorario
espresso in ora, minuti e secondi. Questo eccesso di informazioni determinato dal
fatto che abbiamo scelto il tipo di dato DATETIME (cio data e ora) per il campo Valuta.

probabile che, considerata la natura di questi dati, linformazione aggiuntiva


sullora fornita dal tipo DATETIME non sia necessaria: possiamo modificare questo tipo di
dato della colonna Valuta mediante il comando ALTER TABLE, che pu essere scritto in
questo modo:
ALTER TABLE Movimenti MODIFY Valuta DATE

Dopo aver eseguito questo comando, possiamo visualizzare il contenuto della tabella
e verificheremo che adesso il campo Valuta contiene soltanto la data e non pi le
indicazioni di ora, minuti e secondi.
ALTER TABLE una istruzione SQL molto potente, che nella implementazione MySQL del

linguaggio contiene anche clausole non previste dallo standard: in particolare, la


clausola MODIFY esiste soltanto in MySQL ed derivata da un altro importante RDBMS
chiamato Oracle.
Lo stesso comando in SQL Server ha questa forma:
ALTER TABLE Movimenti ALTER COLUMN Valuta DATE

Proviamo ora a creare un nuovo record utilizzando una forma sintattica diversa del
comando INSERT. Digitiamo:
INSERT Movimenti
(Banca, Conto, Tipo, Num, Valuta, Importo, Causale, Descrizione)
VALUES('Popolare', '44053', 'ass', '457', '2016/10/17', -185.92, 'Acquisto servizi', 'RAS')

Eseguiamo il comando e successivamente visualizziamo il contenuto della tabella


Movimenti, ottenendo il risultato che compare nella Figura 3.12.
Figura 3.12 Un secondo record stato inserito nella tabella Movimenti.

Rispetto alla forma sintattica che abbiamo usato in precedenza:


INSERT nome_tabella VALUES(valore_effettivo, . . . )

la variante del comando INSERT che abbiamo utilizzato ha la forma:


INSERT nome_tabella (nome_campo, . . . ) VALUES(valore_effettivo, . . . )

Questa variante consente di specificare anche un solo nome_campo, fra parentesi tonde
dopo il nome_tabella, indicando un valore_effettivo, fra parentesi tonde dopo la parola
chiave VALUES per ciascun nome_campo indicato in precedenza. Gli elementi nome_campo,
e valore_effettivo, vanno scritti separandoli con una virgola.
Per i campi che non sono indicati, SQL inserisce il valore predefinito, che NULL se
non stato prestabilito un vincolo NOT NULL .
Nellesempio, abbiamo specificato un valore per tutti i campi eccetto il primo, Codice.
Come possiamo vedere dai risultati, trattandosi di un campo caratterizzato come chiave
primaria (quindi NOT NULL per definizione) e definito come AUTO_INCREMENT, ha assunto
automaticamente il valore 2.
Per capire meglio come funziona il meccanismo di AUTO_INCREMENT, eseguiamo le
seguenti operazioni: eliminiamo il record appena inserito e poi reinseriamolo con lo
stesso enunciato INSERT di prima. Digitiamo quindi:
DELETE FROM Movimenti WHERE Codice=2

In questo modo si elimina il record che ha 2 nel campo Codice. Digitiamo ed


eseguiamo di nuovo il comando INSERT:
INSERT Movimenti
(Banca, Conto, Tipo, Num, Valuta, Importo, Causale, Descrizione)
VALUES('Popolare', '44053', 'ass', '457', '2016/10/17', -185.92, 'Acquisto servizi', 'RAS')

Eseguendo successivamente la query SELECT * FROM Movimenti, otteniamo il risultato che


vediamo nella Figura 3.13.
Figura 3.13 Il secondo record ha ora un valore diverso nel campo Codice.

Il valore di un campo definito come PRIMARY KEY e anche come AUTO_INCREMENT viene
ottenuto incrementando di uno il valore massimo utilizzato per quel campo in occasione
dellultimo inserimento e questo anche se il record che ha utilizzato quel valore
massimo non esiste pi.
Nel nostro caso, lultimo inserimento aveva determinato lassegnazione del valore 2
al campo Codice del record inserito. Questo record stato eliminato e poi ne stato
inserito un altro (che per combinazione uguale a quello eliminato, ma il fatto
irrilevante).
Potremmo aspettarci che AUTO_INCREMENT sommi uno al valore di Codice dellunico
record presente (che 1) e quindi assegni 2 allo stesso campo del nuovo record. Non
cos. Essendo una chiave primaria, il campo Codice non pu rischiare di avere duplicati,
quindi AUTO_INCREMENT fa riferimento non allultimo valore esistente, ma allultimo
utilizzato e genera automaticamente un valore diverso da quello (in questo caso, 3). Se
provassimo di nuovo a eliminare il secondo record e poi ne inserissimo ancora una
volta uno, uguale o diverso, il suo campo Codice conterrebbe il valore 4.
In una tabella pu esistere una sola colonna caratterizzata come AUTO_INCREMENT e questa
deve essere una chiave, non necessariamente una chiave primaria. Se provassimo a
eseguire un enunciato come questo:
CREATE TABLE Persone
(Nome CHAR(20),
Cognome CHAR(25),
Successione INT AUTO_INCREMENT)

otterremmo la seguente segnalazione di errore:


Incorrect table definition; There can only be one auto column and it must be defined as a key

Come abbiamo visto sopra, limpostazione del commando CREATE TABLE in ambiente
SQL Server leggermente diversa, vale a dire la riga che definisce e imposta il campo
per la chiave primaria ha questa forma:
nome_campo INT IDENTITY(s, p) PRIMARY KEY

dovuta al fatto che la clausola AUTO_INCREMENT che consente di generare automaticamente


una chiave primaria numerica e univoca non disponibile in SQL Server.
Per ottenere lo stesso risultato ricorriamo a una funzione chiamata:
IDENTITY(s, p)

dove i due valori racchiusi fra parentesi rappresentano rispettivamente il valore


iniziale e il passo di incremento dei successivi valori numerici che verranno generati per
dare univocit alla chiave primaria.
Nella forma che abbiamo usato:
Codice INT IDENTITY(1,5) PRIMARY KEY,

la prima volta che si inserisce un record nella tabella il campo della chiave primaria
avr valore 1 e negli inserimenti successivi il valore della chiave primaria sar
incrementato di 5 rispetto al primo valore disponibile.
La scelta di un valore da dare al parametro p del tutto libera e nulla impedisce di
scrivere la clausola nella forma
IDENTITY (1,1)

ottenendo cos un passo di incremento di 1, come quello che viene generato con la
clausola AUTO_INCREMENT in MySQL.
Naturalmente possibile lavorando sia con MySQL sia con SQL Server utilizzare
per la chiave primaria di ogni nuovo record un codice numerico o alfabetico gi in uso
nel contesto lavorativo in cui si utilizza il database, rinunciando cos allincremento
automatico del codice numerico assegnato alla chiave primaria: in tal caso, la
responsabilit dellinserimento di un valore univoco per la chiave primaria passa dal
computer alloperatore umano, con tutti i rischi che questo comporta.

Chiave primaria composta


Possono esistere situazioni nelle quali le informazioni da gestire con una tabella
contengono elementi che, combinati fra loro, definiscono in modo univoco un record,
costituendo in questo modo una chiave primaria composta, cio formata da pi colonne.
In casi di questo genere, non necessario creare nei record una chiave primaria
separata, eventualmente con un campo qualificato come AUTO_INCREMENT o ricorrendo alla
clausola IDENTITY, ma basta far sapere a SQL quali sono i campi da considerare
collettivamente come chiave primaria.
Per esempio, potremmo avere, come nella tabella che segue, un codice che identifica
il tipo di progetto e un altro codice che identifica la commessa a fronte della quale in
corso un progetto di quel tipo: i codici numerici associati con il codice Tipo permettono
di distinguere in modo univoco le singole attivit:
T ipo proget t o Codice commessa Descrizione Dat a inizio Dat a fine
AU 1 Addestramento in aula 31/05/2016 30/06/2016
AU 2 Addestramento sul campo 30/06/2016 09/08/2016
AU 3 Test di qualificazione 09/08/2016 19/08/2016
SD 1 Sviluppo Database Alfa 31/05/2016 29/08/2016
SD 2 Sviluppo Database Beta 31/05/2016 28/09/2016
AU 4 Assistenza rete 30/06/2016 09/08/2016

Una tabella Progetti, strutturata come nellesempio, si pu creare con il seguente


enunciato SQL:
CREATE TABLE Progetti
(TipoProgetto CHAR(2) NOT NULL,
CodiceCommessa INT NOT NULL,
PRIMARY KEY(TipoProgetto, CodiceCommessa),
Descrizione VARCHAR(30),
DataInizio DATE,
DataFine DATE)

Per creare una chiave primaria articolata su pi campi, la clausola PRIMARY KEY va
utilizzata in un modo diverso da quello che abbiamo visto in precedenza, per la tabella
Movimenti.
Infatti, quando la chiave primaria su un solo campo, la clausola va scritta sulla stessa
riga che definisce il campo; per esempio, con questo enunciato:
Codice INT NOT NULL PRIMARY KEY,

si assegna al campo Codice il ruolo di chiave primaria mentre contestualmente se ne


definisce il tipo dati come INT e si stabilisce che non pu contenere valori Null.
Quando, invece, si vuole avere una chiave primaria su pi campi, la clausola PRIMARY
KEY va scritta separatamente, con lo schema sintattico:
PRIMARY KEY(campo1, campo2, ),

dopo aver definito in precedenza i vari campo1, campo2 e cos via, avendo assegnato
il tipo di dati e specificando per ciascuno il vincolo di NOT NULL .
Possiamo vedere nella Figura 3.14 la struttura della tabella Progetti dopo che stata
creata con lenunciato CREATE TABLE Progetti descritto sopra.
Figura 3.14 La struttura della tabella Progetti nei due ambienti.

Possiamo immettere i dati nella tabella Progetti utilizzando il seguente enunciato:


INSERT INTO Progetti (TipoProgetto, CodiceCommessa, Descrizione, DataInizio, DataFine)
VALUES ('AU',1,'Addestramento in aula','2016/05/31','2016/06/30'),
('AU',2,'Addestramento sul campo','2016/06/30','2016/08/09'),
('AU',3,'Test di qualificazione','2016/08/09','2016/08/19'),
('SD',1,'Sviluppo Database Alfa','2016/05/31','2016/08/29'),
('SD',2,'Sviluppo Database Beta','2016/05/31','2016/09/28'),
('AU',4,'Assistenza Rete','2016/06/30','2016/08/09')

Dopo linserimento dei dati nella tabella Progetti, lesecuzione del comando
SELECT * FROM Progetti

produce il risultato che si pu vedere nella Figura 3.15.


Figura 3.15 La tabella Progetti con i dati.

Come si pu vedere, linterfaccia grafica evidenzia con lo stesso simbolo di una


piccola chiave Yale le due colonne che formano la chiave primaria di questa tabella.
La clausola PRIMARY KEY fa parte dello standard SQL ed disponibile in tutte le
implementazioni di SQL. Lutilizzo che abbiamo descritto qui vale sia per MySQL sia
per SQL Server.
Tabelle e indici
Nel gergo di SQL, il termine chiave o key sinonimo di indice e si riferisce a
uno strumento interno a SQL, che pu essere creato e utilizzato per rendere pi veloci le
operazioni di selezione dei dati dalle tabelle, cio le query.
Vediamo prima come si crea un indice e poi ne approfondiremo il significato.
La tabella Movimenti ha un campo di tipo DATE chiamato Valuta, destinato a contenere
una data. Creiamo un indice su quel campo utilizzando una variante del comando ALTER
TABLE, che pu avere questa forma:
ALTER TABLE Movimenti ADD INDEX Data (Valuta)

Dopo aver eseguito il comando visualizziamo la struttura modificata della tabella


Movimenti, che si presenter come nella Figura 3.16.

Figura 3.16 La struttura della tabella Movimenti evidenzia la presenza di un indice sul campo Valuta.

La clausola che abbiamo utilizzato nellistruzione ALTER TABLE ha la seguente struttura


sintattica in MySQL:
ADD INDEX [nome_indice] (nome_colonna_indice,...)

dove nome_indice facoltativo e nome_colonna_indice il nome della colonna sulla


quale si vuole costruire lindice. possibile costruire un indice composto da pi
colonne, basta indicarle fra le parentesi tonde, separando i nomi con una virgola. Per
esempio, si pu costruire un indice NomeCompleto sulle due colonne Cognome e Nome
di una tabella Nomi, in questo modo:
ALTER TABLE Nomi ADD INDEX NomeCompleto (Cognome, Nome)

Si pu utilizzare un comando specifico per creare un indice, invece di una clausola di


ALTER TABLE. Il comando ha questa struttura sintattica:
CREATE INDEX nome_indice ON nome_tabella (nome_colonna, nome colonna,...)

per cui, con un enunciato come il seguente:


CREATE INDEX NomeCompleto ON Indirizzi (Cognome, Nome)

si ottiene lo stesso risultato generato dallenunciato precedente, che utilizza ALTER TABLE
completato con la clausola ADD INDEX.
Il comando CREATE INDEX si pu usare in MySQL in alternativa ad ALTER TABLE ed
disponibile in SQL Server, dove si pu utilizzare soltanto in questa forma.
Una tabella pu contenere fino a 16 indici e un indice non pu essere pi lungo di 256
caratteri. Non obbligatorio definire un indice per una tabella, per pu essere utile:
vediamo perch.
Quando si esegue una query SQL, con un comando SELECT dove specificato con la
clausola WHERE un criterio di selezione, vengono prese in considerazione tutte le tabelle
indicate nel comando, leggendo sequenzialmente le righe (i record) ed esplorando tutte
le colonne (i campi) per trovare i record che soddisfano il criterio di selezione espresso
dalla clausola WHERE.
Se la tabella contiene un indice e la selezione comporta un confronto su un campo
indicizzato, lesplorazione avviene molto pi velocemente, perch SQL pu posizionarsi
direttamente sul primo record che contiene un valore che soddisfa il criterio di ricerca.
In termini pratici, la query:
SELECT Codice, Importo, Descrizione, Valuta
FROM Movimenti
WHERE Valuta > '2016-10-16'

si esegue in un tempo molto pi veloce se esiste un indice sul campo Valuta, perch
lindice consente a SQL di arrivare in un colpo solo al primo dei record che ha una data
posteriore al 16 ottobre 2016 e di estrarre tutti i successivi, senza dover leggere i
precedenti. La differenza di velocit si pu apprezzare, ovviamente, quando si lavora su
tabelle di una certa dimensione, diciamo da 10.000 record in su, mentre irrilevante nel
caso di tabelle pi piccole.
In termini fisici, un indice una piccola tabella composta da record con due campi: il
primo contiene un valore generato da SQL che identifica univocamente i record della
tabella indicizzata e il secondo contiene il valore (la chiave) del campo sul quale stato
fatto lindice. Il meccanismo di esplorazione attivato da un comando SELECT controlla tutti
i secondi campi di ogni record dellindice e, quando individua una corrispondenza,
utilizza il primo campo per posizionarsi sul record corrispondente della tabella
indicizzata. La creazione di un indice aggiunge quindi una tabella, sia pure di dimensioni
ridotte, al database e ne incrementa le dimensioni.
Quando si modifica il contenuto di una tabella, aggiungendo o eliminando record, gli
eventuali indici definiti su quella tabella vanno rigenerati, operazione che allunga a volte
in modo sensibile i tempi di aggiornamento effettivi di una tabella.
Per queste due ragioni aumento delle dimensioni del database e allungamento dei
tempi di aggiornamento opportuno creare indici soltanto quando i vantaggi che se ne
possono derivare compensano gli svantaggi.
Anche la chiave primaria un indice, caratterizzato dal fatto che assume un valore
diverso, senza alcun duplicato, per ciascun record. Gli altri indici, invece, possono avere
valori duplicati; per esempio, in un indice sul campo Provincia di una tabella di indirizzi
molto probabile che compaiano pi volte valori come MI, PV oppure TO , corrispondenti a
indirizzi di persone che abitano in citt diverse, che appartengono alla stessa provincia.
Nelloutput del comando DESCRIBE, MySQL segnala nella colonna Key la differenza fra
un indice chiave primaria e un indice che ammette duplicati: la sigla PRI indica che il
campo parte di una chiave primaria, mentre la sigla MUL segnala che quel campo una
chiave indice che consente duplicati.

Come funziona un indice


Nella Figura 3.17 vediamo una versione esemplificativa, ridotta a quattro colonne,
della tabella Movimenti.
A lato schematizzata la tabella, composta da due sole colonne, che corrisponde
allindice Data, costruito sulla colonna Valuta della tabella Movimenti. La prima colonna
contiene i valori Codice, che identificano univocamente ciascun record. La seconda
colonna contiene i valori Valuta. Lindice ordinato sulla seconda colonna, per cui i
record si susseguono dalla data pi remota (la minima) alla pi recente (la massima).
Figura 3.17 La tabella Movimenti e lindice Data.

Quando SQL riceve il comando


SELECT * FROM Movimenti
WHERE Valuta = 23/01/2016

si accorge che la tabella ha un indice sulla colonna Valuta e lo esplora, portandosi


direttamente sul record che sta al centro dellindice. Controlla il valore della data e
constata che minore di quella che sta cercando.
Questo vuol dire che la met superiore dellindice pu essere trascurata. Si porta al
centro della seconda met e riesegue il controllo. Anche in questo caso trova una data
antecedente a quella che gli serve.
Sa quindi che il valore che cerca, se esiste, deve trovarsi nellultimo tratto dellindice.
Inizia una lettura sequenziale dei soli record dellultimo segmento dellindice e trova la
data che stata richiesta.
Acquisisce il valore del primo campo del record trovato nellindice e si serve di
questo valore per estrarre direttamente dalla tabella Movimenti il record che soddisfa la
query.
Questo modo di procedere per successive segmentazioni di un indice in due parti si
chiama ricerca binaria e consente di ridurre notevolmente il numero di accessi al disco
necessari per estrarre record da tabelle indicizzate.
La Figura 3.18 tenta di dare una rappresentazione grafica del meccanismo della
ricerca binaria.

Figura 3.18 Il meccanismo della ricerca binaria.

In assenza dellindice Data, SQL in questo caso avrebbe dovuto leggere 12 record per
trovare quello giusto. Con lindice, ha dovuto fare soltanto 4 letture, un risparmio del 75
per cento.
Le implementazioni commerciali di SQL dedicano particolare attenzione alla messa a
punto di algoritmi di ricerca binaria molto raffinati, che riescono a ridurre al massimo
le operazioni di accesso ai dischi, che sono di solito molto onerose in termini di tempo.
Quando lhardware lo consente, gli indici vengono elaborati interamente in memoria,
dove si possono eseguire ripetute segmentazioni binarie a velocit elevatissime, e si
accede al disco soltanto per estrarre i record reperiti.
Il modo in cui vengono realizzate materialmente le funzionalit previste da SQL va al
di l degli obiettivi di questo libro; qui abbiamo fatto uneccezione, data limportanza
pratica, oltre che concettuale, degli indici.

Valori predefiniti
Quando si esamina la struttura di una tabella in MySQL con il comando DESCRIBE
riferendolo a una qualunque tabella, viene presentato uno specchietto come quello che si
pu vedere sopra nella Figura 3.10, che riepiloga alcune informazioni su ciascun campo.
La colonna Default dello specchietto indica il valore predefinito che SQL attribuisce a un
campo quando si inserisce un nuovo record senza specificare un valore per quel campo.
Il valore Default NULL per tutti i campi non numerici e 0 per quelli numerici.
Vediamo un semplice esempio. Con questo enunciato:
INSERT Movimenti (Banca, Conto, Importo)
VALUES ('Popolare', '455', 5000)

inseriamo un nuovo record nella tabella Movimenti specificando espressamente


soltanto il contenuto dei due campi Banca e Conto.
Eseguiamo questo enunciato e subito dopo diamo il comando di selezione di tutti i
record. Otterremo una schermata simile a quella che vediamo nella Figura 3.19.

Figura 3.19 I campi non specificati per il nuovo record hanno valori predefiniti (Default) pari a Null o a zero.

possibile ottenere un valore Default diverso specificandolo espressamente come


clausola di un enunciato CREATE TABLE o ALTER TABLE. Vediamo un esempio utilizzando il
secondo comando.
Digitiamo ed eseguiamo:
ALTER TABLE Movimenti MODIFY Conto CHAR (5) NOT NULL DEFAULT "nnnnn"

In questo modo, se inseriamo un nuovo record nella tabella Movimenti senza


specificare un valore per il campo Conto, invece di Null troveremo la stringa di caratteri
nnnnn.
Verifichiamo il funzionamento di questa modifica, inserendo un nuovo record con due
soli campi definiti:
INSERT Movimenti (Banca, Importo)
VALUES('Popolare', -185.92)
Come si vede nella Figura 3.20, invece di Null troviamo nnnnn nella colonna Conto.

Figura 3.20 Leffetto della definizione di un nuovo valore Default.

Il comando ALTER TABLE prevede una clausola SET DEFAULT pi comoda da utilizzare della
clausola MODIFY che abbiamo visto sopra.
La sintassi di questa variante del comando ALTER TABLE la seguente:
ALTER TABLE nome_tabella ALTER [COLUMN] nome_colonna {SET DEFAULT valore_predefinito | DROP DEFAULT}

dove la parola chiave COLUMN facoltativa, largomento nome_colonna obbligatorio e


deve essere seguito in alternativa da una delle due clausole SET DEFAULT o DROP DEFAULT. La
clausola SET DEFAULT deve essere seguita da un valore_predefinito, cio il valore effettivo
da utilizzare come predefinito, mentre non si deve mettere alcuna indicazione se si
utilizza la clausola DROP DEFAULT, che elimina il valore predefinito esistente.
Se volessimo predisporre un valore diverso da Null per il campo Banca della tabella
Movimenti, un enunciato come questo:
ALTER TABLE Movimenti ALTER COLUMN Banca SET DEFAULT "Popolare"

inserirebbe la stringa di caratteri Popolare nel campo Banca ogni volta che si inserisce
un nuovo record in Movimenti senza specificare un valore per quel campo.
Per eliminare il valore predefinito e ripristinare Null, il comando pu essere:
ALTER TABLE Movimenti ALTER Banca DROP DEFAULT

In SQL Server il comando ALTER TABLE disponibile con le stesse funzionalit e una
sintassi leggermente diversa.
Volendo modificare il valore predefinito per una colonna, quale potrebbe essere la
colonna Tipo nella tabella Movimenti, il comando da utilizzare :
ALTER TABLE Movimenti
ADD CONSTRAINT Vincolo DEFAULT 'aaa' FOR Tipo;

dove Vincolo un nome di fantasia per individuare la modifica. Questo nome da


utilizzare per eliminare la modifica, in questo modo:
ALTER TABLE Movimenti DROP Vincolo;
Modificare tabelle
Le tabelle di un database si possono modificare sia nel loro contenuto, aggiungendo o
eliminando record, sia nella loro struttura, inserendo nuove colonne o eliminando
colonne esistenti, aggiungendo o togliendo indici. anche possibile, come abbiamo
visto in precedenza, modificare il tipo di dati di un campo mediante listruzione:
ALTER TABLE tabella MODIFY campo TIPODATO

dove TIPODATO il nuovo tipo di dato che sostituisce quello esistente di campo.
Le operazioni di inserimento e di eliminazione di record non modificano la struttura
di una tabella e appartengono quindi alla componente Data Manipulation Language
(DML) di SQL, invece che alla componente Data Definition Language (DDL) di cui ci
occupiamo in questo capitolo.
Le modifiche di struttura si eseguono con i comandi ALTER TABLE e DROP , il primo dei
quali prevede numerose clausole e opzioni, mentre il secondo piuttosto semplice e
diretto.
Per leliminazione di una tabella, lo standard SQL prevede questa sintassi per il
comando DROP :
DROP TABLE tabella RESTRICT | CASCADE

La clausola RESTRICT inibisce lesecuzione del comando se esistono connessioni fra la


tabella richiamata nel comando e altri elementi del database, mentre la clausola CASCADE
propaga a cascata leliminazione su tutti gli elementi con i quali la tabella da eliminare
collegata.
Non tutte le implementazioni di SQL rispettano lo standard, in particolare MySQL non
offre le opzioni RESTRICT e CASCADE per la sua versione del comando DROP TABLE, la cui
sintassi pi semplicemente questa:
DROP [TEMPORARY] TABLE [IF EXISTS] tabella1 [, tabella2, . . . ]

dove la clausola IF EXISTS previene una segnalazione di errore se il comando viene


riferito a una tabella che non esiste. Lopzione TEMPORARY limita ulteriormente la portata
del comando alle sole tabelle temporanee. La variante MySQL di questo comando
consente di eseguirlo su un numero illimitato di tabelle, invece che su una sola per volta.
Lesecuzione di DROP TABLE in MySQL estremamente pericolosa, perch provoca
leliminazione senza appello di tutte le tabelle indicate nellenunciato.
Per eliminare un indice, si ha a disposizione il comando DROP INDEX, che in MySQL ha
questa sintassi:
DROP INDEX nome_indice ON nome_tabella
Lo stesso comando disponibile come clausola di ALTER TABLE, nella forma:
ALTER TABLE nome_tabella DROP INDEX nome_indice

per cui possiamo eliminare lindice Data dalla tabella Movimenti con uno o laltro di
questi enunciati:
DROP INDEX Data ON Movimenti
ALTER TABLE Movimenti DROP INDEX Data

Riepilogando, possiamo dire che la parola chiave DROP il killer di SQL. Quando
utilizzata come clausola nel comando ALTER TABLE elimina definitivamente lelemento al
quale si riferisce.
Il comando prevede quattro forme per questa clausola:
DROP DEFAULT
DROP INDEX nome_indice
DROP nome_colonna
DROP PRIMARY KEY

ciascuna delle quali elimina lelemento indicato.


La parola chiave ADD , in un certo senso, linverso di DROP e viene utilizzata come
clausola del comando ALTER TABLE per aggiungere qualcosa.
Dopo aver creato una tabella e aver cominciato a utilizzarla, potremmo accorgerci
che i campi in cui si articolano i record, cio le colonne della tabella, non sono
sufficienti per acquisire le informazioni necessarie.
Possiamo aggiungere una nuova colonna a una tabella esistente utilizzando il
comando ALTER TABLE con la clausola ADD , in questo modo:
ALTER TABLE nome_tabella ADD nome_campo TIPODATO

Supponiamo di aver bisogno di aggiungere unannotazione, un commento, alle righe


della tabella Movimenti.
Il comando che ci serve pu essere formulato in questo modo:
ALTER TABLE Movimenti ADD Commento TEXT

Se, in un secondo tempo, ci accorgessimo che questa nuova colonna dopo tutto
inutile, potremmo eliminarla col comando:
ALTER TABLE Movimenti DROP Commento

Leliminazione di una colonna comporta, naturalmente, la perdita di tutti i dati che


questa colonna contiene.
Si pu cambiare il nome di una tabella, lasciando inalterati struttura e contenuto, con
la clausola RENAME di ALTER TABLE, usando questa sintassi:
ALTER TABLE nome_tabella
RENAME [AS] nuovo_nome_tabella

In SQL Server le operazioni di modifica del nome di una tabella o di una colonna si
possono eseguire agevolmente anche utilizzando linterfaccia grafica disponibile nella
finestra Esplora oggetti di Management Studio: un clic destro sul nome dellelemento da
modificare fa uscire un menu di scelta rapida allinterno del quale si seleziona Rinomina
abilitando cos la modifica diretta da tastiera del nome selezionato, come si vede nella
Figura 3.21.

Figura 3.21 Dalla finestra Esplora oggetti di Management Studio si possono agevolmente modificare i nomi di
database, tabelle e colonne.
Riepilogo della sintassi
Tutte le funzionalit della componente Data Definition Language di SQL sono
riconducibili a due sole potentissime istruzioni, CREATE TABLE e ALTER TABLE, che abbiamo
visto per parti in questo capitolo.
Rispetto allo standard del linguaggio, tutte le implementazioni di SQL esistenti in
commercio hanno aggiunto modifiche, arricchimenti e peculiarit, quindi per creare e
modificare tabelle necessario far riferimento alla specifica sintassi adottata per questi
due comandi dallimplementazione che si utilizza.
Qui di seguito riportiamo lo schema sintattico delle due istruzioni cos come risultano
implementate in MySQL e in SQL Server (i due sistemi per creare e gestire database
relazionali che utilizziamo per sviluppare gli esempi).

Sintassi di ALTER TABLE in MySQL


ALTER [IGNORE] TABLE nome_tabella
[specifica_di_modifica [, specifica_di_modifica] ...]
[opzioni partizione]

specifica_di_modifica

opzioni_tabella

| ADD [COLUMN] nome_colonna definizione_colonna


[FIRST | AFTER nome_colonna ]
| ADD [COLUMN] (nome_colonna definizione_colonna,...)
| ADD {INDEX|KEY} [nome_indice]
[tipo_indice] (nome_colonna_indice,...) [opzione _indice] ...
| ADD [CONSTRAINT [symbol]] PRIMARY KEY
[tipo_indice] (nome_colonna_indice,...) [opzione _indice] ...
| ADD [CONSTRAINT [simbolo]]
UNIQUE [INDEX|KEY] [nome_indice]
[tipo_indice] (nome_colonna_indice,...) [opzione _indice] ...
| ADD FULLTEXT [INDEX|KEY] [nome_indice]
(nome_colonna_indice,...) [opzione _indice] ...
| ADD SPATIAL [INDEX|KEY] [nome_indice]
(nome_colonna_indice,...) [opzione _indice] ...
| ADD [CONSTRAINT [simbolo]]
FOREIGN KEY [nome_indice] (nome_colonna_indice,...)

definizione_riferimento

| ALGORITHM [=] {DEFAULT|INPLACE|COPY}


| ALTER [COLUMN] nome_colonna {SET DEFAULT letterale | DROP DEFAULT}
| CHANGE [COLUMN] vecchio_nome_colonna nuovo_nome_colonna definizione_colonna
[FIRST|AFTER nome_colonna]
| LOCK [=] {DEFAULT|NONE|SHARED|EXCLUSIVE}
| MODIFY [COLUMN] nome_colonna definizione_colonna
[FIRST | AFTER nome_colonna]
| DROP [COLUMN] nome_colonna
| DROP PRIMARY KEY
| DROP {INDEX|KEY} nome_indice
| DROP FOREIGN KEY simbolo_chiave_esterna
| DISABLE KEYS
| ENABLE KEYS
| RENAME [TO|AS] nuovo_nome_tabella
| RENAME {INDEX|KEY} vecchio_nome_indice TO nuovo_nome_indice
| ORDER BY nome_colonna [, nome_colonn
Sintassi di ALTER TABLE in SQL Server
ALTER TABLE [ nome_database . [ nome_schema ] . | nome_schema . ] nome_tabella
{
ALTER COLUMN nome_colonna
{
[ nome_tipo_schema. ] nome_tipo [ ( { precisione [ , scala ]
| max | raccolta_schema_xml } ) ]
[ COLLATE nome_collazione ]
[ NULL | NOT NULL ] [ SPARSE ]

| {ADD | DROP }
{ ROWGUIDCOL | PERSISTED | NOT FOR REPLICATION | SPARSE }
}
| [ WITH { CHECK | NOCHECK } ]

| ADD
{
<definizione_colonna >
| <definizione_colonna_calcolata >
| <vincoli_tabella >
| <definizione_insieme_colonne >
} [ ,...n ]

| DROP
{
[ CONSTRAINT ]
{
nome_vincolo
[ WITH
( <opzione_eliminazione_vincolo_clustered > [ ,...n ] )
]
} [ ,...n ]
| COLUMN
{
nome_colonna
} [ ,...n ]
} [ ,...n ]
| [ WITH { CHECK | NOCHECK } ] { CHECK | NOCHECK } CONSTRAINT
{ ALL | nome_vincolo [ ,...n ] }

Sintassi di CREATE TABLE in MySQL


Il comando CREATE TABLE si articola in tre parti, chiamate:
create_definition (definizione_della_creazione)
table_options (opzioni_della_tabella)
partition_options (opzioni_per_la_partizione)

Le opzioni per la partizione hanno a che fare con le funzioni di server e non con il
linguaggio SQL e si possono trascurare.
La definizione_della_creazione a sua volta si articola in due blocchi: nome_colonna e
definizione_colonna
CREATE [TEMPORARY] TABLE [IF NOT EXISTS] nome_tabella
[(definizione_della_creazione,...)]
[opzioni_della_tabella]
[opzioni_per_la_partizione]
[IGNORE | REPLACE]
[AS] espressione query

CREATE [TEMPORARY] TABLE [IF NOT EXISTS] nome_tabella


{ LIKE vecchio_nome_tabella | (LIKE vecchio_nome_tabella) }

definizione_della_creazione:
nome_colonna definizione_colonna
| [CONSTRAINT [simbolo]] PRIMARY KEY [tipo_indice] (nome_col_indice,...)
[opzione_indice] ...
| {INDEX|KEY} [nome_indice] [tipo_indice] (nome_col_indice,...)
[opzione_indice] ...
| [CONSTRAINT [simbolo]] UNIQUE [INDEX|KEY]
[nome_indice] [tipo_indice] (nome_col_indice,...)
[opzione_indice] ...
| {FULLTEXT|SPATIAL} [INDEX|KEY] [nome_indice] (nome_col_indice,...)
[opzione_indice] ...
| [CONSTRAINT [simbolo]] FOREIGN KEY
[nome_indice] (nome_col_indice,...) definizione_riferimento
| CHECK (espressione)

definizione_colonna

tipo_di_dato [NOT NULL | NULL] [DEFAULT valore_predefinito]


[AUTO_INCREMENT] [UNIQUE [KEY] | [PRIMARY] KEY]
[COMMENT stringa]
[COLUMN_FORMAT {FIXED|DYNAMIC|DEFAULT}]
[STORAGE {DISK|MEMORY|DEFAULT}]
[definizione_riferimento]
| tipo_di_dato [GENERATED ALWAYS] AS (espressione)
[VIRTUAL | STORED] [UNIQUE [KEY]] [COMMENT commento]
[NOT NULL | NULL] [[PRIMARY] KEY]

tipo_di_dato:

BIT[(lunghezza)]
| TINYINT[(lunghezza)] [UNSIGNED] [ZEROFILL]
| SMALLINT[(lunghezza)] [UNSIGNED] [ZEROFILL]
| MEDIUMINT[(lunghezza)] [UNSIGNED] [ZEROFILL]
| INT[(lunghezza)] [UNSIGNED] [ZEROFILL]
| INTEGER[(lunghezza)] [UNSIGNED] [ZEROFILL]
| BIGINT[(lunghezza)] [UNSIGNED] [ZEROFILL]
| REAL[(lunghezza,decimali)] [UNSIGNED] [ZEROFILL]
| DOUBLE[(lunghezza,decimali)] [UNSIGNED] [ZEROFILL]
| FLOAT[(lunghezza,decimali)] [UNSIGNED] [ZEROFILL]
| DECIMAL[(lunghezza[,decimali])] [UNSIGNED] [ZEROFILL]
| NUMERIC[(lunghezza[,decimali])] [UNSIGNED] [ZEROFILL]
| DATE
| TIME[(fsp)]
| TIMESTAMP[(fsp)]
| DATETIME[(fsp)]
| YEAR
| CHAR[(lunghezza)] [BINARY]
[CHARACTER SET nome_insieme_di_caratteri] [COLLATE nome_collazione]
| VARCHAR(lunghezza) [BINARY]
[CHARACTER SET nome_insieme_di_caratteri] [COLLATE nome_collazione]
| BINARY[(lunghezza)]
| VARBINARY(lunghezza)
| TINYBLOB
| BLOB
| MEDIUMBLOB
| LONGBLOB
| TINYTEXT [BINARY]
[CHARACTER SET nome_insieme_di_caratteri] [COLLATE nome_collazione]
| TEXT [BINARY]
[CHARACTER SET nome_insieme_di_caratteri] [COLLATE nome_collazione]
| MEDIUMTEXT [BINARY]
[CHARACTER SET nome_insieme_di_caratteri] [COLLATE nome_collazione]
| LONGTEXT [BINARY]
[CHARACTER SET nome_insieme_di_caratteri] [COLLATE nome_collazione]
| ENUM(valore1,valore2,valore3,...)
[CHARACTER SET nome_insieme_di_caratteri] [COLLATE nome_collazione]
| SET(valore1,valore2,valore3,...)
[CHARACTER SET nome_insieme_di_caratteri] [COLLATE nome_collazione]

Sintassi di CREATE TABLE in SQL Server


CREATE TABLE
[ nome_database . [ nome_schema ] . | nome_schema . ] nome_tabella
[ AS FileTable ]
( { <definizione_colonna > | <definizione_colonna_calcolata >
| <definizione_insieme_colonne > | [ <vincolo_tabella > ] [ ,...n ] } )
[ ON { nome_schema_partizione ( nome_partizione_colonne) | filegroup
| "default" } ]
[ { TEXTIMAGE_ON { filegroup | "default" } ]
[ FILESTREAM_ON { nome_schema_partizione | filegroup
| "default" } ]
[ WITH ( <opzione_tabella > [ ,...n ] ) ]
[ ; ]

<definizione_colonna > ::=


nome_colonna <tipo_di_dato>
[ FILESTREAM ]
[ COLLATE nome_collazione ]
[ SPARSE ]
[ NULL | NOT NULL ]
[
[ CONSTRAINT nome_vincolo ] DEFAULT espressione_costante ]
| [ IDENTITY [ ( base_incremento ) ] [ NOT FOR REPLICATION ]
]
[ ROWGUIDCOL ]
[ <vincolo_colonna > [ ...n ] ]

<tipo_di_dato > ::=


[ nome_schema_tipo . ] nome_tipo
[ ( precisione [ , scala ] | max |
[ { CONTENT | DOCUMENT } ] raccolta_schema_xml) ]

<vincolo_colonna > ::=


[ CONSTRAINT nome_vincolo ]
{ { PRIMARY KEY | UNIQUE }
[ CLUSTERED | NONCLUSTERED ]
[
WITH FILLFACTOR = fattore_riempimento
| WITH ( < opzione_indice > [ , ...n ] )
]
[ ON { nome_schema_partizione ( nome_colonna_partizione)
| filegroup | "default" } ]

| [ FOREIGN KEY ]
REFERENCES [ nome_schema . ] nome_tabella_referenziata [ ( colonna_ref ) ]
[ ON DELETE { NO ACTION | CASCADE | SET NULL | SET DEFAULT } ]
[ ON UPDATE { NO ACTION | CASCADE | SET NULL | SET DEFAULT } ]
[ NOT FOR REPLICATION ]

| CHECK [ NOT FOR REPLICATION ] ( espressione_logica )


}

<definizione_colonna_calcolata > ::=


nome_colonna AS espressione_colonna_calcolata
[ PERSISTED [ NOT NULL ] ]
[
[ CONSTRAINT nome_vincolo ]
{ PRIMARY KEY | UNIQUE }
[ CLUSTERED | NONCLUSTERED ]
[
WITH FILLFACTOR = fattore_di_riempimento
| WITH ( <index_option> [ , ...n ] )
]
[ ON { nome_schema_partizione ( nome_colonna_partizione )
| filegroup | "default" } ]

| [ FOREIGN KEY ]
REFERENCES nome_tabella_referenziata [ ( ref_column ) ]
[ ON DELETE { NO ACTION | CASCADE } ]
[ ON UPDATE { NO ACTION } ]
[ NOT FOR REPLICATION ]

| CHECK [ NOT FOR REPLICATION ] ( espressione logica )


]
<definizione_insieme_colonna > ::=
nome_insieme_colonna XML COLUMN_SET FOR ALL_SPARSE_COLUMNS

< vincoli_tabella > ::=


[ CONSTRAINT nome_vincolo]
{
{ PRIMARY KEY | UNIQUE }
[ CLUSTERED | NONCLUSTERED ]
(column [ ASC | DESC ] [ ,...n ] )
[
WITH FILLFACTOR = fattore_di_riempimento
|WITH ( <opzione_indice > [ , ...n ] )
]
[ ON { nome_schema_partizione (nome_colonna_partizione)
| filegroup | "default" } ]
| FOREIGN KEY
( colonna [ ,...n ] )
REFERENCES nome_tabella_referenziata [ ( rif_colonna [ ,...n ] ) ]
[ ON DELETE { NO ACTION | CASCADE | SET NULL | SET DEFAULT } ]
[ ON UPDATE { NO ACTION | CASCADE | SET NULL | SET DEFAULT } ]
[ NOT FOR REPLICATION ]
| CHECK [ NOT FOR REPLICATION ] ( connessione logica)
}
Capitolo 4
Interrogare i database

Chi arrivato a leggere fino a questo capitolo potrebbe essersi chiesto per quale
ragione ricorrere a un server per database della potenza (e della conseguente
complessit) di MySQL o di SQL Server per costruire tabelle: in fondo tutti abbiamo nei
nostri computer una versione pi o meno recente di Excel che ci permette di creare
agevolmente tabelle (anche molto grandi e impegnative) e dove una volta inseriti alla
rinfusa un po di dati in una colonna possibile ordinarli con un semplice comando,
come si vede nella Figura 4.1.

Figura 4.1 In un foglio di lavoro Excel semplice e intuitivo inserire dati in una tabella e ordinarli.

La domanda legittima e merita una risposta adeguata.


Lidea di fondo dei sistemi di database relazionali o RDBMS quella di tenere
separata la gestione dei dati dalla loro fruizione: i dati, organizzati in tabelle, una volta
creati stanno sempre dove sono stati messi, salvo interventi con il comando ALTER TABLE
che abbiamo visto nel Capitolo 3; per consultare quelle tabelle si crea una copia
momentanea dei soli dati che servono, prelevati nellordine e nella quantit che si
desidera esplorando in modo selettivo le tabelle che interessano.
Grazie a unorganizzazione di questo genere, le applicazioni gestionali che hanno
bisogno di dati possono attingere in modi diversi e coerenti con le loro specifiche
necessit a un unico grande database, senza doversi preoccupare ogni volta di come
gestire i dati che utilizzano, quindi:
le applicazioni per le vendite attingeranno alle tabelle Prodotti, Clienti e Ordini,
le applicazioni per la gestione della produzione si riferiranno alle tabelle Prodotti e
Magazzini e
le applicazioni per la gestione delle risorse umane si avvarranno delle tabelle
Personale, OreLavorate, Commesse
e cos via, ciascuna facendo confluire nelle tabelle che utilizza gli aggiornamenti
specifici nati dalla sua attivit:
consegna dei prodotti,
avanzamento delle commesse,
movimenti di materiali nei magazzini,
calcoli delle ore lavorate per commessa,
liquidazione degli stipendi
e via enumerando.
Tutto questo con le tabelle che si possono costruire con Excel non si potrebbe fare
altrettanto agevolmente.
Per utilizzare i dati delle tabelle se ne fa una copia temporanea, specificando un
criterio di selezione ed eventualmente una modalit di ordinamento: questa copia si
chiama vista e viene generata con listruzione SELECT dello Structured Query Language,
con la quale si formula una domanda (query in inglese) che indica quali dati presentare,
da quali tabelle e come visualizzarli.
Lo schema sintattico standard di questa istruzione il seguente:
SELECT [ ALL | DISTINCT ] elenco_di_selezione
[INTO nuova_tabella]
FROM tabella_origine [, . . . ]
[JOIN condizione_join ]
[WHERE condizione_di_selezione]
[GROUP BY espressione_di_raggruppamento]
[HAVING condizione_di_ricerca]
[ORDER BY criterio_ordinamento [ASC | DESC] ]

SELECT prevede sette clausole, una sola delle quali, FROM , obbligatoria. Per alcune
clausole sono previsti parametri aggiuntivi, specificati con le parole chiave ON, IN e AS . In
SELECT si possono inoltre utilizzare operatori aritmetici, logici e di concatenamento e
funzioni matematiche e statistiche.
Data la complessit e la versatilit dellistruzione SELECT, la esporremo per passi
successivi, approfondendo gradualmente le caratteristiche e le funzionalit di ciascuna
clausola.
Nella Figura 4.2 mostriamo una tabella chiamata Indirizzi che andremo a utilizzare per
molti esempi che seguono in questo capitolo.
Figura 4.2 La tabella di prova Indirizzi.
Forme semplici di SELECT
Nella sua forma pi semplice, che abbiamo gi utilizzato in alcuni esempi del capitolo
precedente, il comando SELECT si compone di due sole clausole:
SELECT elenco_di_selezione
FROM tabella_origine

dove elenco_di_selezione un elenco di colonne da estrarre dalla tabella_origine


specificata nella clausola FROM . Quando si vogliono estrarre tutte le colonne da una
tabella, elenco_di_selezione pu essere sostituito dal carattere asterisco, per cui il
comando:
SELECT * FROM Indirizzi

estrae tutto il contenuto della tabella Indirizzi.


Ast erisco e ALL
Il carattere asterisco non va confuso con il qualificatore ALL , utilizzabile in alternativa a DISTINCT; questi due
qualificatori devono essere sempre seguiti da un elenco_di_selezione, per cui lenunciato
SELECT ALL FROM Indirizzi

sintatticamente sbagliato e genera una segnalazione di errore. La sua forma corretta , invece:
SELECT ALL Prov FROM Indirizzi

ed estrae tutti i valori della colonna Prov della tabella Indirizzi.

Largomento elenco_di_selezione deve contenere almeno un nome di colonna, scelto


fra le colonne contenute nella tabella_origine indicata come argomento della clausola
FROM . Se lelenco_di_selezione contiene pi nomi di colonne, questi vanno separati con

virgole:
SELECT Appellativo, Nome, Cognome
FROM Indirizzi

Lordine con il quale compaiono le colonne nellelenco_di_selezione pu essere


diverso da quello in cui sono disposte le colonne nella tabella_origine e sar quello col
quale verranno forniti i risultati della query; il comando:
SELECT Cognome, Prov
FROM Indirizzi

produce il risultato che possiamo vedere nella Figura 4.3, dove le colonne Cognome e
Prov occupano il primo e il secondo posto, anche se nella tabella Indirizzi si trovano
rispettivamente nellottava e nella quarta posizione.
Figura 4.3 Le colonne di una query sono disposte nellordine indicato nellelenco_di_selezione dellistruzione
SELECT.

I nomi delle colonne selezionate che fanno da intestazione alle righe del risultato di
una query possono essere diversi da quelli presenti nella tabella_origine. Questi nomi
diversi sono detti alias e si ottengono utilizzando la parola chiave AS nella formulazione
della query. Vediamo come utilizzare gli alias.
Nel nostro database abbiamo una tabella Clienti con questa struttura:
Campo Tipo di dato
----- --------------------
CodCli VARCHAR(5)
RagSoc VARCHAR(30)
Indirizzo VARCHAR(35)
Citt VARCHAR(25)
CAP VARCHAR(5)
Prov CHAR(2)
PIVA VARCHAR(11)

I nomi di alcuni campi sono poco comprensibili: desideriamo visualizzare


unintestazione diversa quando estraiamo le colonne corrispondenti.
Digitiamo ed eseguiamo questa query:
SELECT
CodCli
AS 'Codice cliente',
RagSoc
AS 'Ragione sociale',
PIVA
AS 'Partita IVA'
FROM Clienti

Dovremmo ottenere il risultato che vediamo nella Figura 4.4.

Figura 4.4 La query seleziona le colonne CodCli, RagSoc e PIVA, presentandole con unintestazione pi
comprensibile.

consentito mescolare riferimenti con AS a riferimenti ai nomi effettivi in una stessa


clausola SELECT. Per esempio, la query
SELECT
RagSoc
AS 'Ragione sociale',
Citt,
Prov
AS 'Provincia'
FROM Clienti

produrrebbe questo risultato:


Ragione sociale Citt Provincia
---------------------------- ----- ---------
Bertani & C Sas Pavia PV
Carpenteria Audace SpA Pinerolo TO
Cattabeni Impianti Snc Sappada BL
Meccanica Parodi Srl Ivrea TO
Mobilificio Pittaluga Snc Origgio VA
Prefabbricati Anselmo Sas Leno BS
Gli alias compaiono soltanto nel risultato della query, mentre i campi nelle tabelle
mantengono i loro nomi.
Il ricorso allalias pu risultare comodo quando il nome del campo oscuro,
complesso o comunque si preferisce presentarlo in una forma differente. In determinate
circostanze, come vedremo pi avanti, lalias svolge un ruolo fondamentale per
lesecuzione di alcuni tipi di query.
La preposizione AS un qualificatore del comando SELECT, non una clausola. La
clausola SELECT pu utilizzare altri due qualificatori oltre ad AS : ALL e DISTINCT.

ALL serve per specificare che la query dovr estrarre tutti gli elementi indicati, ma in
pratica non si utilizza mai, perch SELECT estrae sempre tutti gli elementi.
Con DISTINCT si specifica, invece, che la query dovr trascurare gli eventuali valori
ripetuti, presentando soltanto valori univoci.
Vediamo un esempio pratico.
La tabella Indirizzi contiene alcuni nomi di persone che risiedono nella stessa
provincia. Vogliamo sapere quante sono le province diverse in cui risiedono le persone
elencate nella tabella.
Possiamo ottenere questo risultato con una query che utilizza il qualificatore DISTINCT e,
per avere un risultato pi chiaro, anche il qualificatore AS , in questo modo:
SELECT DISTINCT
Prov
AS 'Province diverse'
FROM Indirizzi

Vediamo il risultato di questa query nella Figura 4.5.


Abbiamo visto fin qui le forme pi semplici che pu assumere una query, utilizzando
soltanto la sua clausola principale, SELECT. Vediamo ora quali risultati si possono ottenere
ricorrendo anche alla clausola ORDER BY.
Figura 4.5 La query con il qualificatore DISTINCT presenta soltanto valori univoci e il qualificatore
AS ne descrive il contenuto.
La clausola ORDER BY
La struttura sintattica di questa clausola :
ORDER BY criterio_ordinamento [ASC | DESC]

e la si utilizza per assegnare alle righe selezionate dalla query un ordine diverso da
quello col quale si trovano nella tabella di origine:
criterio_ordinamento elenca una o pi delle colonne presenti nella tabella_origine,
specificate direttamente o tramite un loro alias; la o le colonne scelte come
criterio_ordinamento sono dette nel gergo dei database chiavi di ordinamento;
le parole chiave ASC e DESC indicano il tipo di ordinamento e sono abbreviazioni delle
parole inglesi ascending (ordinamento crescente, dal pi piccolo al pi grande) e
descending (decrescente, dal pi grande al pi piccolo); se non viene specificato il
tipo, viene assunto per impostazione predefinita lordinamento crescente.
Riprendiamo lesempio che abbiamo visto nel paragrafo precedente e impostiamo la
query in modo che lelenco delle province diverse compaia disposto in ordine
decrescente. Formuliamo la nuova query in questo modo:
SELECT DISTINCT
Prov
AS 'Province diverse'
FROM Indirizzi
ORDER BY Prov DESC

e otteniamo il risultato che possiamo vedere nella Figura 4.6.


Figura 4.6 Lelenco delle province diverse presentate in ordine alfabetico decrescente.

La clausola ORDER BY nel criterio_ordinamento, pu far riferimento a una o a pi


colonne presenti nella tabella specificata come tabella_origine nella clausola FROM , anche
se non sono indicate nella clausola SELECT.
Quando si specificano pi colonne, la prima colonna detta chiave di ordinamento
primaria, la successiva la chiave di ordinamento secondaria e cos via. Per esempio,
con questa query:
SELECT
Prov,
Cognome
FROM Indirizzi
ORDER BY Prov, Cognome

otteniamo il risultato mostrato nella Figura 4.7.


Come possiamo vedere, la colonna Prov ordinata alfabeticamente e i cognomi dei
residenti sono disposti in ordine alfabetico nelle province che hanno pi residenti (BG,
BS, BZ, CR e MI).
Figura 4.7 Con la clausola ORDER BY si pu ordinare su pi colonne.
Le funzioni
Lo standard SQL prevede un certo numero di funzioni, comandi che hanno la forma:
FUNZIONE(argomento)

e restituiscono un valore eseguendo una specifica operazione (definita dalla parola


chiave che identifica la funzione) in base a uno o pi valori indicati come argomento.
Esistono anche funzioni che restituiscono un risultato senza che si debba fornire un
argomento (perch ne utilizzano uno intrinseco); per esempio, in MySQL la funzione
CURRENT_TIME() restituisce la data e lora attuali ricavandole dallorologio del sistema,

mentre la funzione CURRENT_DATE() fornisce la data odierna, per cui con una query scritta in
questo modo:
SELECT 'Oggi il: ', CURRENT_DATE(), ' e sono le: ', CURRENT_TIME()

si ottiene il risultato che possiamo vedere qui di seguito:

In SQL Server la funzione equivalente GETDATE(), che fornisce data e ora del momento
in cui viene eseguita. Si pu ottenere un risultato simile a quello che abbiamo visto sopra
in MySQL scomponendo il risultato fornito dalla funzione GETDATE() di SQL Server con
laiuto di unaltra funzione, chiamata CONVERT(), in questo modo:
SELECT 'Oggi il: ', CONVERT (date, GETDATE()), ' e sono le: ', CONVERT (time, GETDATE())

Lo standard SQL prevede numerose funzioni, che sono classificate in due grandi
famiglie:
funzioni di aggregazione: operano su un insieme di valori (per esempio, tutta una
colonna) e restituiscono un unico valore riepilogativo; si possono utilizzare
soltanto allinterno di istruzioni SELECT;
funzioni scalari: agiscono su un solo valore (largomento) e restituiscono un valore
basato sullargomento.
Le funzioni previste dallo standard sono poco pi di una ventina, le versioni di questo
linguaggio realizzate con MySQL e SQL Server mettono a disposizione numerose
funzioni non standard, concepite per avere in un unico ambiente di sviluppo anche
funzionalit per la gestione del sistema, che per loro natura non fanno parte dello
Structured Query Language in senso proprio. Nelle Tabelle che seguono riassumiamo le
funzioni disponibili sia in MySQL sia in SQL Server, indipendentemente dal fatto che
siano previste oppure no dallo standard del linguaggio.
Tabella 4.1 Funzioni matematiche. Quelle indicate in corsivo sono disponibili soltanto in MySQL, tutte le
altre sono disponibili sia in MySQL sia in SQL Server.
Nome Descrizione
ABS() Restituisce il valore assoluto
ACOS() Restituisce larco coseno
ASIN() Restituisce larco seno
ATAN() Restituisce larco tangente
ATAN2(), ATAN() Restituisce larco tangente dei due argomenti
CEILING() Restituisce il minor valore intero non inferiore allargomento
CONV() Converte numeri fra basi numeriche differenti
COS() Restituisce il coseno
COT() Restituisce la cotangente
CRC32() Calcola un valore di controllo basato sulla ridondanza ciclica (CRC)
DEGREES() Converte radianti in gradi
EXP() Eleva e (la base dei logaritmi naturali) alla potenza dellargomento
FLOOR() Restituisce il maggior valore intero non pi grande dellargomento
LN() Restituisce il logaritmo naturale dellargomento
LOG() Restituisce il logaritmo naturale del primo argomento
LOG10() Restituisce il logaritmo in base 10 dellargomento
LOG2() Restituisce il logaritmo in base 2 dellargomento
MOD() Restituisce il resto di una divisione
PI() Restituisce il valore di pi greco
POW() Restituisce largomento elevato alla potenza specificata. Sinonimo di POWER( )
POWER() Restituisce largomento elevato alla potenza specificata
RADIANS() Restituisce largomento convertito in radianti
RAND() Restituisce un valore casuale a virgola mobile
ROUND() Arrotonda largomento
SIGN() Restituisce il segno dellargomento
SIN() Restituisce il seno dellargomento
SQRT() Restituisce la radice quadrata dellargomento
TAN() Restituisce la tangente dellargomento
TRUNCATE() Tronca fino al numero di decimali specificato

Tabella 4.2 Funzioni per data e ora. Quelle indicate in corsivo sono disponibili soltanto in MySQL, tutte le
altre sono disponibili sia in MySQL sia in SQL Server.
Nome Descrizione
ADDDATE() Somma valori di ora (intervalli) a un valore data
ADDTIME() Somma valori di ora
CONVERT_TZ() Converte da un fuso orario a un altro
CURDATE() Restituisce la data attuale
CURTIME() Restituisce lora attuale
DATE() Estrae la parte data da unespressione data o data/ora
DATE_ADD() Somma valori di ora (intervalli) a un valore data
DATE_FORMAT() Formatta una data come specificato
DATE_SUB() Sottrae un valore temporale (intervallo) da una data
DATEDIFF() Sottrae due date
DAYNAME() Restituisce il nome del giorno della settimana
DAYOFMONTH() Restituisce il giorno del mese (0-31)
DAYOFWEEK() Restituisce lindice per il giorno della settimana dellargomento
DAYOFYEAR() Restituisce il giorno dellanno (1-366)
EXTRACT() Estrae parte di una data
FROM_DAYS() Converte un numero di un giorno in una data
FROM_UNIXTIME() Formatta la marcatura temporale UNIX come una data
GET_FORMAT() Restituisce una stringa col formato della data
HOUR() Estrae lora
LAST_DAY Restituisce lultimo giorno del mese per largomento
MAKEDATE() Crea una data a partire da anno e giorno dellanno
MAKETIME() Crea un valore di ora in base a ora, minuti, secondi
MICROSECOND() Restituisce i microsecondi dallargomento
MINUTE() Restituisce il minuto dallargomento
MONTH() Restituisce il mese dallargomento data
MONTHNAME() Restituisce il nome del mese
NOW() Restituisce data e ora attuali
PERIOD_ADD() Somma un periodo a un anno-mese
PERIOD_DIFF() Restituisce il numero di mesi fra periodi
QUARTER() Restituisce il trimestre da un argomento data
SEC_TO_TIME() Converte secondi nel formato HH:MM:SS
SECOND() Restituisce il secondo (0-59)
STR_TO_DATE() Converte una stringa in una data
SUBTIME() Sottrae valori di ora
SYSDATE() Restituisce lora di esecuzione della funzione
TIME() Estrae la porzione ora dallespressione indicata
TIME_FORMAT() Formatta come valore di ora
TIME_TO_SEC() Restituisce largomento convertito in secondi
TIMEDIFF() Sottrae valori di ora
TIMESTAMP() Quando ha un solo argomento, questa funzione restituisce la data oppure la data/ora; con
due argomenti, la somma degli argomenti
TIMESTAMPADD() Somma un intervallo a unespressione data/ora
TIMESTAMPDIFF() Sottrae un intervallo da unespressione data/ora
TO_DAYS() Restituisce largomento data convertito in giorni
TO_SECONDS() Restituisce largomento data o data/ora convertito in secondi a partire dallanno 0
UNIX_TIMESTAMP() Restituisce una marcatura temporale UNIX
UTC_DATE() Restituisce la data UTC attuale
UTC_TIME() Restituisce lora UTC attuale
UTC_TIMESTAMP() Restituisce la data e lora UTC attuali
WEEK() Restituisce il numero della settimana
WEEKDAY() Restituisce lindice per il giorno della settimana
WEEKOFYEAR() Restituisce la settimana di calendario della data (1-53)
YEAR() Restituisce lanno
YEARWEEK() Restituisce lanno e la settimana

Tabella 4.3 Funzioni per le stringhe. Quelle indicate in corsivo sono disponibili soltanto in MySQL, tutte le
altre sono disponibili sia in MySQL sia in SQL Server.
Nome Descrizione
ASCII() Restituisce il codice ASCII del carattere pi a sinistra
BIN() Restituisce una stringa che contiene la rappresentazione binaria di un numero
BIT_LENGTH() Restituisce la lunghezza dellargomento in bit
CHAR() Restituisce il carattere per ciascun intero dellargomento
CHAR_LENGTH() Restituisce il numero dei caratteri nellargomento
CONCAT() Restituisce una stringa concatenata
CONCAT_WS() Restituisce una stringa concatenata con separatori
ELT() Restituisce una stringa al numero indice

EXPORT_SET() Restituisce una stringa tale per cui per ogni bit impostato nei bit del valore si ottiene una
stringa on e per ciascun bit non impostato si ottiene una stringa off
FIELD() Restituisce lindice (posizione) del primo argomento negli argomenti successivi
FIND_IN_SET() Restituisce la posizione dellindice del primo argomento entro il secondo argomento
FORMAT() Restituisce un numero formattato per il numero specificato di posizioni decimali
FROM_BASE64() Decodifica su una stringa a base 64 e restituisce un risultato
HEX() Restituisce una rappresentazione esadecimale di una stringa o un decimale
INSERT() Inserisce una sottostringa nella posizione specificata per il numero specificato di caratteri
INSTR() Restituisce lindice della prima occorrenza della sottostringa
LEFT() Restituisce il numero specificato di caratteri da sinistra
LENGTH() Restituisce la lunghezza di una stringa in byte
LIKE Corrispondenza semplice dello schema
LOAD_FILE() Carica il file indicato
LOCATE() Restituisce la posizione della prima occorrenza di una sottostringa
LOWER() Restituisce largomento in caratteri minuscoli
LPAD() Restituisce la stringa dellargomento con laggiunta a sinistra della stringa specificata
LTRIM() Elimina gli spazi vuoti in testa

MAKE_SET()
Restituisce un insieme di stringhe separate da virgole che hanno impostato il bit
corrispondente nellinsieme dei bit
MATCH Esegue una ricerca di corrispondenza su tutto il testo
MID() Restituisce una sottostringa che inizia dalla posizione specificata
NOT LIKE Negazione della corrispondenza semplice dello schema
OCT() Restituisce una stringa con la rappresentazione in ottale di un numero
ORD() Restituisce il codice del carattere per il carattere pi a sinistra dellargomento
QUOTE() Attribuisce un codice di escape allargomento per poterlo usare in un enunciato SQL
REGEXP Ricerca di corrispondenza con uno schema utilizzando espressioni regolari
REPEAT() Ripete una stringa il numero specificato di volte
REPLACE() Rimpiazza le occorrenze di una stringa specificata
REVERSE() Inverte i caratteri in una stringa
RIGHT() Restituisce il numero specificato di caratteri da destra
RPAD() Accoda una stringa il numero specificato di volte
RTRIM() Elimina gli spazi vuoti in fondo

Nelle implementazioni commerciali di SQL (DB2, Oracle, MySQL, SQL Server e via
enumerando) si possono utilizzare tutte le funzioni SQL standard, pi numerosissime
altre (in MySQL sono pi di cento), soprattutto di tipo matematico, che mettono a
disposizione una notevole flessibilit per ricavare valori calcolati dalle tabelle di un
database.
Fanno inoltre parte dello standard di SQL un certo numero di simboli, chiamati
operatori, utilizzati per indicare operazioni aritmetiche o logiche: sono elencati nella
Tabella 4.2.
Tabella 4.4 Operatori aritmetici e logici di SQL.
Simbolo Ut ilizzo
+ Addizione e concatenamento
- Sottrazione
* Moltiplicazione
\ Divisione
= Uguaglianza
<> oppure != Disuguaglianza
< Confronto per minore
> Confronto per maggiore
<= Confronto per minore o uguale
>= Confronto per maggiore o uguale
Vediamo ora con alcuni esempi, come ottenere maggiori informazioni da una query
utilizzando funzioni e operatori aritmetici.
Nei record che compongono la tabella Indirizzi c un campo DataNascita che
contiene la data di nascita della persona. Vogliamo sapere quando cade il compleanno di
ciascuna persona elencata nella tabella. In altri termini, desideriamo conoscere il giorno
e il mese di nascita di ciascuno. Per ottenere questo risultato possiamo utilizzare una
query impostata cos:
SELECT
Nome,
DAYOFMONTH(DataNascita)
AS Giorno,
MONTH(DataNascita)
AS Mese,
DataNascita
FROM Indirizzi

Lesecuzione della query produce il seguente risultato:


Nome Giorno Mese Dat aNascit a
Ivo 9 11 1985-11-09
Luigi 28 6 1961-06-28
Bruna 8 10 1958-10-08
Rino 16 11 1959-11-16
Alfredo 1 9 1968-09-01
Aldo 20 9 1963-09-20
Rosario 26 8 1961-08-26
Enrico 17 6 1981-06-17
Enrica 14 2 1958-02-14
Peter 28 8 1958-08-28
Matteo 4 3 1965-03-04
Argia 28 12 1963-12-28
Franz 5 1 1980-01-05
Angelo 31 8 1969-08-31
Giuditta 6 4 1979-04-06
Franco 30 6 1964-06-30
Alina 7 10 1957-10-07
Ivo 11 1 1971-01-11
Gino 8 10 1959-10-08
Mario 25 6 1964-06-25
Romeo 16 8 1958-08-16
Marina 14 7 1958-07-14
Ugo 26 12 1957-12-26
Olga 15 5 1961-05-15
Walter 5 10 1967-10-05
Hu 20 2 1958-02-20
Hussein 16 11 1967-11-16
Antonietta 2 6 1971-06-02

La query utilizza due funzioni di MySQL DAYOFMONTH() e MONTH() che prendono come
argomento (il riferimento fra le parentesi tonde) il valore della colonna DataNascita,
estraendo rispettivamente il mese e il giorno dalla data contenuta in quella colonna. Le
intestazioni delle due colonne generate dalle funzioni sono state create con il
qualificatore AS .
La stessa query in SQL Server si crea utilizzando la funzione DATEPART(), che accetta
come argomenti i valori day, month e year che estraggono rispettivamente il giorno, il mese
e lanno da un campo con tipo dato DATE fornito come secondo argomento.
SELECT
Nome,
DATEPART(day,DataNascita)
AS Giorno,
DATEPART(month,DataNascita)
AS Mese,
DataNascita
FROM Indirizzi

Proviamo ora a lavorare sulle stringhe, utilizzando la funzione CONCAT, che la


versione MySQL e SQL Server della funzione SQL standard CONCATENATE. La sua struttura
sintattica molto semplice
CONCAT(stringa1, stringa2, [,...n])

Gli elementi da concatenare sono separati da virgole e possono essere valori di


colonne oppure stringhe di caratteri racchiuse fra virgolette semplici. Eseguendo questa
query:
SELECT
CONCAT(Nome, ' ', Cognome)
AS 'Nome completo'
FROM Indirizzi
ORDER BY Cognome

concateniamo i valori delle colonne Nome e Cognome, inserendo fra i due una stringa
formata da un carattere spazio, per tenere separato il nome dal cognome (senza questa
stringa di separazione, otterremmo MarioRossi invece di Mario Rossi). Gi che ci siamo,
ordiniamo le righe in base al campo Cognome. Possiamo vedere il risultato della query
nella Figura 4.8.
Si osservi che la clausola
ORDER BY Cognome

ha funzionato correttamente anche se la clausola SELECT ha utilizzato un alias.


consentito utilizzare una funzione come argomento di unaltra funzione. In casi di
questo genere, SQL sviluppa la funzione annidata ricavando un valore che diventa
argomento della funzione che fa da contenitore. Con questa query:
SELECT
Nome,
Cognome,
CONCAT(DAYOFMONTH(DataNascita), " - ", MONTHNAME(DataNascita))
AS Compleanno,
DataNascita
FROM Indirizzi
ORDER BY Cognome

utilizziamo la funzione MONTHNAME(), che specifica di MySQL e, come si pu vedere


nella figura, ricava il nome (in inglese) del mese dallargomento di tipo Date che le viene
fornito (DataNascita, nel nostro caso).
La funzione CONCAT() concatena le stringhe generate dalle due funzioni DAYOFMONTH() e
MONTHNAME(), separandole con un trattino, come possiamo vedere dalla Figura 4.9.

Figura 4.8 La colonna alias Nome completo aggrega le due colonne Nome e Cognome.
Figura 4.9 La colonna alias Compleanno aggrega due valori ricavati dalla colonna DataNascita.

Lo stesso risultato si ottiene in SQL Server utilizzando questo enunciato:


SELECT
Nome,
Cognome,
CONCAT(DATEPART(day,DataNascita),' - ',DATENAME(month,DataNascita))
AS Compleanno,
DataNascita
FROM Indirizzi
ORDER BY Cognome

In SQL Server, lequivalente di MONTHNAME() di MySQL la funzione DATENAME() che prende


due argomenti, il nome (in inglese) della componente di data e il nome del campo che
contiene una data. Se SQL Server installato con lopzione della lingua italiana, il
risultato della funzione DATENAME() in italiano, quindi Novembre invece di November, se il
campo al quale si riferisce di tipo DATE e contiene il valore 11.
Proviamo ora a vedere un utilizzo pi interessante dellannidamento di funzioni, cio
di una funzione che ne utilizza unaltra come suo argomento, o, pi precisamente,
acquisisce come argomento il risultato di unaltra funzione.
Vogliamo sapere quanti anni hanno, alla data di oggi, le persone elencate nella tabella
Indirizzi. Possiamo ottenere il risultato che ci interessa con questa query:
SELECT
Nome,
Cognome,
YEAR(NOW())-YEAR(DataNascita)
AS Et
FROM Indirizzi
ORDER BY Et

La query si serve di due funzioni di MySQL: YEAR() e NOW(). Con YEAR() si ricava il valore
dellanno da una data (nel caso specifico, il valore del campo DataNascita). La funzione
NOW() non chiede un argomento, perch acquisisce la data e lora correnti del sistema e li

mette a disposizione di SQL.


In SQL Server lequivalente della funzione NOW() di MySQL la funzione GETDATE(), per
cui la query corrispondente ha questa forma:
SELECT
Nome,
Cognome,
YEAR(GETDATE())-YEAR(DataNascita)
AS Et
FROM Indirizzi
ORDER BY Et

Funzioni senza argoment i


Quasi tutte le funzioni prevedono lindicazione di uno o pi argomenti entro la coppia di parentesi tonde che
segue la parola chiave che identifica la funzione.
La coppia di parentesi tonde deve essere scritta, senza alcun carattere al suo interno, anche quando la
funzione, come NOW(), non richiede argomenti.

Dando alla funzione YEAR() come argomento la funzione NOW(), si ottiene il valore
dellanno della data del sistema. Sottraendo da questo valore lanno contenuto nel campo
DataNascita ottenuto anche in questo caso mediante la funzione YEAR() si ricava un
valore che corrisponde allet della persona nellanno in corso, che viene presentato nel
risultato della query (Figura 4.10) con la colonna alias Et.
Figura 4.10 La colonna alias Et il risultato di un calcolo ottenuto con due funzioni.

Colonne calcolat e
Le colonne alias, che si ottengono col qualificatore AS, possono contenere, come abbiamo appena visto,
valori calcolati, ricavati mediante funzioni da valori esistenti in altre colonne.
Questa funzionalit di SQL va tenuta ben presente quando si progettano le tabelle. I campi di una tabella
devono contenere sempre valori indipendenti, non ricavabili da altri campi nella stessa tabella. Se, quando
si utilizza il database, serve un valore che si pu derivare da un campo esistente, possibile creare una
colonna alias, che d dinamicamente il valore calcolato. Se avessimo inserito nella struttura della tabella
Indirizzi un campo Et, di tipo INT, con let di ogni persona, questo valore sarebbe diventato obsoleto
dopo pochi mesi. Avendo predisposto un campo DataNascita, possiamo ricavarne in qualunque momento
una colonna Et, aggiornata allanno in corso oppure riferita a un qualsiasi altro anno.
Come regola generale, non si devono mai creare campi calcolati quando si struttura una tabella, ma vanno
predisposti campi indipendenti, alcuni dei quali si potrebbero utilizzare eventualmente per generare colonne
calcolate. Per esempio, in una tabella Prodotti pu bastare un solo campo PrezzoUnitario: limporto dellIVA
si pu ricavare moltiplicando PrezzoUnitario per 0.20 (quando laliquota IVA il 20%) e limporto da
fatturare si pu ottenere moltiplicando PrezzoUnitario per 1.20.

Il risultato della query dimostra anche che la clausola ORDER BY pu essere applicata su
qualunque colonna o gruppo di colonne richiamate nella clausola SELECT della query,
quindi anche su una colonna calcolata o alias, come la colonna Et che abbiamo
generato con le funzioni YEAR .
Esaminiamo nei particolari come si pu ottenere una colonna calcolata con un
operatore aritmetico.
Abbiamo una tabella Dipendenti che contiene gli stessi dati della tabella Indirizzi, con
laggiunta di tre colonne: Regione, DataAssunzione e Bonus, questultima contenente
valori in euro.
I dipendenti che risiedono in Piemonte sono stati assegnati a una commessa che durer
parecchio tempo e si realizzer in Inghilterra, per cui il loro bonus dovr essere pagato
in sterline invece che in euro. Al momento in cui scriviamo (maggio 2016) un euro vale
0,76 sterline: si tratta quindi di individuare i dipendenti da assegnare alla nuova
commessa e limporto in sterline del bonus che hanno maturato.
Con questa semplice istruzione otteniamo lelenco dei dipendenti che ci interessano,
insieme con il valore del bonus maturato da ciascuno di loro:
SELECT
Nome,
Cognome,
Bonus
FROM dipendenti
WHERE Regione = 'Piemonte'

Vediamo il risultato nella Figura 4.11.

Figura 4.11 Il risultato della query che individua i dipendenti da trasferire.

In questa query usiamo una nuova e fondamentale clausola dello Structured Query
Language che si chiama WHERE e che approfondiremo nel prossimo capitolo: qui ci baster
sapere che con questa clausola si specifica un vincolo allistruzione SELECT, indicando
dove (where appunto) concentrare la selezione.
Si tratta adesso di eseguire il calcolo che riguarda il bonus dei quattro dipendenti
assegnati alla nuova commessa.
La query che ci serve pu avere questa forma:
SELECT
Nome,
Cognome,
Bonus
AS 'BonusEuro',
Bonus*0.76)
AS 'BonusPound'
FROM dipendenti
WHERE Regione = 'Piemonte'

producendo il seguente risultato:


Nome Cognome BonusEuro BonusPound
Matteo Immuni 600 456.00
Romeo Sogni 800 608.00
Rosario Fuzzi 600 456.00
Ugo Uguali 800 608.00

Abbiamo ottenuto il nuovo valore che ci interessa (la conversione in sterline


dellimporto in euro usando come valore di cambio 0,76) usando loperatore aritmetico
asterisco (*), uno degli operatori aritmetici riepilogati sopra nella Tabella 4.2.
Supponiamo di voler rateizzare in tre parti uguali il bonus da corrispondere in
sterline, per poterlo erogare cos rateizzato al verificarsi di tre distinti momenti di
verifica dello sviluppo della commessa a Londra. Per eseguire questa divisione
possiamo utilizzare loperatore aritmetico / in questo modo:
SELECT
Nome,
Cognome,
Bonus/3)
AS 'RataBonusEuro',
Bonus*0.76)/3
AS 'RataBonusPound'
FROM dipendenti
WHERE Regione = 'Piemonte'

Il risultato in SQL Server si presenta in questo modo:


Nome Cognome Rat aBonusEuro Rat aBonusPound
Rosario Fuzzi 200 152
Matteo Immuni 200 152
Romeo Sogni 266,666666666667 202,666666666667
Ugo Uguali 266,666666666667 202,666666666667

e analogamente in MySQL:
Nome Cognome Rat aBonusEuro Rat aBonusPound
Rosario Fuzzi 200.0000 152.000000
Matteo Immuni 200.0000 152.000000
Romeo Sogni 266.6667 202.666667
Ugo Uguali 266.6667 202.666667

Osserviamo che i valori delle rate in euro e in sterline sono rappresentati con molti
decimali: trattandosi di importi in denaro, bastano due soli decimali, che potremmo
ottenere ricorrendo in MySQL a una fra due funzioni aritmetiche: TRUNCATE o ROUND .
Entrambe prevedono due argomenti:
TRUNCATE(X,D)
ROUND(X,D)

dove X il valore decimale sul quale agire e D il numero di decimali che si


vogliono ottenere.
Se eseguissimo di nuovo la query con la funzione TRUNCATE impostata su due posizioni
decimali:
SELECT
Nome,
Cognome,
TRUNCATE(Bonus/3,2)
AS 'RataBonusEuro',
TRUNCATE((Bonus*0.76)/3,2)
AS 'RataBonusPound'
FROM dipendenti
WHERE Regione = 'Piemonte'

otterremmo in MySQL un risultato di questo genere:


Nome Cognome Rat aBonusEuro Rat aBonusPound
Rosario Fuzzi 200.00 152.00
Matteo Immuni 200.00 152.00
Romeo Sogni 266.66 202.66
Ugo Uguali 266.66 202.66

Come possiamo vedere, TRUNCATE() si limita a tagliar via i decimali in eccesso. un po


drastico, perch in alcuni record il terzo e il quarto decimale formano un valore
maggiore di 50.
Ricorriamo allora alla funzione ROUND :
SELECT
Nome,
Cognome,
ROUND(Bonus/3,2)
AS 'RataBonusEuro',
ROUND((Bonus*0.76)/3,2)
AS 'RataBonusPound'
FROM dipendenti
WHERE Regione = 'Piemonte'

che produce un risultato pi corretto, come possiamo vedere dalla Figura 4.12.
NOT A
La funzione TRUNCATE() di MySQL non va confusa con il comando TRUNCATE TABLE, una variante del
comando DELETE che abbiamo descritto alla fine del Capitolo 3. La funzione ROUND() esiste anche in SQL
Server e funziona esattamente come quella che abbiamo visto con MySQL. Non disponibile, invece, in
SQL Server la funzione TRUNCATE().

Figura 4.12 Il risultato delle divisioni arrotondato con la funzione ROUND.

Nellipotesi che presto o tardi a tutti i dipendenti possa capitare di dover lavorare per
un lungo periodo nel Regno Unito, trasformiamo la colonna BonusPound usata fin qui
come alias in una colonna effettiva della tabella Dipendenti.
Creiamo in primo luogo una nuova colonna nella tabella Dipendenti di MySQL con
questo enunciato:
ALTER TABLE Dipendenti
ADD BonusPound DECIMAL(6,2) NOT NULL

Verifichiamo poi, con:


DESCRIBE Dipendenti

che la tabella Dipendenti contenga effettivamente una colonna BonusPound di tipo


DECIMAL (6,2).

Eseguiamo successivamente questo enunciato:


UPDATE Dipendenti
SET BonusPound=ROUND(Bonus*0.76,2)

Formulato in questo modo, cio senza una clausola WHERE, il comando UPDATE esegue
loperazione indicata dalla clausola SET su tutti i record. Questa clausola pu contenere
un valore assoluto (per esempio una stringa o un numero) oppure una espressione (come
in questo caso).
La presenza dellespressione:
BonusPound=ROUND(Bonus*0.76,2)

fa s che il valore da immettere nel campo BonusPound venga ricalcolato per ogni
singolo record in funzione del valore del campo Bonus dello stesso record.
Possiamo controllare il risultato eseguendo lenunciato:
SELECT
Cognome,
Bonus,
BonusEuro
FROM Dipendenti

In entrambi i sistemi dovremmo vedere lo stesso risultato. In SQL Server la scheda


Risultato 1 dovrebbe presentarsi come nella Figura 4.13.
Quando si ha bisogno di aggiornare automaticamente il contenuto di unintera
colonna, sempre opportuno eseguire prima una query SELECT con il qualificatore AS , per
controllare che cosa si ottiene nella colonna alias. Quando il risultato soddisfacente, si
pu procedere alla modifica effettiva della tabella con il comando UPDATE, utilizzando
nella sua clausola SET lespressione che ha generato la colonna alias.
Se, a un certo punto, ritenessimo che la colonna BonusPound non sia pi necessaria,
perch non ci sono pi commesse attive in Inghilterra, possiamo eliminarla con questo
semplice enunciato:
ALTER TABLE Dipendenti
DROP COLUMN BonusPound
Figura 4.13 La colonna BonusPound stata popolata automaticamente con i valori calcolati.
Funzioni di aggregazione
Quelle che abbiamo visto finora sono funzioni scalari, che agiscono su un solo
valore, quello indicato come argomento in ciascuno degli esempi. Eseguendo una
funzione scalare con un enunciato SELECT, il risultato restituito dalla funzione viene
assegnato a ogni singola riga richiamata da SELECT.
Le funzioni di aggregazione producono un risultato diverso: non generano righe
modificate, ma producono un unico valore, ottenuto prendendo in considerazione tutta la
colonna o le colonne indicate come argomento.
Vediamo un primo, semplice esempio: vogliamo conoscere let media e lanzianit di
servizio media degli impiegati i cui dati si trovano nella tabella Dipendenti, strutturata
come si pu vedere qui di seguito:
Campo T ipo dat i
IDPersona int(11)
Appellativo varchar(5)
Nome varchar(25)
Cognome varchar(25)
Indirizzo varchar(30)
CAP varchar(5)
Provincia char(2)
Citt varchar(30)
DataNascita date
DataAssunzione date
Regione varchar(15)
Bonus decimal(6,2)

Verifichiamo preliminarmente le et e le anzianit di servizio effettive (allanno in


corso) degli impiegati eseguendo questa query in MySQL:
SELECT
Cognome,
DataNascita,
DataAssunzione,
YEAR(NOW())-YEAR(DataAssunzione)
AS Anzianit,
YEAR(NOW())-YEAR(DataNascita)
AS Et
FROM Dipendenti
ORDER BY Cognome

Le funzioni scalari YEAR() e NOW() producono il risultato che possiamo vedere nella
Figura 4.14.
Figura 4.14 La query calcola e presenta le et e le anzianit dei singoli dipendenti.

Lo stesso risultato si ottiene in SQL Server utilizzando la funzione GETDATE() invece di


NOW() :
SELECT
Cognome,
DataNascita,
DataAssunzione,
YEAR(GETDATE())-YEAR(DataAssunzione)
AS Anzianit,
YEAR(GETDATE())-YEAR(DataNascita)
AS Et
FROM Dipendenti
ORDER BY Cognome

Per ottenere la media delle et e delle anzianit di servizio possiamo usare la funzione
di aggregazione AVG(), il cui nome deriva dallinglese average, che vuol dire appunto
media.
Non dobbiamo fare altro che racchiudere fra le parentesi tonde della funzione le due
funzioni scalari che abbiamo utilizzato prima, con qualche leggera modifica, in questo
modo per MySQL:
SELECT
AVG(YEAR(NOW())-YEAR(DataAssunzione))
AS 'Anzianit media',
AVG(YEAR(NOW())-YEAR(DataNascita))
AS 'Et media'
FROM Dipendenti

e in questaltro per SQL Server:


SELECT
AVG(YEAR(GETDATE())-YEAR(DataAssunzione))
AS 'Anzianit media',
AVG(YEAR(GETDATE())-YEAR(DataNascita))
AS 'Et media'
FROM Dipendenti
Il risultato in MySQL dovrebbe essere quello riportato nella Figura 4.15:

Figura 4.15 La query calcola e presenta i valori medi di et e di anzianit per lintera tabella.

La funzione AVG() agisce sullargomento che viene inserito fra le parentesi tonde, che
pu essere il nome di una colonna oppure unespressione (come nella query che stiamo
commentando).
Oltre ad AVG(), fanno parte dello standard SQL anche le funzioni SUM , COUNT, MAX e MIN, che
sono recepite in tutte le implementazioni di SQL.
Lo schema sintattico delle funzioni di aggregazione il seguente:
nome_della_funzione ([ALL | DISTINCT] espressione)

La clausola ALL pu non essere specificata, perch il modo di operare predefinito,


che prende in considerazione tutti i valori indicati nellespressione. La clausola DISTINCT
fa in modo che la funzione prenda in considerazione soltanto i valori diversi fra quelli
indicati.
La funzione COUNT consente tre formulazioni diverse:

COUNT(*) Conteggia tutte le righe di una tabella, comprese quelle che contengono
valori NULL .
COUNT( espressione) Conteggia tutte le righe che hanno valori diversi da NULL nella
colonna specificata con lespressione.
COUNT(DISTINCT espressione) Conteggia il numero di righe nella colonna indicata

dallespressione che non contengono valori NULL e che hanno valori diversi.

Le funzioni AVG , SUM , MAX e MIN lavorano soltanto su valori numerici e ignorano le righe
che contengono NULL .
Possiamo utilizzare la funzione ROUND per eliminare i decimali dal risultato.
Modificando in questo modo la query:
SELECT
ROUND(AVG(YEAR(NOW())-YEAR(DataAssunzione)),0)
AS 'Anzianit media',
ROUND(AVG(YEAR(NOW())-YEAR(DataNascita)),0)
AS 'Et media'
FROM Dipendenti

otteniamo questo risultato:


Anzianit media Et media
--------------- ---------
27 51
La clausola GROUP BY
In molti casi si utilizzano le funzioni di aggregazione insieme con la clausola GROUP BY
del comando SELECT, clausola che, come lascia capire il suo nome, ha funzioni di
raggruppamento.
Quando si utilizza la clausola GROUP BY si deve segnalare sia in SELECT sia in GROUP BY la
stessa colonna, che quella in base alla quale si intende raggruppare il risultato della
funzione di aggregazione.
Vediamo come si pu utilizzare la clausola GROUP BY per conoscere let media e
lanzianit di servizio media degli impiegati suddivisi per regione. In MySQL la query
pu essere formulata in questo modo:
SELECT
Regione,
ROUND(AVG(YEAR(NOW())-YEAR(DataAssunzione)) )
AS 'Anzianit media',
ROUND(AVG(YEAR(NOW())-YEAR(DataNascita)))
AS 'Et media'
FROM Dipendenti
GROUP BY Regione

Il risultato dovrebbe presentarsi come nella Figura 4.16.

Figura 4.16 Le funzioni di aggregazione possono dare risultati distinti per gruppi diversi.

In questultima query, la clausola


GROUP BY Regione

fa s che le due medie siano calcolate raggruppando i valori di Et e di Anzianit in


base ai valori diversi presenti nel campo Regione della tabella.
Non indispensabile specificare la colonna di raggruppamento nella clausola SELECT,
per, formulata in questo modo:
SELECT
ROUND(AVG(YEAR(NOW())-YEAR(DataAssunzione)) )
AS 'Anzianit media',
ROUND(AVG(YEAR(NOW())-YEAR(DataNascita)))
AS 'Et media'
FROM Dipendenti
GROUP BY Regione

la query produrrebbe un risultato poco comprensibile, anche se aritmeticamente


corretto:
Anzianit media Et media
32 55
23 49
25 49
32 57
26 49
32 59

consentito elencare nella clausola SELECT pi campi oltre a quello utilizzato per il
raggruppamento, ma si ottengono risultati sconcertanti. Per esempio, questa query in
MySQL e in SQL Server:
SELECT
Regione,
COUNT(*)
AS Dipendenti
FROM Dipendenti
GROUP BY Regione

fornisce un conteggio delle persone presenti in ciascuna Regione, come illustra la


Figura 4.17.
Figura 4.17 La funzione di aggregazione COUNT insieme con la clausola GROUP BY.

Per, se aggiungessimo un altro campo in SELECT, per esempio Cognome,


SELECT
Regione,
Cognome,
COUNT(*)
AS Dipendenti
FROM Dipendenti
GROUP BY Regione

otterremmo questo risultato:


Regione Cognome Dipendent i
Emilia Ghisalberti 3
Liguria Yassud 1
Lombardia Antimi 13
Piemonte Fuzzi 4
Trentino Hermann 4
Veneto Quadri 1

Questo risultato deriva dal fatto che il campo Cognome non elencato nella clausola
GROUP BY per cui, nelleseguire la query, SQL preleva soltanto il primo valore che trova

nel campo Cognome in ogni gruppo regionale.


Niente impedisce di indicare pi di una colonna nella clausola GROUP BY. Per esempio,
questa query:
SELECT
Regione,
Nome,
COUNT(*)
AS Dipendenti
FROM Dipendenti
GROUP BY Regione, Nome

genera in MySQL e in SQL Server il risultato che possiamo vedere nella Figura 4.18.
Siccome le 27 persone della tabella Dipendenti hanno tutte un nome diverso, tranne
due, GROUP BY ha generato 25 gruppi di una sola persona e 1 gruppo con due persone. In
altri termini, se si chiede un raggruppamento su pi di una colonna, se ne ottiene
comunque uno solo.
Non consentito formulare query che mescolano assieme nella clausola SELECT
riferimenti a colonne effettive e a valori da ottenere con funzioni di aggregazione senza
specificare anche la clausola GROUP BY. Per esempio, questa query:
SELECT
Regione,
COUNT(*)
AS Dipendenti
FROM Dipendenti

produce un errore, che in SQL Server viene segnalato in questo modo:


La colonna Dipendenti.Regione non valida nellelenco di selezione perch non inclusa n in una
funzione di aggregazione n nella clausola GROUP BY.

mentre viene eseguita imperturbabilmente in MySQL, dando il bislacco risultato


Regione Dipendent i
Lombardia 27
Figura 4.18 Come agisce la clausola di raggruppamento quando si indicano due colonne.

La clausola GROUP BY dispone i risultati della query nellordine alfabetico della colonna
alias generata dalla funzione di aggregazione sulla quale si fonda, quindi, la query
SELECT
Regione,
SUM(Bonus)
AS TotaleBonus
FROM Dipendenti
GROUP BY Regione

genererebbe questo risultato:


Regione Tot aleBonus
Emilia 2600
Liguria 600
Lombardia 9600
Piemonte 2800
Trentino 2600
Veneto 600

Si pu modificare questa impostazione predefinita aggiungendo alla query una


clausola ORDER BY:
SELECT
Regione,
SUM(Bonus)
AS TotaleBonus
FROM Dipendenti
GROUP BY Regione
ORDER BY TotaleBonus

Il risultato verrebbe presentato in questo modo:


Regione Tot aleBonus
Liguria 600
Veneto 600
Trentino 2600
Emilia 2600
Piemonte 2800
Lombardia 9600

In tutte le query che abbiamo presentato in questo capitolo abbiamo utilizzato una sola
volta la clausola WHERE. In assenza di questa clausola, SELECT estrae intere colonne,
producendo, quindi, una specie di tabella virtuale, composta da un sottoinsieme delle
colonne della tabella indicata nella clausola FROM . Queste tabelle virtuali nel gergo di SQL
prendono il nome di proiezioni. Esistono, come vedremo pi avanti, comandi SQL con i
quali si pu trasformare una proiezione in una tabella effettiva.
Gli elementi estratti in base a un criterio espresso con la clausola WHERE si chiamano
selezioni. Anche le selezioni si possono trasformare in tabelle effettive, ma di solito si
generano per ottenere un risultato specifico: una serie di informazioni accurate, estratte
da una o pi tabelle, secondo un determinato criterio.
La clausola WHERE ha un ruolo fondamentale nelle query e la trattiamo separatamente nel
prossimo capitolo.
Capitolo 5
Creare selezioni

Con la clausola WHERE del comando SELECT si specifica un criterio in base al quale
selezionare il contenuto delle colonne indicate nella clausola SELECT. Il criterio si formula
con unespressione, costruita con gli operatori di confronto:
> maggiore di
< minore di
= uguale a
>= maggiore di o uguale a
<= minore di o uguale a
<>
oppure != diverso da

gli operatori logici:


AND
OR
NOT

e altri operatori previsti dallo standard di SQL, che vedremo pi avanti.


Lespressione pu contenere un solo criterio di selezione, formulato con un semplice
operatore di confronto, per esempio:
WHERE Prezzo > 500

oppure pu combinare assieme pi criteri, associandoli con gli operatori logici, come
in questo caso:
WHERE Prezzo > 500 AND Colore = "Rosso"

Gli elementi messi a confronto devono essere omogenei: numeri con numeri, stringhe
con stringhe e date con date. Non si possono usare gli operatori di confronto fra
elementi con tipo dati NULL .
Quando lespressione utilizzata nella clausola WHERE fa riferimento a stringhe, queste
vanno racchiuse fra virgolette semplici.
Inoltre, lespressione pu richiamare funzioni e utilizzare operatori aritmetici.
Se in un enunciato SELECT la clausola FROM indica pi di una sola tabella come origine
dei dati, il criterio espresso con WHERE pu riferirsi a varie caratteristiche di colonne
ricavate da tutte le tabelle interessate dalla query, circoscrivendo in modo molto accurato
il risultato che si intende ottenere.
In questo capitolo facciamo vedere come si utilizza la clausola WHERE: iniziamo con
alcuni esempi semplici per poi passare a forme via via pi complesse.
Per agevolare il riscontro dei risultati degli esempi, riportiamo qui di seguito la
strttura delle due tabelle sulle quali eseguiremo gran parte delle query dimostrative.
La tabella Indirizzi:

La tabella Dipendenti:
Selezionare con criteri semplici
Per sapere quali persone nella tabella Dipendenti hanno un bonus superiore a 600
euro, possiamo eseguire questa query:
SELECT
Nome,
Cognome,
Bonus
FROM Dipendenti
WHERE Bonus > 600

Il risultato dovrebbe presentarsi come nella Figura 5.1.

Figura 5.1 Le persone che hanno un bonus superiore a 600.

La clausola WHERE deve essere seguita da almeno unespressione, che specifica il


criterio da rispettare per la selezione. In questo caso, lespressione
Bonus > 600

Se, invece di conoscere i nomi delle persone che hanno un bonus maggiore di 600
euro, volessimo sapere soltanto quante sono, la query pu essere riformulata utilizzando
una funzione di aggregazione:
SELECT
COUNT(Bonus) AS 'Bonus oltre 600'
FROM Dipendenti
WHERE Bonus > 600

La query stabilisce il criterio di selezione per la funzione COUNT, che esegue il


conteggio dei soli record della tabella Dipendenti che hanno un valore maggiore di 600
nel campo Bonus. Il risultato di questa query riportato nella Figura 5.2.
Figura 5.2 La clausola WHERE delimita largomento della funzione COUNT.

Nella query abbiano utilizzato il qualificatore AS per avere unintestazione pi


leggibile nel risultato (se mancasse, il risultato comparirebbe con lintestazione
COUNT(Bonus)).
Selezionare valori NULL
Gli operatori di confronto non funzionano sui campi che hanno come tipo dato NULL .
Per selezionare campi di questo tipo bisogna ricorrere a due operatori speciali: IS NULL e
IS NOT NULL , con i quali, come lasciano intendere le loro parole chiave, si verifica se un
campo contiene oppure no un valore NULL (si ricordi che NULL non uno zero e neppure
una stringa vuota).
Modifichiamo un record della tabella Dipendenti, attribuendo il valore NULL al campo
Regione del record che ha come chiave primaria il 27, in questo modo:
UPDATE Dipendenti
SET Regione= Null
WHERE IDPersona=27

A questo punto, lenunciato:


SELECT * FROM Dipendenti
WHERE Regione IS NOT NULL

estrae 26 record invece dei 27 contenuti nella tabella Dipendenti che abbiamo
modificato.
Formulando una query in questo modo:
SELECT
COUNT(*) AS 'Regione non definita'
FROM Dipendenti
WHERE Regione IS NULL

otteniamo come risultato il valore 1, corrispondente al numero di record nei quali il


campo Regione contiene NULL .
Se, invece, usassimo un operatore di confronto al posto delloperatore IS NULL :
SELECT
COUNT(*)
FROM Dipendenti
WHERE Regione = NULL

la query darebbe come risultato zero invece di uno. E lo stesso accadrebbe scrivendo
la clausola WHERE in questo modo:
WHERE Regione <> NULL

mentre otterremmo un valore corretto (26), formulando la clausola con loperatore IS


NOT NULL :
SELECT
COUNT(*)
FROM Dipendenti
WHERE Regione IS NOT NULL

Gli operatori logici


In SQL si possono utilizzare gli operatori logici classici, identificati dalle parole
chiave AND , OR , NOT, pi altri, specifici del linguaggio, che hanno la forma BETWEEN. . . AND e
LIKE. La regola sintattica per il loro utilizzo molto semplice e si pu cogliere
direttamente con alcuni esempi.
Con questa query:
SELECT
IDPersona,
Nome,
Cognome,
Prov
FROM Indirizzi
WHERE Prov ='MI'
OR Prov ='PV'
ORDER BY Prov

otteniamo il risultato che possiamo vedere nella Figura 5.3. Abbiamo aggiunto la
clausola ORDER BY per elencare separatamente le righe del risultato.
Figura 5.3 Nelle due province di Milano e di Pavia risiedono sette persone.

Luso delloperatore logico OR fra i due criteri Prov ='MI' e Prov ='PV' significa che sono
da estrarre soltanto i record che hanno la stringa 'MI' oppure la stringa 'PV' nel campo
Prov.
Vogliamo sapere quali signore laureate sono nate dopo il 1972. Per farlo, eseguiamo
questa query:
SELECT
Appellativo,
Nome,
Cognome,
DataNascita
FROM Indirizzi
WHERE Appellativo = 'Dr.sa'
AND YEAR(DataNascita)> 1970

Il risultato dovrebbe presentarsi come nella Figura 5.4.


Figura 5.4 Esiste una sola dottoressa nata dopo il 1970.

Lutilizzo di un solo criterio di selezione, come in questa query:


SELECT
Appellativo,
Nome,
Cognome,
DataNascita
FROM Indirizzi
WHERE Appellativo = 'Dr.sa'

avrebbe generato il risultato che possiamo vedere nella Figura 5.5.

Figura 5.5 La tabella Indirizzi contiene i dati di tre dottoresse.

Laggiunta delloperatore logico AND nella clausola WHERE impone che vengano
selezionati i record che hanno Dr.sa nel campo Appellativo e contemporaneamente un
valore maggiore di 1970 nella parte anno del campo DataNascita, per cui la query della
Figura 5.5 genera tre righe, mentre quella della Figura 5.4 ne genera una sola.
consentito utilizzare pi di un solo operatore logico nello stesso criterio di
selezione.
Per esempio, con questa query:
SELECT
Nome,
Cognome
FROM Dipendenti
WHERE DataNascita < '1970-01-01'
AND Bonus > 600
AND Provincia = 'MI'

si ottiene un solo record, mentre con questaltra query:


SELECT
Nome,
Cognome
FROM Dipendenti
WHERE DataNascita < '1970-01-01'
AND Bonus > 600
OR Provincia = 'MI'

i record che soddisfano la condizione sono dieci: laggiunta di un nuovo operatore AND
restringe il criterio di selezione, mentre laggiunta di un OR lo amplia.
Vediamo un altro esempio di utilizzo di pi operatori logici in una clausola WHERE un
po articolata.
Vogliamo individuare nella tabella Indirizzi i nomi delle persone che sono nate sotto il
segno zodiacale dei Pesci, cio in una data compresa fra il 19 febbraio e il 20 marzo.
Per individuare le componenti del campo DataNascita che ci interessano, il giorno del
mese e il mese dellanno, utilizziamo due funzioni disponibili in MySQL che abbiamo
gi visto nel Capitolo 4, e precisamente:
MONTH , che estrae il mese (da 1 a 12) e
DAYOFMONTH , che estrae il giorno del mese (da 1 a 31)

In SQL Server si ottengono gli stessi risultati con la funzione DATEPART(), che ha la
seguente sintassi:
DATEPART( componente_data, valore_data)

Il parametro componente_data pu essere:


YEAR (per ottenere lanno);
MONTH per il mese (si ottiene un valore da 1 a 12);
DAY per il giorno (si ottiene un valore da 1 a 31);
DAYOFYEAR per il giorno dellanno (si ottiene un numero fra 1 e 366) e
WEEKDAY per il giorno della settimana (si ottiene un numero da 1 a 7 dove 1 indica il
luned).
Il parametro valore_data pu essere un valore assoluto (una data scritta fra apici come
per esempio '2016-09-24') oppure il riferimento a un campo con tipo dato DATE o DATETIME in
una tabella.
La query che ci interessa pu avere questa forma:
SELECT
Nome,
Cognome,
DataNascita AS 'Bilancia'
FROM Indirizzi
WHERE DAYOFMONTH(DataNascita) >= 23
AND MONTH(DataNascita) = 9
OR DAYOFMONTH(DataNascita) <= 23
AND MONTH(DataNascita) = 10

Eseguendola in MySQL possiamo constatare (Figura 5.6) che vi sono quattro persone
nate sotto il segno della Bilancia.
Opportunamente riformulata in SQL Server, la query si presenta in questo modo:
SELECT
Nome,
Cognome,
DataNascita AS 'Bilancia'
FROM Indirizzi
WHERE DATEPART(DAY, DataNascita) >= 23
AND DATEPART(MONTH,DataNascita) = 9
OR DATEPART(DAY, DataNascita) <= 23
AND DATEPART(MONTH, DataNascita) = 10

Figura 5.6 La query utilizza due funzioni e tre operatori logici.

Loperatore BETWEEN...AND
Questo operatore permette di specificare un intervallo di valori nel criterio di
selezione. Supponiamo di voler avere un elenco delle persone nate fra il 1960 e il 1970.
Possiamo ottenere questo elenco utilizzando in MySQL la funzione YEAR (che estrae
lanno da un campo di tipo Date) e valutando con BETWEEN...AND il valore restituito dalla
funzione:
SELECT
Appellativo,
Nome,
Cognome,
DataNascita
FROM Indirizzi
WHERE YEAR(DataNascita)
BETWEEN 1960
AND 1970
ORDER BY DataNascita

La Figura 5.7 mostra il risultato della query.

Figura 5.7 Dieci persone sono nate fra il 1960 e il 1970.

La stessa query si pu scrivere in questo modo in SQL Server:


SELECT
Appellativo,
Nome,
Cognome,
DataNascita
FROM Indirizzi
WHERE DATEPART(YEAR, DataNascita)
BETWEEN 1960
AND 1970
ORDER BY DataNascita

ottenendo, naturalmente, lo stesso risultato.


Con loperatore logico BETWEEN...AND si pu stabilire un criterio di selezione flessibile,
indicando un intervallo di valori che soddisfano la condizione WHERE, invece di specificare
un solo valore puntuale.
Tradotta in italiano, la clausola WHERE di questa query significa:
Dove lanno del campo DataNascita compreso fra il 1960 e il 1970.

Nella query abbiamo utilizzato anche la clausola ORDER BY per ordinare le righe in base
alla data di nascita e facilitare il controllo dei dati estratti.
Possiamo utilizzare BETWEEN...AND insieme con unaltra funzione per le date di MySQL
per ottenere lo stesso risultato che abbiamo ottenuto nel paragrafo precedente con tre
operatori logici.
La funzione MySQL che ci interessa si chiama DAYOFYEAR e restituisce un numero
dordine compreso fra 1 e 366 per il giorno specificato come suo argomento (la
numerazione basata su un anno bisestile), quindi DAYOFYEAR("2004-02-19") restituisce 50,
mentre D AYOFYEAR("2004-03-20") restituisce 80.
Riscritta con BETWEEN...AND e DAYOFYEAR , la query per estrarre i nati sotto il segno della
Bilancia pu assumere questa forma:
SELECT
Nome,
Cognome,
DataNascita AS "Bilancia"
FROM Indirizzi
WHERE DAYOFYEAR(DataNascita)
BETWEEN DAYOFYEAR("2016-09-23")
AND DAYOFYEAR("2016-10-23")

producendo il risultato che vediamo nella Figura 5.8, identico a quello della Figura
5.6.
Figura 5.8 Si pu ottenere lo stesso risultato con una query che utilizza operatori e funzioni diverse.

La stessa query in SQL Server si scrive in questo modo:


SELECT
Nome,
Cognome,
DataNascita AS 'Bilancia'
FROM Indirizzi
WHERE DATEPART(DAYOFYEAR, DataNascita)
BETWEEN DATEPART(DAYOFYEAR, '2016-09-23')
AND DATEPART(DAYOFYEAR, '2016-10-23')

Loperatore LIKE
A tutti pu capitare un momento di amnesia e di non ricordare pi se quel certo cliente
si chiama Banti o Baffi o Baschi. O magari Rabani. Di sicuro nel nome cera la sillaba
ba Loperatore LIKE probabilmente un residuo dellutopia originale, che si
proponeva di creare con SQL un linguaggio per interrogare un database con frasi
dellinglese parlato, e serve giusto per formulare query indicando come criterio di
confronto soltanto una frazione, anche piccola, di una stringa di caratteri.
Una query formulata in questo modo:
SELECT
Nome
FROM Indirizzi
WHERE Nome LIKE 'E%'
ORDER BY Nome

produce il risultato che possiamo vedere nella Figura 5.9.


Figura 5.9 La query ha estratto i tre nomi della tabella Indirizzi che iniziano con la lettera E.

Possiamo invertire il risultato, riformulando la query con laggiunta delloperatore


logico NOT, in questo modo:
SELECT
Nome
FROM Indirizzi
WHERE Nome NOT LIKE 'E%'
ORDER BY Nome

Otterremo lelenco di tutti i nomi che non iniziano con la lettera E.


Si pu puntualizzare il criterio di selezione per loperatore LIKE indicando uno schema
di confronto. Lo schema si costruisce con caratteri jolly. Nellesempio abbiamo usato lo
schema E%, con il carattere jolly %, che sta a significare un numero qualunque di
caratteri. Correttamente, la query ha restituito tutti e solo i nomi che iniziano con la
lettera E.
Il carattere jolly % utilizzabile pi volte entro lo schema di confronto. Per esempio,
con questa query:
SELECT
Cognome
FROM Indirizzi
WHERE Cognome LIKE '%ol%'

si otterrebbe il seguente risultato:


Cognome
Bolli
Polpi
Volli
Zolletta

cio lestrazione dei cognomi che contengono le lettere ol in una qualunque


posizione.
Viene spontanea la domanda: si pu utilizzare il carattere jolly % per selezionare
tramite LIKE un campo nel quale contenuto il carattere %? S, possibile, ricorrendo a un
piccolo accorgimento.
Supponiamo di avere una tabella chiamata TestLIKE che contiene questi record:
IDRecord RagSoc Slogan
1 F.lli Manzini Srl Consegniamo ovunque
2 Gamma Sas Soddisfatti al 100 %
3 Russo Salvatore Snc Spedizioni garantite
4 Pasqualetti & C Srl Mai meno del 100%
5 WR & Associati Srl Sempre a disposizione
6 Universal Trasporti Srl Se non vi diamo il 100% non ci pagate

Come facciamo a estrarre tutti e solo i record che contengono il carattere % nel campo
Slogan?
Con una query come questa:
SELECT *
FROM TestLIKE
WHERE Slogan LIKE '%\%%'

otteniamo il risultato illustrato nella Figura 5.10.

Figura 5.10 Il risultato della query che utilizza il carattere escape \.

In MySQL il carattere speciale \ (barra inclinata a sinistra) ha funzioni di escape, come


si dice nel gergo della programmazione, ovvero segnala a SQL che il carattere che viene
immediatamente dopo va considerato alla lettera, per quello che , e non per quello che
significa nel sistema. Facendo quindi precedere il carattere % dal carattere \ nello schema
di confronto, facciamo sapere a LIKE che intendiamo cercare il carattere % e non
utilizzarlo come carattere jolly. Quindi, la clausola
LIKE '% \ % %'

significa un numero qualsiasi di caratteri, seguiti dal carattere %, seguito a sua volta
da un numero qualsiasi di caratteri
Modificando in questo modo la clausola LIKE:
SELECT *
FROM TestLIKE
WHERE Slogan LIKE '% \ %'

cio, non inserendo il carattere % dopo la coppia \%, otterremmo soltanto due record:
IDRecord RagSoc Slogan
2 Gamma Sas Soddisfatti al 100 %
4 Pasqualetti & C Srl Mai meno del 100%

perch il record numero 6 contiene altri caratteri dopo il %.


In SQL Server la funzione di escape in un enunciato LIKE svolta da una coppia di
parentesi quadre che devono racchiudere il carattere da prendere, per cos dire, alla
lettera , quindi la query per ottenere tutti e tre gli slogan che contengono il carattere %
va formulata in questo modo:
SELECT *
FROM TestLIKE
WHERE Slogan LIKE '%[%]%'

Oltre al carattere jolly %, lo standard SQL prevede il carattere _ (trattino di


sottolineatura, in inglese underscore) che sta a significare un solo carattere qualsiasi.
Questa query:
SELECT
Cognome
FROM Indirizzi
WHERE Cognome LIKE '_o__i'

produce il risultato che vediamo nella Figura 5.11.


Figura 5.11 Il risultato della query che utilizza il carattere jolly _.

Vengono estratti, cio, i sette cognomi della tabella Indirizzi che iniziano con una
lettera qualunque, hanno o come seconda lettera, seguita da due lettere qualsiasi e
terminano con la lettera i.
Combinando opportunamente i caratteri jolly % e _ e ricorrendo, quando il caso, al
carattere escape \ o [ ], si possono costruire schemi di confronto molto raffinati.
In MySQL, la clausola LIKE con uno schema di confronto costruito con caratteri jolly
pu essere utilizzata anche su campi numerici, mentre lo standard SQL prevede luso di
caratteri jolly soltanto per confronti su stringhe di caratteri.
Selezionare con criteri complessi
Nella clausola WHERE si possono utilizzare altri due elementi sintattici, oltre a quelli che
abbiamo descritto fin qui, rappresentati dalle parole chiave IN e HAVING . Il primo un
predicato, cio un elemento che completa e modifica il significato di una clausola,
mentre il secondo una clausola vera e propria, che pu essere utilizzata al posto della
clausola WHERE. Entrambi si utilizzano per definire query con criteri di selezione pi
complessi di quelli che abbiamo visto negli esempi precedenti.

Il predicato IN
Vogliamo estrarre alcune colonne dalla tabella Indirizzi, limitando la selezione ai
record che hanno determinati valori nel campo Provincia. Nellipotesi che i campi da
estrarre siano Nome, Cognome, Provincia e che le province da considerare siano PC, MI
e PV, questa query potrebbe risolvere il problema:
SELECT
Nome,
Cognome,
Prov
FROM Indirizzi
WHERE Prov='PC'
OR Prov='MI'
OR Prov='PV'
ORDER BY Prov

La clausola WHERE utilizza tre condizioni collegate con loperatore logico OR . Il risultato
dovrebbe presentarsi come nella Figura 5.12.
Figura 5.12 La selezione ottenuta con loperatore logico OR.

Possiamo ottenere lo stesso risultato che si vede nella figura mediante una query
leggermente diversa:
SELECT
Nome,
Cognome,
Prov
FROM Indirizzi
WHERE Prov IN ('PC', 'MI', 'PV')
ORDER BY Prov

Questa query pi elegante, cio pi concisa (che il criterio delleleganza nello


Structured Query Language), perch ottiene il risultato mediante il predicato IN.
Il predicato IN, seguito da un elenco di valori separati da virgole, circoscrive il
confronto eseguito da WHERE ai valori indicati nellelenco.
Come vedremo pi avanti, il predicato IN si dimostra molto utile in query pi
complesse di questa.

La clausola HAVING
Vogliamo selezionare un particolare valore raggruppato, per esempio, vedere soltanto
i totali che superano un determinato limite. Specificamente, desideriamo sapere in quali
regioni il totale del Bonus maggiore di 2400.
Verrebbe spontaneo utilizzare la clausola WHERE per circoscrivere loutput della query,
in questo modo:
SELECT
Regione,
SUM(Bonus) AS TotaleBonus
FROM Dipendenti
GROUP BY Regione
WHERE TotaleBonus > 2400

Cos facendo, per, si ottiene una segnalazione di errore, che viene denunciata come
errore di sintassi sia in MySQL sia in SQL Server.
Come che sia, errore di sintassi o no, il fatto vero che non consentito ricorrere alla
clausola WHERE per stabilire un criterio di selezione riferito a valori raggruppati mediante
la clausola GROUP BY.
Per ottenere il risultato che ci interessa, dobbiamo servirci della clausola HAVING , che
svolge correttamente il ruolo di WHERE in riferimento a un valore ottenuto mediante la
clausola di raggruppamento GROUP BY.
Struttura sintattica e comportamento di HAVING sono gli stessi di WHERE. Questa query, che
utilizza HAVING , genera correttamente in MySQL il risultato della Figura 5.13.
SELECT
Regione, SUM(Bonus) AS TotaleBonus
FROM Dipendenti
GROUP BY Regione
HAVING TotaleBonus > 2400

Nella stessa figura vediamo che per ottenere lo stesso risultato in SQL Server la query
deve essere formulata in questo modo:
SELECT
Regione,
SUM(Bonus) AS TotaleBonus
FROM Dipendenti
GROUP BY Regione
HAVING SUM(Bonus) > 2400

Figura 5.13 La query di raggruppamento individua soltanto alcuni valori.

La clausola WHERE non si pu utilizzare su valori raggruppati, cio ottenuti con GROUP BY,
ma perfettamente utilizzabile in riferimento a valori sui quali agiscono funzioni di
aggregazione, come per esempio:
SELECT
SUM(Bonus) AS TotaleBonus
FROM Dipendenti
WHERE Bonus < 700

Sia in MySQL sia in SQL Server, questa query calcola correttamente e presenta nella
colonna alias TotaleBonus un unico valore che la sommatoria dei valori della colonna
Bonus prendendo in considerazione soltanto quelli maggiori di 700.
Lutilizzo contemporaneo in una stessa query delle clausole WHERE e HAVING consentito,
purch siano riferite a valori appropriati, per richiede particolare attenzione, perch
pu indurre a pericolosi errori logici, che in quanto tali non possono essere segnalati da
SQL. Vediamo un esempio puntuale.
Disponiamo di una tabella Assegni, che ha questo contenuto:
IDAss NumAss Beneficiario Dat a Import o Causale
1 8.009 New Leasing SpA 2016-01-05 250.75 Rata leasing auto
2 8.010 Anna Rossi 2016-01-10 600.80 Assegno mensile Gennaio
3 8.008 ASM SpA 2016-01-15 150.25 Servizi municipali
4 8.011 Cassa 2016-01-18 120.00 Prelievo contante
5 8.012 Superette Aurora Srl 2016-01-12 600.98 Forniture alimentari
6 8.013 Lina Devoti 2016-01-15 510.80 Servizi domestici
7 8.014 Cassa 2016-01-20 110.00 Prelievo contante
8 8.015 Immobiliare Dante Srl 2016-01-22 1781.78 Affitto I Trimestre
9 8.016 Cassa 2016-01-24 150.00 Prelievo contante
10 8.016 Superette Aurora Srl 2016-01-25 300.15 Forniture alimentari
11 8.018 Cassa 2016-01-30 130.00 Prelievo contante
12 8.019 ASM SpA 2016-01-02 98.92 Servizi municipali
13 8.020 Cassa 2016-01-02 75.50 Prelievo contante
14 8.022 Superette Aurora Srl 2016-01-25 90.75 Materiali pulizia

Eseguiamo questa query in MySQL con una clausola GROUP BY e una clausola HAVING :
SELECT
Beneficiario,
SUM(Importo) as Totale,
COUNT(Beneficiario) Numero_assegni
FROM Assegni
GROUP BY Beneficiario
HAVING Totale > 100

La stessa query in SQL Server si presenta in questo modo:


SELECT
Beneficiario,
SUM(Importo) as Totale,
COUNT(Beneficiario) Numero_assegni
FROM Assegni
GROUP BY Beneficiario
HAVING SUM(Importo) > 100

Otteniamo in entrambi i sistemi questo risultato:


Beneficiario Tot ale Numero_assegni
Anna Rossi 600.80 1
ASM SpA 249.17 2
Cassa 585.50 5
Immobiliare Dante Srl 1781.78 1
Lina Devoti 510.80 1
New Leasing SpA 250.75 1
Superette Aurora Srl 991.88 3

Modifichiamo la query in MySQL, introducendo anche una clausola WHERE, riferendola


correttamente a una colonna non ottenuta per raggruppamento.
SELECT
Beneficiario,
SUM(Importo) Totale,
COUNT(Beneficiario) Numero_assegni
FROM Assegni
WHERE Importo >=150
GROUP BY Beneficiario
HAVING Totale > 100

La stessa query in SQL Server si presenta in questo modo:


SELECT
Beneficiario,
SUM(Importo) Totale,
COUNT(Beneficiario) Numero_assegni
FROM Assegni
WHERE Importo >=150
GROUP BY Beneficiario
HAVING SUM(Importo) > 100

La query produce un risultato ben diverso, uguale in tutti e due i sistemi:


Beneficiario Tot ale Numero_assegni
Anna Rossi 600.80 1
ASM SpA 150.25 1
Cassa 150.00 1
Immobiliare Dante Srl 1781.78 1
Lina Devoti 510.80 1
New Leasing SpA 250.75 1
Superette Aurora Srl 901.13 2

Che cosa accaduto? Quello che accade sempre quando si fa un errore logico: il
computer (meglio, lo Structured Query Language, in questo caso) fa quello che gli
abbiamo detto di fare, purch sia corretto sintatticamente, anche se sbagliato
logicamente. Per effetto della clausola WHERE, la clausola GROUP BY viene eseguita soltanto
sui valori maggiori di o uguali a 150 contenuti nella colonna Importo, quindi i gruppi
sono gli stessi (sette in entrambe le query), ma i valori delle colonne alias Totale e
Numero_assegni sono diversi nei gruppi ASM SpA, Cassa e Superette Aurora Srl.

La funzione CASE
La funzione CASE, introdotta nello standard del linguaggio soltanto con la versione
SQL99, mette a disposizione funzionalit per confronti iterativi (del genere
IF...THEN...ELSE) utilizzabili entro un enunciato SELECT o UPDATE. Con CASE si valuta un elenco

di condizioni e si ricava un valore da ciascuna delle condizioni valutate.


La sintassi di CASE prevede due varianti:

una per eseguire un confronto diretto su un valore (di solito, il contenuto di un


campo);
unaltra per eseguire confronti su valori logici, di solito derivati da una funzione
che pu restituire un valore logico vero o falso.
Questa la prima variante.
CASE valore
WHEN valore_di_confronto THEN risultato
[WHEN valore_di_confronto THEN risultato]
[ELSE risultato_alternativo]
END

Questa variante restituisce il risultato quando valore = valore_di_confronto. Il


risultato_alternativo viene restituito se valore <> valore_di_confronto.
Questa la seconda variante.
CASE
WHEN valore_logico THEN risultato
[WHEN valore_logico THEN risultato]
[ELSE risultato_alternativo]
END

Questa variante restituisce il risultato quando il valore_logico esaminato si dimostra


vero. Se nessun valore_logico risulta vero, viene restituito il risultato_alternativo
indicato con ELSE. Se non c la clausola ELSE, viene restituito NULL .
Una query costruita con la prima variante della funzione CASE potrebbe essere scritta in
questo modo e funziona in MySQL come in SQL Server:
SELECT
CASE Appellativo
WHEN 'Dr.' THEN 'Laureato'
WHEN 'Dr.sa' THEN 'Laureata'
WHEN 'Prof.' THEN 'Laureato'
WHEN 'Ing.' THEN 'Laureato'
ELSE 'Senza laurea'
END
AS 'Titolo di studio',
CONCAT(Appellativo, ' ', Cognome)
AS 'Nominativo'
FROM Indirizzi

La formula CASE prende in considerazione la colonna Appellativo ed esegue quattro


confronti fra il suo valore e altrettante stringhe, ciascuna delle quali un
valore_di_confronto diverso. Quando trova coincidenza fra un valore e un
valore_di_confronto sostituisce il valore con il risultato nella riga che va a comporre. Se
nessuno dei quattro confronti produce un risultato, utilizza per la riga il
risultato_alternativo.
La Figura 5.14 mostra il risultato della query: nella colonna alias Titolo di studio
compaiono i risultati o i risultati_alternativi generati dalla funzione CASE, che descrivono
il titolo di studio delle persone, elencate nella colonna alias Nominativo, ottenuta per
concatenamento delle colonne Appellativo e Cognome.

Figura 5.14 Un esempio di query costruita con la prima variante sintattica della funzione CASE.

Si noti che tutto quanto compreso fra la riga che inizia con la parola chiave CASE e la
riga che contiene la sola parola chiave END , in termini di struttura sintattica un unico
elemento della clausola SELECT, che individua una colonna da estrarre, quindi deve essere
separato con una virgola dagli altri elementi che compongono la clausola SELECT. La
stessa query si pu scrivere in questo modo:
SELECT
CONCAT(Appellativo, " ", Cognome) AS "Nominativo",
CASE Appellativo
WHEN "Dr." THEN "Laureato"
WHEN "Dr.sa" THEN "Laureata"
WHEN "Prof." THEN "Laureato"
WHEN "Ing." THEN "Laureato"
ELSE "Senza laurea"
END
AS "Titolo di studio"
FROM Indirizzi

ottenendo lo stesso risultato della versione precedente, con la sola differenza che la
colonna alias Nominativo compare in prima posizione e la colonna alias Titolo di
studio in seconda posizione.
Si osservi che nelle clausole WHEN...THEN non si esplicita un operatore di confronto, che
sottinteso, perch lunico tipo di confronto possibile in questa variante di CASE quello
per uguaglianza.
Diamo ora un esempio per la seconda variante della sintassi della funzione CASE. Nei
paragrafi dedicati agli operatori logici e a BETWEEN...AND , abbiamo visto come si pu
ricavare il segno zodiacale di una persona sulla base del risultato della funzione
appropriata per conoscere il numero ordinale del giorno in una data (un valore
compreso fra 1 e 365) applicata a un campo che contiene una data di nascita. Come
ricordiamo (si veda il paragrafo Le funzioni del Capitolo 4) questa funzione DAYOFYEAR
nel caso di MySQL ed GETPART(DAYOFYEAR, data) in SQL Server,
La seconda variante della sintassi di CASE ci consente di creare ununica query che tiene
conto di tutte le possibili coppie di valori numerici che individuano linizio e la fine di
un segno zodiacale. Le coppie di numeri sui quali si basa la query sono riepilogate nella
Tabella 5.1.
Tabella 5.1 I segni zodiacali con le loro date e i giorni corrispondenti.
Segno Int ervallo dat e Primo giorno Ult imo giorno
Acquario 21 gennaio 18 febbraio 21 49
Pesci 19 febbraio 20 marzo 50 80
Ariete 21 marzo 20 aprile 81 111
Toro 21 aprile 20 maggio 112 141
Gemelli 21 maggio 21 giugno 142 173
Cancro 22 giugno 22 luglio 174 204
Leone 23 luglio 22 agosto 205 235
Vergine 23 agosto 22 settembre 236 266
Bilancia 23 settembre 23 ottobre 267 297
Scorpione 24 ottobre 22 novembre 298 327
Sagittario 23 novembre 21 dicembre 328 356
Capricorno 22 dicembre 20 gennaio 357 20

E questa la query che individua il segno dello zodiaco ricavandolo dal campo
DataNascita di ciascun record della tabella Indirizzi.
SELECT
CASE
WHEN DAYOFYEAR(DataNascita)
BETWEEN 21 AND 49 THEN 'Acquario'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 50 AND 79 THEN 'Pesci'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 80 AND 110 THEN 'Ariete'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 111 AND 140 THEN 'Toro'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 141 AND 172 THEN 'Gemelli'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 173 AND 203 THEN 'Cancro'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 204 AND 235 THEN 'Leone'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 236 AND 264 THEN 'Vergine'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 265 AND 296 THEN 'Bilancia'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 297 AND 326 THEN 'Scorpione'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 327 AND 355 THEN 'Sagittario'
ELSE 'Capricorno'
END
AS 'Segno zodiacale',
Nome,
Cognome,
DataNascita AS 'Data di nascita'
FROM Indirizzi
ORDER BY 'Segno zodiacale'

Il risultato di questa query riportato nella Figura 5.15.

Figura 5.15 La query che individua il segno zodiacale, basata sulla seconda variante della funzione CASE.

La lunghezza di questa query le d un aspetto formidabile, ma in realt molto


lineare: la funzione CASE verifica per dodici volte se il numero dordine della data di
nascita, ottenuto con la funzione DAYOFYEAR , compreso entro uno specifico intervallo e
ogni volta che trova una corrispondenza inserisce il nome del segno nella colonna alias
Segno zodiacale. C soltanto un piccolo dettaglio da considerare: la corrispondenza fra
il numero dordine di una data e un segno zodiacale viene verificata con la clausola
BETWEENAND , perch per ogni segno il numero del primo giorno maggiore di quello

dellultimo. Nel caso, per, del Capricorno, che sta a cavallo fra dicembre e gennaio, il
numero dordine del primo giorno (22 dicembre) 357 e quello dellultimo giorno (20
gennaio) 20. Per le date che ricadono nellintervallo del Capricorno, quindi,
lespressione
WHEN DAYOFYEAR(DataNascita) BETWEEN 357 AND 20 THEN 'Capricorno'

non darebbe un risultato corretto.


Abbiamo volto a nostro vantaggio questa difficolt utilizzando la clausola ELSE:
qualunque data che non trova riscontro in uno degli undici confronti della prima parte di
CASE per definizione da attribuirsi al Capricorno, quindi la clausola ELSE restituisce

direttamente Capricorno senza ricorrere a BETWEENAND .


La versione per SQL Server della query che stiamo commentando questa:
SELECT
CASE
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 21 AND 49 THEN 'Acquario'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 50 AND 79 THEN 'Pesci'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 80 AND 110 THEN 'Ariete'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 111 AND 140 THEN 'Toro'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 141 AND 172 THEN 'Gemelli'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 173 AND 203 THEN 'Cancro'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 204 AND 235 THEN 'Leone'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 236 AND 264 THEN 'Vergine'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 265 AND 296 THEN 'Bilancia'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 297 AND 326 THEN 'Scorpione'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 327 AND 355 THEN 'Sagittario'
ELSE 'Capricorno'
END
AS 'Segno zodiacale',
Nome,
Cognome,
DataNascita AS 'Data di nascita'
FROM Indirizzi
ORDER BY 'Segno zodiacale'

NOT A
I puristi dello Structured Query Language (che spesso hanno atteggiamenti di tipo fondamentalista)
avversano la funzione CASE, perch introduce una componente procedurale in un linguaggio che si
caratterizza come non procedurale.
La sintassi delle varianti di CASE che abbiamo illustrato quella di MySQL e di SQL
Server.
La funzione CASE in entrambe le sue varianti pu essere utilizzata anche in un enunciato
basato sul comando UPDATE, per eseguire un aggiornamento automatico dei valori di una
colonna esistente.
Come si ricorder, la clausola SET di UPDATE ha questa struttura:
SET nome_campo = espressione

dove espressione pu essere unintera clausola CASE.


Se volessimo, quindi, inserire automaticamente il segno zodiacale di tutte le persone
in una nuova colonna chiamata Segno della tabella Indirizzi, non dovremmo fare altro
che creare la nuova colonna con questo enunciato:
ALTER TABLE Indirizzi ADD Segno VARCHAR(15)

e poi eseguire questa query di aggiornamento:


UPDATE Indirizzi
SET Segno = CASE
WHEN DAYOFYEAR(DataNascita)
BETWEEN 21 AND 49 THEN 'Acquario'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 50 AND 79 THEN 'Pesci'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 80 AND 110 THEN 'Ariete'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 111 AND 140 THEN 'Toro'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 141 AND 172 THEN 'Gemelli'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 173 AND 203 THEN 'Cancro'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 204 AND 235 THEN 'Leone'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 236 AND 264 THEN 'Vergine'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 265 AND 296 THEN 'Bilancia'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 297 AND 326 THEN 'Scorpione'
WHEN DAYOFYEAR(DataNascita)
BETWEEN 327 AND 355 THEN 'Sagittario'
ELSE "Capricorno"
END

(Ricordiamo che i rientri delle righe sono facoltativi e non hanno alcun effetto
sullesecuzione dei comandi SQL: qui li utilizziamo per distinguere meglio i vari
elementi di enunciati complessi.)
La stessa query in SQL Server si presenta in questo modo:
UPDATE Indirizzi
SET Segno = CASE
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 21 AND 49 THEN 'Acquario'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 50 AND 79 THEN 'Pesci'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 80 AND 110 THEN 'Ariete'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 111 AND 140 THEN 'Toro'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 141 AND 172 THEN 'Gemelli'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 173 AND 203 THEN 'Cancro'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 204 AND 235 THEN 'Leone'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 236 AND 264 THEN 'Vergine'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 265 AND 296 THEN 'Bilancia'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 297 AND 326 THEN 'Scorpione'
WHEN DATEPART(DAYOFYEAR, DataNascita)
BETWEEN 327 AND 355 THEN 'Sagittario'
ELSE 'Capricorno'
END

Dopo aver eseguito i due comandi, possiamo controllare i risultati


dellaggiornamento con una query come questa:
SELECT
Nome,
Cognome,
DataNascita AS "Data di nascita",
Segno
FROM Indirizzi

la quale dovrebbe generare un elenco come quello che possibile vedere nella Figura
5.16.

Figura 5.16 Lenunciato UPDATE, costruito su una funzione CASE, ha aggiornato selettivamente i valori della
colonna Segno.

A stretto rigore, loperazione che abbiamo appena descritto (creare un campo e


riempirlo automaticamente di dati derivati mediante una funzione da un altro campo)
potrebbe essere considerata inutile e impropria:
inutile, perch le informazioni sul segno zodiacale si possono ricavare in
qualunque momento dal campo DataNascita;
impropria, perch il principio generale che regola la creazione delle tabelle di un
database SQL stabilisce che ciascuna colonna deve contenere un dato indipendente
da tutti gli altri e non derivabile da altre colonne nella stessa tabella. lo stesso
principio che orienta a non utilizzare campi calcolati.
In realt, le informazioni sulle quali si basa lalgoritmo di calcolo del segno zodiacale
(quelle della Tabella 5.1) non sono elementari come per esempio quelle che servono per
calcolare let attuale di una persona a partire dalla sua data di nascita, quindi, qualora
facesse comodo avere gi predisposto nella tabella il dato sul segno zodiacale, la query
di aggiornamento che abbiamo descritto potrebbe rivelarsi utile e opportuna.
Possiamo eliminare agevolmente la colonna Segno dalla tabella Indirizzi ricorrendo
alla clausola DROP del comando ALTER TABLE:
ALTER TABLE Indirizzi
DROP Segno

Utilizzare insieme INSERT e SELECT


In determinati casi pu fare comodo travasare dati da una tabella a unaltra. Questa
operazione si pu eseguire combinando insieme in un solo enunciato i comandi INSERT e
SELECT , in una modalit che abbiamo gi visto nel Capitolo 3.
Facciamo una prova un po pi complessa, predisponendo in MySQL una tabella che
chiameremo Finale, destinataria del travaso, con questo enunciato:
CREATE TABLE Finale
IDPersona INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
Nome VARCHAR(25),
Cognome VARCHAR(25),
Citt VARCHAR(30),
Provincia CHAR(2))

La struttura della nuova tabella dovrebbe presentarsi in questo modo, eseguendo il


comando DESCRIBE Finale in MySQL:
Field Type Null Key Default Ext ra
IDPersona INT(11) PRI [NULL] AUTO_INCREMENT
Nome VARCHAR(25) YES [NULL]
Cognome VARCHAR(25) YES [NULL]
Citt VARCHAR(30) YES [NULL]
Provincia CHAR(2) YES [NULL]
Proviamo ora a travasare dalla tabella Indirizzi dieci record nella tabella Finale,
utilizzando questo enunciato:
INSERT INTO
Finale(Nome, Cognome, Citt, Provincia)
SELECT Indirizzi.Nome,
Indirizzi.Cognome,
Indirizzi.Citt,
Indirizzi.Prov
FROM Indirizzi
WHERE Indirizzi.Indirizzi_id <=10

Se ora selezioniamo tutti i record della tabella Finale, otteniamo il risultato che
possiamo vedere nella Figura 5.17.
Il ricorso alla colonna WHERE ha limitato il travaso a dieci record. Si osservi la
formulazione dellelenco delle colonne nella query:
SELECT
Indirizzi.Nome,
Indirizzi.Cognome,
Indirizzi.Citt,
Indirizzi.Prov

I nomi delle colonne da selezionare sono qualificati col nome della tabella dalla quale
provengono, separato da un carattere punto: Indirizzi.Nome invece di Nome, per esempio.
In questo caso la qualificazione ridondante perch la query di selezione agisce su una
sola tabella, quindi si potrebbe omettere, mentre obbligatoria quando si selezionano da
pi tabelle dorigine campi che hanno lo stesso nome.
Fra le colonne delle tabelle di origine e di quella di destinazione deve esserci
omogeneit di dati, nel senso che i campi di origine e destinazione devono avere gli
stessi tipi di dati. comunque possibile, ricorrendo agli alias nella SELECT, adattare il tipo
dei campi di origine a quelli di destinazione.
Figura 5.17 Nella tabella Finale sono stati travasati dieci record dalla tabella Indirizzi.

Mettendo insieme la sintassi di INSERT che abbiamo visto nel Capitolo 3 e quella che
stiamo esaminando con questo esempio, la struttura sintattica completa di INSERT :
INSERT [INTO] nome_tabella [(elenco_colonne)]
{VALUES (valori) | SELECT ... | SET colonna=valore }

In tutte le query che abbiamo illustrato in questo capitolo e nel precedente, la clausola
FROM indica una sola tabella dorigine dei dati. Il comando SELECT dispiega tutta la sua

potenza (e la sua utilit) soprattutto nelle query che cercano ed estraggono informazioni
elaborando dati da pi tabelle.
Lutilizzo di pi tabelle dorigine comporta anche lutilizzo di una nuova clausola del
comando SELECT, identificata dalla parola chiave JOIN: si tratta di una clausola di grande
importanza, che approfondiamo nel prossimo capitolo.
Capitolo 6
Lavorare con pi tabelle

Nel Capitolo 1 abbiamo descritto sinteticamente le possibili relazioni fra tabelle,


soffermandoci sulla relazione uno a molti che quella usata pi spesso nei database.
Riprendiamo qui questo tema, dando una forma concreta allesempio del Capitolo 1,
dove parlavamo di due tabelle Indirizzi e Telefoni, correlate da una colonna Raccordo
che funge da chiave esterna nella tabella Telefoni e i cui valori possono essere soltanto
quelli esistenti nella colonna IDPersona, che la chiave primaria della tabella Indirizzi,
secondo lo schema che vediamo qui di seguito:
Indirizzi
IDPersona chiave primaria
Cognome
Nome
Indirizzo
Citt
CAP
Provincia

Telefoni
CodiceTelefono chiave primaria
Raccordo chiave esterna
NumeroTelefono

Disponiamo gi di una tabella Indirizzi, possiamo creare la tabella Telefoni eseguendo


questo enunciato:
CREATE TABLE Telefoni
(IDTel INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
Raccordo INT NOT NULL,
Tipo VARCHAR(20),
NumeroTel VARCHAR(25))

Successivamente, prepariamo un file di testo con questi dati:


1; 1; Casa; 026707040
2; 1; Ufficio; 026599789
3; 1; Cell; 3392583691
4; 2; Casa; 0372209087
5; 3; Casa; 037175954
6; 3; Ufficio; 038936348
7; 3; Cell; 3383692584
8; 4; Casa; 034129835
9; 5; Ufficio; 0382364955
10; 6; Casa; 030258367
11; 7; Casa; 035852149
12; 7; Cell; 3472783692
13; 8; Casa; 0321456321
14; 9; Ufficio; 0471753159
15; 10; Casa; 015564312
16; 10; Cell; 3392588527
17; 11; Casa; 037287954
18; 12; Casa; 0383528417
19; 13; Ufficio; 035639528
20; 14; Casa; 03312698741
21; 15; Casa; 030789632
22; 16; Ufficio; 0422693625
23; 17; Casa; 01612587354
24; 18; Ufficio; 0461365478
25; 19; Casa; 0141443215
26; 20; Casa; 0523760057
27; 21; Ufficio; 0382364992
28; 22; Casa; 0473621954
29; 23; Casa; 024231368
30; 24; Ufficio; 0362258941
31; 25; Casa; 038377325
32; 26; Casa; 0471251318
33; 27; Casa; 037232159
34; 28; Cell; 338123432165
Salviamo il file col nome Telefoni.txt in una cartella qualunque del disco C, che
potrebbe essere \Esempi.
Popoliamo di dati la tabella Telefoni eseguendo questo enunciato in MySQL:
LOAD DATA INFILE "C:/Esempi/Telefoni.txt"
INTO TABLE Telefoni
FIELDS TERMINATED BY ';'
LINES TERMINATED BY '\r'

In SQL Server il comando da usare il seguente:


BULK INSERT telefoni FROM 'C:\Esempi\telefoni.txt'
WITH (
DATAFILETYPE = 'char',
FIELDTERMINATOR = ';',
ROWTERMINATOR = '\n'
);

Controlliamo quanto abbiamo fatto fin qui: la nuova tabella Telefoni dovrebbe
presentarsi come nella Figura 6.1.
Figura 6.1 Il contenuto della nuova tabella Telefoni in SQL Server.

Il lavoro di preparazione terminato. Proviamo a eseguire questa query:


SELECT Telefoni.Raccordo, Indirizzi.Nome,
Indirizzi.Cognome, Telefoni.Tipo, Telefoni.NumeroTel
FROM Indirizzi, Telefoni
WHERE Indirizzi.IDPersona=Telefoni.Raccordo
ORDER BY Cognome

Possiamo vedere il risultato nella Figura 6.2: a ogni riga corrisponde un numero di
telefono diverso, associato al nome del suo titolare. Come si pu rilevare dalla figura,
Giovanni Abate e Bruna Bolli hanno tre numeri di telefono, Rosario Fuzzi ne ha due e
gli altri uno ciascuno.
Figura 6.2 La query associa correttamente i vari numeri di telefono a ciascuna persona.

La differenza, che sostanziale, fra la query con la quale abbiamo concluso questo
esempio e tutte quelle che abbiamo visto nei capitoli precedenti sta nel fatto che in questo
caso la query agisce su due tabelle: Indirizzi e Telefoni, estraendo colonne da entrambe,
secondo il criterio indicato nella clausola WHERE.
Nella terminologia dello Structured Query Language si chiama join (congiunzione,
collegamento o relazione) sia il risultato di una query eseguita su pi tabelle, sia il
meccanismo che caratterizza la relazione fra le tabelle.
Il collegamento o join reso possibile dal fatto che le tabelle messe in relazione
hanno un campo in comune che viene richiamato espressamente dalla clausola WHERE.
Questo campo pu avere lo stesso nome nelle tabelle messe in join, ma non
necessariamente, tant vero che nellesempio che stiamo commentando il campo che
stabilisce il join si chiama IDPersona nella tabella Indirizzi e Raccordo nella tabella
Telefoni.
essenziale, per, che il join avvenga fra colonne che hanno lo stesso tipo di dati, nel
nostro caso sono entrambe predisposte per ricevere valori di tipo INT.
I campi richiamati nella query sono identificati e distinti fra loro esplicitando il nome
della tabella seguito da un punto e poi dal nome del campo: nome_tabella.nome_campo.
Quando le query utilizzano join fra pi tabelle, opportuno seguire questa semplice
convenzione per esplicitare quali campi vengono da quali tabelle.
Attenersi a questa regola obbligatorio quando si richiamano colonne che hanno lo
stesso nome, ma si trovano in tabelle diverse; in questi casi essenziale distinguere, per
esempio, il campo Cognome di una tabella Indirizzi dal campo con lo stesso nome della
tabella Clienti, specificando Indirizzi.Cognome e Clienti.Cognome. Nei casi in cui non c
ambiguit, si pu indicare soltanto il nome_campo, ma meglio abituarsi a scrivere
sempre nome_tabella.nome_campo quando si fa un join, anche se molto semplice.
Che cosa succede se si esegue una query attingendo campi da due tabelle senza
esplicitare un join con la clausola WHERE? Vediamolo con un semplice esperimento.
Torniamo a eseguire la query di prima eliminando il join (cio la clausola WHERE), in
questo modo:
SELECT Nome, Cognome, Telefoni.Tipo, Telefoni.NumeroTel
FROM Indirizzi, Telefoni

Otteniamo lo sconcertante risultato che vediamo nella Figura 6.3.


Questa query ha generato la bellezza di 952 righe! Ma dove andata a prenderle, visto
che la tabella Indirizzi contiene soltanto 28 record e la tabella Telefoni 34? E poi, perch
952 e non, per esempio, 62 (la somma delle righe delle due tabelle)?
Se abbiamo la pazienza di scorrere le righe del risultato, ci accorgiamo che ciascun
elemento della tabella Indirizzi stato accoppiato con tutti gli altri elementi della tabella
Telefoni, per cui il risultato il prodotto di 28 per 34, che d, appunto, 952.
Unoperazione combinatoria di questo genere si chiama prodotto cartesiano ed
uninsidia sempre latente quando si fanno i join: formulandoli male o non formulandoli
affatto si ottengono tabelle virtuali mostruose e del tutto inutili. Finch si fanno prove su
piccolissime tabelle, poco male, ma in un database di produzione se una query male
impostata esegue il prodotto cartesiano di due tabelle, una di 30.000 righe e unaltra di
10.000, ciascuna lunga 1000 caratteri, genera la bellezza di trecento milioni di record per
un totale di trecento miliardi di caratteri, quanto basta per far traballare anche un
computer mainframe di buona potenza.
Figura 6.3 Il risultato della query che estrae campi da due tabelle senza che sia specificato un vincolo di join.

Per prevenire il rischio di generare un prodotto cartesiano, si deve obbligatoriamente


specificare, con una clausola WHERE o con unaltra clausola che vedremo pi avanti, che
tipo di relazione o join si intende creare fra le tabelle. quello che abbiamo fatto
definendo la clausola WHERE in questo modo:
WHERE Indirizzi.IDPersona=Telefoni.Raccordo
La clausola JOIN
Se eseguiamo questa query:
SELECT
Telefoni.Raccordo,
Indirizzi.Nome,
Indirizzi.Cognome,
Telefoni.Tipo,
Telefoni.NumeroTel
FROM Indirizzi
INNER JOIN Telefoni
ON Indirizzi.IDPersona=Telefoni.Raccordo
ORDER BY Indirizzi.Cognome

avremo un risultato identico a quello che avevamo ottenuto con la query precedente:
SELECT
Telefoni.Raccordo,
Indirizzi.Nome,
Indirizzi.Cognome,
Telefoni.Tipo,
Telefoni.NumeroTel
FROM Indirizzi, Telefoni
WHERE Indirizzi.IDPersona=Telefoni.Raccordo
ORDER BY Cognome

Ci dovuto al fatto che, in alternativa alla clausola WHERE, si pu stabilire una


relazione fra tabelle con una clausola specifica, chiamata JOIN, che ha la struttura
sintattica:
JOIN tabella1 ON tabella1.colonna_x = tabella2.colonna_y

dove colonna_x di solito la chiave primaria di tabella1 e colonna_y di solito la


chiave esterna di tabella2. Diciamo di solito, perch un join pu essere eseguito anche
fra colonne qualsiasi, purch i loro tipi di dati corrispondano.
Lo Structured Query Language prevede vari tipi di join, che sono supportati in diverse
maniere dalle specifiche implementazioni del linguaggio.
Si usa distinguere fra due tipi generali di join:
equi-join: clausola JOIN nella quale la condizione specificata dopo il qualificatore ON
uneguaglianza;
non equi-join: clausola JOIN nella quale la condizione specificata un confronto
diverso dalleguaglianza.
Oltre a questa distinzione, basata sul tipo di confronto, si distinguono join interni
(INNER JOIN) da join esterni (OUTER JOIN), join a sinistra (LEFT JOIN) e join a destra (RIGHT JOIN).
Esistono poi un FULL JOIN e un CROSS JOIN.
I join di tipo INNER sono quelli utilizzati pi comunemente. Una query che utilizza un
INNER JOIN restituisce tutte le righe che si accoppiano e soltanto quelle. Di solito proprio
questo tipo di risultato che si vuole ottenere.
LEFT JOIN
Possono esistere situazioni nelle quali si vogliono vedere tutti i record della tabella
del lato sinistro della relazione, che abbiano oppure no record con i quali agganciarsi
nel lato destro; in questi casi, si pu utilizzare un LEFT OUTER JOIN. Un caso tipico pu
essere la verifica dei clienti che hanno emesso ordini in un determinato periodo.
Abbiamo una tabella chiamata OrdiniLuglio, i cui record sono composti da
CodiceCliente, CodiceOrdine, Quantit, Descrizione e PrezzoUnitario. Questa tabella si
trova sul lato molti di una relazione uno a molti con la tabella Clienti, tramite il campo
CodCli (che nella tabella Clienti la chiave primaria) e il campo CodiceCliente (che in
OrdiniLuglio la chiave esterna).
Tabella 6.1 La tabella Clienti dellesempio.
CodCli RagSoc
BERTA Bertani & C Sas
CARAU Carpenteria Audace SpA
CATTI Cattabeni Impianti Snc
MECPA Meccanica Parodi Srl
MOPIT Mobilificio Pittaluga Snc
PREFA Prefabbricati Anselmo Sas

Tabella 6.2 La tabella OrdiniLuglio dellesempio.


CodiceClient e CodiceOrdine Dat aOrdine Quant it Descrizione
CATTI CAT01 2004-07-05 1340 Chiodo 50x10
CATTI CAT02 2004-07-05 1770 Rondella 12/6
CATTI CAT02 2004-07-05 1930 Rondella 10/6
MOPIT MOP01 2004-07-15 300 Chiodo 40x10
MOPIT MOP01 2004-07-15 1370 Rondella 12/5
MOPIT MOP01 2004-07-15 120 Bullone brugola 9x3,5
MOPIT MOP01 2004-07-15 320 Chiodo 4x1

Lesecuzione di questa query:


SELECT
Clienti.RagSoc AS Ragione_Sociale,
OrdiniLuglio.DataOrdine,
OrdiniLuglio.Descrizione,
OrdiniLuglio.Quantit
FROM Clienti
INNER JOIN OrdiniLuglio
ON Clienti.CodCli=OrdiniLuglio.CodiceCliente

produrrebbe questo elenco:


Ragione_Sociale Dat aOrdine Descrizione Quant it
Cattabeni Impianti Snc 2016-07-05 Chiodo 50x10 1340
Cattabeni Impianti Snc 2016-07-05 Rondella 12/6 1770
Cattabeni Impianti Snc 2016-07-05 Rondella 10/6 1930
Mobilificio Pittaluga Snc 2016-07-15 Chiodo 40x10 300
Mobilificio Pittaluga Snc 2016-07-15 Rondella 12/5 1370
Mobilificio Pittaluga Snc 2016-07-15 Bullone brugola 9x35 120
Mobilificio Pittaluga Snc 2016-07-15 Chiodo 4x1 320

cio tutte e solo le righe delle due tabelle che trovano corrispondenza fra i campi
CodCli e CodiceCliente; mentre lutilizzo di un LEFT JOIN al posto dellINNER JOIN:
SELECT
Clienti.RagSoc AS Ragione_Sociale,
OrdiniLuglio.DataOrdine,
OrdiniLuglio.Descrizione,
OrdiniLuglio.Quantit
FROM Clienti
LEFT OUTER JOIN OrdiniLuglio
ON Clienti.CodCli=OrdiniLuglio.CodiceCliente

provocherebbe lestrazione di tutte le righe della tabella del lato sinistro (lato uno),
anche di quelle che non hanno corrispondenza con le righe della tabella del lato destro
(lato molti), come si vede nella Figura 6.4.

Figura 6.4 Leffetto di un LEFT OUTER JOIN.

I clienti che non hanno emesso ordini nel mese di luglio sono immediatamente
visibili, perch la loro Ragione sociale compare una sola volta e nelle colonne della
tabella OrdiniLuglio richiamate dalla query invece di un valore c NULL .
La parola chiave OUTER in MySQL irrilevante, quelle che contano per caratterizzare un
JOIN sono INNER , LEFT o RIGHT, quindi la clausola:
LEFT OUTER JOIN OrdiniLuglio
ON Clienti.CodCli=OrdiniLuglio.CodiceCliente

produrrebbe lo stesso risultato se la scrivessimo nella forma:


LEFT JOIN OrdiniLuglio
ON Clienti.CodCli=OrdiniLuglio.CodiceCliente

In altre implementazioni di SQL, invece, la parola chiave OUTER obbligatoria.


possibile qualificare ulteriormente una query su pi tabelle aggiungendo una
clausola WHERE dopo la clausola JOIN. Se completassimo in questo modo la query di tipo
INNER JOIN che abbiamo provato prima:
SELECT
Clienti.RagSoc AS Ragione_Sociale,
OrdiniLuglio.DataOrdine,
OrdiniLuglio.Descrizione,
OrdiniLuglio.Quantit
FROM Clienti
INNER JOIN OrdiniLuglio
ON Clienti.CodCli=OrdiniLuglio.CodiceCliente
WHERE OrdiniLuglio.Descrizione LIKE "Chiodo%"

otterremmo soltanto queste righe:


Ragione_Sociale Dat aOrdine Descrizione Quant it
Cattabeni Impianti Snc 2016-07-05 Chiodo 50x10 1340
Mobilificio Pittaluga Snc 2016-07-15 Chiodo 40x10 300
Mobilificio Pittaluga Snc 2016-07-15 Chiodo 4x1 320

Join particolari
Nel Capitolo 1 abbiamo accennato brevemente alle relazioni molti a molti e al fatto
che per poterle gestire in un database relazionale bisogna scomporle in due o pi
relazioni uno a molti, creando contestualmente una tabella che faccia da ponte e consenta
una relazione ordinata fra le coppie di tabelle: non un concetto immediatamente
intuitivo, e sar pi semplice capirlo con un esempio concreto.
In un nostro database chiamato Biblioteca abbiamo quattro tabelle: Autori, Titoli,
Editori e Titolo_Autore., le cui strutture sono descritte nelle Tabelle 6.3-6.6.
Tabella 6.3 La struttura della tabella Autori del database dimostrativo Biblioteca.
Colonna T ipo dat i Chiave primaria
ID_Au INT(11) S
Autore VARCHAR(16)

Tabella 6.4 La struttura della tabella Titoli del database dimostrativo Biblioteca.
Colonna T ipo dat i Chiave primaria
Titolo VARCHAR(66)
AnnoPubbl INT(11)

ISBN VARCHAR(13) S
ID_Ed INT(11)

Tabella 6.5 La struttura della tabella Editori del database dimostrativo Biblioteca.
Colonna T ipo dat i Chiave primaria
ID_Ed INT(1) S
Nome VARCHAR(45)

Descrizione VARCHAR(255)

Citt VARCHAR(25)

Stato VARCHAR(5)

Tabella 6.6 La struttura della tabella Titolo_Autore del database dimostrativo Biblioteca.
Colonna T ipo dat i Chiave primaria
ISBN VARCHAR(13) S
ID_Au INT(11) S

La tabella Titoli contiene informazioni sui libri, ognuno individuato dal codice ISBN,
cio lInternational Standard Book Number che identifica in modo univoco ogni libro in
circolazione. Questa tabella sta dal lato molti di una relazione uno a molti con la tabella
Editori, dove lo stesso codice ISBN che in Titoli la chiave primaria la chiave
esterna.
La tabella Autori contiene due sole colonne, ID_Au, la chiave primaria, che identifica
univocamente ciascun autore, e la colonna Autore, che contiene il nome dellautore.
Dato che fra Autori e Titoli esiste una relazione molti a molti, la tabella di raccordo
Titolo_Autore fa da ponte fra Autori e Titoli, avendo un campo ISBN e un campo ID_Au
per ogni coppia autore-titolo. La Figura 6.5 illustra le strutture delle tabelle e le loro
relazioni.
Figura 6.5 Strutture e relazioni delle tabelle Autori, Titolo_Autore, Titoli ed Editori.

Vogliamo ottenere un catalogo che elenchi tutti i titoli dei libri in ordine alfabetico,
associando a ciascun titolo il nome dellautore; se un libro ha pi autori, il catalogo
mostrer pi righe del titolo, ciascuna associata al nome di uno degli autori.
Nel costruire la query che dovr darci il nostro risultato faremo riferimento ai nomi
delle tabella con un alias: una tecnica consentita dallo standard SQL anche per i nomi
delle tabelle e non soltanto per quelli delle colonne. Di solito si ricorre a un alias per il
nome di una colonna per far comparire nei risultati intestazioni pi descrittive al posto
dei nomi usati internamente: per esempio, Ragione sociale invece di RagSoc. Nelle query
che attingono dati da pi tabelle, dove obbligatorio usare la forma
nome_tabella.nome_colonna, si usano alias per i nomi delle tabelle per ridurre la
quantit di caratteri da digitare. Lalias si specifica nella clausola FROM , in uno di questi
due modi:
FROM Indirizzi A
FROM Indirizzi AS A

che assegnano entrambi lalias A alla tabella Indirizzi.


La query per generare il catalogo pu assumere questa forma:
SELECT
a.Autore,
t.Titolo,
e.Nome AS Editore
FROM
Autori a,
Titolo_Autore t1,
Titoli t,
Editori e
WHERE a.ID_Au=t1.ID_Au
AND t.ISBN=t1.ISBN
AND e.IDEd=t.ID_Ed
ORDER BY t.Titolo

oppure questaltra, che non utilizza gli alias:


SELECT
Autore,
Titolo,
Nome AS Editore
FROM
Autori,
Titolo_Autore,
Titoli,
Editori
WHERE autori.ID_Au=titolo_autore.ID_Au
AND titoli.ISBN=titolo_autore.ISBN
AND editori.IDEd=Titoli.ID_Ed
ORDER BY titoli.Titolo

I risultati della query dovrebbero presentarsi come nella Figura 6.6. Possiamo vedere
che le righe 8 e 9 hanno lo stesso titolo e lo stesso editore con due autori diversi. Lo
stesso vale per le righe 11 e 12, per le righe 14 e 15 e altre ancora.

Figura 6.6 I titoli che hanno pi autori sono ripetuti pi volte.

La query stata scritta facendo rientrare alcuni elementi, cos pi facile distinguerli:
in tutte le implementazioni di SQL consentito inserire spazi e a capo nei comandi,
come meglio si crede, perch linterprete dei comandi non ne tiene conto.
Nonostante il suo aspetto formidabile, la query che stiamo commentando non contiene
nulla che gi non sia stato visto e spiegato in precedenza.
La clausola FROM assegna gli alias, per cui:

la tabella Autori diventa a,


Titolo_Autore diventa t1,
Titoli si chiama t,
Editori diventa e.
Questi alias sono utilizzati nella clausola SELECT per qualificare i nomi delle colonne da
estrarre, anteponendo a ciascuna lalias della tabella da cui proviene.
La clausola WHERE stabilisce tre condizioni che, essendo legate dalloperatore logico AND ,
devono essere tutte contemporaneamente vere affinch linsieme delle condizioni risulti
soddisfatto.
La stessa query pu essere formulata utilizzando JOIN invece di WHERE, in questo modo:
SELECT
a.Autore,
t.Titolo,
e.Nome AS Editore
FROM Titoli t
INNER JOIN Titolo_Autore t1
ON t.ISBN = t1.ISBN
INNER JOIN Autori a
ON a.ID_Au = t1.ID_Au
INNER JOIN Editori e
ON e.IDEd = t.ID_Ed
ORDER BY t.Titolo

ottenendo lo stesso risultato che si vede nella Figura 6.6.


Per avere un elenco dei soli titoli che hanno pi di un autore, potremmo modificare la
query in questo modo:
SELECT
t.Titolo AS 'Titoli con pi autori'
FROM
Autori a,
Titolo_Autore t1,
Titoli t
WHERE a.ID_Au=t1.ID_Au
AND t.ISBN=t1.ISBN
GROUP BY t.Titolo
HAVING Count(t.Titolo)>1
ORDER BY t.Titolo

La prima parte della query (clausole SELECT e FROM ) compone i record associando
correttamente le colonne dalle tre tabelle; nella seconda parte i record vengono
raggruppati mediante la clausola GROUP BY, nella quale la clausola HAVING fa considerare
soltanto le righe che danno un valore maggiore di uno quando vengono conteggiate con
la funzione di aggregazione COUNT.
Dal momento che la query utilizza GROUP BY, per le ragioni che abbiamo illustrato nel
paragrafo La clausola HAVING del Capitolo 5 non possiamo elencare nella clausola
SELECT altre colonne oltre a Titolo (e neppure avrebbe senso farlo, in questo caso).
Le sottoquery
In determinate situazioni pu essere necessario eseguire una query per ricavare
risultati intermedi, da elaborare successivamente con unaltra query. Lo standard SQL
consente di eseguire operazioni di questo genere con un solo enunciato, composto da
una query principale e un numero illimitato di query subordinate o sottoquery.
Per gli esempi di sottoquery utilizzeremo tre tabelle chiamate Ordini, Clienti e
Venditori, che hanno le strutture e i contenuti che sono elencati nelle Tabelle 6.7-6.9.
Tabella 6.7 Struttura e contenuti della tabella Ordini dellesempio.
Onum Import o Cdat a Cnum Vnum
301 18.69 2016-03-07 C08 V07
302 1900.10 2016-03-07 C07 V04
303 767.19 2016-03-07 C01 V01
305 5160.45 2016-03-07 C03 V02
306 1098.16 2016-03-07 C08 V07
307 75.75 2016-04-07 C04 V02
308 4723.10 2016-05-07 C06 V01
309 1713.23 2016-04-07 C02 V03
310 1309.95 2016-06-07 C04 V02
311 9891.88 2016-06-07 C06 V01

Tabella 6.8 Struttura e contenuti della tabella Clienti dellesempio.


Cnum Cnome Cit t Classe Vnum
C01 Heredia Torino 100 V01
C02 Gennari Roma 200 V03
C03 Lucchini Genova 200 V02
C04 Gastoni Verona 300 V02
C06 Carletti Torino NULL V01
C07 Padovani Roma 100 V04
C08 Camilli Genova 300 V07

Tabella 6.9 Struttura e contenuti della tabella Venditori dellesempio.


Vnum Vnome Cit t Perc
V01 Parini Torino 0,12
V02 Savoldi Genova 0,13
V03 Astolfi Bologna 0,10
V04 Masseroni Torino 0,11
V07 Russo Milano 0,15
Iniziamo con un esempio semplice: vogliamo elencare i contratti del venditore
Masseroni, ma non ricordiamo il suo codice (Vnum) che chiave primaria nella tabella
Venditori e chiave esterna nelle tabelle Ordini e Clienti.
Eseguiamo questa query:
SELECT *
FROM Ordini
WHERE
Vnum = (SELECT Vnum
FROM Venditori
WHERE Vnome='Masseroni')

il cui risultato riportato nella Figura 6.7.

Figura 6.7 La query ha dato un risultato corretto.

In questa query abbiamo due enunciati SELECT: il primo forma la query principale o
esterna e il secondo la sottoquery, detta anche query interna. Lesecuzione
dellenunciato procede dallinterno verso lesterno, per cui viene prima eseguita la
sottoquery:
SELECT
Vnum
FROM Venditori
WHERE Vnome='Masseroni')

che estrae il valore V04, il codice Vnum del venditore Masseroni.


Questo valore viene fornito alla query principale, facendole assumere la forma:
SELECT *
FROM Ordini
WHERE Vnum='V04'

Cos trasformata, la query pu estrarre correttamente la riga:


302 1900.10 2016/03/07 C07 V04

lunica riga della tabella Ordini che trova corrispondenza con il criterio espresso nella
clausola WHERE della query esterna.
Se formulassimo la query composta in questo modo:
SELECT *
FROM Ordini
WHERE
Vnum = (SELECT Vnum
FROM Venditori
WHERE Citt="Torino")

la sottoquery estrarrebbe due valori (V01 e V04) e la query esterna non potrebbe essere
eseguita, in quanto sintatticamente scorretta, perch assumerebbe la forma:
SELECT *
FROM Ordini
WHERE Vnum= "V01" "V04"

In MySQL non si ha alcuna segnalazione di errore, semplicemente la query non


restituisce alcun risultato, mentre in SQL Server si riceve questa puntuale segnalazione
di errore:
Messaggio 512, livello 16, stato 1, riga 1
La sottoquery ha restituito pi di un valore. Ci non consentito per le sottoquery che seguono i
caratteri =, !=, <, <= , >, >= o utilizzate come espressione.

Come lascia capire il commento esplicativo della segnalazione di errore emessa da


SQL Server, bisogna evitare di predisporre nella clausola WHERE della query esterna un
operatore di confronto, perch questo pu trattare un solo dato e non una serie di valori.
Nel paragrafo Il predicato IN del Capitolo 5 abbiamo osservato che una query
costruita in questo modo:
SELECT
Nome,
Cognome,
Provincia
FROM Indirizzi
WHERE Provincia IN ('PC', 'MI', 'PV')
ORDER BY Provincia

produce lo stesso risultato di questaltra:

SELECT
Nome,
Cognome,
Provincia
FROM Indirizzi
WHERE Provincia='PC'
OR Provincia='MI'
OR Provincia='PV'
ORDER BY Provincia

Ci dovuto al fatto che il predicato IN un operatore logico col quale si presenta


(inserendoli fra parentesi tonde) un elenco di valori che la clausola WHERE pu considerare
validi (cio hanno valore logico TRUE).
Tenendo conto di questa funzionalit di IN, possiamo riformulare in questo modo
lenunciato che prima ha dato errore:
SELECT *
FROM Ordini
WHERE Vnum IN
(SELECT Vnum
FROM Venditori
WHERE Citt='Torino')
sostituendo, cio, nella clausola WHERE della query esterna, il segno di uguale con il
predicato IN. La query esterna a questo punto non ha difficolt a gestire lelenco di valori
che riceve dalla sottoquery e produce il risultato che possiamo vedere nella Figura 6.8.

Figura 6.8 La query principale associata a una sottoquery mediante IN genera un risultato corretto.

In termini generali, quando non si pu essere sicuri che una sottoquery generi un solo
valore, si pu mettere il suo risultato a disposizione della clausola WHERE della query
esterna mediante loperatore IN.
Potremmo riformulare in questo modo la query del primo esempio:
SELECT *
FROM Ordini
WHERE Vnum IN
(SELECT Vnum
FROM Venditori
WHERE Vnome='Masseroni')

ottenendo lo stesso risultato mostrato nella Figura 6.7, perch lelenco di valori forniti
alla clausola WHERE mediante IN pu contenere anche un solo valore.
Nel paragrafo La clausola HAVING del Capitolo 5 abbiamo visto che HAVING equivale
a WHERE e serve per definire criteri di scelta fra un insieme di valori ottenuti per
raggruppamento, cio mediante la clausola GROUP BY. Vediamo come si pu sfruttare la
funzionalit di HAVING in una query composta.
Nel nostro database i clienti sono classificati con un valore Classe che pu assumere
un valore compreso fra 100 e 300. Vogliamo contare i clienti che hanno una Classe
superiore alla media dei clienti di Genova.
Prepariamo ed eseguiamo questa query composta:
SELECT
Classe,
Count(Cnum) AS ConteggioClienti
FROM Clienti
GROUP BY Classe
HAVING Classe > (SELECT AVG(Classe)
FROM Clienti
WHERE Citt = 'Genova');

Il risultato riportato nella Figura 6.9: vi sono due clienti con una Classe superiore
alla media dei clienti di Genova.

Figura 6.9 Una query composta che utilizza HAVING.

Sottoquery correlate
Una sottoquery viene detta correlata quando fa riferimento alla tabella richiamata
dalla clausola FROM della query esterna e produce un valore ogni volta che la query
esterna prende in esame una riga. Le sottoquery correlate sono uno dei concetti pi
sottili dello Structured Query Language e possono essere molto potenti, a condizione di
capire bene come si comportano e che cosa producono.
Vogliamo trovare tutti i clienti che hanno emesso ordini il 7 marzo 2016: possiamo
ottenere questo risultato in vari modi; proviamo a risolverlo con questa query composta
che utilizza una sottoquery correlata:
SELECT *
FROM Clienti esterna
WHERE '2016/03/07'
IN (SELECT Odata
FROM Ordini interna
WHERE esterna.Cnum = interna.Cnum);

Il risultato riportato nella Figura 6.10.


Figura 6.10 La query composta ha eseguito pi volte la sottoquery correlata.

Per rendere pi chiaro il funzionamento della query che stiamo commentando,


abbiano utilizzato gli alias esterna e interna rispettivamente per le tabelle Clienti
(sulla quale lavora la query principale o esterna) e Ordini (sulla quale si esegue la query
correlata o interna). Siccome il valore della colonna Cnum della query esterna varia, la
query interna deve essere eseguita separatamente per ciascuna riga della query esterna.
La riga della query esterna in base alla quale viene eseguita la query interna si chiama
convenzionalmente riga candidata.
Seguiamo passo per passo il procedimento.
Client i
Cnum Cnome Cit t Classe Vnum
C01 Heredia Torino 100 V01
C02 Gennari Roma 200 V03
C03 Lucchini Genova 200 V02
C04 Gastoni Verona 300 V02
C06 Carletti Torino NULL V01
C07 Padovani Roma 100 V04
C08 Camilli Genova 300 V07

Ordini
Onum Import o Cdat a Cnum Vnum
301 18.69 2016-03-07 C08 V07
302 1900.10 2016-03-07 C07 V04
303 767.19 2016-03-07 C01 V01
305 5160.45 2016-03-07 C03 V02
306 1098.16 2016-03-07 C08 V07
307 75.75 2016-04-07 C04 V02
308 4723.10 2016-05-07 C06 V01
309 1713.23 2016-04-07 C02 V03
310 1309.95 2016-06-07 C04 V02
311 9891.88 2016-06-07 C06 V01

Seleziona la prima riga dalla tabella Clienti.


La memorizza come riga candidata nella tabella alias esterna.
Cnum Cnome Cit t Classe Vnum
C01 Heredia Torino 100 V01

Esegue la sottoquery, che percorre tutta la tabella Ordini per trovare le righe nelle
quali la colonna Cnum ha lo stesso valore di esterna.Cnum.
Trova la riga con Onum 303, che ha C01 nella colonna Cnum.

Onum Import o Odat a Cnum VNum


303 767.19 2016/03/10 C01 V01

Accantona il valore della colonna Odata da ciascuna riga di Ordini che soddisfa la
condizione esterna.Cnum = interna.Cnum e crea un insieme di valori Odata.
Esamina questo insieme per vedere se ce ne sono col valore 7 marzo 2016. Da quelli
che trova (ne trova uno subito) ricava le informazioni per selezionare la riga
corrispondente e inviarla come output della query principale.
Ripete lintero ciclo dalla riga successiva di Clienti (Gennari) e continua fino a
quando ha controllato tutte le righe di Clienti.
Come si vede, con una sottoquery correlata si pu eseguire un lavoro notevolmente
complesso.
Non volendo ricorrere alla sottoquery, si pu ottenere lo stesso risultato con un join
(cio con la clausola WHERE), in questo modo:
SELECT primo.Cnum, primo.Cnome
FROM Clienti primo, Ordini secondo
WHERE primo.Cnum=secondo.Cnum
AND secondo.Odata = '2016/03/07'
Figura 6.11 La query senza sottoquery come viene eseguita in MySQL.

Come si pu vedere dalla Figura 6.11, questa query produce una riga in pi della
precedente (la riga di Camilli, Cnum C08, compare due volte), perch alla data del 7
marzo Camilli ha due ordini. Specificando nella query il qualificatore DISTINCT, in questo
modo:
SELECT DISTINCT primo.Cnum, primo.Cnome
FROM Clienti primo, Ordini secondo
WHERE primo.Cnum=secondo.Cnum
AND secondo.Odata = '2016/03/07'

il risultato diventa:
Cnum Cnome
C01 Heredia
C03 Lucchini
C07 Padovani
C08 Camilli

Vediamo un altro esempio di sottoquery correlata. Desideriamo estrarre dalla tabella


Ordini le righe corrispondenti agli ordini che hanno importi superiori alla media di
ciascun cliente. Ecco la query:
SELECT *
FROM Ordini esterna
WHERE Importo > (SELECT AVG(importo)
FROM Ordini interna
WHERE interna.Cnum=esterna.Cnum);

Il risultato quello che si pu vedere nella Figura 6.12.


Figura 6.12 La query ha estratto tre ordini.

Quasi tutte le righe della tabella Ordini sono riferite a un solo cliente, quindi vengono
scartate dalla query perch la media dellimporto coincide con limporto stesso e non
viene quindi soddisfatta la condizione WHERE. Le medie degli importi per i tre clienti che
hanno due ordini sono rispettivamente:
C04 692,85
C06 7307,44
C08 558,42
quindi la query ha derivato correttamente nuove informazioni dai dati esistenti.
Si noti che questo risultato non ottenibile con un join, cio ricorrendo alla sola
clausola WHERE, senza utilizzare una sottoquery, perch non consentito confrontare valori
singoli con valori aggregati.
Ecco un altro problema che si presta molto bene a essere risolto con una sottoquery
correlata: desideriamo conoscere il totale degli importi della tabella Ordini, raggruppati
per data, eliminando tutte le date nelle quali la somma non supera almeno di 2000,00
limporto massimo di ciascun gruppo di date.
Questa la query:
SELECT
Odata,
SUM(Importo) AS TotaleImporti
FROM Ordini esterna
GROUP BY Odata
HAVING SUM(Importo) > (SELECT 2000.00 + MAX(Importo)
FROM Ordini interna
WHERE esterna.Odata = interna.Odata);

La query produce il seguente risultato:


Odat a Tot aleImport i
2016-03-07 8944.59
Gli ordini sono raggruppabili su quattro date, ma solo un gruppo soddisfa la
condizione, quello ottenuto aggregando in base alla data del 7 marzo 2016:
301 18.69 2016-03-07 C08 V07
302 1900.10 2016-03-07 C07 V04
303 767.19 2016-03-07 C01 V01
305 5160.45 2016-03-07 C03 V02
306 1098.16 2016-03-07 C08 V07

Infatti, sommando 2000.00 al valore massimo di questo gruppo (5160.45) si ottiene 7160.45,
che inferiore alla somma dei cinque valori che formano il gruppo.
Una sottoquery correlata con la query principale tramite la clausola HAVING viene
valutata per ciascun gruppo della query esterna, non per ciascuna riga. Quando la query
esterna si basa su GROUP BY e HAVING , la sottoquery correlata deve utilizzare una clausola
WHERE e non pu contenere clausole GROUP BY o HAVING .

Loperatore logico EXISTS


In questo capitolo e nel precedente abbiamo utilizzato pi volte gli operatori logici
comuni a tutti i linguaggi di programmazione (AND , OR e NOT) e altri specifici dello
Structured Query Language, quali BETWEEN, LIKE e IN. La loro funzionalit simile a quella
delle funzioni, perch restituiscono un valore quando sono associati con uno o pi
argomenti. Il valore che restituiscono detto booleano o logico e pu essere soltanto TRUE
(vero) o FALSE (falso).
In questo e nel prossimo paragrafo completiamo la rassegna degli operatori logici
disponibili in SQL, mostrando alcuni esempi di utilizzo di EXISTS , ANY, SOME e ALL .
Loperatore EXISTS si applica a una sottoquery e restituisce TRUE se la sottoquery genera
almeno una riga. Ecco un semplice esempio: prima di lavorare sulla tabella Clienti
vogliamo verificare se c qualche cliente che risiede a Genova. Possiamo trovare la
risposta con una query composta come questa:
SELECT Cnum, Cnome, Citt
FROM Clienti
WHERE EXISTS (SELECT *
FROM Clienti
WHERE Citt='Genova');

Il risultato della query riportato nella Figura 6.13.


Figura 6.13 confermato che nella tabella Clienti esistono clienti che risiedono a Genova.

Verrebbe fatto di chiedersi perch mai si dovrebbe usare una query come questa,
quando la verifica sarebbe pi agevole e immediata con
SELECT Cnum, Cnome, Citt
FROM Clienti
WHERE Citt='Genova';

che non utilizza una sottoquery e produrrebbe questo utile e sintetico risultato:
Cnum Cnome Cit t
C03 Lucchini Genova
C08 Cavilli Genova

Niente da obiettare: vero, si pu ottenere un risultato migliore e pi sintetico, in casi


come questo, evitando di utilizzare loperatore EXISTS . Questo operatore, insieme con
altri tre ALL , ANY e SOME), si utilizza per qualificare le sottoquery, ma ha senso servirsene
soltanto per lavorare su sottoquery correlate.
Loperatore EXISTS riferito a una sottoquery produce un solo risultato fra due possibili:
vero o falso. Se la sottoquery estrae qualche dato, una riga o centomila, EXISTS invia il
suo risultato (che TRUE, vero, in questo caso) alla query esterna. Se la sottoquery non
estrae dati, EXISTS lo fa sapere alla query esterna inviandole il valore logico FALSE.
Nellesempio elementare che stiamo commentando, EXISTS segnala TRUE alla query
esterna, avendo verificato che la sottoquery produce un certo numero di righe. In base al
segnale ricevuto, viene eseguita la query esterna
SELECT Cnum, Cnome, Citt
FROM Clienti

che genera le sette righe riportate nella Figura 6.13.


Quando si lavora con una sottoquery correlata, la clausola EXISTS viene valutata
separatamente per ciascuna riga della tabella sulla quale si muove la query esterna. In
questi casi, EXISTS si dimostra utile, perch pu generare una risposta diversa per ciascuna
riga esplorata dalla query esterna.
Creiamo una query composta con una sottoquery correlata per ottenere questo
risultato: sapere quali venditori hanno pi di un cliente.
SELECT DISTINCT Vnum
FROM Clienti esterna
WHERE EXISTS (SELECT *
FROM Clienti interna
WHERE interna.Vnum = esterna.Vnum
AND interna.Cnum<>esterna.Cnum);

Molto sobriamente, la query restituisce questo risultato:


Vnum
V01
V02

La query interna cerca le righe che corrispondono col valore Vnum (cio clienti che
hanno lo stesso venditore) ma hanno un Cnum diverso (quindi un altro cliente). Quando
la sottoquery trova una riga con queste caratteristiche, EXISTS segnala TRUE alla query
esterna, che quindi estrae il valore corrispondente di Vnum.
Il qualificatore DISTINCT nella clausola SELECT della query esterna serve per evitare che
loutput venga raddoppiato, dato che lintera query lavora su due istanze (individuate
dagli alias esterna e interna) della stessa tabella Clienti.
Proviamo a rendere pi chiaro il risultato che abbiamo ottenuto, visualizzando anche
nome e citt dei venditori che sono stati individuati. Ecco la query modificata:
SELECT DISTINCT prima.Vnum, Vnome, prima.Citt
FROM Venditori prima, Clienti seconda
WHERE EXISTS (SELECT *
FROM Clienti terza
WHERE seconda.Vnum = terza.Vnum
AND seconda.Cnum <> terza.Cnum)
AND prima.Vnum = seconda.Vnum;

Il risultato della query dovrebbe presentarsi come nella Figura 6.14.


La sottoquery uguale alla precedente, abbiamo soltanto cambiati gli alias perch in
questo caso si agisce su due tabelle.
La query esterna specifica nella clausola FROM un join fra le tabelle Venditori e Clienti,
in modo da poter ricavare i valori di Vnome e di Citt dalla tabella Venditori quando la
sottoquery correlata le segnala che ha trovato corrispondenza fra Vnum nei due alias di
Clienti, ma contestualmente i valori di Cnum sono diversi negli stessi alias.
Il vincolo
AND prima.Vnum = seconda.Vnum

che completa la query esterna deve essere soddisfatto perch la condizione WHERE sia
valida.

Figura 6.14 La query produce informazioni pi complete.

Dal momento che loperatore EXISTS genera un valore logico TRUE o FALSE,
perfettamente legittimo combinarlo con loperatore logico NOT, invertendo in questo
modo il risultato prodotto da EXISTS .
Mentre con le query precedenti abbiamo verificato quali venditori hanno pi di un
cliente, con questa controlliamo quali venditori non hanno pi di un cliente:
SELECT DISTINCT Vnum
FROM Clienti esterna
WHERE NOT EXISTS (SELECT *
FROM Clienti interna
WHERE interna.Vnum = esterna.Vnum
AND interna.Cnum<>esterna.Cnum);

Come si pu immaginare, la query restituisce questo risultato:


Vnum
V03
V04
V07
Gli operatori logici ANY, SOME e ALL
In inglese i termini any e some sono sostanzialmente sinonimi, con differenze
determinate occasionalmente dal contesto e dalluso. Nello standard SQL sono presenti
entrambe le parole chiave ANY e SOME utilizzabili nello stesso modo: per indicare un
qualunque valore, ma almeno uno, da un possibile elenco. Possiamo considerare ANY (e
SOME ) come una variante pi elastica di IN, in quanto si pu associare ANY con uno qualsiasi
degli operatori di confronto, cosa che con IN non consentita. Per esempio, con questa
query:
SELECT *
FROM Venditori
WHERE Citt = ANY (SELECT Citt
FROM Clienti);

possiamo sapere quali venditori hanno clienti che risiedono nella loro stessa citt,
come vediamo dal risultato che si vede nella Figura 6.15.

Figura 6.15 Il risultato di una query con loperatore ANY.

Questaltra query:
SELECT *
FROM Ordini
WHERE Importo >= ANY (SELECT Importo
FROM Ordini
WHERE Odata = '2016/06/07');

estrae tutti gli ordini che hanno un importo maggiore di o uguale ad almeno uno degli
ordini del 7 giugno 2016, come si pu vedere dalla Figura 6.16.
Figura 6.16 Loperatore ANY pu essere associato con qualunque operatore di confronto.

Sostituendo in questa query ANY con SOME si ottiene lo stesso risultato.


Concludiamo questa rassegna degli operatori logici con qualche esempio di utilizzo
di ALL .
Con questa query:
SELECT *
FROM Clienti
WHERE Classe > ALL (SELECT Classe
FROM Clienti
WHERE Citt = 'Roma');

vogliamo sapere quali clienti hanno una classe superiore a quella di tutti i clienti di
Roma.
Il risultato dovrebbe presentarsi come nella Figura 6.17.
Figura 6.17 Effetto delloperatore ALL.

Nella tabella Clienti vi sono due clienti di Roma, che hanno Classe 100 e 200. Cos la
query, correttamente, estrae i record dei clienti il cui valore Classe maggiore di 200.
Normalmente loperatore ALL si utilizza con operatori di disuguaglianza (> , < e <> )
perch, come indica il suo nome, stabilisce per la query principale il vincolo che tutti i
risultati della sottoquery abbiano una determinata relazione con il criterio di selezione
espresso nella clausola WHERE della query principale. La stessa query con loperatore = al
posto di >
SELECT *
FROM Clienti
WHERE Classe = ALL (SELECT Classe
FROM Clienti
WHERE Citt = 'Roma');

non darebbe alcun risultato (e non avrebbe neppure alcun senso). Nel caso specifico,
non otterremmo alcun risultato specificando in questo modo la clausola WHERE
WHERE Classe < ALL

perch le classi sono 100, 200 e 300 e siccome i due clienti di Roma hanno classe 100 e 200
non ne esistono che abbiano un valore inferiore a 100 o a 200 nel campo Classe.
Invece, la query:
SELECT *
FROM Clienti
WHERE Classe < ALL (SELECT Classe
FROM Clienti
WHERE Citt = 'Verona');

produce questo risultato:


Cnum Cnome Cit t Classe Vnum
C01 Heredia Torino 100 V01
C02 Gennari Roma 200 V03
C03 Lucchini Genova 200 V02
C07 Padovani Roma 100 V04

perch lunico cliente di Verona ha classe 300. Si osservi che sono stati estratti soltanto
quattro record, perch quello del cliente C06 ha NULL nel campo Classe, quindi viene
ignorato.
La query dalla quale siamo partiti in questo esempio, quella con la clausola
WHERE Classe > ALL

pu essere riformulata senza loperatore ALL , utilizzando EXISTS in questo modo:


SELECT *
FROM Clienti esterna
WHERE NOT EXISTS (SELECT *
FROM Clienti interna
WHERE esterna.classe <= interna.classe
AND interna.citt = 'Roma');

Come si pu vedere dalla Figura 6.18, la query formulata con EXISTS invece che con ALL
estrae anche il record col campo Classe pari a NULL .

Figura 6.18 Mentre con ALL si ignorano i valori NULL, con EXISTS vengono presi in considerazione.

Le query a campi incrociati


Il loro nome in inglese cross-tab query e con questo ci si riferisce a query i cui
risultati vengono presentati in una forma tabellare particolare, simile a quella che si
ottiene molto spesso con Excel, quando si creano tabelle a doppia entrata, nella forma:
I Trimest re II Trimest re III Trimest re IV Trimest re
Arance 5000 4500 1000 3000
Mele 1000 5000 2500 1500
Pere 1500 6000 3000 2000

Vediamo come si potrebbe costruire una query a campi incrociati sulla base di una
tabella chiamata Risultati, i cui record contengono un importo per ciascun trimestre:
Anno Trimest re Import o
2015 1 1300
2015 2 1500
2015 3 1400
2015 4 1200
2016 1 1400
2016 2 1700
2016 3 1500
2016 4 1200

Vogliamo raggruppare una serie di importi in base alle loro date, disponendoli in
colonne sotto le date.
Eseguiamo questa query in SQL Server:
SELECT
Anno,
SUM(CASE Trimestre WHEN 1 THEN Importo ELSE 0 END) AS T1,
SUM(CASE Trimestre WHEN 2 THEN Importo ELSE 0 END) AS T2,
SUM(CASE Trimestre WHEN 3 THEN Importo ELSE 0 END) AS T3,
SUM(CASE Trimestre WHEN 4 THEN Importo ELSE 0 END) AS T4
FROM Risultati
GROUP BY Anno

Possiamo vedere il risultato nella Figura 6.19.


Figura 6.19 La query a campi incrociati.

Per ottenere il risultato illustrato nella figura abbiamo utilizzato la funzione CASE.
La query seleziona lanno e raggruppa i valori del campo Importo per ciascuno dei
possibili valori del campo Trimestre. I valori cos estratti vengono raggruppati in base
allanno.
Anche se, in questo caso, esiste un solo valore per trimestre, bisogna utilizzare la
funzione di aggregazione SUM sui singoli valori di Importo, altrimenti non si potrebbero
raggruppare i trimestri in base allanno.
Combinando insieme due query, possiamo ottenere un risultato pi raffinato: una
colonna di totali immediatamente dopo i valori dei trimestri, ricorrendo a una
sottoquery, in questo modo:
SELECT
P1.*,
(P1.Trim1 + P1.Trim2 + P1.Trim3 + P1.Trim4) AS TotaleAnno
FROM (SELECT
Anno,
SUM(CASE P.Trimestre WHEN 1 THEN P.Importo ELSE 0 END)
AS Trim1,
SUM(CASE P.Trimestre WHEN 2 THEN P.Importo ELSE 0 END)
AS Trim2,
SUM(CASE P.Trimestre WHEN 3 THEN P.Importo ELSE 0 END)
AS Trim3,
SUM(CASE P.Trimestre WHEN 4 THEN P.Importo ELSE 0 END)
AS Trim4
FROM Risultati AS P
GROUP BY P.Anno) AS P1;
ottenendo il risultato che si pu vedere nella Figura 6.20.

Figura 6.20 Una query a campi incrociati con totali di riga.

In questo caso, la sottoquery esegue il calcolo sui valori trimestrali, basandosi


sullalias P della tabella Risultati, mentre la query esterna totalizza i risultati della
sottoquery riferendosi alla tabella Risultati con lalias P1.

Gli operatori per gli insiemi


Fanno parte dello standard dello Structured Query Language tre operatori che
consentono di operare su insiemi di dati contenuti in tabelle diverse, chiamati INTERSECT,
EXCEPT e UNION.
Mediante loperatore UNION si combinano insieme i risultati di due query distinte: non si
tratta, quindi, di query composte con sottoquery come quelle che abbiamo esaminato nei
paragrafi precedenti.
Con una query di unione, come vengono anche dette le query che utilizzano
loperatore UNION, possiamo, per esempio, formare un unico elenco con tutti i clienti e
tutti i venditori che hanno sede in una determinata citt. Ecco un esempio:
SELECT Vnum, Vnome
FROM Venditori
WHERE Citt = 'Torino' UNION SELECT Cnum, Cnome
FROM Clienti
WHERE Citt = 'Torino';

Il risultato dovrebbe presentarsi come nella Figura 6.21.

Figura 6.21 Loutput complessivo di due query.

Lenunciato formato da due query distinte e indipendenti luna dallaltra, il cui


risultato viene presentato unito grazie alleffetto della clausola UNION che unisce le due
query.
Le intestazioni delle colonne che si vedono nella Figura 6.20 sono ambigue, ma si
possono rendere pi chiare (Figura 6.22) ricorrendo a un alias, in questo modo:
SELECT
Vnum as Codice,
Vnome as Nome
FROM Venditori
WHERE Citt = 'Torino'
UNION SELECT Cnum, Cnome
FROM Clienti
WHERE Citt = 'Torino';
Figura 6.22 In una query di unione opportuno assegnare nomi pi chiari alle colonne.

Si possono combinare assieme query con la clausola UNION soltanto se sono UNION
compatibili, vale a dire se hanno lo stesso numero di colonne e le colonne contengono
gli stessi tipi di dati: non si pu fare lunione di una colonna con dati numerici di tipo
DECIMAL con una che contiene caratteri di testo di tipo CHAR o VARCHAR .

Con la clausola UNION si esegue in effetti lunione di due insiemi, definiti dalle due
query di selezione raccordate con UNION.
Lo standard SQL consente anche altre due tipiche operazioni sugli insiemi:
lintersezione e la differenza. Lintersezione produce soltanto gli elementi che esistono
contemporaneamente nei due insiemi richiamati con le query, mentre la differenza
produce soltanto gli elementi che appartengono al primo insieme ma non al secondo.
Per stabilire lintersezione si utilizza una clausola INTERSECT, mentre la differenza viene
definita con una clausola EXCEPT, che in alcune implementazioni prende il nome di MINUS .
Mentre con UNION si opera su due o pi query aggregandone i risultati in un unico
insieme, con gli operatori INTERSECT ed EXCEPT si opera su due query per ottenere risultati
secondo lo schema che vediamo nella Figura 6.23:
Figura 6.23 Lo schema classico delle operazioni sugli insiemi.

Dallo schema illustrato nella figura si ricava che:


per ottenere i dati nellinsieme A che non si sovrappongono con B si usa A EXCEPT B;
per ottenere soltanto i dati che si sovrappongono nei due insiemi si usa A INTERSECT B;
per ottenere i dati nellinsieme B che non si sovrappongono con A si usa B EXCEPT A.

Per completezza, aggiungiamo quanto abbiamo visto prima e cio che per ottenere i
dati in tutte e tre le aree senza duplicati si usa A UNION B.
Qualche esempio chiarir meglio come funzionano e come si possono usare questi
operatori logici.
Prepariamo una semplice tabella che chiamiamo Pranzo, dotata di un solo campo
Portata e inseriamo in questo campo i seguenti record:
Birra

Caff

Calamari

Mela

Olive

Pane

Salame

Ripetiamo loperazione preparando una seconda tabella che chiamiamo Cena, con la
stessa struttura e questi dati:
Cotoletta
Insalata

Mela

Melanzane

Olive

Pane

Vino

Adesso andiamo a eseguire questa query che utilizza loperatore UNION su entrambe le
tabelle:
SELECT Portata
FROM Pranzo
UNION SELECT Portata FROM Cena

Nella Figura 6.24 vediamo elencati 11 elementi, che sono i 7 della tabella Pranzo e i 7
della tabella Cena al netto delle ripetizioni.

Figura 6.24 La querv UNION combina gli elenchi dalle due tabelle Pranzo e Cena.

Supponendo che le due tabelle elenchino quello che abbiamo mangiato ieri, possiamo
rispondere con gli operatori INTERSECT ed EXCEPT a queste domande:

Che cosa abbiamo consumato a pranzo e non a cena?


SELECT Portata
FROM Pranzo
EXCEPT SELECT Portata FROM Cena

Risposta: Birra
Caff

Calamari

Salame

Che cosa abbiamo consumato a cena e non a pranzo?


SELECT Portata
FROM Cena
EXCEPT SELECT Portata FROM Pranzo

Risposta: Cotoletta
Insalata

Melanzane

Vino

Che cosa abbiamo consumato in entrambi i pasti?


SELECT Portata
FROM Pranzo
INTERSECT SELECT Portata FROM Cena

Risposta: Mela
Olive

Pane

Naturalmente gli operatori INTERSECT ed EXCEPT possono dimostrarsi molto utili quando si
lavora su tabelle pi impegnative di quelle che abbiamo usato per questi esempi.
Capitolo 7
Estrarre dati

Nei capitoli precedenti abbiamo presentato gli aspetti pi significativi dello Structured
Query Language, dalle sue origini alla struttura sintattica dei suoi comandi,
approfondendo le tecniche principali per creare database e tabelle al loro interno,
inserire dati nelle tabelle cos create ed estrarli selettivamente con le query. In alcuni
passaggi di questa rassegna abbiamo anche visto in che modo uno strumento per
lavorare sui dati radicalmente diverso dallo SQL, qual Excel, pu essere usato per far
arrivare dati nelle tabelle di un database.
Tutto questo va molto bene, ma adesso sorge spontanea una domanda: come si
possono utilizzare i dati che risiedono in un database gestito con MySQL o con SQL
Server? In altri termini, quando otteniamo mediante una query una serie significativa di
dati possiamo usarli in un altro contesto, senza essere costretti a restare allinterno
dellinterfaccia utente dello strumento SQL nel quale abbiamo eseguito la query?
La risposta ovviamente positiva: in questo capitolo vedremo in quali modi si
possono ricavare informazioni da database gestiti con MySQL o con SQL Server per
utilizzarle con altri strumenti, in particolare con Excel, che pu essere il complemento
ideale di qualunque sistema per creare e gestire database con lo Structured Query
Language.
A questo proposito, come abbiamo gi visto nel Capitolo 4, dove abbiamo presentato
le funzioni di SQL (si vedano le Tabelle 4.1 e 4.2), bene ricordare che nelle
implementazioni di SQL sono disponibili poche decine di funzioni, mentre in Excel se ne
possono contare pi di 300, come si vede dalla Tabella 7.1.
Tabella 7.1 Le funzioni di Excel raggruppate per categoria.
Cat egoria Numero di funzioni
Database 12
Data e ora 20
Cubo 7
Finanziarie 53
Informative 18
Logiche 7
Ricerca e riferimento 18
Matematiche e trigonometriche 60
Statistiche 83
Testo 24
Estrarre i dati manualmente
Dalle interfacce grafiche Workbench di MySQL e Management Studio di SQL Server
si possono copiare agevolmente negli Appunti di Windows i risultati di una query,
scegliendo in quale forma acquisire quei dati per utilizzarli in seguito in unaltra
applicazione, per esempio in un foglio di lavoro di Excel.
Vediamo con un paio di esempi come si pu fare. Eseguiamo in MySQL la query
seguente, dopo aver attivato il database che contiene la tabella che ci interessa.
SELECT Nome, Cognome, Citt
FROM Indirizzi
WHERE Prov='MI'
ORDER BY Cognome

Otteniamo come risultato un pacchetto di tre record composti da tre colonne ciascuno,
che compare nella fascia inferiore della finestra della query.
Un clic sul riquadro vuoto nellangolo superiore sinistro della tabellina, dove si
incrociano le righe e le colonne del risultato, la seleziona tutta e un clic destro su una
delle righe selezionate fa comparire il menu contestuale che vediamo riprodotto nella
Figura 7.1.
Figura 7.1 Le opzioni per acquisire negli Appunti il risultato di una query eseguita in MySQL.

Selezioniamo lopzione Copy Row (tab separated) e apparentemente non accade nulla.
Usciamo da MySQL Workbench senza chiuderlo e apriamo un foglio di lavoro Excel
vuoto, dove selezioniamo una qualunque cella con un clic e diamo il comando da tastiera
Ctrl+V e cos possiamo ottenere il risultato che possiamo vedere qui di seguito:

Abbiamo a disposizione le stesse funzionalit, con qualche lieve differenza, in SQL


Server, come vediamo dalla Figura 7.2.
Figura 7.2 Le opzioni per acquisire negli Appunti il risultato di una query eseguita in SQL Server.

Se scegliamo lopzione indicata con il puntatore nella figura, otteniamo lo stesso


risultato che abbiamo ottenuto in MySQL, con laggiunta di una riga di intestazione sulle
colonne.

Conservare le query come viste


Di norma, il risultato di una query viene presentato nellinterfaccia utente
dellapplicazione SQL che si sta usando, dalla quale pu essere prelevato con un
semplice comando di copia, come abbiamo visto nel paragrafo precedente, ma il
pacchetto di righe che forma la query vera e propria non viene salvato nel contesto in cui
viene eseguita. Naturalmente niente impedisce di salvare il testo di una query
trasferendolo in un documento di testo scritto con Blocco note di Windows, in modo da
averlo a portata di mano quando dovesse servire di nuovo per ricavare dati con la stessa
query da tabelle che sono state aggiornate dalla volta precedente che la query stata
eseguita.
Ma non necessario aggiungere questa piccola procedura al lavoro quotidiano che si
esegue con un database gestito con MySQL o con SQL Server: entrambi gli strumenti
applicativi consentono di creare un oggetto SQL chiamato VIEW, vale a dire vista, che
una query salvata allinterno del sistema proprio per poterla utilizzare di nuovo quando
serve.
ovvio che non vale la pena di ricorrere a una VIEW per memorizzare una query
estremamente banale, come potrebbe essere questa:
SELECT * FROM Dipendenti

anche se niente impedisce di farlo.


I vantaggi delle VIEW si apprezzano quando si tratta di eseguire query complesse, come
quella che segue:
SELECT
t.titolo,
e.nome as editore
FROM
Autori a,
Titolo_Autore t1,
Titoli t,
Editori e
WHERE a.ID_Au=t1.ID_Au
AND t.ISBN=t1.ISBN
AND e.IDEd=t.ID_Ed
ORDER BY t.titolo

La sintassi del comando per creare una vista molto semplice:


CREATE VIEW nome della vista [elenco colonne] AS
enunciato SELECT

per cui possiamo provare subito a creare una vista che incorpori la query di cui sopra
in questo modo:
CREATE VIEW ListaLibri
AS SELECT
t.titolo,
e.nome as editore
FROM
Autori a,
Titolo_Autore t1,
Titoli t,
Editori e
WHERE a.ID_Au=t1.ID_Au
AND t.ISBN=t1.ISBN
AND e.IDEd=t.ID_Ed

Dopo aver eseguito questo comando in entrambi gli ambienti di prova, vale a dire
MySQL e SQL Server, apparentemente non accade nulla, per adesso possiamo eseguire
la query integrata nella vista con un semplice enunciato SELECT che potrebbe essere scritto
in questo modo:
SELECT * FROM ListaLibri
ORDER BY titolo

ottenendo un elenco completo di tutti i titoli dei libri, compresi gli autori e gli editori,
contenuti nelle tre tabelle richiamate dalla query integrata nella vista. Possiamo vedere
una parte del risultato nella Figura 7.3.
Figura 7.3 Il risultato della query che richiama una vista.

Scorrendo i risultati, notiamo che alcuni titoli sono ripetuti perch hanno pi autori.
Volendo una lista di titoli univoci dovremmo rinunciare agli autori, definendo una nuova
vista che chiamiamo TitoliUnivoci con questo enunciato:
CREATE VIEW TitoliUnivoci
AS SELECT DISTINCT
t.titolo,
e.nome as editore
FROM
Autori a,
Titolo_Autore t1,
Titoli t,
Editori e
WHERE a.ID_Au=t1.ID_Au
AND t.ISBN=t1.ISBN
AND e.IDEd=t.ID_Ed

Dopo aver creato la nuova vista possiamo mandarla in esecuzione con questo
enunciato:
SELECT * FROM TitoliUnivoci
ORDER BY titolo

che produce il risultato che possiamo vedere nella Figura 7.4.


Figura 7.4 La nuova vista costruita con la clausola DISTINCT nella sua query interna produce un elenco di titoli
univoci.

Qualche lettore attento avr notato che la query integrata nellenunciato col quale si
crea una vista non utilizza la clausola ORDER BY, che viene invece richiamata dalla query
che attiva la vista. Ci dovuto al fatto che nellenunciato SELECT contenuto in una VIEW non
sono ammessi:
clausole GROUP BY, ORDER BY o HAVING ,
operatori UNION o INTERSECT,
funzioni di raggruppamento.
Le clausole e le funzionalit non consentite nella definizione della query integrata in
una vista si possono aggiungere liberamente nella query che richiama la vista, come
abbiamo visto sopra.
Estrarre i dati automaticamente
Le funzionalit che abbiamo visto nel Capitolo 3 per acquisire in database MySQL o
SQL Server dati memorizzati su fogli di lavoro Excel sono disponibili anche per
eseguire loperazione inversa, vale a dire inviare insiemi di dati da database SQL a fogli
di lavoro Excel. Servendosi di VIEW si possono inviare a Excel non soltanto intere tabelle,
ma selezioni di dati anche da pi tabelle ottenute con query che incorporano una o pi
viste.

Inviare dati a Excel


Lottima integrazione fra MySQL ed Excel rende facile al limite della banalit ottenere
in un foglio di lavoro una copia di una tabella da un database MySQL: basta aprire il file
Excel dove si intende depositare i dati, selezionare la prima cella dellintervallo scelto
per ricevere i dati e aprire la scheda Dati nella barra multifunzione: alla estremit destra
di questa barra troveremo il pulsante MySQL for Excel, un clic su quel pulsante fa uscire
sulla destra del foglio di lavoro la finestra di dialogo nella quale possiamo attivare la
connessione con MySQL e allinterno della connessione scegliere il database di origine
dei dati da acquisire in Excel, come vediamo nella Figura 7.5.
Figura 7.5 La finestra di dialogo di MySQL aperta nel foglio di lavoro Excel per fornire i dati che ci
interessano.

Nellelenco dei database disponibili selezioniamo quello che ci interessa e al suo


interno troviamo lelenco delle tabelle e delle VIEW.
Nellesempio che stiamo esaminando selezionata la vista TitoliUnivoci, un clic sul
pulsante Import MySQL Data fa aprire la finestra di dialogo Import Data from MySQL,
che presenta unanteprima dei record che verranno interessati dalloperazione,
visualizzando il nome della vista e il conteggio delle righe che la compongono.
Sono disponibili anche alcune comode opzioni per impostare limportazione: si pu
scegliere se includere i nomi delle colonne come intestazioni e se si vuole creare una
tabella Pivot con i dati importati.
anche consentito limitare il numero delle righe da importare, scegliendo da quale
riga fare limportazione e si possono aggiungere eventualmente campi di sommatoria:
la Figura 7.6 mostra la finestra di dialogo per limportazione con tutte le sue opzioni.

Figura 7.6 La finestra di dialogo Import Data from MySQL, con le sue molteplici opzioni.

Fatte le opportune selezioni, un clic sul pulsante di comando Import esegue


materialmente limportazione dei record nel foglio di lavoro a partire dalla cella
selezionata allinizio della procedura.
Il grande vantaggio che si ha nellimportare da MySQL in Excel utilizzando una VIEW
sta nel fatto che si possono selezionare dati da pi tabelle e secondo criteri anche
complessi, predisposti nella VIEW usata per limportazione, mentre limportazione diretta
di una o pi tabelle (cosa che si pu fare agevolmente con la tecnica che abbiamo appena
descritto) lascia poi allutente del foglio di lavoro lonere di selezionare i dati con
Excel, operazione che in quel contesto si pu eseguire con le funzionalit di filtro che
diamo per conosciute.
Anche in SQL Server si pu esportare il contenuto di una tabella o di una VIEW da un
database a un foglio di lavoro Excel utilizzando la stessa procedura che abbiamo visto
nel Capitolo 3 per limportazione. Vediamo brevemente come si pu operare,
utilizzando come esempio la stessa VIEW che abbiamo importato in Excel da MySQL.
In primo luogo dobbiamo predisporre una cartella Excel nella quale andare a
travasare i dati che ci interessano da SQL Server.
Fatto questo, attiviamo il database nel quale abbiamo salvato la vista che ci interessa e,
con un clic destro sul nome del database nella finestra Esplora oggetti, facciamo
scendere il menu contestuale nel quale selezioniamo Attivit, provocando luscita del
menu di secondo livello, dove selezioniamo Esporta dati.
Compare la finestra di dialogo che funge da copertina per la procedura, procediamo
oltre sulla prima finestra di dialogo operativa e l andiamo a selezionare lorigine dei
dati da esportare, come nella Figura 7.7.

Figura 7.7 Come prima cosa segnaliamo di voler esportare da un database SQL Server.

Nella finestra di dialogo successiva stabiliamo una connessione col database che ci
interessa, quindi passiamo avanti e scegliamo la cartella Excel di destinazione (quella
che avevamo creato in precedenza),
Ci vengono offerte due opzioni:
Copia i dati da una o pi tabelle o viste;
Scrivi una query per specificare i dati da trasferire.
Dal momento che abbiamo gi pronta la vista che ci interessa, optiamo per la prima
soluzione e proseguiamo con un clic su Avanti.
Compare lelenco delle tabelle e delle viste disponibili nel database che abbiamo
selezionato allinizio della procedura guidata.
Scegliamo la vista TitoliUnivoci e nella stessa finestra di dialogo facciamo clic sul
pulsante Anteprima, in modo da ottenere la comparsa di unanteprima dei risultati che ci
interessano, che si presenter come nella Figura 7.8.

Figura 7-8 Lanteprima dei risultati permette di controllare ci che si va a esportare.

Se lanteprima ci soddisfa, un clic sul pulsante OK la chiude e possiamo passare


allesecuzione dellesportazione, che avviene senza problemi facendoci trovare il
risultato che ci interessa in un foglio di lavoro della cartella Excel che avevamo
predisposta e intitolato col nome della VIEW che stata usata (TitoliUnivoci, nel nostro
caso).

Inviare i risultati in un file


Anche se Excel a tutti gli effetti il complemento ideale di SQL, non siamo costretti a
inviare i risultati di una query o di una vista esclusivamente a un foglio di lavoro Excel:
possiamo trasferirli anche in un semplice file di testo, utilizzabile in seguito da altre
applicazioni.
In MySQL possiamo trasferire il risultato di una query direttamente in un file
utilizzando un enunciato SELECT costruito nel modo seguente.
SELECT nomi delle colonne
FROM nome della tabella
INTO OUTFILE 'Nome del file.txt'
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'

Per cui con questa specifica query:


SELECT *
FROM dipendenti
INTO OUTFILE 'C:\Esempi\dipendenti.txt'
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'

dovremmo creare un nuovo file contenente tutti i record della tabella dipendenti,
chiamato dipendenti.txt nel percorso C:\Esempi\. Malauguratamente, quando andiamo a
eseguire la query riceviamo questa segnalazione di errore:
Error Code: 1290. The MySQL server is running with the --secure-file-priv option so it cannot execute
this statement .

Lopzione a cui fa riferimento il messaggio di errore si chiama secure-file-priv,


impostata come attiva nel corso dellinstallazione di MySQL e interviene quando si
esegue la query impedendo che venga creato un file in un qualsiasi percorso nel disco
del computer.
Per superare questo ostacolo abbiamo due possibilit: disattivare lopzione oppure
aggirarla.
Per disattivarla dobbiamo individuare il file di testo che contiene le impostazioni della
nostra versione di MySQL e modificarlo. Questa operazione relativamente semplice e
va eseguita quando MySQL non attivo, il che pu essere un problema se stato
installato in modo che si attivi contestualmente allavvio del nostro computer.
Se non c questo vincolo, il file da modificare si chiama my.ini e si trova nel percorso:
C:\ProgramData\MySQL\MySQL Server 5.7 .
I file con lestensione .ini sono file di testo che contengono informazioni (e
disposizioni) in merito alla configurazione di un servizio. Possiamo aprirlo con Blocco
note e cercare la sezione che ci interessa, che si presenta in questo modo:
# Secure File Priv.
secure-file-priv="C:/ProgramData/MySQL/MySQL Server 5.7/Uploads"

Mettiamo in testa alla riga che imposta la condizione il carattere #, in questo modo:
# secure-file-priv="C:/ProgramData/MySQL/MySQL Server 5.7/Uploads"

salviamo il file e il vincolo sparito, perch in un file .ini il carattere # in testa a una
riga la trasforma da riga di comando in una riga di commento. Se loperazione non
riesce, perch lavvio di MySQL contestuale allavvio di Windows, possiamo aggirare
il vincolo usando il percorso che consentito, cio quello indicato nellimpostazione
che abbiamo visto sopra, per cui, modificando opportunamente in questo modo la query
di poco fa:
SELECT *
FROM dipendenti
INTO OUTFILE 'C:/ProgramData/MySQL/MySQL Server 5.7/Uploads/dipendenti.txt'
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'

possiamo eseguirla senza problemi; successivamente, ci baster reperire il file che ci


interessa dipendenti.txt in fondo al percorso prestabilito, dal quale possiamo
copiarlo o spostarlo dove meglio ci conviene.
In SQL Server non disponibile un meccanismo relativamente semplice come quello
che abbiamo appena visto nel caso di MySQL, quindi non possibile salvare i risultati di
una query o di una VIEW in uno specifico file di testo usando una query simile quella
utilizzata in MySQL. Per ottenere il risultato che ci interessa possiamo seguire due
strade, entrambe molto semplici anche se basate su meccanismi diversi.
La prima labbiamo gi conosciuta e utilizzata per inviare i risultati di una query o il
contenuto di una vista a un foglio di lavoro Excel (si veda sopra, le Figure 7.7 e 7.8).
La stessa procedura guidata consente di inviare un qualunque contenuto di un database,
quindi unintera tabella, i risultati di una query o il contenuto di una vista a un file di
testo non strutturato, detto nel gergo interno di SQL Server flat file.
Dopo aver avviato il meccanismo dellesportazione guidata, nella finestra di dialogo
che vediamo nella Figura 7.9, intitolata Scelta destinazione, si seleziona lopzione
Destinazione file flat.
Figura 7.9 La schermata Scelta destinazione della esportazione guidata consente di scegliere di esportare i dati
in un file di testo non strutturato.

Il resto della procedura guidata procede nel modo che gi conosciamo e al termine
troviamo i dati che ci interessano nel file di tipo .txt che abbiamo designato come
destinazione del trasferimento.
In alternativa a questo procedimento, per trasferire i risultati di una query a un file
esterno si pu utilizzare uno strumento chiamato bcp, che troviamo descritto in questo
modo nella guida di SQL Server:
Lutilit bcp consente di eseguire operazioni di copia bulk dei dati tra unistanza di Microsoft SQL Server e un file
di dati in un formato specificato dallutente.

Lutilit bcp pu essere utilizzata per importare un numero elevato di nuove righe nelle tabelle di SQL Server o
per esportare dati dalle tabelle in file di dati.

La sintassi dellutilit bcp relativamente semplice:


bcp [nome del database] schema.
{nome della tabella | nome della vista | "query" {in file di dati | out file di dati | queryout file di
dati | format nul}
Il comando bcp deve essere seguito facoltativamente dal nome del database e da quello
dello schema, se non sono indicati si suppone che il database sia quello attivo al
momento.
Seguono poi in alternativa il nome di una tabella o quello di una vista o il testo di
una query racchiuso fra virgolette. Siccome il comando bcp pu essere usato
indifferentemente per caricare dati dallesterno in un database e scaricare dati da un
database verso lesterno, bisogna specificare la direzione delloperazione usando le
parole chiave in oppure out seguite dal nome del file di dati interessato. Possiamo
trascurare il parametro format e non menzionare la trentina di ulteriori parametri che si
possono indicare, alcuni in alternativa fra loro altri in sequenze obbligate: questi
parametri si devono specificare dopo il nome del file di dati interessato e sono espressi
come lettere singole, maiuscole o minuscole, precedute da un segno meno: -R , -S , -t, -T.
Per trasferire i risultati di una query in un file di testo chiamato dipendenti.txt che sta
nella cartella C:\Esempi, il comando bcp che ci serve il seguente:
bcp "SELECT * FROM demo.dbo.dipendenti " queryout C:\Esempi\dipendenti.txt -c -t, -T -S

Sfortunatamente, non esiste una versione di bcp attivabile dallinterno dellinterfaccia


utente di SQL Server, cio il Management Studio, perch previsto che si usi questo
strumento dalla riga dei comandi di Windows, dopo essersi posizionati correttamente
entro il server. Inoltre, lesecuzione del comando bcp prevede la disponibilit di uno
strumento di interconnessione fra il server e il resto del computer chiamato driver
ODBC, sigla che sta per Open Data Base Connectivity. Si tratta di uno strumento software
che deve essere installato nel computer e si ottiene dal sito Microsoft gratuitamente
andando a cercarlo nella pagina dei download per SQL Server. Il file da scaricare si
chiama msodbcsql.msi e la sua estensione indica che si tratta di un file che installa software
di sistema. Una volta scaricato questo file su un computer dove presente anche SQL
Server basta eseguirlo e le funzionalit di ODBC sono installate e funzionanti.
A questo punto interviene unaltra complicazione: non potendo (o, pi precisamente
nel nostro caso, non volendo) usare la riga comandi, possiamo attivare lo strumento bcp
soltanto da una procedura: che cos?
In SQL Server si chiama stored procedure un file di testo che contiene una serie di
comandi da eseguire tutti di seguito, fra i quali possono esserci oltre ai comandi SQL
che abbiamo visto e descritto in tutti i capitoli precedenti, anche spezzoni di codice di un
linguaggio di programmazione molto semplice e intuitivo derivato dal BASIC,
Una stored procedure esattamente lopposto di una query, che lespressione
principe dello Structured Query Language, con la quale si dichiara quello che si vuole
ottenere, mentre una stored procedure, come il suo nome lascia capire, un insieme di
istruzioni con le quali si dice al computer che cosa deve fare, un passo dopo laltro.
Considerato luso al quale sono destinato, le procedure vengono di solito conservate
allinterno del sistema con un nome, come le viste, e per questo si chiamano stored
procedure.
Una stored procedure usa variabili identificate dal carattere at @ davanti al nome e
caratterizzate con gli stessi tipi di dati che si usano in SQL; quasi sempre in una stored
procedure ci sono comandi operativi che hanno la forma SET per impostare variabili
oppure EXEC, che richiamano il nome di una funzionalit, unutility come bcp, per esempio,
mandandola in esecuzione.
Stabilito tutto questo, la procedura che dobbiamo scrivere deve specificare il nome del
file nel quale depositare i risultati della query, il percorso dove si dovr salvare quel file,
la query che genera i dati e alcuni parametri per il comando che esegue lutility bcp.
Ecco quindi la procedura da eseguire:
DECLARE
@FileOutput NVARCHAR(100),
@PercorsoFile NVARCHAR(100),
@Comandobcp NVARCHAR(1000)
SET @Comandobcp = 'bcp "SELECT * FROM demo.dbo.dipendenti " queryout '
SET @PercorsoFile = 'C:\Esempi\'
SET @FileOutput = 'Dipendenti.txt'
SET @Comandobcp = @Comandobcp + @PercorsoFile + @FileOutput + ' -c -t, -T -S'
EXEC master..xp_cmdshell @Comandobcp

Dopo aver scritto diligentemente questo pacchetto di righe nella finestra per le query
di Management Studio, diamo il comando Esegui e la procedura viene eseguita
correttamente, elencando nellarea Risultati i passi operativi che ha eseguito (Figura
7.10).
Figura 7.10 Nella sezione Risultati sono elencati i passi eseguiti con la procedura.

Se andiamo a vedere il contenuto della cartella C:\Esempi del nostro computer troviamo
il file Dipendenti.txt del quale trascriviamo qui di seguito qualche riga:
1.0,Dr.,Luigi,Antimi,Via Scope, 25,20075,LO,Lodi,Lombardia,1985-11-09,2011-11-03,800.0
2.0,Dr.sa,Bruna,Bolli,Via Cerro, 12,22053,LC,Lecco,Lombardia,1961-06-28,1983-06-23,600.0
3.0,Sig.,Rino,Cordi,Via Romana, 113,27100,PV,Pavia,Lombardia,1958-10-08,1979-10-03,800.0

Adesso che sappiamo come fare a estrarre dati da un database gestito con MySQL o
con SQL Server, vediamo nel prossimo e ultimo capitolo alcuni esempi di come si
possono utilizzare in Excel dati ricavati mediante query o viste da una o pi tabelle di un
database.
Capitolo 8
Integrazione con Excel

Gli utenti di Excel sanno che questo strumento dotato di una poderosa funzionalit
che lo rende il complemento ideale per qualsiasi analisi di dati: la grafica.
Il modo pi adeguato, quindi, per utilizzare SQL ed Excel insieme per analizzare dati
consiste nellassegnare:
ai sistemi che lavorano con lo Structured Query Language il compito di:
creare e gestire database,
estrarre selettivamente i dati;
ad Excel il compito di:
applicare sui dati cos estratti le funzioni matematiche, statistiche e finanziarie
che si riterranno opportune,
rappresentare i dati nelle forme grafiche pi adeguate, scegliendole fra i pi di
70 tipi disponibili, che vediamo elencati nella Tabella 8.1.
Tabella 8.1 I tipi di grafici disponibili in Excel.
Cat egoria T ipi
Istogramma 19
Grafico a linee 7
Grafico a torta 6
Grafico a barre 15
Grafico ad area 6
Grafico a dispersione 5
Grafico azionario 4
Grafico a superficie 4
Grafico ad anello 2
Grafico a bolle 2
Grafico a radar 3

Vediamo in questo capitolo conclusivo come sfruttare le funzionalit grafiche di Excel


per elaborare flussi di dati ottenuti con query SQL. Nelle ultime pagine vedremo infine
come convogliare un flusso di dati SQL in una tabella pivot in Excel.
I grafici principali
Abbiamo una tabella di Ordini, nella quale fra altre colonne ve n una intitolata
TipoPagamento. Proviamo a eseguire la query seguente:
SELECT TipoPagamento, COUNT(*) AS Conta
FROM Ordini
GROUP BY TipoPagamento
ORDER BY Conta

con la quale intendiamo conteggiare i vari tipi di pagamento associati agli ordini.
Il risultato si presenta in questo modo:
T ipoPagament o Cont a
?? 313
OC 8214
DB 12739
MC 47318
AE 47382
VI 77017

dove le sigle nella colonna TipoPagamento identificano una carta di credito (AE per
American Express, VI per Visa e cos via) mentre il doppio punto interrogativo
corrisponde a ordini per i quali non stata identificata la societ che ha emesso la carta
di credito.
Acquisiamo il risultato della query negli Appunti di Windows e da l lo scarichiamo in
un foglio di lavoro Excel; in quel contesto, dopo aver selezionato lintero blocchetto di
righe con la relativa intestazione, premiamo il tasto di funzione F11: allistante Excel
crea un grafico a colonne che rappresenta i dati selezionati (Figura 8.1).
Figura 8.1 Il grafico a colonne creato automaticamente da Excel.

Il grafico inquadrato in una nuova scheda della barra multifunzione intitolata


Progettazione. Alla sua destra troviamo una scheda intitolata Sposta grafico: un clic su
quella scheda fa uscire la finestra di dialogo omonima che vediamo nella figura, dove
possiamo scegliere se spostare il grafico in un nuovo foglio di lavoro oppure in quello
dal quale siamo partiti, dove avevamo incollato i risultati della query.
Questa seconda opzione ci permette di avere in un solo foglio i due elementi che ci
interessano: i risultati della query e il grafico costruito automaticamente a partire da quei
risultati.
Questo modo di procedere per ottenere un grafico pu andar bene per creare
occasionalmente e in fretta un grafico a barre o a torta, ma per sfruttare bene le
funzionalit di Excel per i grafici bisogna conoscere, almeno a grandi linee, i concetti
sui quali si basano e imparare a utilizzare gli strumenti che concorrono alla creazione
dei grafici.
A questo scopo, iniziamo col descrivere i singoli passi necessari per ottenere un
grafico scegliendo deliberatamente ogni sua caratteristica.
Si parte ovviamente dai dati, che si trovano nel foglio di lavoro in cui li abbiamo
trasferiti dopo averli copiati dallinterfaccia utente di MySQL o di SQL Server:
supponiamo che si trovino nellintervallo A1:B7e che oltre ai dati veri e propri vi siano
anche le intestazioni. Dopo aver fatto clic su A1 premiamo la combinazione di tasti
Ctrl+Maiusc+* selezionando cos lintero intervallo che ci interessa.
Nella barra multifunzione selezioniamo il comando Inserisci e nella sezione Grafici
scegliamo la prima opzione in alto a sinistra, Inserisci istogramma o grafico a barre: un
clic su quellopzione fa scendere un menu contestuale che illustra graficamente quali
forme abbiamo a disposizione per il nostro grafico. Optiamo per la prima, Colonne 2D
raggruppate e immediatamente nel foglio di lavoro si inserisce il grafico che abbiamo
scelto.
Il grafico sarebbe gi pronto, ma opportuno aggiungere alcuni elementi descrittivi
che ne miglioreranno laspetto rendendolo al tempo stesso pi comprensibile.
Un clic sul bordo esterno del grafico fa uscire alla sua destra lelenco a discesa che
vediamo qui di seguito:

In questo elenco possiamo scegliere gli elementi da inserire nel grafico e scrivere
direttamente il loro contenuto quando si tratta di didascalie. Per capire meglio quello che
si sta facendo opportuno conoscere almeno per sommi capi la nomenclatura dei
grafici, con particolare riferimento ai grafici a barre come quello che stiamo
esaminando.

Nomenclatura dei grafici


Un grafico un disegno geometrico che rappresenta relazioni fra serie di dati
numerici, in base a una convenzione opportunamente definita. Lelemento essenziale di
un grafico, quindi, la serie dei dati da rappresentare e la sua finalit principale quella
di evidenziare differenze fra i dati di una o pi serie. Le serie dei dati sono associate a
categorie, per esempio possono rappresentare ricavi riferiti a date, prodotti o aree
geografiche, cos da poter mettere a confronto ricavi ottenuti in periodi diversi, o per
vari prodotti o in aree di vendita distinte.
Se i dati da rappresentare graficamente in Excel derivano da una query SQL, le
categorie di cui parliamo si ottengono usando opportunamente le clausole WHERE e GROUP BY
nellenunciato SELECT che ricava i dati dal database.
I dati vengono rappresentati con varie tecniche su un piano geometrico, detto area del
tracciato, delimitato da due assi, uno verticale chiamato asse delle y e uno orizzontale,
lasse delle x.
I valori da visualizzare in un grafico formano una serie e vengono rappresentati
rapportandoli a una scala di riferimento per definire le distanze di questi valori
dallorigine dellasse delle y, che per questa ragione oltre a chiamarsi asse principale e
asse della serie viene detto anche asse dei valori.
La larghezza dellarea del tracciato, alla cui base corre lasse delle x, viene suddivisa
in tante parti quante sono le categorie da rappresentare. Contestualmente, viene assegnato
un numero dordine a ciascun elemento della serie. La numerazione comincia da 1. La
funzione dellasse delle x in questa operazione fa s che venga chiamato anche asse delle
categorie.
Sullasse dei valori vengono tracciate delle tacche di graduazione, a distanza regolare;
da queste tacche si diramano altrettante sottili linee orizzontali, che si sviluppano fino
allestremit opposta dellarea del tracciato, in modo da creare una griglia che facilita la
lettura dei simboli utilizzati nellarea del tracciato.
Il valore di ciascuna grandezza rapportato alla scala di riferimento e il suo numero
dordine nella serie determinano le coordinate del punto che rappresenta ogni grandezza
nellarea del tracciato.
Definite le coordinate dove collocare i punti nellarea del tracciato rispetto agli assi x
e y, il grafico viene disegnato scegliendo una fra le molte convenzioni grafiche
sviluppate a questo scopo dalla prassi scientifica e gestionale. I punti possono essere
tracciati con forme geometriche regolari, barre o cerchi, che prendono il nome di
marcatori dei dati. Per rendere pi chiaro il significato dei marcatori spesso si scrive
accanto a ciascuno una descrizione, detta etichetta dei dati.
Il significato complessivo dei marcatori dei dati pu essere chiarito con una scritta
esplicativa che si chiama legenda e che viene di solito racchiusa in una cornice per
distinguerla dal grafico vero e proprio. Sempre a fini esplicativi, un grafico viene
spesso completato con scritte che formano i titoli del grafico e degli assi.
Nella Figura 8.2 vediamo come si pu completare il grafico con gli elementi
descrittivi che abbiamo appena visto.
Figura 8.2 Gli elementi di un grafico.

I grafici nelle celle


Nelle versioni pi recenti di Excel sono state introdotte alcune funzionalit per i
grafici che si possono attivare direttamente nelle celle del foglio di lavoro, creando cos
grafici che non sono figure separate dal testo, ma elementi integrati nelle celle. Queste
funzionalit sono particolarmente sviluppate in Excel 2016: vediamo di che cosa si tratta
con un paio di esempi.
Riprendiamo la tabella di valori che abbiamo utilizzato per lesempio precedente,
depurandola della riga relativa alle carte di credito non conosciute, in modo che abbia
questo aspetto:
T ipo pagament o Cont eggio
AE 47.382
DB 12.739
MC 47.318
OC 8.214
VI 77.017
Sfruttando alcune funzioni di Excel, possiamo ottenere nel foglio di lavoro leffetto
che vediamo nella Figura 8.3.

Figura 8.3 Un grafico a barre orizzontali ottenuto direttamente nelle celle del foglio di lavoro.

Leffetto che si vede nella Figura stato ottenuto utilizzando la funzione RIPETI(c,n) di
Excel, che prevede due argomenti, un carattere da ripetere e un numero di ripetizioni,
per cui
=RIPETI( "g"; 5)

produce nella cella in cui inserito come formula una successione di lettere come
questa:
ggggg

Le formule inserite nella colonna D del foglio di lavoro che vediamo nella Figura 8.3
sono le seguenti, che inseriscono il carattere g in ciascuna riga corrispondente alla
riga dei valori che stanno sotto lintestazione Conteggio. Il numero delle ripetizioni
dato dalla formula:
20 * cella / max(valore prima cella: valore ultima cella)

=RIPETI("g";20*B2/MAX($B$1:$B$6))
=RIPETI("g";20*B3/MAX($B$1:$B$6))
=RIPETI("g";20*B4/MAX($B$1:$B$6))
=RIPETI("g";20*B5/MAX($B$1:$B$6))
=RIPETI("g";20*B6/MAX($B$1:$B$6))

Leffetto grafico ottenuto assegnando alla cella che contiene la formula il tipo di
carattere Webdings, nel quale la lettera g rappresentata con un blocchetto nero.
Il valore 20 usato nella formula serve per stabilire un limite superiore alla lunghezza
delle barre orizzontali.
NOT A
Per visualizzare le formule contenute in un foglio di lavoro si preme la combinazione di tasti
Ctrl+Maiusc+(. Lo stesso comando ripristina la visualizzazione normale.

Lidea di visualizzare barre orizzontali che rappresentano valori direttamente nelle


celle di un foglio di lavoro intrinseca in Excel 2016 al punto che si possono ottenere
diagrammi a barre orizzontali direttamente con la formattazione condizionale.
Creiamo una copia della nostra tabellina dimostrativa in una qualunque posizione
libera del foglio di lavoro e dopo averla selezionata diamo il comando Formattazione
condizionale nella barra multifunzione, scegliendo nel menu a discesa che si apre il
comando Barre dei dati e nel successivo elenco di opzioni selezionando la prima voce
fra quelle che compaiono nellelenco Riempimento sfumato. Otteniamo in questo modo il
risultato che possiamo vedere nella Figura 8.4.
Se non vogliamo vedere i valori sui quali si sovrappongono le barre orizzontali
dobbiamo selezionare la colonna Conteggio, fare un clic destro per ottenere il menu di
scelta rapida e in questo dobbiamo selezionare Formato celle.
Nella finestra di dialogo corrispondente scegliamo Personalizzato e nel campo del
valore digitiamo tre volte il carattere punto e virgola. Confermata questa opzione, nel
foglio di lavoro le barre orizzontali non mostrano pi i valori ai quali si riferiscono
(parte inferiore della Figura 8.4).
Perch t re punt i e virgola?
Le opzioni di formato per un intervallo in un foglio di lavoro Excel sono quattro: per valori positivi, zero,
negativi e testo, separate ciascuna da un punto e virgola. Scrivendo come formato Personalizzato tre punti
e virgola senza niente in mezzo di fatto si specifica che non si vuole formattare nulla.
Figura 8.4 Le barre dei dati ottenute con la Formattazione condizionale.

Varianti dei grafici a colonna


In Excel esistono numerose varianti dei grafici a colonna, che possono essere
particolarmente utili in determinate circostanze. Vediamo alcune di queste varianti
utilizzabili per rappresentare graficamente il risultato di una query pi articolata di
quella che abbiamo utilizzato fin qui. Con questa query:
SELECT tipopagamento,
SUM(CASE WHEN 0 <= prezzototale AND prezzototale < 10
THEN 1 ELSE 0 END) AS conta_0_10,
SUM(CASE WHEN 10 <= prezzototale AND prezzototale < 100
THEN 1 ELSE 0 END) AS conta_10_100,
SUM(CASE WHEN 100 <= prezzototale AND prezzototale < 1000
THEN 1 ELSE 0 END) AS conta_100_1000,
SUM(CASE WHEN prezzototale >= 1000
THEN 1 ELSE 0 END) AS conta_1000,
COUNT(*) AS conta,
SUM(prezzototale) AS ricavo
FROM ordini
GROUP BY tipopagamento
ORDER BY tipopagamento

si ottengono:
il numero di ordini per ciascun tipo di pagamento;
il numero di ordini il cui ammontare sta negli intervalli fra 0-10, 10-100, 100-1000
e oltre 1000;
il ricavo totale per ciascun tipo di pagamento.
Vediamo i risultati nella Tabella 8.2: questa tabella di valori si presta egregiamente a
essere visualizzata con grafici a colonna combinati, come negli esempi riportati nella
Figura 8.5 nella pagina a fronte.
Tabella 8.2 I risultati della query.
t ipopagament o cont a_0_10 cont a_10_100 cont a_100_1000 cont a_1000 cont a ricavo
?? 298 11 4 0 313 1.184,17
AE 1.483 36.093 9.341 465 47.382 4.656.038,04
DB 5.356 6.524 823 36 12.739 471.008,74
MC 1.862 38.458 6.797 201 47.318 3.302.579,39
OC 3.643 4.042 518 11 8.214 264.647,83
VI 3.206 62.993 10.545 273 77.017 5.013.438,13
Figura 8.5 Tre tipi di grafici per gli stessi dati.

Il primo grafico della Figura 8.5 rappresenta i numeri degli ordini raggruppati per
dimensioni (da 0 a 10, da 10 a 100, da 100 a 1000, da 1000 in su) affiancando le colonne.
Alcune combinazioni sono talmente piccole che le colonne non si vedono nemmeno.
Il grafico evidenzia immediatamente che alcuni tipi di pagamento (VI, MC e AE)
predominano e che la maggior parte degli ordini si concentra nel gruppo da 10 a 100.
Il grafico al centro della Figura 8.5 mostra gli ordini su colonne impilate. In questo
modo si vedono a colpo docchio i totali degli ordini per tipo di carta di credito e
allinterno dei totali ben evidenziato il gruppo in cui si concentrano prevalentemente
gli ordini.
Il terzo grafico della Figura 8.5 riconduce i valori rappresentati nel secondo grafico a
una dimensione comune e normalizzata, cosa che permette di valutare meglio
lincidenza relativa dei vari raggruppamenti di ordini.

Confrontare valori diversi


I grafici di Excel consentono di presentare su un unico piano pi serie di valori
diversi, come per esempio numero di eventi e importi economici. Questo risultato si
ottiene impostando nello stesso grafico due assi dei valori, uno per ciascuna delle serie
da rappresentare, mettendo cos in evidenza possibili correlazioni fra le serie.
Grafici con queste caratteristiche si possono costruire sia utilizzando istogrammi, sia
utilizzando linee e, volendo, anche possibile combinare nello stesso grafico barre con
linee. Vediamo con qualche esempio che cosa si pu ottenere e in che modo.
Prendiamo i risultati della query che abbiamo usato per gli esempi del paragrafo
precedente e inseriamoli in un nuovo foglio di lavoro riorganizzati in questo modo:
t ipopagament o cont a ricavo
?? 313 1.184,17
AE 47.382 4.656.038,04
DB 12.739 471.008,74
MC 47.318 3.302.579,39
OC 8.214 264.647,83
VI 77.017 5.013.438,13

Selezioniamo lintero intervallo con la combinazione di tasti Ctrl+Maiusc+* dopo


aver fatto clic su una qualunque cella allinterno della tabella.
Scegliamo la sezione Inserisci nella barra multifunzione e in questa selezioniamo il
comando Inserisci istogramma o grafico a barre.
A fianco della tabella con i dati comparir un grafico con le barre verticali che
rappresentano i ricavi: dove sono finiti i numeri degli ordini corrispondenti alla colonna
conta della tabella? Ci sono anche loro, ma non sono visibili perch prevalgono le barre
che rappresentano la serie dei ricavi, che si sovrappone alle barre che rappresentano i
conteggi degli ordini associati alle varie carte di credito.
Per ottenere un grafico plausibile, nel quale siano evidenziate su scale diverse le due
serie di dati, dobbiamo procedere nel modo seguente. Un clic destro su una delle
colonne dei ricavi la seleziona e fa uscire un menu contestuale nel quale scegliamo
lopzione Formato serie dei dati, provocando lapertura sulla destra del foglio di lavoro
di una finestra di dialogo con lo stesso titolo, dove possiamo scegliere fra due opzioni
per tracciare la serie selezionata: lungo lasse principale o lungo un asse secondario.
Scegliamo questa seconda opzione e chiudiamo la finestra di dialogo con un clic sul
pulsante in alto a destra identificato da una croce di SantAndrea.
Nella sezione Strumenti grafico/Formato della barra multifunzione scegliamo Asse
secondario verticale valori ottenendo cos la selezione della colonna di valori che si
trova a destra del grafico e che fa riferimento alla serie dei ricavi.
Un clic destro sulla colonna che appare circondata da una cornice di selezione fa
aprire la finestra di dialogo a destra del foglio di lavoro intitolata Formato asse, nella
quale ritocchiamo il valore del limite massimo dei valori portandolo a 8.000.000.
Ripetiamo la stessa operazione per la colonna dei valori dellasse principale verticale
(quello degli ordini) e impostiamo il valore massimo su 80.000, chiudendo poi la
finestra di dialogo Formato asse. Con queste due operazioni abbiamo una griglia
orizzontale uniforme per entrambe le colonne.
Essendo sovrapposte e su scale diverse il caso di intervenire per differenziarle
meglio.
Procediamo in questo modo:
con un doppio clic sulla colonna degli ordini, facciamo aprire la finestra di dialogo
Formato serie di dati, dove impostiamo su 0 il valore per lopzione
Sovrapposizione serie e su 50 il valore per lopzione Distanza fra le barre;
con un doppio clic sulla barra dei ricavi, facciamo aprire la corrispondente finestra
di dialogo Formato serie di dati, dove impostiamo su 100 il valore per lopzione
Sovrapposizione serie e su 400 il valore per lopzione Distanza fra le barre.
Nella sezione Strumenti grafico/Progettazione della barra multifunzione eseguiamo il
comando Aggiungi elemento grafico, che presenta un elenco a discesa degli elementi che
possono essere presenti in un grafico e in questo elenco selezioniamo Titoli degli assi,
facendo cos uscire un elenco degli assi ai quali si possono assegnare titoli: scegliamo di
dare un titolo ai due assi dei valori, quello principale e quello secondario, entrambi
verticali, digitando nello schema che ci viene presentato il testo che preferiamo, che va a
inserirsi correttamente anche se compare scritto in verticale.
Aggiungiamo con lo stesso meccanismo una legenda e possiamo salvare il nostro
lavoro, che dovrebbe presentarsi come nella Figura 8.6.
Figura 8.6 Il grafico con due assi dei valori rappresenta due serie diverse.

I grafici a linee
In alternativa ai grafici a colonne ci sono i grafici a linee, nei quali i valori che
formano la serie da rappresentare sono disposti come punti nellarea del grafico e una
linea congiunge tali punti. Possiamo vedere un esempio nella Figura 8.7, che rappresenta
un grafico a linee ottenuto utilizzando i risultati della query che abbiamo usato finora,
limitatamente ai conteggi delle vendite associate a ciascuna carta di credito.
t ipopagament o cont a_0_10 cont a_10_100 cont a_100_1000 cont a_1000 cont a
?? 298 11 4 0 313
AE 1.483 36.093 9.341 465 47.382
DB 5.356 6.524 823 36 12.739
MC 1.862 38.458 6.797 201 47.318
OC 3.643 4.042 518 11 8.214
VI 3.206 62.993 10.545 273 77.017
Figura 8.7 Il grafico a linee degli ordini conteggiati per gruppi.

I grafici sparkline
In Excel 2016 disponibile un tipo di grafico introdotto in Excel 2010, radicalmente
nuovo e diverso dai precedenti, imparentato con i grafici a barre orizzontali che
abbiamo visto prima e che si chiama sparkline. Si tratta di grafici a linee o a barre
estremamente compatti e inseriti in una sola cella di un foglio di lavoro: il principale
vantaggio di questo genere di grafici sta nel fatto che vengono mostrati immediatamente
accanto alle serie di dati che rappresentano.
Per vedere come funzionano i grafici sparkline creiamo una nuova query con questo
contenuto:
SELECT tipopagamento AS 'Carta di credito',
SUM(CASE WHEN MONTH(dataordine) = 1 THEN 1 ELSE 0 END)
AS 'Gen',
SUM(CASE WHEN MONTH(dataordine) = 2 THEN 1 ELSE 0 END)
AS 'Feb',
SUM(CASE WHEN MONTH(dataordine) = 3 THEN 1 ELSE 0 END)
AS 'Mar',
SUM(CASE WHEN MONTH(dataordine) = 4 THEN 1 ELSE 0 END)
AS 'Apr',
SUM(CASE WHEN MONTH(dataordine) = 5 THEN 1 ELSE 0 END)
AS 'Mag',
SUM(CASE WHEN MONTH(dataordine) = 6 THEN 1 ELSE 0 END)
AS 'Giu',
SUM(CASE WHEN MONTH(dataordine) = 7 THEN 1 ELSE 0 END)
AS 'Lug',
SUM(CASE WHEN MONTH(dataordine) = 8 THEN 1 ELSE 0 END)
AS 'Ago',
SUM(CASE WHEN MONTH(dataordine) = 9 THEN 1 ELSE 0 END)
AS 'Set',
SUM(CASE WHEN MONTH(dataordine) = 10 THEN 1 ELSE 0 END)
AS 'Ott',
SUM(CASE WHEN MONTH(dataordine) = 11 THEN 1 ELSE 0 END)
AS 'Nov',
SUM(CASE WHEN MONTH(dataordine) = 12 THEN 1 ELSE 0 END)
AS 'Dic'
FROM Ordini
WHERE YEAR(dataordine) = 2015
GROUP BY tipopagamento
ORDER BY tipopagamento

Acquisiamo il risultato dellesecuzione della query e inseriamolo in un nuovo foglio


di lavoro dove si presenter nel modo che vediamo nella parte superiore della Figura
8.8.
Dopo aver selezionato lintervallo escludendo le intestazioni delle colonne, facciamo
clic sulla scheda Inserisci della barra multifunzione e andiamo a selezionare il comando
Grafici sparkline/Linee, ottenendo la comparsa della tipica finestra di dialogo di
selezione di Excel che vediamo qui sotto.

che predisposta per selezionare due intervalli: quello dei dati da rappresentare
graficamente e quello in cui inserire i grafici sparkline da creare.
Il primo intervallo gi selezionato e corrisponde allinsieme dei dati che abbiamo
predisposto nel foglio di lavoro; indichiamo per lintervallo di destinazione la colonna
che si trova immediatamente a destra dellintervallo dei dati di origine (questa posizione
non obbligatoria, semplicemente la pi comoda ai fini della nostra esposizione).
Un clic sul pulsante OK conclude loperazione, la finestra di dialogo si chiude e i
grafici sparkline compaiono nel punto che abbiamo designato, come possiamo vedere
nella Figura 8.8.
Figura 8.8 Il grafico sparkline inserito nel foglio di lavoro in corrispondenza diretta con le serie che rappresenta.

Questi simpatici e comodi piccoli grafici integrati nel foglio di lavoro, pur
chiamandosi sparkline, possono essere costruiti anche con barre al posto delle linee:
per farlo basta selezionare lintero intervallo dei grafici ottenuti in precedenza e
scegliere il comando Istogramma nella scheda Progettazione della nuova sezione
Strumenti grafici sparkline che si attivata nella barra multifunzione, ottenendo il
risultato che vediamo qui di seguito:
Acquisire dati in Excel da SQL Server
Nel Capitolo 7 abbiamo visto quanto integrato MySQL con Excel, al punto che i dati
contenuti nei database creati e gestiti con questo strumento si rendono disponibili non
appena si seleziona la scheda Dati nella barra multifunzione in un foglio di lavoro.
Nella Figura 8.9 vediamo come articolata questa connessione prestabilita fra Excel e
MySQL:
1. un clic sul pulsante MySQL for Excel allestremit destra della scheda Dati fa
scendere una finestra di dialogo verticale che si dispone subito a destra del foglio di
lavoro;
2. qui possiamo selezionare la connessione che ci interessa fra quelle disponibili e
anche collaudarla;
3. per verificare se funziona, si fa un clic destro sul nome della connessione, facendo
cos uscire un conciso menu contestuale che permette di scegliere fra eliminare la
connessione (Delete), modificarla (Edit) o aggiornarla (Refresh);
4. selezioniamo Edit e si apre una grande finestra di dialogo al centro del foglio di
lavoro, dove possiamo intervenire, volendo, su tutti i parametri che definiscono la
connessione;
5. un clic sul pulsante di comando Test connection lancia la connessione e il risultato
della prova viene comunicato con una breve finestra di messaggio che si
sovrappone a quella del test.
Figura 8.9 La verifica della connessione fra Excel e MySQL.

Per utilizzare da un foglio di lavoro Excel dati contenuti in un database gestito con
SQL Server si ha a disposizione una funzionalit che si attiva anchessa dalla scheda Dati
della barra multifunzione: il pulsante pi a sinistra di quella scheda intitolato Carica
dati esterni, un clic su quel pulsante fa scendere un elenco di altri pulsanti di comando
fra i quali si sceglie Da altre origini. Attivando questo pulsante si apre un lungo elenco a
discesa di possibili fonti di dati esterni, la cui prima voce Da SQL Server, come
vediamo nella Figura 8.10.

Figura 8.10 La successione di comandi per avviare lacquisizione di dati da SQL Server.
Se scegliamo quellopzione si apre una breve sequenza di finestre di dialogo, nelle
quali siamo invitati a specificare come prima cosa il nome del server e le condizioni di
accesso.
Se non ricordiamo il nome del server possiamo ottenerlo dallo stesso SQL Server
eseguendo in Management Studio la query seguente:
SELECT @@SERVERNAME;

che genera nella scheda Risultati una sola stringa di caratteri corrispondente al nome
del server attivo in quel momento.
Proseguendo si arriva alla finestra di dialogo che vediamo nella Figura 8.11, dove
scegliamo il database col quale connetterci e la o le tabelle da acquisire.

Figura 8.11 La selezione dei dati da acquisire dal database SQL Server.

Fatta questa scelta si viene invitati a salvare, non obbligatoriamente, i parametri scelti
nelle finestre di dialogo precedenti in un file particolare per il quale possiamo scegliere
il nome, una eventuale descrizione e il percorso di salvataggio, ma non lestensione, che
.odc e sta a indicare che si tratta di un file di tipo ODBC, sul quale torneremo fra poco.
Dopo lultima finestra di dialogo compare una finestra pi piccola nella quale
possiamo selezionare il foglio di lavoro e la cella dalla quale si inserir la tabella
selezionata. Se abbiamo selezionato pi tabelle, possiamo indicare soltanto la cella
dorigine per ciascuna e verranno inserite nella cartella di lavoro tutte le tabelle che
abbiamo selezionato, ognuna in un foglio di lavoro separato.
Usare query per acquisire dati
Questo modo di operare per ottenere dati da database SQL Server sicuramente
altrettanto semplice e immediato di quello disponibile per acquisire intere tabelle da
database MySQL. Entrambi i metodi, per pur essendo indubbiamente comodi e
semplici da usare sono decisamente troppo semplici e per cos dire brutali perch
lacquisizione dei dati avviene senza lintermediazione di una query, lo strumento
principe di SQL, sul quale ci siamo soffermati tanto a lungo nei capitoli precedenti.
La domanda quindi : possiamo acquisire selettivamente in un foglio di lavoro Excel
dati da tabelle contenute in database gestiti con SQL Server e MySQL? In altri termini,
possibile fare queste operazioni usando il comando SELECT di SQL con tutta la sua
potenza? La risposta positiva, e per sapere come si fa dobbiamo procedere con una
breve divagazione.
Selezioniamo nel nostro computer il file con estensione .odc che abbiamo salvato al
termine del collegamento con SQL Server, Un clic destro sul suo nome in Esplora file fa
uscire un elenco di opzioni, fra le quali selezioniamo il comando Edit with Notepad
oppure Apri con/Blocco note.
In entrambi i casi si apre un file di testo dal contenuto prolisso e apparentemente
misterioso. Non facciamoci prendere dal panico e scorriamo il testo senza modificarlo
fino a raggiungere questa stringa:
<odc:ConnectionString>

immediatamente dopo compare una lunga sequenza di caratteri, che si chiude con la
stringa
</odc:ConnectionString>

che diversa dalla prima soltanto per la barra inclinata a destra che viene subito dopo
la parentesi angolare aperta.
Selezioniamo tutto il blocco di caratteri che si trova fra queste due stringhe,
facciamone una copia con la combinazione di tasti Ctrl+C, chiudiamo il file .odc, senza
modificarlo e apriamo un nuovo documento Blocco note vuoto, nel quale incolliamo la
serie di caratteri che abbiamo selezionato prima usando la combinazione di caratteri
Ctrl+V.
Quello che compare nella finestra del nuovo documento di testo si presenta cos:
Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=True;Data Source=MarcoFerrero-
HP;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID=MARCOFERRERO-HP;Use
Encryption for Data=False;Tag with column collation when possible=False;Initial Catalog=Biblioteca

Non perdiamoci danimo: basta mettere un a capo dopo ogni carattere punto e virgola
e otteniamo un elenco meno oscuro:
Provider=SQLOLEDB.1;
Integrated Security=SSPI;
Persist Security Info=True;
Data Source=MarcoFerrero-HP;
Use Procedure for Prepare=1;
Auto Translate=True;
Packet Size=4096;
Workstation ID=MARCOFERRERO-HP;
Use Encryption for Data=False;
Tag with column collation when possible=False;
Initial Catalog=Biblioteca

Questa che vediamo la stringa di connessione, vale a dire una successione di


caratteri di testo che tutti insieme compongono le informazioni necessarie per stabilire
una connessione fra Excel e SQL Server. Ai fini del risultato che ci interessa, alcuni di
questi elementi non sono necessari, perci la stringa di connessione si pu ridurre in
questo modo:
Provider=SQLOLEDB.1;
Integrated Security=SSPI;
Data Source=MarcoFerrero-HP;
Workstation ID=MARCOFERRERO-HP
Initial Catalog=Biblioteca

Cos ridotta possiamo utilizzarla in un programma scritto in Visual Basic for


Applications (in sigla VBA), il linguaggio di programmazione interno di tutte le
applicazioni Office e quindi anche di Excel.
Per poter scrivere e utilizzare un programma scritto con questo linguaggio
necessario aprire la scheda Sviluppo della barra multifunzione di Excel.
NOT A
possibile che la scheda Sviluppo non sia visibile nella versione di Excel che stiamo utilizzando, cosa
che si rimedia facilmente: basta selezionare il comando Opzioni nellelenco che si apre facendo clic sul
comando File. Nellelenco a discesa delle Opzioni si sceglie Personalizzazione barra multifunzione e nella
articolata finestra di dialogo che si apre basta fare clic sulla casella di controllo vuota che sta accanto alla
voce Sviluppo, nellelenco Schede principali e quindi fare clic su OK per stabilizzare la scelta e chiudere la
finestra di dialogo Opzioni.

Nella scheda Sviluppo si seleziona il comando Visual Basic e si viene portati in un


ambiente radicalmente diverso, che lambiente di lavoro per creare programmi in
Visual Basic for Applications, che sono detti genericamente routine. Vediamo questo
ambiente nella Figura 8.12.
Figura 8.12 Lambiente di sviluppo per Visual Basic for Applications in Excel.

La finestra VBA si articola in due aree, quella di sinistra, che contiene i riferimenti e i
comandi per attivare vari elementi della cartella di lavoro e quella di destra, pi ampia e
al momento vuota, che un contenitore molto simile allarea di scrittura di Blocco note,
dove si scrivono gli enunciati del linguaggio Visual Basic for Applications.
Larea di destra detta anche Modulo e pu contenere pi di un modulo, ciascuno
individuato da un nome, che si crea dando il comando Inserisci/Modulo dalla barra dei
menu.
Per il momento abbiamo un solo modulo, chiamato Modulo 1 e ci pu bastare. Per
non farci distrarre da altri elementi chiudiamo la sezione di sinistra con un clic sul
pulsante con la croce di SantAndrea che si trova in testa a quella sezione, in modo da
avere tutto uno spazio libero e comodo per lavorare.
La routine che dobbiamo creare per poter eseguire una query SQL direttamente dal
nostro foglio di lavoro Excel si articola in alcuni elementi:
la definizione della stringa di collegamento per agganciare il database SQL Server
che ci interessa;
la definizione della query che intendiamo eseguire;
le indicazioni da dare a Excel su come utilizzare i risultati della query.
In VBA le routine devono avere un nome e si scrivono facendo precedere il nome
dalla parola chiave Sub e accodando al nome una coppia di parentesi tonde, in questo
modo:
Sub GeneraQuery()

Scriviamo questa successione di caratteri nellarea del modulo e poi premiamo Invio,
ottenendo il risultato che possiamo vedere qui di seguito:
Sub GeneraQuery()

End Sub

intervenuto leditor del linguaggio VBA che ha completato il nome della routine
con la riga End Sub che obbligatoria. Adesso sta a noi inserire fra le due righe che
inquadrano la routine le istruzioni che ci servono.
Abbiamo bisogno di impostare due variabili che corrispondono alle stringhe di testo
rispettivamente della stringa di connessione e della query che intendiamo eseguire.
Chiamiamo queste due variabili strConnetti e strSQL e scriviamo le istruzioni
corrispondenti subito sotto il nome della routine, in questo modo
Dim strConnetti, strSQL As String

Assegniamo un valore a ciascuna delle due variabili inserendo il carattere = fra il


nome della variabile a sinistra e il suo contenuto a destra, in questo modo:
strConnetti = "Provider=SQLOLEDB.1;Integrated Security=SSPI;" & _
"Data Source=MarcoFerrero-HP;Workstation ID=MARCOFERRERO-HP;" & _
"Initial Catalog=Biblioteca"

in questo modo la variabile strConnetti contiene lintera stringa di connessione che


abbiamo visto prima, che appare divisa in tre sezioni per chiarezza espositiva (le sezioni
sono ottenute con la coppia di caratteri & _, che segnala allEditor di VBA di considerare
i tre spezzoni di stringa come ununica successione di caratteri)
Impostiamo anche la query che vogliamo eseguire, che potrebbe essere:
SELECT DISTINCT Titolo FROM Titoli
ORDER BY Titolo

In VBA possiamo scriverla in questo modo:


strSQL = "SELECT DISTINCT Titolo FROM Titoli " & _
"ORDER BY Titolo"

Subito dopo scriviamo anche questo comando VBA:


MsgBox strConnetti & vbCr & vbCr & strSQL

in modo che lintera routine abbia laspetto seguente:


Sub GeneraQuery()
Dim strConnetti, strSQL As String
strConnetti = "Provider=SQLOLEDB.1;Integrated Security=SSPI;" & _
"Data Source=MarcoFerrero-HP;Workstation ID=MARCOFERRERO-HP;" & _
"Initial Catalog=Biblioteca"
strSQL = "SELECT DISTINCT Titolo FROM Titoli " & _
"ORDER BY Titolo"
MsgBox strConnetti & vbCr & vbCr & strSQL
End Sub
Inseriamo il puntatore del mouse in un punto qualunque della routine e premiamo il
tasto di funzione F5, ottenendo cos, allinterno del foglio di lavoro Excel dal quale
siamo partiti, la comparsa della piccola finestra di messaggio che segue.

Dopo aver verificato grazie a questa finestra di messaggio che il contenuto delle
variabili che abbiamo impostato corretto, possiamo chiudere questa finestra con un
clic su OK e, tornati automaticamente nelleditor di VBA, possiamo anche cancellare
listruzione che ha generato la finestra di messaggio (MsgBox ), perch non ci serve pi.
Abbiamo bisogno di una nuova variabile che chiameremo rsDati, e che dobbiamo
impostare nella nostra routine con questa forma:
Dim rsDati As ADODB.Recordset

In questo modo specifichiamo che questa variabile destinata a contenere un insieme


di record di tipo ADODB.
Dopo aver fatto nascere la variabile con lenunciato precedente, dobbiamo impostarla,
cosa che si fa con questo enunciato:
Set rsDati = New ADODB.Recordset

Assegneremo alla nuova variabile il compito di aprire il database che ci interessa e di


eseguire su quel database la query che abbiamo predisposto, tutto questo semplicemente
con le seguenti quattro parole in codice, da disporre cos:
rsDati.Open strSQL, strConnetti

Lenunciato nella sua sobriet fa eseguire il metodo Open alloggetto rsDati indicandogli
il database da aprire (quello specificato nella variabile strConnetti) e la query da eseguire
(indicata nella variabile strSQL ).
Loggetto rsDati apre il database ed esegue la query, riempiendosi di dati. Bisogna
dirgli che cosa se ne deve fare: glielo si dice con questo enunciato:
Foglio1.Range("P1").CopyFromRecordset rsDati

Letto da destra verso sinistra significa che loggetto rsDati, che un contenitore di
record e si suppone pieno, deve copiarli nellintervallo che inizia dalla cella P1 del
foglio di lavoro che si chiama Foglio1.
Non sappiamo quanti record contenga il nostro oggetto rsDati, ma sappiamo che la
lista per quanto lunga possa essere si chiude con un carattere speciale, detto EOF (che
sta per End of File).
Stabiliamo quindi che il comando di copiare dallinsieme di record estratti con la
query nellintervallo del foglio di lavoro va eseguito fino a quando lestrazione
raggiunge il segnale di fine file, racchiudendo lenunciato operativo entro una clausola
IFTHENELSE, nel modo che segue.

If Not rsDati.EOF Then


Foglio1.Range("P1").CopyFromRecordset rsDati
Else
MsgBox "Errore!"
End If

Quando loperazione si conclusa correttamente ci liberiamo delloggetto rsDati con


queste istruzioni:
rsDati.Close

Set rsDati = Nothing

La nostra routine VBA adesso completa e si presenta in questo modo nello spazio
dellEditor di VBA:
Sub GeneraQuery()
Dim strConnetti, strSQL As String
strConnetti = "Provider=SQLOLEDB.1;Integrated Security=SSPI;" & _
"Data Source=MarcoFerrero-HP;Workstation ID=MARCOFERRERO-HP;" & _
"Initial Catalog=Biblioteca"
strSQL = "SELECT DISTINCT Titolo FROM Titoli " & _
"ORDER BY Titolo"
Dim rsDati As ADODB.Recordset
Set rsDati = New ADODB.Recordset
rsDati.Open strSQL, strConnetti
If Not rsDati.EOF Then
Foglio1.Range("P1").CopyFromRecordset rsDati
Else
MsgBox "Errore!"
End If
rsDati.Close
Set rsDati = Nothing
End Sub

Inseriamo il puntatore del mouse in un punto qualunque della routine e premiamo il


tasto di funzione F5: invece di un risultato siamo fulminati da un tenebroso e
incomprensibile messaggio di errore.
Che cosa accaduto?
Molto semplicemente, abbiamo dimenticato di spiegare a quale famiglia appartiene
loggetto rsDati che deve fare tutto il lavoro. Lo abbiamo dichiarato come
ADODB.Recordset

cio come un insieme di record che appartiene alla famiglia ADO per database,
chiamata in sigla ADODB.
Per far sapere a Visual Basic for Applications di quali strumenti stiamo parlando
dobbiamo prima di eseguire questa routine aprire la finestra di dialogo Riferimenti
dal menu Strumenti di VBA e far scorrere il lunghissimo elenco di elementi fino ad
arrivare ai due componenti che ci servono e che si chiamano:
Microsoft ActiveX Data Objects 6.1 Library
Microsoft ActiveX Data Objects Recordset 2.8 Library.
Una volta trovati, facciamo un clic sulla casella di controllo che sta alla sinistra del
loro nome in modo da selezionarli e la finestra di dialogo Riferimenti si presenter
come nella Figura 8.13.

Figura 8.13 La finestra di dialogo Riferimenti completa dei richiami agli strumenti ActiveX.

Dopo questa modifica e dopo aver chiuso la finestra di dialogo Riferimenti,


lesecuzione della nostra routine avviene senza intoppi e in qualche secondo troviamo
nellintervallo che abbiamo indicato il risultato dellesecuzione della nostra query.
Acquisire dati da MySQL
Possiamo ottenere gli stessi servizi in Excel con VBA anche interfacciandoci con un
database MySQL, a condizione di aver predisposto una adeguata connessione ODBC.
Le funzionalit ODBC in MySQL non sono integrate nello strumento, come in SQL
Server, ma vanno installate deliberatamente, scaricando un file di sistema dal sito web di
MySQL e installandolo. Loperazione estremamente semplice e non richiede molto
tempo, trattandosi di un file di dimensioni piuttosto contenute (8.16 MB), che si chiama:
mysql-connector-odbc-5.3.6-winx64.msi

e si trova allindirizzo:
https://dev.mysql.com/downloads/connector/odbc/

Lestensione .msi del nome del file fa capire che si tratta di uno strumento per
linstallazione, che viene eseguita quando si fa doppio clic sul nome del file.
Se la nostra macchina Windows a 64 bit opportuno scaricare la versione per
Windows a 64 bit, anche se poi, come vedremo, si utilizzeranno connessioni ODBC a 32
bit.
Gi che siamo nel sito web per il download di MySQL e suoi accessori, conviene
attivare questo link:
MySQL Connector/ODBC Developer Guide /

per scaricare il manuale di MySQL Connector ODBC nel formato che si preferisce (
disponibile in .PDF A4 e in ePub, ovviamente in inglese).
Una volta fatti i download e lopportuna installazione siamo pronti a passare
alloperativit.
Ci serve innanzitutto una stringa di connessione, cosa che si ottiene con i seguenti
passaggi:
1. un clic destro su Start di Windows 10 fa uscire in verticale un elenco di comandi
scritti in bianco su nero fra i quali selezioniamo Pannello di controllo;
2. nella ben nota schermata riepilogativa del Pannello di controllo selezioniamo
Strumenti di amministrazione e nellelenco che si apre scegliamo Origini dati
ODBC 32 bit, ottenendo lapertura della finestra di dialogo Amministratore origine
dati ODBC;
3. ci portiamo sulla scheda DNS di sistema e facciamo clic sul pulsante Aggiungi,
ottenendo cos lapertura della finestra di dialogo Crea nuova origine dati, dove
dobbiamo per prima cosa selezionare il connettore ODBC che ci interessa, che si
chiama My SQL ODBC 5.1 Driver.
Un clic su Fine conclude la prima parte delloperazione e fa aprire la finestra di
dialogo che vediamo nella Figura 8.14, intitolata MySQL/ODBC Data Source
Configuration, dove dobbiamo inserire:
un nome convenzionale per il connettore ODBC che stiamo creando;
una descrizione (facoltativa) che aiuti a ricordare a che cosa serve quel connettore;
il nome del server, dellutente e la password di accesso a MySQL;
il nome del database MySQL col quale stabilire la connessione.
Dopo aver completato le caselle di testo disponibili con i dati necessari un clic sul
pulsante di comando Test fa aprire la rassicurante piccola finestra di messaggio che
vediamo nella figura. Un clic su OK nella finestra di messaggio e successivamente su OK
nella finestra di dialogo chiudono le finestre e stabilizzano le scelte: abbiamo creato
materialmente il collegamento ODBC che ci serve.

Figura 8.14 La finestra di dialogo per creare il connettore ODBC in MySQL.

A questo punto possiamo procedere a collaudare il nostro strumento: apriamo una


cartella di lavoro Excel nuova, attiviamo lEditor di VBA, creiamo in questo un nuovo
modulo e vi digitiamo la seguente routine:
Sub AcquisisceDaMySQL()

'Crea le variabili

Dim strSQL, strCollega As String


Dim rsDati As ADODB.Recordset
Set rsDati = New ADODB.Recordset
'Imposta le variabili

strCollega = "DSN=ConnMySQL"

strSQL = "SELECT Nome, Cognome, YEAR(Now())-YEAR(datanascita) AS Et " & _


"FROM dipendenti ORDER BY Et"

'Presenta il contenuto delle variabili stringa

MsgBox strCollega & vbCr & strSQL

'Apre il recordset rsDati e lo associa alla tabella del database


'e all'enunciato SQL

rsDati.Open strSQL, strCollega

'Se il recordset non vuoto scarica il contenuto


'nell'intervallo che inizia da A1 del foglio di lavoro Foglio1

If Not rsDati.EOF Then


Foglio1.Range("A1").CopyFromRecordset rsDati
Else
MsgBox "Errore!"
End If

'Chiude il recordset ed elimina la variabile corrispondente

rsDati.Close
Set rsDati = Nothing
End Sub

(le righe precedute dal carattere apice (') e qui stampate in corsivo sono considerate
commenti dallEditor di VBA, vengono visualizzate in verde, servono per documentare
il codice e non interferiscono con il suo corretto funzionamento).
Prima di eseguire questa routine dobbiamo impostare il contenuto della finestra di
dialogo Riferimenti nello stesso modo in cui labbiamo impostato per eseguire
lesempio di connessione ODBC con SQL Server: vale a dire, dobbiamo aver impostato
questi riferimenti
Microsoft ActiveX Data Objects 6.1 Library
Microsoft ADO Ext 6.0 for DDL and Security
Microsoft ActiveX Data Objects Recordset 6.0 Library
come nella Figura 8.15.
Figura 8.15 La finestra Riferimenti di VBA mostra i component utilizzati dalla routine.

La nostra query
SELECT Nome, Cognome, YEAR(Now())-YEAR(Datanascita) AS Et
FROM dipendenti
ORDER BY Et

ha acquisito il contenuto dei campi Nome, Cognome e datanascita dalla tabella


Dipendenti nel database Prova, ha eseguito un calcolo usando le funzioni Now() e YEAR(),
presentando il risultato col nome alias Et e ha elencato tutto secondo lordine crescente
di questultimo valore, come possiamo vedere nella Figura 8.16.
Figura 8.16 Il risultato della query.

Connessione a 32 bit
Anche se il nostro computer una macchina Windows a 64 bit e la versione di MySQL che stiamo
utilizzando a 64 bit, la generazione di una stringa di connessione ODBC a 64 bit per un database MySQL
non funziona ed necessario crearne una a 32 bit, che funziona perfettamente.

Con questi due ultimi esempi abbiamo appena sfiorato la punta delliceberg ideale
rappresentato dallo strumento Visual Basic for Applications in Excel.
Chi scrive ha realizzato per Apogeo ledizione italiana di numerosi libri di uno
specialista in materia, Mike Davis, fra i quali si sente di raccomandare ai lettori che
volessero approfondire largomento il libro Costruire applicazioni con Excel 2013, che
contiene ampie e complete spiegazioni associate a numerosi e chiari esempi su come
utilizzare VBA per ottenere da Excel prestazioni professionali.
SQL e tabelle pivot
A partire dalledizione 5, pubblicata nel 1993, Excel si arricchito di una funzionalit
chiamata tabella pivot, che nelle successive edizioni stata via via resa pi potente e
articolata, fino a farla diventare una colonna portante di quellapplicazione.
Di che cosa si tratta? Detto in pochissime parole, una tecnica per organizzare i dati
in tabelle che si appoggiano idealmente a una colonna, che fa da perno (da qui il nome)
intorno alla quale si possono far ruotare gli altri dati contenuti nella tabella stessa per
esaminarli in diverse possibili associazioni. Un semplice esempio chiarir i concetti di
base.
In un foglio di lavoro abbiamo i seguenti dati, organizzati in forma tabellare come si
fa abitualmente.
Tabella 8.1 Dati elementari per una tabella pivot.
Area Prov Art icolo Pezzi Ricavi
I BG A-10 123 12.300,00
I BG B-90 295 44.250,00
I BG C-50 414 16.560,00
I BG D-70 278 20.850,00
I CO A-10 217 21.700,00
I CO B-90 164 24.600,00
I CO C-50 451 18.040,00
I CO D-70 208 15.600,00
II MI A-10 447 44.700,00
II MI B-90 103 15.450,00
II MI C-50 129 5.160,00
II MI D-70 506 37.950,00
II PV A-10 479 47.900,00
II PV B-90 140 21.000,00
II PV C-50 247 9.880,00
II PV D-70 272 20.400,00

Operando opportunamente su questo insieme di dati posiamo ottenere con pochi clic il
risultato che vediamo nella Figura 8. 17 in alto.
Figura 8.17 La tabella pivot ottenuta a partire dai dati della Tabella 8.1.

Trascinando col mouse lintestazione della colonna e spostandolo verso il basso a


sinistra otteniamo una diversa configurazione degli stessi dati, come nella Figura 8.17 in
basso.
Le tecniche per ottenere tabelle pivot sono complesse ma non complicate e nel libro di
Mike Davis che abbiamo citato nel paragrafo precedente sono esposte con abbondanza di
dettagli ed esempi per una quarantina di pagine.
Forte del know-how che ha acquisito e sviluppato in Excel per le tabelle pivot,
Microsoft ha deciso di rendere agevole la acquisizione di dati in SQL Server per poterli
elaborare successivamente come tabelle pivot.
A questo scopo ha aggiunto alcune clausole allenunciato SELECT, che si possono usare
osservando la seguente struttura sintattica:
SELECT
[colonna non pivot ], -- facoltativo
[ulteriori colonne non pivot], -- facoltative
[prima colonna pivot],
[ulteriori colonne pivot]
FROM (
SELECT query che produce dati sql per pivot
-- seleziona colonne pivot come dimensioni e
-- colonne di valori come misure da tabelle sql
) AS AliasTabella
PIVOT
(
<funzione di aggregazione>(colonna per aggregazione o colonna con misure) -- MIN,MAX,SUM,etc
FOR [<nome Colonna che contiene valori per colonne tabella pivot>]
IN (
[prima colonna pivot], ..., [ultima colonna pivot]
)
) AS AliasTabellaPivot
ORDER BY clausola -- facoltativa

Lesempio che segue utilizza il database dimostrativo AdventureWorks distribuito


come aggiunta a SQL Server. Per avere questo database necessario procurarsi il file
denominato AdventureWorks2014.bak dal sito web di Microsoft e scaricarlo in una cartella
qualunque, che potrebbe essere C:\Esempi. Questo file la versione di backup del database
originale con tutti i suoi dati. Per ripristinarlo nella versione di SQL Server che abbiamo
installato ci basta trascrivere in un file di testo con Blocco note lo script seguente:
USE [master]

RESTORE DATABASE AdventureWorks2014


FROM disk= 'C:\Esempi\AdventureWorks2014.bak'
WITH MOVE 'AdventureWorks2014_data' TO 'C:\Program Files\Microsoft SQL
Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\AdventureWorks2014.mdf',
MOVE 'AdventureWorks2014_Log' TO 'C:\Program Files\Microsoft SQL
Server\MSSQL12.MSSQLSERVER\MSSQL\DATA\AdventureWorks2014.ldf'
,REPLACE

badando che nella riga FROM disk= sia inserito il nome, completo di percorso, del file
che abbiamo ottenuto dal sito web di Microsoft.
Fatto questo, si seleziona tutto il testo e si d il comando Copia, successivamente si va
nella finestra Query di Management Studio e si incolla lo script, dando poi il comando di
esecuzione della query: se tutto andato bene, abbiamo il database AdventureWorks2014
installato nel nostro ambiente di prova e pronto per essere usato.
Fra le numerosissime tabelle (una cinquantina) in cui si articola il database
dimostrativo che abbiamo appena descritto, scegliamo di acquisire selettivamente un
certo numero di dati dalla tabella Production.WorkOrder, nella quale sono contenuti gli
ordini di lavorazione individuati da un codice di prodotto (campo ProductID) e da una
data prevista di consegna (campo DueDate). Vogliamo ottenere per la tabella pivot che
ci interessa creare dati sui prodotti raggruppati per mese con la quantit ordinata per
ciascun codice prodotto (campo OrderQty).
Questa la query che andiamo a usare:
SELECT *
FROM (SELECT YEAR(DueDate) [Anno],
CASE MONTH(DueDate)
WHEN 1 THEN 'Gennaio'
WHEN 2 THEN 'Febbraio'
WHEN 3 THEN 'Marzo'
WHEN 4 THEN 'Aprile'
WHEN 5 THEN 'Maggio'
WHEN 6 THEN 'Giugno'
WHEN 7 THEN 'Luglio'
WHEN 8 THEN 'Agosto'
WHEN 9 THEN 'Settembre'
WHEN 10 THEN 'Ottobre'
WHEN 11 THEN 'Novembre'
WHEN 12 THEN 'Dicembre'
END as [Mese],
ProductID as CodProdotto,
OrderQty
FROM Production.WorkOrder
) WorkOrders
PIVOT
(
SUM(OrderQty)
FOR [Mese] IN (
[Gennaio],
[Febbraio],
[Marzo],
[Aprile],
[Maggio],
[Giugno],
[Luglio],
[Agosto],
[Settembre],
[Ottobre],
[Novembre],
[Dicembre]
)
) AS PivotTable
ORDER BY [Anno], CodProdotto

La eseguiamo e otteniamo un robusto pacchetto di righe, per lesattezza 652 come


possiamo vedere nella Figura 8.18.
Figura 8.18 La query ha generato una tabella composta da 652 righe.

Selezioniamo lintero risultato con un clic sullincrocio fra righe e colonne


nellintestazione in alto a sinistra della sezione Risultati, un clic destro sui risultati cos
selezionati ci d lopportunit di scegliere il comando Copia con intestazioni, lo
eseguiamo e usciamo da SQL Server.
Passiamo a Excel, apriamo una nuova cartella di lavoro e nel primo foglio inseriamo
i risultati della query.
Dalla barra multifunzione attiviamo il comando Trova e seleziona, in questo
scegliamo lopzione Trova e sostituisci e nella finestra di dialogo che compare diamo
lordine di sostituire le scritte NULL che sono presenti in numerose celle del foglio di
lavoro con una dicitura pi sobria, per esempio ND: "NULL" potrebbe allarmare i futuri
utenti della nostra tabella pivot che non necessariamente conoscono il significato a uso
esclusivo dello Structured Query Language di questo termine.
Selezioniamo successivamente lintero intervallo che contiene i dati premendo la
combinazione di tasti Ctrl+Maiusc+* e dalla barra multifunzione apriamo la scheda
Inserisci: allinizio della scheda troviamo due comandi Tabella Pivot e Tabelle pivot
consigliate. Optiamo per il secondo comando, che fa scendere gli schemi che vediamo
nella Figura 8.19.
Figura 8.19 Le tabelle pivot proposte da Excel per i dati contenuti nellintervallo selezionato.

Selezioniamo lopzione che compare sulla destra, quindi facciamo clic su OK, la
finestra di dialogo si chiude e nella nostra cartella di lavoro viene creato un nuovo
foglio, che contiene la tabella pivot che abbiamo scelto.
Le intestazioni usate dal generatore della tabella pivot sono generiche, quindi
possiamo migliorare laspetto del nostro risultato selezionando le celle con le
intestazioni e modificandone a piacere il contenuto descrittivo per renderlo pi chiaro,
come possiamo vedere qui di seguito.
Anno Tot ale prodot t i per anno
2011 74807
2012 111308
2013 169019
2014 149105
Totale complessivo 504239

Se selezioniamo una cella qualunque della tabella pivot si apre una finestra di dialogo
intitolata Campi tabella pivot (Figura 8.20) nella quale possiamo selezionare elementi
diversi da aggiungere alla rappresentazione in formato pivot o da eliminare, come per
esempio lelenco dei mesi di un anno particolare: queste operazioni richiedono una
buona competenza in materia e la loro spiegazione esula dallambito che ci siamo
proposti per questo libro. I lettori potranno trovare ampia documentazione in merito nel
citato lavoro di Mike Davis e in altri testi su Excel disponibili nel catalogo Apogeo.

Figura 8.20 La finestra di dialogo Campi tabella pivot dalla quale si pu reimpostare completamente la struttura
di una tabella pivot.
Indice
Introduzione
Presentazione
Tra cronaca e storia
Il ruolo di Microsoft
I grandi computer e i dati
Lavorare con SQL ed Excel
Capitolo 1 - Gli strumenti di lavoro
MySQL
SQL Server
I database dimostrativi
I file di lavoro
Capitolo 2 - Caratteristiche dei database relazionali
Univocit e chiavi primarie
Chiavi esterne e relazioni
Normalizzazione e forme normali
Le interfacce utente
Capitolo 3 - Database e tabelle
Convenzioni per la descrizione della sintassi
Creare un database
Creare tabelle
I tipi di dati per le colonne
Tabelle e indici
Modificare tabelle
Riepilogo della sintassi
Capitolo 4 - Interrogare i database
Forme semplici di SELECT
La clausola ORDER BY
Le funzioni
Funzioni di aggregazione
La clausola GROUP BY
Capitolo 5 - Creare selezioni
Selezionare con criteri semplici
Selezionare valori NULL
Selezionare con criteri complessi
Capitolo 6 - Lavorare con pi tabelle
La clausola JOIN
Le sottoquery
Capitolo 7 - Estrarre dati
Estrarre i strongdati manualmente
Estrarre i strongdati automaticamente
Capitolo 8 - Integrazione con Excel
I grafici principali
Acquisire dati in Excel da SQL Server
Acquisire dati da MySQL
SQL e tabelle pivot