16
Pagina 1 A. Veneziani SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate a seguire useremo il DB “Acquisti”, definito dallo schema: E contenente i dati: mysql> SELECT * FROM Utenti; +-----------+--------+---------+--------+--------+ | Id_Utente | Nome | Cognome | Login | Passwd | +-----------+--------+---------+--------+--------+ | 1 | Mario | Rossini | rossi | red | | 2 | Carlo | Verdi | verdi | green | | 3 | Gianni | Gialli | gialli | yellow | +-----------+--------+---------+--------+--------+ mysql> SELECT * FROM Accessi; +------------+---------------------+-----------+ | Id_Accesso | Momento | Id_Utente | +------------+---------------------+-----------+ | 1 | 2018-03-26 22:15:42 | 1 | | 2 | 2018-03-26 22:16:07 | 1 | | 3 | 2018-03-26 22:18:20 | 1 | | 4 | 2018-03-26 22:19:13 | 1 | | 5 | 2018-03-26 22:20:55 | 1 | | 6 | 2018-03-26 22:22:04 | 1 | | 7 | 2018-03-26 22:23:07 | 1 | | 8 | 2018-03-26 22:27:28 | 2 | | 9 | 2018-03-26 22:38:50 | 2 | | 10 | 2018-03-26 22:39:10 | 2 | | 11 | 2018-03-26 22:41:09 | 2 | | 12 | 2018-03-26 22:44:07 | 2 | | 13 | 2018-03-26 22:44:39 | 2 | +------------+---------------------+-----------+

A. Veneziani SQL funzionalità “avanzate”...Pagina 1 A. Veneziani – SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate

  • Upload
    others

  • View
    10

  • Download
    0

Embed Size (px)

Citation preview

Page 1: A. Veneziani SQL funzionalità “avanzate”...Pagina 1 A. Veneziani – SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate

Pagina 1

A. Veneziani – SQL funzionalità “avanzate”

Consideriamo per i test e gli esempi delle query e delle regole presentate a seguire useremo il DB

“Acquisti”, definito dallo schema:

E contenente i dati:

mysql> SELECT * FROM Utenti;

+-----------+--------+---------+--------+--------+

| Id_Utente | Nome | Cognome | Login | Passwd |

+-----------+--------+---------+--------+--------+

| 1 | Mario | Rossini | rossi | red |

| 2 | Carlo | Verdi | verdi | green |

| 3 | Gianni | Gialli | gialli | yellow |

+-----------+--------+---------+--------+--------+

mysql> SELECT * FROM Accessi;

+------------+---------------------+-----------+

| Id_Accesso | Momento | Id_Utente |

+------------+---------------------+-----------+

| 1 | 2018-03-26 22:15:42 | 1 |

| 2 | 2018-03-26 22:16:07 | 1 |

| 3 | 2018-03-26 22:18:20 | 1 |

| 4 | 2018-03-26 22:19:13 | 1 |

| 5 | 2018-03-26 22:20:55 | 1 |

| 6 | 2018-03-26 22:22:04 | 1 |

| 7 | 2018-03-26 22:23:07 | 1 |

| 8 | 2018-03-26 22:27:28 | 2 |

| 9 | 2018-03-26 22:38:50 | 2 |

| 10 | 2018-03-26 22:39:10 | 2 |

| 11 | 2018-03-26 22:41:09 | 2 |

| 12 | 2018-03-26 22:44:07 | 2 |

| 13 | 2018-03-26 22:44:39 | 2 |

+------------+---------------------+-----------+

Page 2: A. Veneziani SQL funzionalità “avanzate”...Pagina 1 A. Veneziani – SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate

Pagina 2

mysql> SELECT * FROM Acquisti;

+------------+-------------+----------+

| Id_Accesso | Id_Prodotto | Quantita |

+------------+-------------+----------+

| 1 | 2 | 1 |

| 2 | 1 | 2 |

| 7 | 2 | 2 |

| 7 | 4 | 2 |

| 8 | 3 | 2 |

| 9 | 3 | 2 |

| 10 | 2 | 1 |

| 11 | 3 | 2 |

| 12 | 3 | 2 |

| 12 | 4 | 3 |

| 13 | 3 | 2 |

| 13 | 4 | 3 |

+------------+-------------+----------+

mysql> SELECT * FROM prodotti;

+-------------+---------------------+--------+----------+

| Id_Prodotto | NomeProdotto | Prezzo | Quantita |

+-------------+---------------------+--------+----------+

| 1 | Guanti | 3.00 | 13 |

| 2 | Scopa | 5.50 | 8 |

| 3 | 6 bicchieri | 2.50 | 16 |

| 4 | Straccio microfibre | 3.00 | 12 |

+-------------+---------------------+--------+----------+

Funzioni di aggregazione

Le funzioni di aggregazione hanno questo nome perchè appunto aggregano i dati presenti su più record in

un dato complessivo. Tali funzioni risolvono diverse problematiche:

COUNT(...) – conteggio del numero di record risultanti da una certa query

MAX(...) e MIN(...) – individuazione del valore massimo o minimo presente in una certa serie di dati

in un certo campo

AVG(...) – individuazione del valore medio dedotto in base ad una certa serie di dati in un certo

campo

SUM(....) - somma dei valori di un certo campo

Il modo più semplice per utilizzare tali funzioni di aggregazione è quello di farle operare su un solo gruppo

di elementi.

Ad esempio se volessimo il numero di accessi effettuati nel sistema del DB “Acquisti”1: SELECT COUNT(*) As NumeroAccessi FROM Accessi +---------------+

| NumeroAccessi |

+---------------+

| 13 |

+---------------+

Ovviamente tale conteggio si riferisce ad un solo gruppo. Un problema da esso derivato potrebbe essere

trovare gli accessi dopo le 22:40 del giorno 26/3/2018: SELECT COUNT(*) As NumeroAccessi FROM Accessi WHERE Momento > '2018-03-26 22:40' +---------------+

1 Se la tabella Accessi fosse vuota il numero di accessi contato sarebbe 0.

