Cardinalità, traduzione al modello relazionale e forme normali — spiegati con esempi concreti.
La cardinalità di una relazione determina quante tabelle serviranno nel modello relazionale e dove andranno le chiavi esterne (FK).
Ogni istanza di A è associata ad al più una di B, e viceversa.
Esempio: PERSONA e CARTA D'IDENTITÀ. Ogni persona ha una sola CI, ogni CI appartiene a una sola persona.
Un'istanza di A è associata a molte di B; ogni B è associata ad al più una A.
Esempio: DOCENTE e CORSO. Un docente tiene molti corsi, ogni corso ha un solo docente titolare.
Un'istanza di A è associata a molte di B e viceversa.
Esempio: STUDENTE e CORSO. Uno studente segue molti corsi, un corso è seguito da molti studenti.
Nota: la tabella bridge può contenere attributi propri della relazione (es. voto, data_iscrizione) che non appartengono né allo studente né al corso, ma alla coppia (studente, corso).
Alcune proprietà non appartengono a una singola entità, ma alla relazione tra due entità. Questo è un punto critico che cambia la struttura del database.
Esempio — CLIENTE acquista PRODOTTO
La data di acquisto non è una proprietà del cliente (lo stesso cliente può acquistare in giorni diversi) né del prodotto (lo stesso prodotto può essere acquistato in giorni diversi da persone diverse).
La data di acquisto appartiene alla relazione ACQUISTO, cioè alla coppia (cliente, prodotto).
| Cardinalità | Senza attr. di relazione | Con attr. di relazione |
|---|---|---|
| 1:1 | 2 tabelle (FK in una) | 3 tabelle (tabella relazione) |
| 1:N | 2 tabelle (FK nel lato N) | 3 tabelle (tabella relazione) |
| N:N | 3 tabelle (bridge obbligatoria) | 3 tabelle (bridge + attributi) |
Negli esercizi la cardinalità non viene mai data direttamente — devi ricavarla leggendo le frasi del testo. Ogni frase contiene indizi precisi sul minimo e sul massimo di partecipazione.
| Frase nel testo | min | max | Significato |
|---|---|---|---|
| "ogni studente deve seguire almeno un corso" | 1 | N | partecipazione obbligatoria, molti corsi |
| "uno studente può non essere iscritto ad alcun corso" | 0 | N | partecipazione opzionale, molti corsi |
| "ogni impiegato lavora in un solo ufficio" | 1 | 1 | obbligatorio, uno solo |
| "un ufficio può non avere impiegati assegnati" | 0 | N | opzionale, molti impiegati |
| "ogni medico visita più pazienti" | 1 | N | obbligatorio (almeno 1), molti pazienti |
| "ogni paziente ha esattamente un medico di base" | 1 | 1 | obbligatorio, uno solo |
| "un progetto può coinvolgere più dipendenti" | 0 | N | opzionale (può anche averne zero), molti |
"Un docente può tenere più corsi, ma ogni corso è tenuto da esattamente un docente. Un corso deve avere almeno uno studente iscritto; uno studente può essere iscritto a più corsi oppure a nessuno."
| Associazione | Direzione | min | max | Frase che lo dice |
|---|---|---|---|---|
| DOCENTE — CORSO | DOCENTE → CORSO | 1 | N | "può tenere più corsi" |
| CORSO → DOCENTE | 1 | 1 | "esattamente un docente" | |
| STUDENTE — CORSO | CORSO → STUDENTE | 1 | N | "almeno uno studente iscritto" |
| STUDENTE → CORSO | 0 | N | "a più corsi oppure a nessuno" |
Risultato:
DOCENTE — CORSO: cardinalità 1:N → 2 tabelle, FK id_docente in CORSO
STUDENTE — CORSO: cardinalità N:N → 3 tabelle, bridge ISCRIZIONE
Le forme normali sono regole progressive per eliminare ridondanze e anomalie dalle tabelle. Si applicano dopo aver tradotto lo schema E-R in tabelle relazionali.
Partiamo da questa tabella volutamente sbagliata — la stessa che normalizzeremo passo per passo:
| id_ordine | cliente | telefono | prodotti | città | cap |
|---|---|---|---|---|---|
| 1 | Mario Rossi | 333-111 | iPhone, AirPods | Palermo | 90100 |
| 2 | Mario Rossi | 333-111 | iPad | Palermo | 90100 |
| 3 | Luca Bianchi | 347-222 | iPhone | Roma | 00100 |
In rosso: i valori problematici — liste in una cella e dati ripetuti inutilmente.
Problema nella nostra tabella: la colonna prodotti contiene "iPhone, AirPods" — due valori in una cella. Non posso cercare facilmente tutti gli ordini che contengono iPhone, non posso contare i prodotti, non posso gestirli separatamente.
| id_ordine | cliente | prodotti |
|---|---|---|
| 1 | Mario Rossi | iPhone, AirPods |
| id_ordine | cliente | telefono | prodotto | città | cap |
|---|---|---|---|---|---|
| 1 | Mario Rossi | 333-111 | iPhone | Palermo | 90100 |
| 1 | Mario Rossi | 333-111 | AirPods | Palermo | 90100 |
| 2 | Mario Rossi | 333-111 | iPad | Palermo | 90100 |
| 3 | Luca Bianchi | 347-222 | iPhone | Roma | 00100 |
Ora ogni cella ha un solo valore. Ma sorge un problema: la chiave primaria non può più essere solo id_ordine, perché le prime due righe hanno entrambe id_ordine = 1. Due righe con la stessa chiave non si possono distinguere.
Per identificare univocamente ogni riga servono entrambi i campi insieme: id_ordine + prodotto. La riga (1, iPhone) è diversa dalla riga (1, AirPods). Quindi la chiave primaria diventa la coppia (id_ordine, prodotto) — si chiama chiave primaria composta.
Rimane però un problema evidente: telefono, città e cap di Mario Rossi compaiono tre volte. Se Mario cambia numero di telefono, devo aggiornarlo in tre righe diverse — anomalia di aggiornamento. Questo ci porta alla 2NF.
Qual è la differenza tra 2NF e 3NF?
Entrambe parlano di "dipendenze" ma il problema è diverso:
2NF Problema con la chiave
Un dato dipende solo da una parte della chiave primaria composta. Il problema coinvolge sempre la chiave.
Nel nostro esempio: il telefono dipende solo da id_ordine, non dalla chiave intera (id_ordine, prodotto).
3NF Problema tra campi normali
Un dato dipende da un altro campo che non è la chiave. La chiave non c'entra — il problema è tra due campi normali.
Nel nostro esempio: il CAP dipende dalla città — entrambi sono campi normali, nessuno dei due è la chiave.
Ricorda: la nostra chiave primaria è composta da (id_ordine, prodotto). In teoria, per trovare qualsiasi dato in questa tabella dovresti conoscere entrambi.
Ma proviamo a fare una domanda: qual è il telefono dell'ordine 1?
Per rispondere ti basta sapere che l'id_ordine è 1 — non ti serve sapere se ha preso un iPhone o un AirPod. Il numero di telefono è 333-111 in entrambi i casi.
Questo significa che il telefono lo trovi con solo una parte della chiave (id_ordine), senza usare l'altra parte (prodotto). Lo stesso vale per cliente, città e cap.
Soluzione: tutto ciò che riguarda il cliente — nome, telefono, città — va spostato in una tabella CLIENTE separata, con la sua chiave propria (id_cliente). Nella tabella degli ordini rimane solo il riferimento all'id_cliente.
Ora telefono e città compaiono una sola volta per ogni cliente. Se Mario cambia numero di telefono, lo aggiorno in un posto solo. Rimane però un altro problema nella tabella CLIENTE: il cap dipende dalla città — non direttamente dall'id_cliente. Questo ci porta alla 3NF.
Nella tabella CLIENTE abbiamo ancora un problema. Proviamo a fare una domanda: qual è il CAP di Palermo?
Per rispondere non ti serve sapere l'id_cliente — ti basta sapere la città. Il CAP 90100 lo trovi da "Palermo", non da "id_cliente = 1". Significa che il CAP non dipende dalla chiave della tabella (id_cliente), ma da un altro campo non-chiave (città).
Questa situazione si chiama dipendenza transitiva: per arrivare al CAP fai due passi — prima dall'id_cliente trovi la città, poi dalla città trovi il CAP. Come una catena: id_cliente → città → CAP.
Soluzione: città e CAP vanno in una tabella CITTA separata. Nella tabella CLIENTE rimane solo il riferimento alla città tramite id_citta.
Schema finale completo:
Riepilogo delle forme normali
| Forma | Cosa elimina | Condizione |
|---|---|---|
| 1NF | Valori non atomici (liste in una cella) | Ogni cella ha un solo valore |
| 2NF | Dipendenze parziali (da parte della PK composta) | Ogni attributo dipende dall'intera PK |
| 3NF | Dipendenze transitive (attributo → attributo) | Nessun attributo non-chiave dipende da un altro non-chiave |
Esempi concreti
| Forma | Esempio del problema | Dove sta il problema | Come si risolve |
|---|---|---|---|
| 1NF | La cella prodotti contiene "iPhone, AirPods" — una lista di due valori in un campo solo | In una cella con più valori | Una riga separata per ogni prodotto: (ordine 1, iPhone) e (ordine 1, AirPods) |
| 2NF | Chiave: (id_ordine, prodotto). Per sapere il telefono ti basta id_ordine — non serve anche il prodotto | Un dato dipende da metà della chiave composta | Tabella CLIENTE separata con chiave id_cliente |
| 3NF | Chiave: id_cliente. Per sapere il CAP non ti serve id_cliente — ti basta la città. Catena: id_cliente → città → CAP | Un dato dipende da un altro campo normale, non dalla chiave | Tabella CITTA separata con chiave id_citta |