Lezione Precedente Elenco Lezioni Lezione Successiva

Laboratorio di Sistemi Informativi

SQL e MySQL: comandi di manipolazione dei dati

Questa lezione è dedicata a un breve ripasso dei comandi di manipolazione dati di SQL. Un'altra ottima fonte di informazione, con spiegazioni probabilmente molto più chiare e complete, è il vostro libro di testo di basi di dati, che vi invito a consultare.

Il comando SELECT

Abbiamo visto nelle lezioni precedenti come usare il comando SELECT per esaminare il contenuto di una tabella. Il comando SELECT è probabilmente il comando più complesso di SQL e consente di effettuare query di vario tipo.

La sintassi standard di SELECT è la seguente:

SELECT <lista di colonne> FROM <lista di tabelle> WHERE <condizione>

Questa operazione esegue il θ-JOIN delle tabelle indicate FROM secondo la condizione specificata, e visualizza le colonne indicate. Se si mette l'asterisco * al posto della lista colonna, vuol dire che si intende visualizzare tutti i campi disponili.

Ad esempio, consideriamo il database creato dallo script esempioselect.sql. Possiamo vedere quali sono i valori inseriti nella tabella t1 col comando:

SELECT * FROM t1;

o possamo decidere di visualizzare soltanto i nomi presenti nella tabella t1 con

SELECT nome FROM t1;

ottenendo

+-------+
| nome  |
+-------+
| pippo |
| pluto |
| caio  |
| pluto |
+-------+

Molto più interessante è un esempio che coinvolge più di una tabella. Ad esempio, per sapere che stipendio corrisponde a ogni persona della tabella t1, possiamo dare il comando

SELECT * FROM t1, t2 WHERE id=id2;

+------+-------+------+-----------+
| id   | nome  | id2  | stipendio |
+------+-------+------+-----------+
|    1 | pippo |    1 |      1000 |
|    3 | caio  |    3 |      3000 |
|    2 | pluto |    2 |      1900 |
+------+-------+------+-----------+

o, meglio ancora,

SELECT nome,stipendio FROM t1, t2 WHERE id=id2;

+-------+-----------+
| nome  | stipendio |
+-------+-----------+
| pippo |      1000 |
| caio  |      3000 |
| pluto |      1900 |
+-------+-----------+

Possiamo dare anche condizioni più complesse, ad esempio richiedere gli stessi dati di prima ma solo per le persone con stipendio superiore a 2000:

SELECT nome,stipendio FROM t1, t2 WHERE id=id2 and stipendio > 2000;

+------+-----------+
| nome | stipendio |
+------+-----------+
| caio |      3000 |
+------+-----------+

Possiamo anche usare delle funzioni intrinseche oppure operatori di MySQL all'interno del comando SELECT.  Le funzioni intrinseche  sono simili alle funzioni dei linguaggi di programmazione tradizionale, come il C o il Java: prendono un certo numero di argomenti come parametri e restituiscono un risultato. Ad esempio la funzione intrinseca concat() prende un numero a piacere di stringhe come argomenti e restituisce la stringa ottenuta dalla loro concatenazione (è quindi simile all'operatore + per le stringhe di Java, che però non esiste in MySQL, dove + si può usare solo con numeri).

Ad esempio

SELECT concat(nome,' guadagna ',stipendio) FROM t1, t2 WHERE id=id2

usa la funzione intrinseca concat mentre

SELECT nome,stipendio FROM t1, t2 WHERE id=id2 and stipendio * 1.2 > 2000


usa l'operatore *. Vedremo in una lezione successiva alcune delle funzioni che MySQL mette a disposizione.

È anche possibile fare delle query con più di una tabella di base. Ad esempio, la tabella t3 contiene gli indirizzi delle persone nella tabella t1. Se vogliamo sapere nome, stipendio e indirizzo per ogni persona, il comando da dare è:

SELECT nome,stipendio,indirizzo FROM t1, t2, t3 WHERE id=t2.id2 and id=t3.id2;

Notare l'uso della forma t2.id2 e t3.id2 per identificare i campi id2 delle tabelle t2 e t3. Poichè i campi hanno le stesso nome, è necessario indicare anche a quale tabella ci si riferisce.

