Vai al contenuto

SQL: creare le tabelle (DDL)

In questa lezione impari a

  • scrivere ed eseguire comandi SQL nella scheda SQL di phpMyAdmin
  • scegliere il tipo di dato giusto per ogni colonna
  • creare tabelle con chiavi primarie, chiavi esterne e vincoli
  • modificare ed eliminare tabelle con ALTER e DROP

SQL (Structured Query Language, si legge "esse-cu-elle" o "sìquel") è il linguaggio con cui si parla a un database relazionale. È lo stesso in MySQL, MariaDB, PostgreSQL, SQL Server, con piccole differenze. I suoi comandi si dividono in gruppi:

Gruppo A cosa serve Comandi
DDL (Data Definition Language) definire la struttura: database, tabelle, vincoli CREATE, ALTER, DROP
DML (Data Manipulation Language) lavorare sui dati INSERT, UPDATE, DELETE, SELECT
DCL (Data Control Language) gestire i permessi GRANT, REVOKE

In questa lezione trasformiamo lo schema logico della biblioteca in tabelle vere, scrivendo il DDL a mano.

Dove si scrive l'SQL

In phpMyAdmin apri la scheda SQL, scrivi i comandi e premi Esegui (o Ctrl+Invio). Alcune regole:

  • ogni comando finisce con il punto e virgola ;, così se ne possono eseguire più di uno insieme;
  • le parole chiave (CREATE, TABLE…) si possono scrivere maiuscole o minuscole; per convenzione si scrivono maiuscole, i nomi di tabelle e colonne minuscoli;
  • i commenti iniziano con -- (due trattini e uno spazio);
  • le stringhe vanno tra apici singoli: 'Primo Levi'.

Per non toccare il database del corso, in questa lezione lavoriamo su un database nuovo, mia_biblioteca. Il database biblioteca importato nella lezione 1 resta com'è.

Creare il database

CREATE DATABASE mia_biblioteca
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

USE mia_biblioteca;
  • CHARACTER SET utf8mb4 permette di salvare tutti i caratteri: lettere accentate, alfabeti diversi, anche le emoji. Usalo sempre.
  • La collation utf8mb4_unicode_ci decide come si confrontano e ordinano le stringhe; ci significa case insensitive: 'rossi' e 'Rossi' sono uguali in una ricerca.
  • USE sceglie il database su cui lavorare. In phpMyAdmin si ottiene lo stesso cliccando il database a sinistra prima di aprire la scheda SQL.

I tipi di dato

Ogni colonna ha un tipo: il DBMS rifiuta i valori che non c'entrano e sa come ordinarli e confrontarli. I più usati:

Tipo Contenuto Esempio
INT numero intero (fino a circa ±2 miliardi) id, quantità
TINYINT, SMALLINT interi piccoli (fino a 127 e 32.767); UNSIGNED = solo positivi, raddoppia il massimo copie, anno
DECIMAL(p,s) numero decimale esatto: p cifre in tutto, s dopo la virgola prezzi: DECIMAL(7,2) fino a 99.999,99
FLOAT, DOUBLE decimale approssimato misure scientifiche (mai per i soldi)
VARCHAR(n) testo di lunghezza variabile, al massimo n caratteri nome, email
CHAR(n) testo di lunghezza fissa ISBN (13), codice fiscale (16), sigla provincia (2)
TEXT testo lungo, senza limite pratico trama, descrizione
DATE data, nel formato 'AAAA-MM-GG' '2026-10-01'
DATETIME data e ora '2026-10-01 08:30:00'
BOOLEAN vero o falso (in MySQL è un TINYINT che vale 0 o 1) attivo
ENUM('a','b') un valore scelto da un elenco fisso ruolo

Le date si scrivono sempre all'americana, anno-mese-giorno: '2026-10-01' è il 1° ottobre. Il formato italiano si ottiene solo quando le si mostra (lo faremo in PHP).

Creare una tabella

La sintassi è: nome della tabella, poi tra parentesi le colonne separate da virgole, ciascuna con il suo tipo e i suoi vincoli.

CREATE TABLE autori (
  id INT AUTO_INCREMENT PRIMARY KEY,
  nome VARCHAR(50) NOT NULL,
  cognome VARCHAR(50) NOT NULL,
  nazionalita VARCHAR(40)
) ENGINE=InnoDB;
  • PRIMARY KEY: la chiave primaria. Unica e mai vuota.
  • AUTO_INCREMENT: il DBMS assegna da solo 1, 2, 3… a ogni nuova riga. Non dobbiamo inventarci gli id.
  • NOT NULL: la colonna è obbligatoria. nazionalita non ce l'ha, quindi può restare vuota (NULL).
  • ENGINE=InnoDB: il "motore" di memorizzazione. InnoDB è quello predefinito e l'unico che controlla davvero le chiavi esterne: scriverlo non costa nulla e lo rende esplicito.

