Le interrogazioni annidate lezione Lezione 5 5 Le interrogazioni annidate In questa lezione impareremo... • a codificare operazioni insiemistiche su tabelle e relazioni • a eseguire interrogazioni scalari • a condizionare le subquery con gli operatori di quantificazione semplice ed esistenziale ■■ Le operazioni insiemistiche In questa lezione vengono esaminate le interrogazioni nidificate. Esse rappresentano uno strumento valido per effettuare interrogazioni complesse, che non possono essere realizzate con i comandi SQL visti finora. In pratica, un’interrogazione nidificata (o subquery) non è altro che un’interrogazione che permette il confronto tra uno o più attributi e il risultato di una sottointerrogazione. Di seguito elenchiamo i vantaggi offerti dall’uso delle subquery. ◗◗ Le subquery permettono interrogazioni strutturate, rendendo in tal modo possibile la separazione di ogni singola parte significativa del comando SQL. ◗◗ Esse rappresentano un metodo alternativo alle congiunzioni per eseguire operazioni che altrimenti renderebbero necessario l’uso di queste complesse istruzioni. ◗◗ È opinione comune che l’introduzione delle subquery sia stata l’innovazione che ha consentito di definire SQL un vero e proprio linguaggio strutturato. Le operazioni insiemistiche vengono anche chiamate query binarie o “set queries” e permettono di combinare insieme più SELECT per ottenere un unico output. Si ottengono concatenando due query SQL tramite operatori insiemistici di unione, intersezione e differenza. La sintassi generale secondo SQL standard è la seguente: è necessario, però, osservare che sia MySQL sia Access utilizzano il solo operatore UNION, che permette di unire due tabelle mediante un campo comune. Infatti, i due operatori insiemistici INTERSECT ed EXCEPT possono essere sostituiti da interrogazioni equivalenti. Query esterna SQL { UNION | INTERSECT | EXCEPT [ALL] Query interna SQL } P. Camagni, R. Nikolassy, Progettare i database, Nuova Edizione OPENSCHOOL, © Ulrico Hoepli Editore S.p.A. 2014 1 UdA 3 Il linguaggio SQL La funzione di unione può essere espressa mediante la figura che segue, in cui si evidenzia come la struttura possa essere ciclica, ovvero nidificata; inoltre, per ogni result-set si ottiene la somma con la successiva. Le principali caratteristiche dell’operatore UNION sono indicate brevemente di seguito. ◗◗ L’esecuzione di un insieme di più UNION (senza parentesi) avviene in ordine da sinistra verso destra. ◗◗ Le singole SELECT che partecipano alla UNION non possono avere loro clausole ORDER BY; un unico ORDER BY può essere specificato dopo l’ultima SELECT e viene applicato al risultato finale delle operazioni di UNION. ◗◗ Le clausole GROUP BY e HAVING possono essere specificate per le singole clausole SELECT, ma non per il risultato finale. ◗◗ Mediante gli operatori insiemistici si ottiene un’eliminazione dei duplicati e anche delle proiezioni, a meno che non venga usata la clausola ALL. Esempio14 Vediamo un esempio per comprendere meglio il significato di questo operatore. Utilizziamo le due tabelle seguenti: nella prima, denominata Maternità, vi sono i nomi delle madri e dei relativi figli, mentre nella tabella Paternità vi sono i nomi dei padri e dei relativi figli. Tabella MaternitàTabella Paternità La query insiemistica può essere vista come un’unione basata sugli attributi esposti nella target list delle due SELECT. In pratica, vengono unite tutte le tuple delle due tabelle che hanno gli attributi uguali, sia come tipo che come ordine sequenziale. Tale ordine viene chiamato notazione posizionale. Per esempio, si vogliono conoscere tutti i nomi dei genitori e dei relativi figli mediante la seguente query: Select nome_figlio, cognome_figlio, nome_madre, cognome_madre FROM Maternità UNION Select nome_figlio, cognome_figlio, nome_padre, cognome_padre FROM Paternità 2 P. Camagni, R. Nikolassy, Progettare i database, Nuova Edizione OPENSCHOOL, © Ulrico Hoepli Editore S.p.A. 2014 Le interrogazioni annidate Lezione 5 Tale interrogazione comprende da una prima query, il cui risultato viene confrontato con le tuple ottenute dalla seconda. Nelle chermate che seguono sono indicati i passaggi che vengono eseguiti per ottenere il risultato. Nel primo passaggio abbiamo il risultato della prima query, ovvero i nomi delle madri con accanto i relativi figli. Nella schermata relativa al secondo passaggio vengono indicati i record della tabella Paternità che sono il risultato della seconda query indicata dopo la clausola UNION. Primo passaggio intermedio: 2 Secondo passaggio intermedio: Per poter realizzare un’unione è necessario che le due tabelle da unire possiedano lo stesso numero di colonne, e che i domini degli attributi siano uguali. L’unione delle due tabelle produce una nuova tabella che contiene tutte le righe delle tabelle di partenza, escludendo le righe doppie. In questo caso non vi sono righe doppie, per cui si ottiene: P. Camagni, R. Nikolassy, Progettare i database, Nuova Edizione OPENSCHOOL, © Ulrico Hoepli Editore S.p.A. 2014 3 UdA 3 Il linguaggio SQL Quali nomi vengono associati agli attributi del risultato? In realtà gli attributi risultanti possiedono il nome che è stato assegnato dalla prima query. È quindi buona regola assegnare un nome con alias ai campi risultato. Infatti, nell’esempio precedente si notano alcune incongruenze: per esempio, esistono nomi di padri nella colonna nome_madre. Vediamo come riscrivere in modo più corretto la query precedente, ottenendo così i campi Alias e nome genitore al posto di cognome e nome madre: SELECT nome_figlio, cognome_figlio, nome_madre AS “nome genitore”, cognome_madre AS “cognome genitore” FROM Maternità UNION SELECT nome_figlio, cognome_figlio, nome_padre, cognome_padre FROM Paternità Poniamo di voler conoscere tutti coloro che hanno ancora almeno un genitore. Per fare questo è sufficiente ricavare un elenco di tutti i figli dalla tabella Maternità, unendolo all’elenco di tutti i figli della tabella Paternità: SELECT nome_figlio, cognome_figlio FROM Maternità UNION SELECT nome_figlio, cognome_figlio FROM Paternità; Ottenendo così: ▶ Un utilizzo di tale clausola è legato alla possibilità di unire, appunto, le tuple risultanti da più query. Per esempio, se vogliamo conoscere il numero di figli complessivo per ciascun genitore, si deve ricorrere alla clasuola UNION che consente di visualizzare prima il totale dei figli dei padri e poi il totale di figli delle madri. Vediamo il codice in SQL: SELECT P.cognome_padre AS cognome, P.nome_padre AS nome, COUNT(*) AS “totale figli” FROM Paternità P GROUP BY P.cognome_padre, P.nome_padre UNION SELECT M.cognome_madre, M.nome_madre, COUNT(*) AS “totale figli” FROM Maternità M GROUP BY M.cognome_madre, M.nome_madre 4 P. Camagni, R. Nikolassy, Progettare i database, Nuova Edizione OPENSCHOOL, © Ulrico Hoepli Editore S.p.A. 2014 Le interrogazioni annidate Lezione 5 Con il seguente risultato: Prova adesso! APRI IL FILE esempio17 • Utilizzare le operazioni insiemistiche • Applicare i raggruppamenti alle UNION • Utilizzare GROUP BY e HAVING Crea una query che mostri tutti i cognomi disponibili in ordine crescente. 2 Crea una query che mostri tutti i cognomi presenti che iniziano per “C” o per “V”. Per fare questo crea quattro interrogazioni unite da UNION e all’interno di alcune di esse utilizza la clausola WHERE. Crea una query che mostri tutti i nomi dei genitori (padri o madri) che hanno più di un figlio; per fare questo utilizza la clausola HAVING, che selezioni solo i gruppi di cognomi che hanno contato più di un figlio. Confronta la tua soluzione con quella contenuta nel file esempio17_solux ■■ Le query annidate di tipo scalare L’opportunità di eseguire un’interrogazione (query) secondo un determinato ordine temporale risulta di notevole importanza nello schema relazionale. Le ◀ subquery scalari ▶ annidate producono un result-set costituito da una sola tupla con un solo attributo, utilizzabile ovunque sia assegnabile un valore scalare singolo. ◀ Subquery scalari Le subquery scalari rendono le query dinamiche e rispondenti ai dati presenti nel database, piuttosto che a valori esplicitamente impostati nel codice. ▶ Per eseguire due query in modo annidato (una dentro l’altra, per intenderci) è necessario prima di tutto conoscere la sintassi generale delle SELECT nidificate. P. Camagni, R. Nikolassy, Progettare i database, Nuova Edizione OPENSCHOOL, © Ulrico Hoepli Editore S.p.A. 2014 5 UdA 3 Il linguaggio SQL Tale forma è sintetizzata dallo schema che segue, dove si evidenziano le parti che la compongono: prima la query esterna, poi l’operatore relazionale e quindi la subquery o query annidata. query esterna SELECT ... FROM ... WHERE espressione Operatore = != > < !> !< >= <= query interna: restituisce solo un attributo (SELECT ... FROM ... WHERE espressione) Il suo funzionamento è abbastanza semplice. La query che si trova tra le parentesi tonde, chiamata anche select interna o query annidata, viene eseguita per prima; quindi, sulla base del risultato ottenuto, viene eseguita l’altra query, chiamata anche select esterna o query principale. È necessario inoltre ricordare che le sottointerrogazioni non possono contenere operatori insiemistici. La procedura risulterà più chiara con qualche esempio. Si vogliono conoscere i nomi delle madri che hanno figlie con lo stesso nome delle figlie di “Colombo Piero”. L’interrogazione è quindi formata da una query interna che cerca il nome dei figli di “Colombo Piero”, e da una esterna che confronta il nome dei figli delle madri con quello trovato nella query più interna. Vediamo il codice SQL: SELECT cognome_madre, nome_madre FROM Maternità WHERE nome_figlio = ( SELECT nome_figlio FROM Paternità WHERE cognome_padre=”Colombo” and nome_padre=”Piero”); Query esterna Query interna In quest’altro esempio si vede come è possibile usare una query annidata per verificare quali padri hanno avuto meno figli di “Corelli Lina”: SELECT cognome_padre, nome_padre, COUNT(*) AS figli FROM paternità GROUP BY cognome_padre, nome_padre HAVING COUNT(*)< ( SELECT COUNT(*) FROM Maternità WHERE cognome_madre=”Corelli” and nome_madre=”Lina”); L’operazione di nidificazione può anche essere effettuata in riferimento a differenti condizioni nella stessa query. A questo proposito, vediamo un altro esempio: si vuole conoscere il nome e cognome della nonna da parte di padre di “Valli Mario”. Per fare questo è sufficiente selezionare il nome del padre di “Valli Mario” in una query interna. La query più esterna cerca nella tabella Maternità la madre che ha nome e cognome del figlio uguali a quelli risultati dalla query più interna. L’aspetto problematico deriva dal fatto che la query più esterna deve confrontare due attributi, il cognome e il nome. In tal caso è necessario scrivere due query interne: la prima viene confrontata con il cognome, mentre la seconda con il nome. Per migliorarne la leggibilità è consigliabile indentare le query più interne. 6 P. Camagni, R. Nikolassy, Progettare i database, Nuova Edizione OPENSCHOOL, © Ulrico Hoepli Editore S.p.A. 2014 Le interrogazioni annidate Lezione 5 Vediamo il codice: SELECT cognome_madre AS “cognome nonna”, nome_madre AS “nome nonna” FROM Maternità WHERE cognome_figlio= ( SELECT cognome_padre FROM Paternità WHERE cognome_figlio=”Valli” and nome_figlio=”Mario”) and nome_figlio= ( SELECT nome_padre FROM Paternità WHERE cognome_figlio=”Valli” and nome_figlio=”Mario”); Esempio15 In questo esempio abbiamo una base di dati formata da due tabelle: Dipendenti e Mansioni. Per ciascun dipendente è associata una mansione. Tale mansione è presente in una tabella separata, chiamata appunto Mansioni, che è in relazione 1 a N con la tabella Dipendenti. Vediamo di seguito i dati contenuti nelle due tabelle. Tabella DipendentiTabella Mansioni Se volessimo conoscere il cognome e il nome di tutti i dipendenti che hanno stipendio inferiore alla media di quelli con mansione 9, ma superiore alla media di quelli con mansione 10, dovremmo applicare le conoscenze acquisite in campo di condizioni multiple, viste nell’esempio precedente: SELECT cognome, nome, stipendio FROM Dipendenti WHERE stipendio < (SELECT AVG(stipendio) FROM Dipendenti WHERE mansione=9) and stipendio > (SELECT AVG(stipendio) FROM Dipendenti WHERE mansione=10) Si ottiene: P. Camagni, R. Nikolassy, Progettare i database, Nuova Edizione OPENSCHOOL, © Ulrico Hoepli Editore S.p.A. 2014 7 UdA 3 Il linguaggio SQL ■■ Query annidate e join Anche in questo caso utilizziamo il database dell’esempio 18. Supponiamo di voler ottenere la denominazione delle mansioni e i nominativi dei dipendenti che hanno stipendio superiore alla media. Allo scopo di facilitare la comprensione della query, sono necessarie alcune considerazioni. La ricerca dell’attributo della mansione, in questo esempio, associato a un valore calcolato con un operatore di aggregazione (AVG), si può ottenere mediante nidificazione, in modo analogo agli esempi fin qui trattati. Per poter rispondere al quesito, però, è necessario considerare che i dati utili a una delle due SELECT si trovano su tabelle separate, quindi si deve ricorrere anche a una congiunzione (join). La soluzione al quesito viene esposta suddividendo le tre query e valutandone i risultati separatamente. La prima query calcola la media dello stipendio della tabella Dipendenti. SELECT AVG(stipendio) FROM Dipendenti La query seguente invece, mediante l’uso di una join, calcola la media degli stipendi, suddivisi per mansione mediante la clausola GROUP BY: SELECT M.nome, AVG(D.stipendio) AS media FROM Mansioni M INNER JOIN Dipendenti D ON D.mansione=M.ID_mansione GROUP BY M.nome Tale query produce un elenco di tuple, contraddistinte dal nome della mansione e dalla media di stipendio calcolata per ciascuna di esse, sempre che vi siano dipendenti per quella mansione, visto che è stata usata una inner join: ▶ Per conoscere invece quali mansioni hanno una media di stipendio superiore alla media complessiva è necessario annidare la query vista all’inizio. Infine, utilizzando la clausola HAVING è possibile stabilire una condizione sull’attributo media delle tuple sopra indicate. Vediamo il codice completo e il risultato: SELECT M.nome, AVG(D.stipendio) AS media FROM Mansioni M INNER JOIN Dipendenti D ON D.mansione=M.ID_mansione GROUP BY M.nome HAVING AVG(D.stipendio) > (SELECT AVG(stipendio) FROM Dipendenti) Le considerazioni svolte precedentemente sulle tre query e il risultato ottenuto con la figura indicata sopra possono così riassumersi: il raccordo tra le due SELECT, quella interna e quella esterna, è stato ottenuto imponendo una condizione sul raggruppamento mediante clausola HAVING nell’interrogazione esterna. 8 P. Camagni, R. Nikolassy, Progettare i database, Nuova Edizione OPENSCHOOL, © Ulrico Hoepli Editore S.p.A. 2014 Le interrogazioni annidate Lezione 5 Vediamo un altro esempio su come utilizzare le join all’interno di query annidate. In realtà è molto semplice, in quanto abbiamo visto che ciascuna query, anche se annidata, produce una tupla risultato che viene poi confrontata con quelle prodotte dalla query più esterna. In questo ulteriore esempio si vogliono conoscere i nomi dei dipendenti che guadagnano più del Top manager; per fare questo si utilizza una congiunzione interna (inner join) tra le tabelle Dipendenti e Mansioni mediante il campo ID_mansione: SELECT matricola, cognome, nome, stipendio FROM Dipendenti WHERE stipendio > (SELECT stipendio FROM (Dipendenti D INNER JOIN Mansioni M ON D.mansione=M.ID_mansione) WHERE M.nome=”top manager”); Ottenendo le seguenti tuple risultato: ▶ Una proprietà notevole delle query annidate è quella di essere equivalenti, in alcuni casi, alle self join. Per esempio, la query di cui sopra avrebbe potuto essere scritta mediante una self join come segue: SELECT D1.matricola, D1.cognome, D1.nome, D1.stipendio FROM Dipendenti D1, dipendenti D2, Mansioni M WHERE D1.stipendio > D2.stipendio and D2.mansione=M.ID_mansione and M.nome=”top manager”; Ottenendo il medesimo risultato! Nel caso in cui le tuple risultanti dalla query interna siano più di una, il DBMS fornisce un messaggio di errore inequivocabile, che significa che la query interna ha restituito più diuna riga, rendendo così di fatto impossibile il confronto con un attributo della query esterna: Zoom su... Le suBquerY Con operatori aggregati Sempre riguardo al database dell’esempio 18 vogliamo risolvere il problema seguente: vogliamo conoscere il nome dei dipendenti che guadagnano più della media di tutti i dipendenti. Escludendo il ricorso a interrogazioni che elenchino l’attributo nome insieme a una funzione di gruppo, escluso qualunque raggruppamento di tuple, si eseguono due query distinte. Mediante la prima si calcola la media degli stipendi: SELECT AVG(stipendio) FROM Dipendenti; P. Camagni, R. Nikolassy, Progettare i database, Nuova Edizione OPENSCHOOL, © Ulrico Hoepli Editore S.p.A. 2014 9 uda 3 Il linguaggio SQL viene utilizzato come elemento di confronto per la query più esterna, il cui codice è: SELECT cognome, nome FROM Dipendenti WHERE stipendio > 2435,56; Il difetto riscontrabile in questo tipo di approccio è legato alla non generalizzazione della seconda query, in quanto una variazione dei dati della tabella Dipendenti comporta anche una sua modifica. La nidificazione delle query evita questo inconveniente per la modifica della condizione stabilita nell’esempio citato sopra. Si tratta di unire le query precedenti, legandole mediante un operatore di confronto (>): SELECT cognome, nome FROM Dipendenti WHERE stipendio > (SELECT AVG(stipendio) FROM Dipendenti); Ottenendo così: ▶ In questo esempio abbiamo visto come esprimere, mediante la nidificazione, espressioni condizionali in cui si confronta uno scalare con un valore restituito da un operatore aggregato. Prova adesso! • Applicare le subquery • Utilizzare i raggruppamenti nelle subquery APRI IL FILE esempio18 Si vogliono conoscere i nomi, la mansione e la paga oraria dei dipendenti che hanno stipendio superiore a quello dell’autista che guadagna di più. Ti consiglio di calcolare innanzitutto lo stipendio massimo degli autisti come dato scalare nella subquery, quindi nella query più esterna ricerca tutti quei dipendenti che hanno stipendio superiore a 1650. 2 Confronta la tua soluzione con quella contenuta nel file esempio18_solux. ■■ Condizioni su valori non scalari con i quantificatori ALL, ANY e SOME Una delle caratteristiche innovative di SQL fu quella di rendere possibile l’effettuazione di query annidate in grado di confrontare più valori restituiti dalla query più interna. Finora abbiamo ipotizzato che la query interna generasse sempre valori scalari, cioè unici sia come numero di tuple che come attributi. In questo paragrafo ci occupiamo di confrontare su un unico attributo valori non scalari, cioè liste di valori. 10 P. Camagni, R. Nikolassy, Progettare i database, Nuova Edizione OPENSCHOOL, © Ulrico Hoepli Editore S.p.A. 2014 Le interrogazioni annidate Lezione 5 Le clausole ALL, ANY e SOME rendono possibile il confronto tra un attributo e un insieme di valori. Per comprenderne il meccanismo di funzionamento svilupperemo un esempio. Supponiamo di voler conoscere i nomi dei dipendenti che guadagnano meno di 1500 euro con la stessa mansione di quelli che ne guadagnano di più. Il risultato si ottiene calcolando, con una nidificazione, le tipologie di mansione dei dipendenti che guadagnano meno di 1500 euro, confrontate con le mansioni di coloro che guadagnano più di 1500 euro. Purtroppo la query seguente è errata: SELECT cognome, nome, stipendio FROM Dipendenti WHERE stipendio <=1500 AND mansione = (SELECT mansione FROM Dipendenti WHERE stipendio>1500); La subquery, infatti, ha cardinalità maggiore di uno, quindi il confronto non può essere valido. Questo si verifica in quanto la condizione tra le due query è di tipo “ambiguo”: questa, infatti, deve valere per uno o per tutti gli scalari calcolati dall’interrogazione interna? La specificazione di una delle tre clausole ◀ ALL/ANY/SOME ▶ risolve l’ambiguità, in quanto permette di definire un set di valori. Esaminiamo meglio queste clausole. ◀ ALL signica che la condizione deve essere valida “per una qualsiasi tupla” del risult-set interno; SOME è equivalente a ad ANY, ed è stato introdotto in MySQL per correttezza linguistica; ALL identifica “tutte le tuple” della relazione più interna. ▶ Vediamo ora come applicarle nella query vista sopra, correggendo in tal modo l’errore sul confronto: SELECT cognome, nome, stipendio FROM Dipendenti WHERE stipendio <=1500 AND mansione = ANY (SELECT mansione FROM Dipendenti WHERE stipendio>1500); La query deve essere letta in questo modo: “elencare tutti i dipendenti che hanno stipendio minore o uguale a 1500 con mansione uguale a uno qualsiasi tra i dipendenti con la stessa mansione ma con stipendio maggiore di 1500”. Vediamo il risultato ottenuto: La prossima query mostra con un esempio quando ricorrere alla clausola ALL. Si vuole conoscere il nome e la mansione dei dipendenti con uno stipendio più alto di quello di tutti i dipendenti con mansione “autista”. P. Camagni, R. Nikolassy, Progettare i database, Nuova Edizione OPENSCHOOL, © Ulrico Hoepli Editore S.p.A. 2014 11 UdA 3 Il linguaggio SQL In questo caso, è la stessa espressione “maggiore di tutti” a facilitare la comprensione della clausola di relazione con la subquery, che riportiamo di seguito insieme al risultato ottenuto: SELECT M.nome AS mansione, D.cognome, D.nome, D.stipendio FROM Dipendenti D INNER JOIN Mansioni M ON M.ID_mansione=D.mansione WHERE D.stipendio > ALL (SELECT D.stipendio FROM Dipendenti D, Mansioni M WHERE M.nome=”autista” and M.ID_mansione=D.mansione) Oppure ancora, nell’esempio che segue si vogliono elencare tutti i dipendenti che hanno la paga oraria superiore alla media della paga oraria degli impiegati di primo, secondo e terzo livello. Per fare questo si calcola la media della paga oraria riferita agli impiegati (mediante LIKE), quindi la query esterna ricerca tutte le mansioni, con il relativo dipendente con paga oraria maggiore del risultato della subquery interna. Vediamo la codifica, prima per MySQL: SELECT M.nome AS mansione, D.cognome, D.nome, M.tariffa_oraria FROM Dipendenti D INNER JOIN Mansioni M ON M.ID_mansione=D.mansione WHERE M.tariffa_oraria > ALL (SELECT AVG(tariffa_oraria) FROM Mansioni WHERE nome LIKE “impiegato%”) Ottenendo: ▶ Esistono corrispondenze di aggregazione. Si ricorda chedelle il carattere jolly “%”,tra inalcune Accessclausole è sostituito dal carattere Per “*”.esempio, la clausola (= ANY) è del tutto equivalente a (IN), mentre (<> ALL) è del tutto equivalente a (NOT IN). ■■ La quantificazione esistenziale La quantificazione esistenziale permette di verificare l’esistenza di almeno una tupla nel result-set della subquery. La proiezione restituita dalla subquery è assolutamente irrilevante, quindi può essere opportuno indicare sempre una proiezione di tipo star (*). La sua sintassi è la seguente: SELECT attributo1 FROM tabella1 WHERE [NOT] EXISTS (SELECT * FROM tabella2); 12 P. Camagni, R. Nikolassy, Progettare i database, Nuova Edizione OPENSCHOOL, © Ulrico Hoepli Editore S.p.A. 2014 Le interrogazioni annidate Lezione 5 Se una subquery restituisce almeno una tupla, EXISTS fornisce un valore True, altrimenti avrà un valore False. Come si può notare, se la tabella 2 contiene almeno una riga, la condizione WHERE della query esterna è True. Esempio16 In questo esempio utilizziamo come esempio le tabelle che seguono. Tabella Studenti Tabella Esami Vogliamo conoscere l’elenco degli studenti che non hanno superato alcun esame. Per fare questo, si crea un’interrogazione esterna che elenca tutti gli studenti con proiezione sui dati anagrafici, mentre la congiunzione prevede l’uso del quantificatore esistenziale negato con NOT, che significa “non esiste nemmeno una tupla nella subquery”. In questo esempio è stata usata la correlazione per legare tra loro le tabelle Studenti (nella query esterna) con Esami. SELECT ID_studente,cognome, nome FROM Studenti S WHERE NOT EXISTS (SELECT * FROM Esami WHERE S.ID_studente=id_Studente) Per verificare, invece, quali sono gli studenti che hanno sostenuto almeno un esame, si utilizza il quantificatore EXISTS. Vediamone la codifica e il risultato: SELECT ID_studente,cognome, nome FROM Studenti S WHERE EXISTS (SELECT * FROM Esami WHERE S.ID_studente=id_Studente) P. Camagni, R. Nikolassy, Progettare i database, Nuova Edizione OPENSCHOOL, © Ulrico Hoepli Editore S.p.A. 2014 13 uda 3 Il linguaggio SQL Verifichiamo le conoscenze g Esercizi a scelta multipla 1 Quale dei seguenti non è un operatore insiemistico di SQL? a) UNION b) INTERSECT c) POWERSET d) EXCEPT 2 In SQL, volendo dividere le righe di una tabella in insiemi caratterizzati dallo stesso valore degli attribuiti che compaiono come argomento, si usa l’operatore: a) ORDER BY b) GROUP BY c) SELECT BY 3 Dato il seguente schema relazionale: Ordini(ID_ord, articolo, cliente, data, quantità, id_venditore) Venditori(ID_venditore, nome, qualifica) indica quale tra le seguenti query rappresenta i nomi dei venditori che non hanno ancora venduto nulla nell’anno 2006. a) SELECT V.NOME FROM Venditori V WHERE NOT EXISTS (SELECT * FROM Ordini O WHERE O.id_venditore=V.ID_venditori AND year(O.data)=’2006’) b) SELECT * FROM Venditori V WHERE NOT IN (SELECT * FROM Ordini O WHERE O.id_venditore=V.ID_venditori AND year(O.data)=’2006’) c) SELECT V.nome FROM Venditori V WHERE EXISTS (SELECT DISTINCT * FROM Ordini O WHERE O.id_venditore=V.ID_venditori AND year(O.data)=’2006’) d) SELECT V.nome FROM Venditori V WHERE year(O.data)=2006 and NOT EXISTS (SELECT * FROM Venditori V INNER JOIN Ordini O ON O.id_venditore=V.ID_venditori) 3 Indica a quale categoria appartiene la seguente query: SELECT cognome, nome FROM Amici WHERE nome= (SELECT nome FROM Amici WHERE cognome=“Verdini” and nome=“Pio”); a) b) c) d) query derivata query correlata subquery query scalare 4 Indica a quale categoria appartiene la seguente query: SELECT cognome, nome, stipendio FROM Amici WHERE stipendio < 14 P. Camagni, R. Nikolassy, Progettare i database, Nuova Edizione OPENSCHOOL, © Ulrico Hoepli Editore S.p.A. 2014 Le interrogazioni annidate Lezione 5 (SELECT MAX(stipendio) FROM Amici) a) b) c) d) query insiemistica query calcolata query scalare query correlata g Test vero/falso 1 Le clausole HAVING e GROUP BY possono essere specificate per il risultato finale di una UNION. 2 È possibile ottenere query multiple unite con UNION. g Esercizi 1 Illustra come avviene l’operazione di UNION tra due query. 2 Perché è di norma utile assegnare un nome come alias agli attributi risultato della prima query di una UNION? 3 Individua l’errore nella seguente query annidata: SELECT * FROM Correntisti C WHERE CC.cognome<> (SELECT cognome FROM ContiCorrenti CC WHERE year(data)>1986); 4 Riscrivi la seguente query utilizzando le tecniche di annidamento corrette: SELECT D.cognome, D.nome, D.stipendio FROM Impiegati D INNER JOIN Impiegati D1 ON D.ID_superiore=D1.ID_impiegato WHERE D.stipendio>D1.stipendio 5 Riscrivi la seguente query utilizzando le tecniche di annidamento corrette: SELECT cognome, nome, stipendio FROM Dipendenti D, Aziende A WHERE D.id_azienda=A.ID_azienda and A.nome=”FIAT”; 6 Riscrivi la seguente query senza utilizzare le tecniche di annidamento, con e senza l’uso della congiunzione: SELECT * FROM Clienti WHERE cognome =ANY (SELECT cognome FROM Appuntamenti WHERE id_trattamento=ANY (SELECT ID_trattamento FROM Trattamenti WHERE descrizione LIKE “%lamp%”)); P. Camagni, R. Nikolassy, Progettare i database, Nuova Edizione OPENSCHOOL, © Ulrico Hoepli Editore S.p.A. 2014 15 uda 3 Il linguaggio SQL Verifichiamo le competenze g Problemi 1 Considera una tabella a piacere contenente un attributo di valori numerici qualsiasi. Si chiede di scrivere la query che calcola il dato immediatamente superiore al minimo per quell’attributo. 2 Dato lo schema relazionale costituito dalle seguenti tabelle: Persone(nome, reddito, età, sesso, indirizzo, città) Genitori_figli(ID, nome_genitore, nome_figlio, ordine_figlio) esprimi in SQL le seguenti interrogazioni: a) Trovare l’elenco ordinato dei genitori i cui figli guadagnano tutti più di 10.000 b) Elencare per ogni persona l’età media dei suoi figli c) Elencare i figli la cui età è maggiore della media delle età dei figli dello stesso genitore d) Elencare per ogni persona il primo figlio e) Elencare i genitori senza figli 3 Dato lo schema relazionale costituito dalle seguenti tabelle: Pilota Pilota (ID_pilota, nome, cognome, num_mondiali_vinti, nazione) Gara (ID_gara, nome, data, id_vincitore) esprimi in SQL le seguenti interrogazioni: a) Elencare i nomi dei piloti che, a partire dal 1970, hanno vinto almeno 2 gare b) Elencare il nome di tutti i piloti che hanno vinto almeno un mondiale ma non hanno mai vinto il Gran Premio di San Marino c) Elencare quale pilota ha vinto più GP del Brasile d) Elencare quali piloti non hanno mai vinto alcun mondiale pur avendo vinto almeno una gara e) Elencare il pilota italiano che ha vinto più gare della media di tutti i piloti tedeschi 4 Dato lo schema relazionale costituito dalle seguenti tabelle: Programma(ID_programma, nome, tipo, durata) Orario(canale_tv, data, ora, minuti, id_programma) esprimi in SQL le seguenti interrogazioni: a) Elencare i canali televisivi che non hanno mai trasmesso programmi di tipo “intrattenimento” con durata inferiore a 20 minuti nel periodo dal 20/12/2006 al 05/01/2005 b) Elencare il codice, nome, e il canale di programmazione, data e ora di inizio dei relativi programmi di tipo “sport”, trasmessi da più di 2 canali televisivi diversi c) Elencare quali canali televisivi hanno trasmesso, nel periodo gennaio 2005, almeno un programma di genere “sport” ma nessun programma di “cultura” d) Elencare i programmi televisivi con durata inferiore alla media di quelli sportivi e superiore a quella del programma culturale più lungo 16 P. Camagni, R. Nikolassy, Progettare i database, Nuova Edizione OPENSCHOOL, © Ulrico Hoepli Editore S.p.A. 2014