Page 3: A. Veneziani SQL funzionalità “avanzate”...Pagina 1 A. Veneziani – SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate

Pagina 3

| NumeroAccessi |

+---------------+

| 3 |

+---------------+

Il conteggio 3, deriva dai dati che soddisfano la condizione posta, visibili con:

SELECT * FROM Accessi WHERE Momento > '2018-03-26 22:40' +------------+---------------------+-----------+ | Id_Accesso | Momento | Id_Utente | +------------+---------------------+-----------+ | 11 | 2018-03-26 22:41:09 | 2 | | 12 | 2018-03-26 22:44:07 | 2 | | 13 | 2018-03-26 22:44:39 | 2 | +------------+---------------------+-----------+

E così con lo stesso significato potremo aggiungere ulteriori condizioni e contare quante tuple soddisfino le

condizioni poste.

Nel caso alcun record soddisfi la condizione posta, allora il conteggio prodotto dalla COUNT sarà 0:

SELECT COUNT(*) As NumeroAccessi FROM Accessi WHERE Momento > '2018-03-26 23:00' +---------------+

| NumeroAccessi |

+---------------+

| 0 |

+---------------+

Con logica analoga possiamo utilizzare le altre funzioni per ricerche e calcoli su un solo insieme di dati.

Da notare che la funzione COUNT può essere applicata anche su una singola colonna di una tabella. In quel

caso il conteggio conta i valori della colonna che sono diversi da NULL, e non conta invece i NULL presenti

nella colonna stessa.

Ad esempio poniamoci il problema di determinare a quando risalga l’accesso più recente2: SELECT MAX(Momento) FROM Accessi

+---------------------+

| PiuRecente |

+---------------------+

| 2018-03-26 22:44:39 |

+---------------------+

In questo caso il problema di quale sia l’accesso più recente, può essere risolto tramite la query con

subquery: SELECT * FROM Accessi WHERE Momento = (SELECT MAX(Momento) As PiuRecente FROM Accessi) +------------+---------------------+-----------+

| Id_Accesso | Momento | Id_Utente |

+------------+---------------------+-----------+

| 13 | 2018-03-26 22:44:39 | 2 |

+------------+---------------------+-----------+

Il principio di questa query si basa sull’uguagliare il valore del campo Momento (di accesso) al valore

massimo individuato nella colonna Momento, individuato con la opportuna subquery.

E’ possibile uguagliare (o comunque confrontare) i due valori essendo entrambi di tipo datetime.

Un altro problema che può coinvolgere funzioni di aggregazione è il calcolo del valore complessivo dei

prodotti a magazzino. Sulla tabella prodotti potremo allora effettuare la query: SELECT SUM(Quantita * Prezzo) As ValoreTotale FROM Prodotti +--------------+

| ValoreTotale |

2 Se la tabella Accessi fosse vuota il valore ritornato dalla funzione MAX(...) (ed analogamente dalla funzione MIN) sarebbe NULL.

Page 4: A. Veneziani SQL funzionalità “avanzate”...Pagina 1 A. Veneziani – SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate

Pagina 4

+--------------+

| 165.50 |

+--------------+

Ovviamente anche su questo calcolo potrebbero essere fatte delle varianti che selezionino il valore

complessivo solo di alcuni prodotti (ad esempio di tutti quelli con prezzo maggiore di 3.00 euro)3: SELECT SUM(Quantita * Prezzo) As ValoreTotale FROM Prodotti WHERE Prezzo > 3.00 +--------------+

| ValoreTotale |

+--------------+

| 89.50 |

+--------------+

Un altra problematica banale che può coinvolgere una funzione di aggregazione applicata su un solo gruppo

di dati è determinare la media del prezzo dei prodotti4: SELECT AVG(Prezzo) As PrezzoMedio FROM Prodotti

+-------------+

| PrezzoMedio |

+-------------+

| 3.625000 |

+-------------+

Un possibile problema derivabile da quello che ci siamo posti sopra è quali prodotti siano sopra il prezzo

medio: SELECT * FROM Prodotti WHERE Prezzo > (SELECT AVG(Prezzo) As PrezzoMedio FROM Prodotti) +-------------+--------------+--------+----------+

| Id_Prodotto | NomeProdotto | Prezzo | Quantita |

+-------------+--------------+--------+----------+

| 2 | Scopa | 5.50 | 8 |

+-------------+--------------+--------+----------+

Anche questo problema è risolubile, come si vede, tramite una query con subquery.

Si ricordi che nel caso particolare di operazioni su una tabella senza record (vuota), la funzione COUNT

rende 0, le funzioni MIN, MAX, SUM e AVG rendono il valore NULL.

Nel caso sulla colonna sulla quale è applicata una di queste funzioni ci siano valori NULL (ovviamente

perchè il campo stesso ammette tale valore), il valore NULL, viene ignorato, ossia i calcoli si svolgono come

se tale valore non esistesse. Ciò inclusa la media (AVG) per la quale l’elemento NULL non viene

conteggiato, ossia viene ugualmente ignorato.

Funzioni di aggregazione operanti su più gruppi

In molti casi le funzioni di aggregazione non aggregano dati in base ad un unico raggruppamento (come

negli esempi precedenti), ma in base a più raggruppamenti. Si deve in tal caso definire un criterio di

raggruppamento tramite la specifica clausola GROUP BY....

Un esempio di una applicazione del GROUP BY potrebbe essere contare gli accessi effettuati da ogni utente.

E’ ovvio che il problema impone di effettuare il conteggio dei dati derivati dal join tenendo conto dell’ id

dell’utente o altra sua caratteristica (univoca).

Il problema è risolto da questa query: SELECT Nome, Cognome, COUNT(*) As NumeroAccessi FROM Utenti, Accessi WHERE Utenti.Id_Utente = Accessi.Id_Utente

3 Se la tabella Prodotti fosse vuota, la somma SUM(...) applicata alla formula indicata renderebbe NULL. 4 Se la tabella Prodotti fosse vuota il valore della media (AVG) sarà NULL.