In realtà il comando SELECT ha varie altre possibilità che non abbiamo visto. Esaminiamone qualcuna:

Join esterni

Consideriamo ancora il database di esempio creato dallo script esempioselect.sql. Consta di varie tabelle, in particolare ci interessano la t1 e la t2:

Tabella t1
+------+-------+
| id   | nome  |
+------+-------+
|    1 | pippo |
|    2 | pluto |
|    3 | caio  |
|    4 | pluto |
+------+-------+

Tabella t2
+------+-----------+
| id2  | stipendio |
+------+-----------+
|    1 |      1000 |
|    3 |      3000 |
|    2 |      1900 |
|    7 |        10 |
+------+-----------+

Supponiamo di voler sapere, per ogni persona, il relativo stipendio. Una possibilità è il comando:

SELECT id, nome, stipendio FROM t1,t2 where id=id2;

+------+-------+-----------+
| id   | nome  | stipendio |
+------+-------+-----------+
|    1 | pippo |      1000 |
|    3 | caio  |      3000 |
|    2 | pluto |      1900 |
+------+-------+-----------+

La query non restituisce nulla riguardo allo stipendio della persona con id 4 (che è un omonimo della persona di id 2). Questo perchè nella tabella t2 non c'è alcuna riga con id pari a 4. Se volessimo avere comunque informazioni su tutte le persone, anche quelle senza stipendio, dovremmo utilizzare i join esterni:

SELECT id, nome, stipendio FROM t1 LEFT JOIN t2 ON id=id2;

+------+-------+-----------+
| id   | nome  | stipendio |
+------+-------+-----------+
|    1 | pippo |      1000 |
|    2 | pluto |      1900 |
|    3 | caio  |      3000 |
|    4 | pluto |      NULL |
+------+-------+-----------+

Il campio stipendio relativo all'id4 è riempito con NULL.

Analogamente abbiamo anche il RIGHT JOIN:

SELECT id, nome, stipendio FROM t1 RIGHT JOIN t2 ON id=id2;

+------+-------+-----------+
| id   | nome  | stipendio |
+------+-------+-----------+
|    1 | pippo |      1000 |
|    3 | caio  |      3000 |
|    2 | pluto |      1900 |
| NULL | NULL  |        10 |
+------+-------+-----------+

MySQL non dispone del FULL JOIN, ovvero della possibilità di combinare il LEFT e il RIGHT join, in modo da considerare sia le righe della prima tabella senza valori corrispondenti della seconda, sia il viceversa. Se esistesse, il FULL JOIN si comporterebbe in questo modo:

SELECT id, nome, stipendio FROM t1 FULL JOIN t2 ON id=id2;

+------+-------+-----------+
| id   | nome  | stipendio |
+------+-------+-----------+
|    1 | pippo |      1000 |
|    3 | caio  |      3000 |
|    2 | pluto |      1900 |
| NULL | NULL  |        10 |
|    4 | pluto |      NULL |

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


Attenzione. Dopo la clausola ON vanno indicate soltanto le condizioni relative al join. Eventuali altre condizioni vanno di solito indicate, come prima, nella clausola WHERE. Ad esempio:

SELECT id, nome, stipendio FROM t1 LEFT JOIN t2 ON id=id2 WHERE stipendio>1000;

+------+-------+-----------+
| id   | nome  | stipendio |
+------+-------+-----------+
|    3 | caio  |      3000 |
|    2 | pluto |      1900 |
+------+-------+-----------+
2 rows in set (0.00 sec)

mentre

SELECT id, nome, stipendio FROM t1 LEFT JOIN t2 ON id=id2 and stipendio>1600;

+------+-------+-----------+
| id   | nome  | stipendio |
+------+-------+-----------+
|    1 | pippo |      NULL |
|    2 | pluto |      1900 |
|    3 | caio  |      3000 |
|    4 | pluto |      NULL |
+------+-------+-----------+
4 rows in set (0.00 sec)

Nella maggioranza dei casi, quello che vogliamo è il primo risultato e non il secondo.

Union

Un'altrà possibilità del comando SELECT è quella di eseguire due selezioni distinte e di fondere i risultati come se fossero stati ottenuti da un comando solo. Tutto ciò si realizza con la clausola UNION. Ad esempio:

