Vai al contenuto

SQL: inserire, modificare e interrogare i dati (DML)

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.sql e 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'id non si scrive: è AUTO_INCREMENT, lo assegna MySQL (qui sarà 9).
  • Le colonne non indicate prendono il valore DEFAULT oppure NULL.

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 WHERE si modificano TUTTE le righe. UPDATE libri SET copie = 1; mette una copia a ogni libro del catalogo. Prima di un UPDATE (o di un DELETE) scrivi la SELECT con lo stesso WHERE e 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';

AND viene prima di OR, 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 3 tiene le prime 3 righe; LIMIT 5 OFFSET 10 salta 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.

  1. Titolo e anno dei libri pubblicati prima del 1900, dal più vecchio.
  2. Nome e cognome degli studenti delle classi 4A e 5A.
  3. I libri (titolo, anno) usciti tra il 1940 e il 1960, in ordine di anno.
  4. 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:

  1. aggiungere lo studente Pietro Gallo, 3B, email pietro.gallo@scuola.it;
  2. spostarlo in 3A;
  3. registrare che il prestito con id = 3 è stato restituito oggi;
  4. 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.