Page 5: A. Veneziani SQL funzionalità “avanzate”...Pagina 1 A. Veneziani – SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate

Pagina 5

GROUP BY Accessi.Id_Utente +-------+---------+---------------+ | Nome | Cognome | NumeroAccessi | +-------+---------+---------------+ | Mario | Rossini | 7 | | Carlo | Verdi | 6 | +-------+---------+---------------+

Come si vede tramite un opportuno uso della clausola GROUP BY è stato possibile contare gli accessi

effettuati da ogni utente.

In realtà è possibile inserire condizioni che condizionino i dati sottoposti a conteggio, ad esempio considero

solo gli accessi precedenti a 26/3/2018 22:40, e quindi nel nostro caso il risultato diviene:

SELECT Nome, Cognome, COUNT(*) As NumeroAccessi FROM Utenti, Accessi WHERE Utenti.Id_Utente = Accessi.Id_Utente AND Momento < '2018-03-26 22:40' GROUP BY Accessi.Id_Utente +-------+---------+---------------+

| Nome | Cognome | NumeroAccessi |

+-------+---------+---------------+

| Mario | Rossini | 7 |

| Carlo | Verdi | 3 |

+-------+---------+---------------+

Ossia gli accessi dell’utente Verdi, data la nuova condizione da rispettare sono ora in numero minore.

Tuttavia è possibile inserire non solo condizioni sui dati non aggregati (come quella precedente), ma anche

condizioni su dati a loro volta aggregati. Ciò si può fare con la clausola HAVING che seguirà la GROUP BY:

SELECT Nome, Cognome, COUNT(*) As NumeroAccessi FROM Utenti, Accessi WHERE Utenti.Id_Utente = Accessi.Id_Utente AND Momento < '2018-03-26 22:40' GROUP BY Accessi.Id_Utente HAVING NumeroAccessi > 5; +-------+---------+---------------+ | Nome | Cognome | NumeroAccessi | +-------+---------+---------------+ | Mario | Rossini | 7 | +-------+---------+---------------+

In questo caso risultano solo gli accessi di Rossini, in quanto la clausola HAVING (riguardante un dato

anch’esso oggetto di aggregazione, in questo caso il conteggio del numero di accessi) impone che siano

rilevati solo quelli con conteggio maggiore di 5 (quindi il precedente record con conteggio 3 è escluso dalla

tabella).

Ovviamente è possibile impostare condizioni relative alle clausole HAVING anche su dati che non sono

quelli oggetto dell’aggregazione effettuata nella query stessa. Ad esempio ci si ponga il semplice problema

di contare i prodotti il cui prezzo sia superiore alla media Ad esempio pensiamo se si volessero sapere il

numero di prodotti diversi acquistati da ogni utente.

La relativa query è in questo caso piuttosto complessa: SELECT Nome, Cognome, Id_Prodotto, Quantita FROM Utenti, Accessi, Acquisti WHERE Utenti.Id_Utente = Accessi.Id_Utente AND Accessi.Id_Accesso = Acquisti.Id_Accesso

Rappresenta quante operazioni di acquisto sono state effettuate (e su quale prodotto): +-------+---------+-------------+----------+

| Nome | Cognome | Id_Prodotto | Quantita |

+-------+---------+-------------+----------+

| Mario | Rossini | 2 | 1 |

Page 6: A. Veneziani SQL funzionalità “avanzate”...Pagina 1 A. Veneziani – SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate

Pagina 6

| Mario | Rossini | 1 | 2 |

| Mario | Rossini | 2 | 2 |

| Mario | Rossini | 4 | 2 |

| Carlo | Verdi | 3 | 2 |

| Carlo | Verdi | 3 | 2 |

| Carlo | Verdi | 2 | 1 |

| Carlo | Verdi | 3 | 2 |

| Carlo | Verdi | 3 | 2 |

| Carlo | Verdi | 4 | 3 |

| Carlo | Verdi | 3 | 2 |

| Carlo | Verdi | 4 | 3 |

+-------+---------+-------------+----------+

In realtà i prodotti acquistati sono però ripetuti in vari casi e quindi il conteggio deve essere fatto

distinguendo i prodotti con id uguale:

SELECT Nome, Cognome, COUNT(DISTINCT Id_Prodotto) As NumeroProdotti FROM Utenti, Accessi, Acquisti WHERE Utenti.Id_Utente = Accessi.Id_Utente AND Accessi.Id_Accesso = Acquisti.Id_Accesso GROUP BY Utenti.Id_Utente +-------+---------+----------------+

| Nome | Cognome | NumeroProdotti |

+-------+---------+----------------+

| Mario | Rossini | 3 |

| Carlo | Verdi | 3 |

+-------+---------+----------------+

E la query sopra rende il risultato corretto del numero di prodotti diversi acquistati (senza ripetizioni) da

ciascun utente.

Immaginiamo ora di rendere ancor più complessa la query considerando di questi solo i raggruppamenti di

prodotti che hanno quantità media acquistata per utente maggiore di 2.

In realtà possiamo oltre al numero di prodotti acquistati calcolare la quantità media acquistata nelle

operazioni di acquisto per ogni utente. La seguente query mostra anche questo dato (aggregato):

SELECT Nome, Cognome, COUNT(DISTINCT Id_Prodotto) As NumeroProdotti, AVG(Quantita) FROM Utenti, Accessi, Acquisti WHERE Utenti.Id_Utente = Accessi.Id_Utente AND Accessi.Id_Accesso = Acquisti.Id_Accesso GROUP BY Utenti.Id_Utente +-------+---------+----------------+---------------+ | Nome | Cognome | NumeroProdotti | AVG(Quantita) | +-------+---------+----------------+---------------+ | Mario | Rossini | 3 | 1.7500 | | Carlo | Verdi | 3 | 2.1250 | +-------+---------+----------------+---------------+

Una ulteriore condizione potrebbe coinvolgere questo dato aggregato, imponendo appunto che la quantità

