In questa lezione impari a
- inserire, modificare e cancellare righe senza fare danni
- scrivere query SELECT con condizioni anche composte
- ordinare e limitare i risultati
- usare alias e colonne calcolate
Le tabelle ci sono: ora le riempiamo e, soprattutto, impariamo a fare domande al database. Le query di questa lezione si eseguono sul database biblioteca (lezione 1): selezionalo a sinistra in phpMyAdmin prima di aprire la scheda SQL.
Se nelle prove cancelli o rovini qualcosa, niente paura: reimporta
biblioteca.sqle il database torna come nuovo.
INSERT: aggiungere righe
INSERT INTO autori (nome, cognome, nazionalita)
VALUES ('Gianni', 'Rodari', 'italiana');
- Si elencano le colonne, poi i valori nello stesso ordine.
- L'
idnon si scrive: èAUTO_INCREMENT, lo assegna MySQL (qui sarà 9). - Le colonne non indicate prendono il valore
DEFAULToppureNULL.
Più righe in un colpo solo:
INSERT INTO studenti (nome, cognome, classe, email) VALUES
('Davide', 'Conti', '5B', 'davide.conti@scuola.it'),
('Elena', 'Marino', '3B', 'elena.marino@scuola.it');
Un libro di Rodari: la chiave esterna autore_id deve contenere l'id di un autore esistente.
INSERT INTO libri (isbn, titolo, anno, genere, copie, autore_id)
VALUES ('9788847733367', 'Favole al telefono', 1962, 'Racconti', 2, 9);
L'apostrofo nelle stringhe si scrive raddoppiato:
'L''isola di Arturo'. In PHP non ci penseremo più, perché useremo le query preparate (lezione 11).
Gli errori che il DBMS ti restituisce qui sono utili: sono i vincoli che fanno il loro lavoro.
| Errore | Causa |
|---|---|
1062 Duplicate entry '...' for key 'isbn' |
valore ripetuto in una colonna UNIQUE |
1452 Cannot add or update a child row: a foreign key constraint fails |
chiave esterna che punta a una riga che non esiste (per esempio autore_id = 99) |
1048 Column 'titolo' cannot be null |
manca un valore obbligatorio |
4025 CONSTRAINT ... failed (in MySQL: 3819) |
un CHECK non è rispettato |
UPDATE: modificare righe
UPDATE libri
SET copie = 3
WHERE id = 15;
Si possono cambiare più colonne insieme e usare il valore attuale:
UPDATE libri
SET copie = copie + 1, genere = 'Fiabe'
WHERE isbn = '9788847733367';
⚠️ Senza
WHEREsi modificano TUTTE le righe.UPDATE libri SET copie = 1;mette una copia a ogni libro del catalogo. Prima di unUPDATE(o di unDELETE) scrivi laSELECTcon lo stessoWHEREe controlla che trovi solo le righe giuste.
DELETE: cancellare righe
DELETE FROM studenti
WHERE email = 'elena.marino@scuola.it';
Vale la stessa regola: DELETE FROM studenti; svuota la tabella. E qui entrano in gioco le chiavi esterne: se provi a cancellare uno studente che ha prestiti, MySQL rifiuta con l'errore 1451 (Cannot delete or update a parent row), perché abbiamo scelto ON DELETE RESTRICT.
SELECT: interrogare il database
La SELECT è il comando più usato in assoluto. La forma base:
SELECT colonne
FROM tabella
WHERE condizione
ORDER BY colonna
LIMIT quante;
Solo SELECT e FROM sono obbligatori; le altre clausole, se ci sono, vanno in quest'ordine.
SELECT * FROM libri; -- tutte le colonne, tutte le righe
SELECT titolo, anno FROM libri; -- solo due colonne
L'asterisco * è comodo per guardare, ma nei programmi è meglio elencare le colonne che servono davvero.
WHERE: scegliere le righe
SELECT titolo, anno
FROM libri
WHERE genere = 'Fantascienza'
ORDER BY anno;
+--------------+------+
| titolo | anno |
+--------------+------+
| Frankenstein | 1818 |
| 1984 | 1949 |
| Io, robot | 1950 |
| Fondazione | 1951 |
+--------------+------+
Gli operatori di confronto sono =, <> (diverso, si può scrivere anche !=), <, >, <=, >=. Le condizioni si combinano con AND, OR, NOT e le parentesi:
-- romanzi con più di 2 copie, oppure qualsiasi libro di avventura
SELECT titolo, genere, copie
FROM libri
WHERE (genere = 'Romanzo' AND copie > 2) OR genere = 'Avventura';
ANDviene prima diOR, come la moltiplicazione prima dell'addizione. Nel dubbio, metti le parentesi: rendono anche la query più leggibile.
Operatori speciali
| Operatore | Esempio | Trova |
|---|---|---|
BETWEEN a AND b |
anno BETWEEN 1940 AND 1960 |
dal 1940 al 1960, estremi compresi |
IN (...) |
classe IN ('4A', '5A') |
uno qualsiasi dei valori dell'elenco |
LIKE |
titolo LIKE 'Il %' |
testo che segue uno schema |
IS NULL |
data_restituzione IS NULL |
valori mancanti |
IS NOT NULL |
nazionalita IS NOT NULL |
valori presenti |
Nel LIKE ci sono due caratteri jolly:
%= qualsiasi sequenza di caratteri, anche nessuno:'Il %'= inizia con "Il ";_= esattamente un carattere:'_A'= due caratteri, il secondo è A (le classi 4A, 5A…).
SELECT titolo FROM libri WHERE titolo LIKE 'Il %';
Attenzione con NULL. data_restituzione = NULL non funziona: non trova mai niente, perché NULL è "sconosciuto" e un confronto con lo sconosciuto non è né vero né falso. Si scrive sempre IS NULL:
-- i prestiti ancora aperti (libro non restituito)
SELECT id, libro_id, studente_id, data_prestito
FROM prestiti
WHERE data_restituzione IS NULL;
+----+----------+-------------+---------------+
| id | libro_id | studente_id | data_prestito |
+----+----------+-------------+---------------+
| 2 | 7 | 1 | 2026-10-01 |
| 3 | 10 | 2 | 2026-09-20 |
| 5 | 1 | 4 | 2026-09-25 |
| 6 | 9 | 5 | 2026-09-02 |
| 8 | 7 | 7 | 2026-09-28 |
| 10 | 1 | 6 | 2026-10-02 |
+----+----------+-------------+---------------+
Funziona, ma i numeri non dicono molto: chi è lo studente 4? Quale libro è il 9? Per vedere i nomi bisogna unire le tabelle: è l'argomento della prossima lezione.
ORDER BY e LIMIT
SELECT titolo, copie
FROM libri
ORDER BY copie DESC, titolo
LIMIT 3;
+---------------------+-------+
| titolo | copie |
+---------------------+-------+
| 1984 | 5 |
| Se questo è un uomo | 4 |
| Il barone rampante | 3 |
+---------------------+-------+
ASC(crescente) è il valore predefinito,DESCè decrescente.- Con più colonne, la seconda decide solo a parità della prima: tra i libri con 3 copie, vengono in ordine alfabetico.
LIMIT 3tiene le prime 3 righe;LIMIT 5 OFFSET 10salta le prime 10 e ne prende 5. È la base della paginazione dei risultati (lezione 14).
DISTINCT, alias e colonne calcolate
SELECT DISTINCT genere FROM libri ORDER BY genere; -- ogni genere una volta sola
Una colonna del risultato può essere un'espressione, e con AS le si dà un nome (alias):
SELECT CONCAT(nome, ' ', cognome) AS studente, classe
FROM studenti
ORDER BY cognome;
SELECT titolo, anno, YEAR(CURDATE()) - anno AS anni_fa
FROM libri
WHERE genere = 'Avventura';
Alcune funzioni utili di MySQL:
| Funzione | Risultato |
|---|---|
CONCAT(a, b, ...) |
unisce stringhe |
UPPER(s), LOWER(s) |
maiuscolo, minuscolo |
LENGTH(s) |
lunghezza in byte (CHAR_LENGTH in caratteri) |
CURDATE(), NOW() |
data di oggi; data e ora attuali |
YEAR(d), MONTH(d), DAY(d) |
parti di una data |
DATEDIFF(d1, d2) |
giorni tra due date |
DATE_ADD(d, INTERVAL 30 DAY) |
una data spostata in avanti |
DATE_FORMAT(d, '%d/%m/%Y') |
data in formato italiano |
ROUND(x, 2) |
arrotonda a 2 decimali |
-- prestiti aperti, con la scadenza (30 giorni) e la data all'italiana
SELECT id,
DATE_FORMAT(data_prestito, '%d/%m/%Y') AS preso_il,
DATE_FORMAT(DATE_ADD(data_prestito, INTERVAL 30 DAY), '%d/%m/%Y') AS scadenza
FROM prestiti
WHERE data_restituzione IS NULL
ORDER BY data_prestito;
Esercizi
Scrivi le query sul database biblioteca e controlla il risultato in phpMyAdmin.
Esercizio 1 – Prime interrogazioni.
- Titolo e anno dei libri pubblicati prima del 1900, dal più vecchio.
- Nome e cognome degli studenti delle classi 4A e 5A.
- I libri (titolo, anno) usciti tra il 1940 e il 1960, in ordine di anno.
- I diversi generi presenti nel catalogo, in ordine alfabetico.
Mostra la soluzione
-- 1. Frankenstein (1818), Ventimila leghe (1870), Il giro del mondo (1873)
SELECT titolo, anno FROM libri WHERE anno < 1900 ORDER BY anno;
-- 2. Giulia Rossi, Marco Bianchi, Luca Romano, Aisha Benali
SELECT nome, cognome FROM studenti WHERE classe IN ('4A', '5A');
-- 3. sette libri, da La fattoria degli animali (1945) a L'isola di Arturo (1957)
SELECT titolo, anno FROM libri WHERE anno BETWEEN 1940 AND 1960 ORDER BY anno;
-- 4. Avventura, Fantascienza, Memorialistica, Racconti, Romanzo
SELECT DISTINCT genere FROM libri ORDER BY genere;
La 2 si può scrivere anche con classe = '4A' OR classe = '5A', ma IN è più corto e si allunga facilmente.
Esercizio 2 – LIKE a sorpresa. Esegui SELECT titolo FROM libri WHERE titolo LIKE '%il%';. Quanti libri trova? Ti aspettavi Ventimila leghe sotto i mari e Le città invisibili? Come cerchi solo i titoli che contengono la parola "il"?
Mostra la soluzione
Trova 5 libri: Il sistema periodico, Il barone rampante, Le città invisibili, Ventimila leghe sotto i mari, Il giro del mondo in 80 giorni. "Invisilibili" e "Ventimila" contengono le lettere "il" in mezzo alla parola; la ricerca non distingue maiuscole e minuscole, quindi trova anche "Il".
Per la parola intera bisogna considerare gli spazi: all'inizio del titolo, in mezzo o alla fine.
SELECT titolo FROM libri
WHERE titolo LIKE 'il %' OR titolo LIKE '% il %' OR titolo LIKE '% il';
Trova i tre titoli che iniziano con "Il". È il motivo per cui le ricerche "vere" nei siti usano strumenti più evoluti (gli indici FULLTEXT), ma per il nostro catalogo LIKE basta.
Esercizio 3 – Modifiche sicure. Scrivi i comandi per:
- aggiungere lo studente Pietro Gallo, 3B, email
pietro.gallo@scuola.it; - spostarlo in 3A;
- registrare che il prestito con
id = 3è stato restituito oggi; - cancellare Pietro Gallo.
Prima di ogni UPDATE e DELETE, scrivi la SELECT di controllo.
Mostra la soluzione
-- 1
INSERT INTO studenti (nome, cognome, classe, email)
VALUES ('Pietro', 'Gallo', '3B', 'pietro.gallo@scuola.it');
-- 2: controllo, poi modifica
SELECT * FROM studenti WHERE email = 'pietro.gallo@scuola.it';
UPDATE studenti SET classe = '3A' WHERE email = 'pietro.gallo@scuola.it';
-- 3
SELECT * FROM prestiti WHERE id = 3;
UPDATE prestiti SET data_restituzione = CURDATE() WHERE id = 3;
-- 4
SELECT * FROM studenti WHERE email = 'pietro.gallo@scuola.it';
DELETE FROM studenti WHERE email = 'pietro.gallo@scuola.it';
Per individuare la riga si usa una colonna unica (l'email o l'id), mai il nome: di Pietro Gallo potrebbero essercene due. Pietro si può cancellare perché non ha prestiti; se ne avesse, MySQL rifiuterebbe.
Esercizio 4 – Il registro dei ritardi. Mostra i prestiti aperti da più di 30 giorni (alla data di oggi), con l'id, la data del prestito e il numero di giorni passati, dal più vecchio.
Mostra la soluzione
SELECT id, data_prestito, DATEDIFF(CURDATE(), data_prestito) AS giorni
FROM prestiti
WHERE data_restituzione IS NULL
AND DATEDIFF(CURDATE(), data_prestito) > 30
ORDER BY data_prestito;
Il risultato dipende dal giorno in cui la esegui. Per provarla su una data fissa, sostituisci CURDATE() con '2026-10-15': trovi il prestito 6 (Frankenstein, preso il 2 settembre, 43 giorni).
Nella WHERE non si può usare l'alias giorni, perché il WHERE viene valutato prima del calcolo delle colonne: per questo l'espressione si ripete.