select * from t1 UNION select * from t2;

visualizza il contenuto della prima e della seconda tabella una dopo l'altro.

Notare che elementi duplicati vengono eliminati..  Questo ci consente di simulare il FULL JOIN:

SELECT id, nome, stipendio FROM t1 LEFT JOIN t2 on id=id2
UNION SELECT id, nome, stipendio FROM t1 RIGHT JOIN t2 on id=id2;

È possibile anche effettuare una union che non elimini i duplicati, con la clausola UNION ALL:

SELECT id, nome, stipendio FROM t1 LEFT JOIN t2 on id=id2
UNION ALL SELECT id, nome, stipendio FROM t1 RIGHT JOIN t2 on id=id2;

+------+-------+-----------+
| id   | nome  | stipendio |
+------+-------+-----------+
|    1 | pippo |      1000 |
|    3 | caio  |      3000 |
|    2 | pluto |      1900 |
| NULL | NULL  |        10 |
|    4 | pluto |      NULL |

|    1 | pippo |      1000 |
|    2 | pluto |      1900 |
|    3 | caio  |      3000 |
|    4 | pluto |      NULL |
+------+-------+-----------+

MySQL non implementa EXCEPT ed INTERSECT, due altri operatori insiemistici fanno invece parte dello standard SQL. Questi si possono comunque simulare con comandi SQL più complessi.

La clausola GROUP BY

Altra caratteristica del comando SELECT, è la capacità di raggruppare le informazioni in base al valore assunto da uno o più campi e di fornire informazioni statistiche su questi gruppi. Utilizzeremo a questo scopo la tabella t4:

+----------+--------+-----------+
| nome     | valore | categoria |
+----------+--------+-----------+
| giuseppe |    100 | a         |
| marco    |    200 | b         |
| luca     |   NULL | b         |
| luca     |    100 | b         |
| NULL     |   NULL | b         |
| carlo    |   1000 | a         |
+----------+--------+-----------+

Supponiamo di dare il comando

SELECT * FROM t4 GROUP BY categoria;

+----------+--------+-----------+
| nome     | valore | categoria |
+----------+--------+-----------+
| giuseppe |    100 | a         |
| marco    |    200 | b         |
+----------+--------+-----------+

Con l'aggiunta di GROUP BY, per ogni valore di categoria viene visualizzata una e una sola riga corrispondente (la prima). Questa cosa ha di per se poca utilità, ma diventa molto utile quando abbinata alle funzioni aggregate, che consentono di effettuare varie operazioni statistiche.

Ad esempio, se si vuole conoscere il numero di persone appartenente ad ogni categoria, possiamo dare il comando:

SELECT categoria, COUNT(*) FROM t4 GROUP BY categoria;

+-----------+----------+
| categoria | COUNT(*) |
+-----------+----------+
| a         |        2 |
| b         |        4 |
+-----------+----------+

La clausola GROUP BY divide tutte le informazioni in gruppi, ogni gruppo caratterizzato dal fatto di avere la stessa categoria mentre COUNT(*) conta il numero di elementi in ogni gruppo.

Notare che nel conteggio viene anche inserita la riga  (NULL,NULL,'b'). Se si vuole essere più precisi su cosa contare, possiamo indicare tra parentesi a COUNT un nome di un campo. A questo punto, le linee in cui il campo indicato contiene il valore null verranno escluse dal conteggio. Ad esempio:
 
SELECT categoria, COUNT(nome) FROM  t4 GROUP BY categoria;

+-----------+-------------+
| categoria | COUNT(nome) |
+-----------+-------------+
| a         |           2 |
| b         |           3 |
+-----------+-------------+

Infine è possibile indicare che occorre contare i valori senza ripetizioni. Ad esempio

SELECT categoria, COUNT(DISTINCT nome) FROM  t4 GROUP BY categoria;

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

| categoria | COUNT(valore) |
+-----------+---------------+
| a         |             2 |
| b         |             2 |
+-----------+---------------+

Esitono altre funzioni aggregate. Ad esempio la somma (SUM) e la media (AVG):

SELECT categoria, SUM(valore) FROM  t4 GROUP BY categoria;