media acquistata per utente maggiore di 2. Questo problema sarà risolto dalla query: SELECT Nome, Cognome, COUNT(DISTINCT Id_Prodotto) As NumeroProdotti, AVG(Quantita) FROM Utenti, Accessi, Acquisti WHERE Utenti.Id_Utente = Accessi.Id_Utente AND Accessi.Id_Accesso = Acquisti.Id_Accesso GROUP BY Utenti.Id_Utente HAVING AVG(Quantita) > 2 +-------+---------+----------------+---------------+

| Nome | Cognome | NumeroProdotti | AVG(Quantita) |

+-------+---------+----------------+---------------+

| Carlo | Verdi | 3 | 2.1250 |

Page 7: A. Veneziani SQL funzionalità “avanzate”...Pagina 1 A. Veneziani – SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate

Pagina 7

+-------+---------+----------------+---------------+

Anche in questo caso la condizione essendo su un dato aggregato deve essere inserita nella apposita

clausola HAVING (e non nella parte WHERE).

Un altro problema di aggregazione effettuato su gruppi, potrebbe essere quello di trovare la spesa

effettuata per ogni accesso: SELECT Accessi.*, SUM(Acquisti.Quantita * Prezzo) As SpesaPerAccesso FROM Accessi, Acquisti, Prodotti WHERE Accessi.Id_Accesso = Acquisti.Id_Accesso AND Acquisti.Id_Prodotto = Prodotti.Id_Prodotto GROUP BY Accessi.Id_Accesso; +------------+---------------------+-----------+-----------------+ | Id_Accesso | Momento | Id_Utente | SpesaPerAccesso | +------------+---------------------+-----------+-----------------+ | 1 | 2018-03-26 22:15:42 | 1 | 5.50 | | 2 | 2018-03-26 22:16:07 | 1 | 7.00 | | 7 | 2018-03-26 22:23:07 | 1 | 17.00 | | 8 | 2018-03-26 22:27:28 | 2 | 5.00 | | 9 | 2018-03-26 22:38:50 | 2 | 5.00 | | 10 | 2018-03-26 22:39:10 | 2 | 5.50 | | 11 | 2018-03-26 22:41:09 | 2 | 5.00 | | 12 | 2018-03-26 22:44:07 | 2 | 14.00 | | 13 | 2018-03-26 22:44:39 | 2 | 14.00 | +------------+---------------------+-----------+-----------------+

E se si volesse sapere per queste spese per ogni accesso quali di esse sono superiori ai 10 euro, si dovrebbe

far di nuovo ricorso alla clausola aggiuntiva HAVING: SELECT Accessi.*, SUM(Acquisti.Quantita * Prezzo) As SpesaPerAccesso FROM Accessi, Acquisti, Prodotti WHERE Accessi.Id_Accesso = Acquisti.Id_Accesso AND Acquisti.Id_Prodotto = Prodotti.Id_Prodotto GROUP BY Accessi.Id_Accesso HAVING SpesaPerAccesso > 10; +------------+---------------------+-----------+-----------------+ | Id_Accesso | Momento | Id_Utente | SpesaPerAccesso | +------------+---------------------+-----------+-----------------+ | 7 | 2018-03-26 22:23:07 | 1 | 17.00 | | 12 | 2018-03-26 22:44:07 | 2 | 14.00 | | 13 | 2018-03-26 22:44:39 | 2 | 14.00 | +------------+---------------------+-----------+-----------------+

Una ulteriore applicazione di funzioni di aggregazione potrebbe essere numero di pezzi venduti per ciascun

prodotto:

La query mette in evidenza le quantità vendute: SELECT NomeProdotto, Acquisti.Quantita FROM Acquisti, Prodotti WHERE Acquisti.Id_Prodotto = Prodotti.Id_Prodotto +---------------------+----------+

| NomeProdotto | Quantita |

+---------------------+----------+

| Guanti | 2 |

| Scopa | 1 |

| Scopa | 2 |

| Scopa | 1 |

| 6 bicchieri | 2 |

| 6 bicchieri | 2 |

| 6 bicchieri | 2 |

| 6 bicchieri | 2 |

| 6 bicchieri | 2 |

Page 8: A. Veneziani SQL funzionalità “avanzate”...Pagina 1 A. Veneziani – SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate

Pagina 8

| Straccio microfibre | 2 |

| Straccio microfibre | 3 |

| Straccio microfibre | 3 |

+---------------------+----------+

Da questa si può subito dedurre la query che ci permette invece di calcolare le quantità vendute raggruppate per prodotto:

SELECT NomeProdotto, SUM(Acquisti.Quantita) As PezziVenduti FROM Acquisti, Prodotti WHERE Acquisti.Id_Prodotto = Prodotti.Id_Prodotto GROUP BY Prodotti.Id_Prodotto; +---------------------+--------------+

| NomeProdotto | PezziVenduti |

+---------------------+--------------+

| Guanti | 2 |

| Scopa | 4 |

| 6 bicchieri | 10 |

| Straccio microfibre | 8 |

+---------------------+--------------+

Inoltre c’è da tener conto che è possibile effettuare raggruppamenti multipli.

Ad esempio si supponga di voler sapere quanti acquisti (operazioni di acquisto) sono stati effettuati con un

certa quantita di prodotto. Inquesto caso raggrupperemo per Quantita: # numero di acquisti raggruppati per quantita

SELECT Quantita, COUNT(*) As NumeroAcquisti FROM Acquisti GROUP BY Quantita

Che avrà il risultato: +----------+----------------+

| Quantita | NumeroAcquisti |

+----------+----------------+

| 1 | 2 |

| 2 | 8 |

| 3 | 2 |

+----------+----------------+

Ossia leggiamo che sono stati fatti 2 operazioni di acquisto con quantita di prodotto acquistata pari a 1, 8

con quantità pari a 2, e 2 operazioni di acquisto con quantita pari a 3. Il criterio con cui sono stati

raggruppati questi dati è appunto la quantita di prodotto acquistato in ogni operazione di acquisto.

Pensiamo ora di voler raggruppare sia per la quantita acquistata, sia per l’id del prodotto oggetto di

acquisto. In questo caso i criteri di raggruppamento non sono più uno solo, ma due. In pratica in questo