NULL non è zero e non è stringa vuota: significa "valore sconosciuto o mancante". Uno studente senza telefono ha NULL, non 0.

I vincoli

Vincolo Significato
PRIMARY KEY chiave primaria
NOT NULL valore obbligatorio
UNIQUE valore che non si può ripetere in due righe (ma non è la chiave)
DEFAULT valore valore usato se nell'inserimento la colonna non viene indicata
CHECK (condizione) condizione che ogni riga deve rispettare
FOREIGN KEY … REFERENCES … chiave esterna

Ecco la tabella dei libri con tutti i vincoli:

CREATE TABLE libri (
  id INT AUTO_INCREMENT PRIMARY KEY,
  isbn CHAR(13) NOT NULL UNIQUE,
  titolo VARCHAR(150) NOT NULL,
  anno SMALLINT,
  genere VARCHAR(30) NOT NULL,
  copie TINYINT UNSIGNED NOT NULL DEFAULT 1,
  autore_id INT NOT NULL,
  CHECK (copie > 0),
  FOREIGN KEY (autore_id) REFERENCES autori(id)
    ON UPDATE CASCADE
    ON DELETE RESTRICT
) ENGINE=InnoDB;
  • isbn è UNIQUE: non si possono inserire due libri con lo stesso ISBN. Tentare di farlo dà l'errore 1062 Duplicate entry, che in PHP intercetteremo per mostrare un messaggio chiaro.
  • copie vale 1 se non viene indicata, e il CHECK impedisce di scrivere 0.
  • La chiave esterna collega autore_id all'id della tabella autori. La colonna deve avere lo stesso tipo della chiave a cui punta (qui INT).
  • ON DELETE RESTRICT: non si può cancellare un autore che ha libri. ON UPDATE CASCADE: se cambia l'id di un autore (raro), si aggiorna anche nei libri.

L'ordine conta: la tabella autori va creata prima di libri, perché la chiave esterna deve puntare a una tabella che esiste già. Per lo stesso motivo, quando si cancellano, si toglie prima libri e poi autori.

Le altre due tabelle

CREATE TABLE studenti (
  id INT AUTO_INCREMENT PRIMARY KEY,
  nome VARCHAR(50) NOT NULL,
  cognome VARCHAR(50) NOT NULL,
  classe VARCHAR(5) NOT NULL,
  email VARCHAR(100) NOT NULL UNIQUE
) ENGINE=InnoDB;

CREATE TABLE prestiti (
  id INT AUTO_INCREMENT PRIMARY KEY,
  libro_id INT NOT NULL,
  studente_id INT NOT NULL,
  data_prestito DATE NOT NULL,
  data_restituzione DATE NULL,
  CHECK (data_restituzione IS NULL OR data_restituzione >= data_prestito),
  FOREIGN KEY (libro_id) REFERENCES libri(id) ON DELETE RESTRICT,
  FOREIGN KEY (studente_id) REFERENCES studenti(id) ON DELETE RESTRICT
) ENGINE=InnoDB;

La tabella prestiti ha due chiavi esterne: è la traduzione dell'associazione N:M. Il CHECK traduce in SQL uno dei vincoli che avevamo scritto a parole nella lezione 2: la restituzione non può essere prima del prestito.

I CHECK sono rispettati da MySQL dalla versione 8.0.16 e da MariaDB (quella di XAMPP) dalla 10.2. Nelle versioni più vecchie venivano accettati ma ignorati: per questo i controlli importanti si ripetono anche in PHP.

Vuoi vedere com'è fatta una tabella? DESCRIBE libri; mostra colonne, tipi e chiavi. SHOW TABLES; elenca le tabelle del database.

Modificare una tabella: ALTER TABLE

Le tabelle si possono cambiare anche quando contengono già dati.

-- aggiungere una colonna
ALTER TABLE studenti ADD telefono VARCHAR(20);

-- cambiare il tipo di una colonna
ALTER TABLE studenti MODIFY telefono VARCHAR(30);

-- rinominare una colonna
ALTER TABLE studenti RENAME COLUMN telefono TO cellulare;

-- togliere una colonna (e tutti i suoi dati!)
ALTER TABLE studenti DROP COLUMN cellulare;

-- aggiungere un vincolo dopo
ALTER TABLE libri ADD CONSTRAINT anno_valido CHECK (anno <= 2100);

Dare un nome ai vincoli (CONSTRAINT anno_valido) è utile: il messaggio di errore lo cita e così si capisce subito quale regola è stata violata.

Eliminare: DROP

