Quando normalizziamo i dati alla perfezione, ogni tabella diventa super compatta e ogni informazione segue una sola regola. Però, per fare query reali (tipo "Quali studenti sono iscritti al corso SQL?") spesso serve unire un sacco di tabelle. Più tabelle ci sono, più le query diventano complicate e il sistema "scava con la pala".
Sicuramente ormai conosci già i JOIN dalle lezioni precedenti. Ecco un esempio di query che potresti dover fare con un database progettato in modo normale:
SELECT students.name, courses.title
FROM students
JOIN enrollments ON students.id = enrollments.student_id
JOIN courses ON enrollments.course_id = courses.id
WHERE courses.title = 'SQL';
Sembra facile, ma sotto il cofano il server fa un lavoro infernale: legge ogni tabella, unisce i dati, filtra... E se le tabelle sono enormi? Logico pensare che la performance calerà.
La battaglia: normalizzazione vs velocità
Per fortuna (o purtroppo?), nella vita reale i database sono un compromesso. La normalizzazione totale garantisce l'integrità dei dati, ma rallenta le query complesse. Se il database viene usato per analisi e report, a volte conviene denormalizzarlo. È come sostituire 10 scatoline con un baule gigante: recuperi i dati più in fretta, ma rimetterli in ordine è più difficile.
Quando puoi "rilassarti" con la normalizzazione?
Ci sono situazioni dove la denormalizzazione è meglio:
Aggregati usati spesso
Per esempio, immagina che il sistema ogni giorno faccia query per contare quanti studenti ci sono in ogni corso. Con una struttura normalizzata dovresti fareJOIN e
COUNT() ogni volta. Invece puoi aggiungere nella tabella "Courses" una colonna
student_count, aggiornata in automatico quando aggiungi o togli iscrizioni.
-- Colonna denormalizzata
UPDATE courses
SET student_count = (
SELECT COUNT(*)
FROM enrollments
WHERE enrollments.course_id = courses.id
);
Report frequenti
Se il tuo cliente ogni giorno vuole il report "Chi, dove, quando ha comprato?", è più semplice tenere una tabella denormalizzata con righe già pronte tipo "Nome cliente, prodotto, data". La tabella sarà più grossa, ma recuperi i dati al volo.
Tanti read, pochi write
Quando il database viene usato soprattutto per leggere (tipo per analytics), vale la pena sacrificare la normalizzazione per la velocità.
Minimizzare i join con relazioni complesse
Se tra le tabelle ci sono relazioni multilivello (nested) e iJOIN diventano un incubo, elimina qualche livello di normalizzazione.
Esempio: come la denormalizzazione accelera le cose?
Abbiamo tabelle normalizzate di un e-commerce:
Tabella products |
Tabella orders |
Tabella order_items |
|---|---|---|
| id | id | id |
| name | date | order_id |
| price | customer_id | product_id |
| quantity |
Ogni ordine (orders) è composto da righe ordine (order_items). Calcoliamo quanto ha guadagnato il negozio:
SELECT SUM(order_items.quantity * products.price) AS total_revenue
FROM order_items
JOIN products ON order_items.product_id = products.id;
Il processo di join tra order_items e products rallenta la query su grandi volumi di dati.
Struttura denormalizzata
Ora immaginiamo che nella tabella order_items ci sia una colonna "extra" total_price (denormalizzazione):
Tabella order_items |
|---|
| id |
| order_id |
| product_id |
| quantity |
| total_price |
Ora la query è banale:
SELECT SUM(total_price) AS total_revenue
FROM order_items;
Così evitiamo i JOIN e quindi acceleriamo tutto.
Esercizio pratico: ottimizza il database "Vendite"
Dati: tabelle normalizzate
Tabella products |
Tabella sales |
|---|---|
| id | id |
| name | product_id |
| price | date |
| quantity |
Obiettivo: velocizzare le query tipo "Quanto abbiamo guadagnato per ogni prodotto?".
Step 1: aggiungi la colonna total_price nella tabella sales:
ALTER TABLE sales ADD COLUMN total_price NUMERIC;
Step 2: riempi questa colonna con i dati già esistenti:
UPDATE sales
SET total_price = quantity * (
SELECT price
FROM products
WHERE products.id = sales.product_id
);
Step 3: ora le query volano:
SELECT product_id, SUM(total_price) AS total_revenue
FROM sales
GROUP BY product_id;
Ma! La denormalizzazione ha un prezzo
Capisci che "più veloce" non vuol dire sempre "meglio". Con la denormalizzazione arrivano dei problemi:
Storage ridondante
La colonnatotal_price è una copia dei dati, serve spazio extra.
Update complicati
Se il prezzo di un prodotto cambia nella tabella products, dobbiamo aggiornare a mano la colonna total_price corrispondente. Questo può creare inconsistenze.
Anomalie su insert, update e delete
Le informazioni si "desincronizzano" facilmente se ti dimentichi di aggiornare i dati denormalizzati. Per esempio, se cambia il prezzo di un prodotto, non succede in automatico.
Equilibrio: come trovare la via di mezzo?
Scegli cosa conta di più: performance o struttura? Se il database viene letto spesso, adatta la struttura alle query.
Denormalizza solo dove serve. Tipo solo per numeri chiave e report importanti.
Automatizza gli update dei dati denormalizzati. Usa trigger o job per evitare inconsistenze.
GO TO FULL VERSION