caso il gruppo relativo agli acquisti di una quantita 1, si scomporrà in vari sottogruppi di acquisti con

quantita 1, coinvolgenti id prodotto diversi.

Risulterà la query:

# numero di acquisti raggruppati per quantita ed id del prodotto # (raggruppa per quantita, ma separatamente per ogni prodotto) SELECT Quantita, Id_Prodotto, COUNT(*) As NumeroAcquisti FROM Acquisti GROUP BY Quantita, Id_Prodotto +----------+-------------+----------------+

| Quantita | Id_Prodotto | NumeroAcquisti |

+----------+-------------+----------------+

| 1 | 2 | 2 |

| 2 | 1 | 1 |

| 2 | 2 | 1 |

| 2 | 3 | 5 |

| 2 | 4 | 1 |

Page 9: A. Veneziani SQL funzionalità “avanzate”...Pagina 1 A. Veneziani – SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate

Pagina 9

| 3 | 4 | 2 |

+----------+-------------+----------------+

Che indica i dati precedenti ma raggruppati in modo diverso, dato il sottoraggruppamento per id del

prodotto. I prodotti acquistati in quantita 1 riguardano il solo prodotto2, e sono numericamente 2.

I prodotti acquistati in quantita 2 (per ogni operazione di acquisto) sono ora scorporati in diverse sottovoci,

date dagli id dei veri prodotti coinvolti in tali acquisti (prodotti di id 1, 2, 3, 4), con numero di acquisti

diversi in numero a seconda dei casi. Infine i prodotti venduti con quantita 3 sono sempre uno solo (id = 4),

e il numero di acquisti di questo tipo è 2. E’ bene notare che la somma di tutti i numeri di acquisti è anche

in questo caso 12, come nel caso precedente, ma i raggruppamenti multipli hanno portato ad avere una

differente suddivisione nel numero di acquisti.

Ordinamenti

I dati risultato di una query possono anche essere ordinati, qualunque operazione sia stata effettuata

precedentemente. Bisogna scegliere ovviamente un criterio di ordinamento, legato ad una o più colonne

coinvolte nella query.

Un semplice esempio può essere ordinare i nostri prodotti in ordine alfabetico: SELECT * FROM Prodotti ORDER BY NomeProdotto; +-------------+---------------------+--------+----------+

| Id_Prodotto | NomeProdotto | Prezzo | Quantita |

+-------------+---------------------+--------+----------+

| 3 | 6 bicchieri | 2.50 | 16 |

| 1 | Guanti | 3.50 | 13 |

| 2 | Scopa | 5.50 | 8 |

| 4 | Straccio microfibre | 3.00 | 12 |

+-------------+---------------------+--------+----------+

La clausola ASC è quella di default ed infatti il nostro ordinamento è alfabetico ascendente. Per ordinare in

modo inverso basta indicare DESC alla fine della riga ORDER BY; ad esempio nell’ esempio precedente: SELECT * FROM Prodotti ORDER BY NomeProdotto DESC;

Nella tabella Prodotti vi sono diverse colonne che possono essere oggetto di ordinamento. Supponiamo di voler affiancare al primo ordinamento un secondo e supponiamo di modificare il dato di prezzo 3.50 in 3.00. La query:

SELECT * FROM Prodotti ORDER BY Prezzo, NomeProdotto

Ordina in base al nome del prezzo e come secondo criterio di ordinamento in base al nome del prodotto. Quindi se il prezzo risulta uguale a quel punto il nome del prodotto sarà un ulteriore criterio di ordinamento su cui basarsi.

+-------------+---------------------+--------+----------+ | Id_Prodotto | NomeProdotto | Prezzo | Quantita | +-------------+---------------------+--------+----------+ | 3 | 6 bicchieri | 2.50 | 16 | | 1 | Guanti | 3.00 | 13 | | 4 | Straccio microfibre | 3.00 | 12 | | 2 | Scopa | 5.50 | 8 | +-------------+---------------------+--------+----------+

Se si inverte il criterio si inverte il risultato: SELECT * FROM Prodotti ORDER BY Prezzo ASC , NomeProdotto DESC

Page 10: A. Veneziani SQL funzionalità “avanzate”...Pagina 1 A. Veneziani – SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate

Pagina 10

+-------------+---------------------+--------+----------+ | Id_Prodotto | NomeProdotto | Prezzo | Quantita | +-------------+---------------------+--------+----------+ | 3 | 6 bicchieri | 2.50 | 16 | | 4 | Straccio microfibre | 3.00 | 12 | | 1 | Guanti | 3.00 | 13 | | 2 | Scopa | 5.50 | 8 | +-------------+---------------------+--------+----------+

Ovviamente si possono sottoporre ad ordinamento anche dati aggregati, ad esempio ordinare i prodotti in

base ai pezzi venduti per ciscuno di essi; riprendendo una query precedente: SELECT NomeProdotto, SUM(Acquisti.Quantita) As PezziVenduti FROM Acquisti, Prodotti WHERE Acquisti.Id_Prodotto = Prodotti.Id_Prodotto GROUP BY Prodotti.Id_Prodotto ORDER BY PezziVenduti DESC; +---------------------+--------------+

| NomeProdotto | PezziVenduti |

+---------------------+--------------+

| 6 bicchieri | 10 |

| Straccio microfibre | 8 |

| Scopa | 4 |

| Guanti | 2 |

+---------------------+--------------+

La clausola DISTINCT

Supponiamo di porci il (semplice) problema di capire quali quantità siano state acquistate nelle varie

operazioni di acquisto effettuate. Il dato è deducibile in assoluto dalla colonna Quantita della tabella

Acquisti, ma ovviamente è disaggregato. Una soluzione possibile è effettuare un raggruppamento, senza

l’uso, in questo caso non necessario, di funzioni di aggregazione: SELECT Quantita FROM Acquisti GROUP BY Quantita +----------+

| Quantita |

+----------+

| 1 |

| 2 |

| 3 |

+----------+