+-----------+-------------+
| categoria | SUM(valore) |
+-----------+-------------+
| a         |        1100 |
| b         |         300 |
+-----------+-------------+

SELECT categoria, AVG(valore) FROM  t4 GROUP BY categoria;

+-----------+-------------+
| categoria | AVG(valore) |
+-----------+-------------+
| a         |    550.0000 |
| b         |    150.0000 |
+-----------+-------------+

Altre funzioni aggregate sono il massimo MAX(), il minimo MIN(), e la deviazione standard STD().

La clausola HAVING

Le condizioni che si possono specificare con la clausola WHERE sono anche molto complesse, ma non possono riferirsi ai risultati delle funzioni aggregate. Ovvero, non è possibile sapere l'insieme delle categorie, nella tabella t4, che hanno un valore medio superiore a 200. Un comando del tipo:

select categoria,avg(valore) from t4 where avg(valore)>400 group by categoria;

genera errore. Tuttavia, condizioni di questo tipo possono essere espresse con la clausola HAVING.

select categoria, avg(valore) from t4 group by categoria HAVING AVG(valore)>400;

+-----------+-------------+
| categoria | avg(valore) |
+-----------+-------------+
| a         |    550.0000 |
+-----------+-------------+

Notare che con HAVING si possono specificare solo condizioni che coinvolgono i campi presenti nella clausola GROUP BY o il risultato di funzioni aggregate. Condizioni di altro tipo DEVONO essere specificate nella clausola WHERE. Ad esempio:

SELECT categoria, SUM(VALORE) FROM t4 WHERE valore>100 GROUP BY categoria;

+-----------+-------------+
| categoria | SUM(VALORE) |
+-----------+-------------+
| a         |        1100 |
| b         |         300 |
+-----------+-------------+

mentre

SELECT categoria, SUM(VALORE) FROM t4 GROUP BY categoria HAVING valore>100;

genera un errore di sintassi.


Nota. Nell'esecuzione di un comando SELECT, il linguaggio SQL prima considera la clasuole FROM e WHERE, calcola il risultato, e solo successivamente considera le eventuali clausole GROUP BY e HAVING. Per questo motivo, nella clausola WHERE vanno le condizioni da applicare per filtrare l'insieme di prima di effettuare il raggruppamento, e nella clausola HAVING quelle per filtrare i risultati dopo aver raggruppato.

Subqueries

Infine, un comando SELECT può anche apparire come sotto-interrogazione di un altro comando SELECT principale. In particolare, una SELECT può apparire
Non approfondiamo di più il discorso, perché le possibilità offerte dalle subqueries sono tantissime e non abbiamo tempo per analizzarle (anche tenendo conto che le avete viste nel corso di Basi di Dati). Puntualizziamo solo due cose:
  1. molte interrogazioni con subqueries possono essere trasformate in interrogazioni normali usando opportunamente i join esterni o delle tabelle temporanee. Ad esempio, la seconda interrogazione di cui sopra, si può riscrivere come
    SELECT id, nome FROM t1 LEFT JOIN t2 ON id=id2 WHERE id2 IS NULL

  2. il supporto per le subqueries è stato inserito in MySQL solo con la versione 4.1. 

I comandi INSERT, UPDATE e DELETE

Vediamo ora brevemente alcuni comandi per l'aggiornamento della base di dati. Come riferimento per gli esempi si usano sempre le tabelle create dallo script esempioselect.sql.

Differenze tra lo standard SQL e MySQL

Ecco brevemente le differenze principali tra lo standard SQL e l'implementazione di SQL presente in MySQL.

Esercizi e Soluzioni

Esercizio 1

Si consideri la seguente interrogazione SQL:

SELECT AVG(s) FROM (SELECT SUM(valore) AS s FROM t4 GROUP BY categoria) AS t4;

Provare a risolvere questa interrogazione senza usare le subquery (SELECT dentro le SELECT), ma adoperandoadoperando una tabella temporanea per immagazzinare il risultato di SELECT SUM(valore) AS s FROM t4 GROUP BY categoria.

Lezione Precedente Elenco Lezioni Lezione Successiva

Valid HTML 4.01 Transitional Valid CSS!