DROP TABLE prestiti;              -- cancella la tabella e tutti i suoi dati
DROP TABLE IF EXISTS prestiti;    -- come sopra, ma senza errore se non esiste
DROP DATABASE mia_biblioteca;     -- cancella l'intero database

DROP non chiede conferma in SQL e non si annulla: prima di usarlo su dati veri si fa una copia (phpMyAdmin → Esporta). Il file biblioteca.sql del corso inizia proprio con DROP DATABASE IF EXISTS biblioteca: per questo, reimportandolo, si riparte da zero.

Esercizi

Esercizio 1 – Crea le tue tabelle. Esegui in ordine tutti i comandi di questa lezione (database mia_biblioteca, poi le quattro tabelle). Poi controlla con SHOW TABLES; e DESCRIBE prestiti;. Prova anche a creare libri prima di autori in un database vuoto: che errore ottieni?

Mostra la soluzione

SHOW TABLES mostra autori, libri, prestiti, studenti. DESCRIBE prestiti mostra le 5 colonne: id con PRI nella colonna Key, libro_id e studente_id con MUL (sono chiavi esterne, quindi indicizzate), data_restituzione con YES in Null.

Creando libri per prima si ottiene un errore come errno: 150 "Foreign key constraint is incorrectly formed": la tabella autori a cui punta la chiave esterna non esiste ancora.

Esercizio 2 – Scegli i tipi. Scrivi il CREATE TABLE per una tabella prodotti del bar della scuola: codice a barre (13 cifre, unico), nome (obbligatorio), prezzo in euro con i centesimi, quantità in magazzino (mai negativa, 0 se non indicata), data dell'ultimo rifornimento, disponibile sì o no.

Mostra la soluzione
CREATE TABLE prodotti (
  id INT AUTO_INCREMENT PRIMARY KEY,
  codice_barre CHAR(13) NOT NULL UNIQUE,
  nome VARCHAR(80) NOT NULL,
  prezzo DECIMAL(5,2) NOT NULL,
  quantita SMALLINT UNSIGNED NOT NULL DEFAULT 0,
  ultimo_rifornimento DATE,
  disponibile BOOLEAN NOT NULL DEFAULT TRUE,
  CHECK (prezzo >= 0)
) ENGINE=InnoDB;
  • Il codice a barre è CHAR(13) e non INT: è un codice, non un numero con cui fare calcoli (e può iniziare con 0, che un intero perderebbe).
  • Il prezzo è DECIMAL, mai FLOAT: con FLOAT, 0,1 + 0,2 non fa esattamente 0,3.
  • UNSIGNED impedisce le quantità negative.

Esercizio 3 – Le gite in SQL. Scrivi il DDL completo per lo schema logico delle gite (lezione 3, esercizio 1), nell'ordine giusto. La tabella partecipazioni ha come chiave primaria la coppia (classe_id, gita_id).

Mostra la soluzione
CREATE TABLE docenti (
  id INT AUTO_INCREMENT PRIMARY KEY,
  nome VARCHAR(50) NOT NULL,
  cognome VARCHAR(50) NOT NULL,
  materia VARCHAR(50) NOT NULL
) ENGINE=InnoDB;

CREATE TABLE classi (
  id INT AUTO_INCREMENT PRIMARY KEY,
  anno TINYINT NOT NULL,
  sezione CHAR(1) NOT NULL,
  numero_studenti TINYINT UNSIGNED NOT NULL,
  UNIQUE (anno, sezione),
  CHECK (anno BETWEEN 1 AND 5)
) ENGINE=InnoDB;

CREATE TABLE gite (
  id INT AUTO_INCREMENT PRIMARY KEY,
  meta VARCHAR(80) NOT NULL,
  data_partenza DATE NOT NULL,
  giorni TINYINT UNSIGNED NOT NULL DEFAULT 1,
  costo DECIMAL(7,2) NOT NULL,
  docente_id INT NOT NULL,
  FOREIGN KEY (docente_id) REFERENCES docenti(id) ON DELETE RESTRICT
) ENGINE=InnoDB;

CREATE TABLE partecipazioni (
  classe_id INT NOT NULL,
  gita_id INT NOT NULL,
  studenti_partecipanti TINYINT UNSIGNED,
  PRIMARY KEY (classe_id, gita_id),
  FOREIGN KEY (classe_id) REFERENCES classi(id) ON DELETE CASCADE,
  FOREIGN KEY (gita_id) REFERENCES gite(id) ON DELETE CASCADE
) ENGINE=InnoDB;
  • Quando la chiave primaria è composta, si scrive in fondo: PRIMARY KEY (classe_id, gita_id).
  • UNIQUE (anno, sezione) impedisce due classi "5A".
  • Qui CASCADE ha senso: se si cancella una gita annullata, le sue partecipazioni non servono più.