Abbiamo infatti ottenuto le quantita oggetto di acquisti di singoli prodotti.

In alternativa basterebbe fare una comune selezione sulla colonna, forzando i valori risultanti ad essere

distinti. Ciò può essere fatto con la clausola DISTINCT, posta a seguire al comando SELECT:

SELECT DISTINCT Quantita FROM Acquisti +----------+

| Quantita |

+----------+

| 1 |

| 2 |

| 3 |

+----------+

E come si vede il risultato è lo stesso.

Page 11: A. Veneziani SQL funzionalità “avanzate”...Pagina 1 A. Veneziani – SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate

Pagina 11

Un altro problema che potrebbe chiamare in causa la clausola DISTINCT è la questione: individuare quali

utenti abbiano effettuato accessi. In assoluto gli utente che abbiano effettuato accessi sono elencati

tramite la query: SELECT Utenti.* FROM Utenti, Accessi WHERE Utenti.Id_Utente = Accessi.Id_Utente

Ma tale query ha il grosso difetto di elencarli, in generale, in modo ripetuto. +-----------+-------+---------+-------+--------+

| Id_Utente | Nome | Cognome | Login | Passwd |

+-----------+-------+---------+-------+--------+

| 1 | Mario | Rossini | rossi | red |

| 1 | Mario | Rossini | rossi | red |

| 1 | Mario | Rossini | rossi | red |

| 1 | Mario | Rossini | rossi | red |

| 1 | Mario | Rossini | rossi | red |

| 1 | Mario | Rossini | rossi | red |

| 1 | Mario | Rossini | rossi | red |

| 2 | Carlo | Verdi | verdi | green |

| 2 | Carlo | Verdi | verdi | green |

| 2 | Carlo | Verdi | verdi | green |

| 2 | Carlo | Verdi | verdi | green |

| 2 | Carlo | Verdi | verdi | green |

| 2 | Carlo | Verdi | verdi | green |

+-----------+-------+---------+-------+--------+

Per avere dati maggiormente utilizzabili e chiari, è opportuno a questo punto eliminare i duplicati dei dati

ricavati. Ciò è possibile con la clausola DISTINCT:

SELECT DISTINCT Utenti.* FROM Utenti, Accessi WHERE Utenti.Id_Utente = Accessi.Id_Utente +-----------+-------+---------+-------+--------+

| Id_Utente | Nome | Cognome | Login | Passwd |

+-----------+-------+---------+-------+--------+

| 1 | Mario | Rossini | rossi | red |

| 2 | Carlo | Verdi | verdi | green |

+-----------+-------+---------+-------+--------+

Come si vede ora i dati duplicati sono del tutto spariti e restano ripetuti una sola volta gli utenti che hanno

acceduto.

Poniamoci adesso il problema di contare quanti utenti hanno acceduto al sistema di acquisto.

Una possibilità data la query precedente che già da dei dati utili è utilizzarla a sua volta come tabella da cui

dedurre la risposta alla richiesta di cui sopra:

SELECT COUNT(*) As NumeroUtenti FROM (SELECT DISTINCT Utenti.* FROM Utenti, Accessi WHERE Utenti.Id_Utente = Accessi.Id_Utente) As T1;

In realtà qui la sorgente dei dati dalla quale vengono estratti è a sua volta una query, vista come tabella (T1). Un altro modo per dare risposta sarebbe stato effettuare il classico inner join e andare a contare i valori distinti degli utenti, ossia degli id degli utenti:

SELECT COUNT(DISTINCT Utenti.Id_Utente) As NumeroUtenti FROM Utenti, Accessi WHERE Utenti.Id_Utente = Accessi.Id_Utente

Page 12: A. Veneziani SQL funzionalità “avanzate”...Pagina 1 A. Veneziani – SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate

Pagina 12

In questo caso, essendo gli utenti presenti nel risultato della query di base ripetuti più volte, non basta però una COUNT generica che conterebbe le ripetizioni. La query:

SELECT COUNT(*) As NumeroUtenti FROM Utenti, Accessi WHERE Utenti.Id_Utente = Accessi.Id_Utente

Non risponde alla domanda posta e dà un risultato errato: +--------------+ | NumeroUtenti | +--------------+ | 13 | +--------------+

Viceversa la stessa query corretta con il conteggio su una sola colonna (la chiave primaria di Utenti nel

nostro caso) e la clausola di distinguere tra valori identici, ci permette di giungere al risultato: SELECT COUNT(DISTINCT Utenti.Id_Utente) As NumeroUtenti FROM Utenti, Accessi WHERE Utenti.Id_Utente = Accessi.Id_Utente +--------------+

| NumeroUtenti |

+--------------+

| 2 |

+--------------+

UNION e operazioni insiemistiche

Anche in SQL esistono degli operatori per effettuare operazioni su insiemi. Ovviamente questi insiemi

sono costituiti da insiemi di tuple o record, tutti di tipo simile, ossia con lo stesso numero di colonne e di

tipi analoghi.

Consideriamo l’insieme degli utenti che non hanno mai acceduto al sistema a cui siano uniti gli utenti che

hanno acquistato il prodotto 3. Le due query che risolvono i singoli problemi proposti sono SELECT * # tutti gli utenti FROM Utenti WHERE Id_Utente NOT IN # ... che non sono fra quelli che ... (SELECT Utenti.Id_Utente # utenti che hanno acceduto al sistema FROM Utenti, Accessi WHERE Utenti.Id_Utente = Accessi.Id_Utente)

La query proposta qui sopra si basa su una sottrazione tra l’insieme di tutti gli utenti e su questo individuare quelli che non sono nel secondo insieme (subquery), ossia coloro che hanno acceduto al sistema. E’ ovvio che si ottengono gli utenti che non hanno acceduto al sistema.

+-----------+-------+---------+-------+--------+ | Id_Utente | Nome | Cognome | Login | Passwd | +-----------+-------+---------+-------+--------+ | 3 | Carlo | Verdi | verdi | green | +-----------+-------+---------+-------+--------+

Per il secondo problema si analizza cosa hanno acquistato gli utenti e si impone di selezionare solo quelli

che hanno acquistato il prodotto di id 3: SELECT Utenti.* FROM Utenti, Accessi, Acquisti WHERE Utenti.Id_Utente = Accessi.Id_Utente AND Accessi.Id_Accesso = Acquisti.Id_Accesso AND Acquisti.Id_Prodotto = 3;

Da notare che questa query può produrre risultati duplicati. La combinazione dei due gruppi di utenti può avvenire tramite una UNION:

SELECT * # tutti gli utenti

Page 13: A. Veneziani SQL funzionalità “avanzate”...Pagina 1 A. Veneziani – SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate

Pagina 13

FROM Utenti WHERE Id_Utente NOT IN # ... che non sono fra quelli che ... (SELECT Utenti.Id_Utente # utenti che hanno acceduto al sistema FROM Utenti, Accessi WHERE Utenti.Id_Utente = Accessi.Id_Utente) UNION SELECT Utenti.* FROM Utenti, Accessi, Acquisti WHERE Utenti.Id_Utente = Accessi.Id_Utente AND Accessi.Id_Accesso = Acquisti.Id_Accesso AND Acquisti.Id_Prodotto = 3;

che rende tra l’altro un risultato privo di duplicati in quanti dal punto di vista insiemistico essi non possono

esistere: +-----------+-------+---------+-------+--------+

| Id_Utente | Nome | Cognome | Login | Passwd |

+-----------+-------+---------+-------+--------+

| 3 | Carlo | Verdi | verdi | green |

| 2 | Carlo | Verdi | verdi | green |

+-----------+-------+---------+-------+--------+

In pratica la sintassi della UNION si scrive inserendo una indicazione UNION tra due query. Le due query i

cui dati dovranno essere uniti, dovranno produrre:

Stesso numero di colonne

Tipi di colonne identici

Ordine delle colonne uguale

Sotto queste condizioni la UNION tra i due insiemi di tuple sarà possibile.

Una variante della UNION è la UNION ALL, la quale effettua la fusione tra insiemi di tuple eventualmente

inserendo eventuali duplicati delle truple stesse, rendendo quindi il risultato non privo di duplicati e quindi

non un insieme in sesno stretto.

Ad esempio supponiamo di voler unire in un unico risultato gli accessi effettuati dopo le 22:40 del

26/3/2018 e quelli effettuati dall’utente 2: SELECT Accessi.* FROM Accessi WHERE Momento > '2018-03-26 22:40' UNION SELECT Accessi.* FROM Utenti, Accessi WHERE Utenti.Id_Utente = Accessi.Id_Utente AND Utenti.Id_Utente = 2

Il set di queste due query si sovrappone, e quello della prima è contenuto del tutto in quello della seconda: +------------+---------------------+-----------+

| Id_Accesso | Momento | Id_Utente |

+------------+---------------------+-----------+

| 11 | 2018-03-26 22:41:09 | 2 |

| 12 | 2018-03-26 22:44:07 | 2 |

| 13 | 2018-03-26 22:44:39 | 2 |

| 8 | 2018-03-26 22:27:28 | 2 |

| 9 | 2018-03-26 22:38:50 | 2 |

| 10 | 2018-03-26 22:39:10 | 2 |

+------------+---------------------+-----------+

a questo punto l’unione dei due insiemi coincide con quello della seconda query.

Page 14: A. Veneziani SQL funzionalità “avanzate”...Pagina 1 A. Veneziani – SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate

Pagina 14

Esiste però un altra variante della UNION detta UNION ALL. Nel caso però venga usata tale variante si avrà

l’aggiunta comunque nell’insieme dei risultati dei record della prima, assieme ai record della seconda

query, pur creando duplicati: +------------+---------------------+-----------+

| Id_Accesso | Momento | Id_Utente |

+------------+---------------------+-----------+

| 11 | 2018-03-26 22:41:09 | 2 |

| 12 | 2018-03-26 22:44:07 | 2 |

| 13 | 2018-03-26 22:44:39 | 2 |

| 8 | 2018-03-26 22:27:28 | 2 |

| 9 | 2018-03-26 22:38:50 | 2 |

| 10 | 2018-03-26 22:39:10 | 2 |

| 11 | 2018-03-26 22:41:09 | 2 |

| 12 | 2018-03-26 22:44:07 | 2 |

| 13 | 2018-03-26 22:44:39 | 2 |

+------------+---------------------+-----------+

In realtà l‘applicazione della UNION ALL, al contrario della UNION comporta che il risultato possa contenere

tuple duplicate.

Consideriamo ora una operazione di intersezione di due insiemi di tuple. Il comando per metterla in atto è

INTERSECT, non implementato nella sintassi SQL di MySQL. Volendo individuare quali accessi siano in

comune tra quelli dell’utente 2 e quelli effettuati dopo il 26/3/2018 22:40, potremo effettuare la seguente

operazione equivalente che implementa una intersezione tra i due insiemi di tuple:

SELECT Accessi.* FROM Accessi WHERE Momento > '2018-03-26 22:40' AND Id_Accesso IN (SELECT Accessi.Id_Accesso FROM Utenti, Accessi WHERE Utenti.Id_Utente = Accessi.Id_Utente AND Utenti.Id_Utente = 2)

Che produce appunto l’intersezione dei due insiemi: +------------+---------------------+-----------+

| Id_Accesso | Momento | Id_Utente |

+------------+---------------------+-----------+

| 11 | 2018-03-26 22:41:09 | 2 |

| 12 | 2018-03-26 22:44:07 | 2 |

| 13 | 2018-03-26 22:44:39 | 2 |

+------------+---------------------+-----------+

Lo stesso risultato lo otteniamo ovviamente invertendo la query con la sua subquery: SELECT Accessi.* FROM Utenti, Accessi WHERE Utenti.Id_Utente = Accessi.Id_Utente AND Utenti.Id_Utente = 2 AND Id_Accesso IN (SELECT Id_Accesso FROM Accessi WHERE Momento > '2018-03-26 22:40')

Vediamo ora un esempio di differenza tra insiemi sempre effettuata con l’insieme di dati precedente. Supponiamo quindi di voler trovare tutti gli accessi dell’ utente 2 (insieme più ampio), esclusi quelli che siano avvenuti dopo il 26/3/2018 dalle 22:40. Ovviamente questo problema può essere visto come un problema di differenza tra insiemi. Gli elementi

dei due insiemi sono comuni e sono i singoli accessi. SELECT Accessi.* FROM Utenti, Accessi WHERE Utenti.Id_Utente = Accessi.Id_Utente AND

Page 15: A. Veneziani SQL funzionalità “avanzate”...Pagina 1 A. Veneziani – SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate

Pagina 15

Utenti.Id_Utente = 2 AND Id_Accesso NOT IN (SELECT Id_Accesso FROM Accessi WHERE Momento > '2018-03-26 22:40')

L’operatore NOT IN permette di effettuare l’operazione di differenza prendendo l’insieme della query principale (nel ns. caso gli accessi dell’utente di id 2) e escludendo da tale insieme tutti gli elementi (gli accessi) che abbiano id presente nell’ insieme degli id prodotti dalla seconda query (ossia gli accessi avvenuti dopo il 26/3/2018 22:40), ossia quegli accessi dell’utente 2 il cui id non è nell’insieme degli id degli accessi avenuti dopo il 26/3/2018 22:40. Questo realizza quindi una operazione di differenza tra insiemi. La differenza tra insiemi in SQL ha un suo comando specifico (MINUS), il quale però purtroppo non è implementato nell’ SQL di MySQL. Come si è visto quindi anche in questi casi tornano utili due operatori spesso utilizzabili:

IN determina se un certo elemento (del primo insieme) sia presente nel secondo (prodotto dalla subquery)

NOT IN determina se un certo elemento (del primo insieme) non sia presente nel secondo (prodotto dalla subquery)

Alcuni problemi risolvibili con gli operatori IN e NOT IN

Alcuni problemi su insiemi necessitano dell’uso degli operatori su insiemi IN e NOT IN.

Ad esempio poniamoci il problema di voler conoscere il/i prodotto/i che siano stati acquistati solo

dall’utente di id 2. Per risolvere questo problema possiamo individuare tutti i prodotti acquistati da utenti

diversi da 2, dopodichè andremo a trovare il complemento di questo insieme, che escludendo tutti i

prodotti acquistati da utenti diversi da 2, ci darà i prodotti acquistati dall’utente 2 e solo da esso (utenti con

altri id sono esclusi).

Per verificare questa query consideriamo il quadro di insieme dato dalla query: # utente e prodotto acquistato SELECT utenti.Id_Utente, prodotti.Id_Prodotto FROM utenti, accessi, acquisti, prodotti WHERE utenti.Id_Utente = accessi.Id_Utente AND accessi.Id_Accesso = acquisti.Id_Accesso AND acquisti.Id_Prodotto = prodotti.Id_Prodotto; +-----------+-------------+

| Id_Utente | Id_Prodotto |

+-----------+-------------+

| 1 | 1 |

| 1 | 2 |

| 1 | 2 |

| 1 | 4 |

| 2 | 2 |

| 2 | 3 |

| 2 | 3 |

| 2 | 3 |

| 2 | 3 |

| 2 | 3 |

| 2 | 4 |

| 2 | 4 |

+-----------+-------------+

Come si può osservare già ad occhio il prodotto 3 viene acquistato solo dall’utente 2, ma non da 1.

(Osserviamo anche che analoga cosa avviene al prodotto 1, con l’utente di id 1).

A questo punto troviamo tutti i prodotti che sono acquistati da utenti diversi da 2:

SELECT prodotti.Id_Prodotto FROM utenti, accessi, acquisti, prodotti WHERE

Page 16: A. Veneziani SQL funzionalità “avanzate”...Pagina 1 A. Veneziani – SQL funzionalità “avanzate” Consideriamo per i test e gli esempi delle query e delle regole presentate

Pagina 16

utenti.Id_Utente = accessi.Id_Utente AND accessi.Id_Accesso = acquisti.Id_Accesso AND acquisti.Id_Prodotto = prodotti.Id_Prodotto AND Utenti.Id_Utente <> 2; +-------------+

| Id_Prodotto |

+-------------+

| 1 |

| 2 |

| 2 |

| 4 |

+-------------+

Nel risultato ottenuto manca il prodotto 3, che giustamente viene acquistato solo da 2 e quindi non viene acquistato da qualunque utente che non sia 2 (Utenti.Id_Utente <> 2). A questo punto per individuare il prodotto in questione basta prendere tutti i prodotti e togliere da essi i prodotti che hanno acquistato gli utenti diversi da 2. Restano i prodotti acquistati da 2 soltanto:

# prodotti che siano stati acquistati solo dall'utente 2 SELECT * # tutti i prodotti FROM Prodotti WHERE Id_Prodotto NOT IN # prodotti che sono stati acquistati da utenti diversi da 2 (SELECT prodotti.Id_Prodotto FROM utenti, accessi, acquisti, prodotti WHERE utenti.Id_Utente = accessi.Id_Utente AND accessi.Id_Accesso = acquisti.Id_Accesso AND acquisti.Id_Prodotto = prodotti.Id_Prodotto AND Utenti.Id_Utente <> 2);

che rende: +-------------+--------------+--------+----------+ | Id_Prodotto | NomeProdotto | Prezzo | Quantita | +-------------+--------------+--------+----------+ | 3 | 6 bicchieri | 2.50 | 16 | +-------------+--------------+--------+----------+

Il solo prodotto 3. In modo simmetrico chidendo quali siano i prodotti acquistati dal solo utente 1 con query del tutto analoga (cambia solo l’ultima parte in Utenti.Id_Utente <> 1) si ottiene correttamente:

+-------------+--------------+--------+----------+ | Id_Prodotto | NomeProdotto | Prezzo | Quantita | +-------------+--------------+--------+----------+ | 1 | Guanti | 3.00 | 13 | +-------------+--------------+--------+----------+