La modellazione dei dati è un processo che mappa entità, attributi e relazioni prima di decidere come un database le archivierà. Le entità sono la parte facile – ogni prodotto conosce i propri utenti, ordini e post. Il mestiere sta nelle relazioni: quali record puntano a quali, quanti, e dove quell’arco vive fisicamente. Con gli archi giusti le query si scrivono da sole; con quelli sbagliati ogni feature combatte contro lo schema.
Punti chiave
| Domanda | Risposta |
|---|---|
| I tre livelli | Concettuale (che cosa) → logico (struttura) → fisico (motore) |
| I tre archi | Uno-a-uno · uno-a-molti · molti-a-molti |
| Strumenti relazionali | Chiavi esterne; tabelle di collegamento per il molti-a-molti |
| Strumenti documentali | Incorporamento, Pointer (riferimenti), Relation/array di ID |
| La regola moderna | Modellare per i pattern di accesso – ciò che si legge insieme vive insieme |
Le stesse relazioni, in entrambi i mondi
Prima il relazionale – la chiave esterna porta l’uno-a-molti, e il molti-a-molti deve scomporsi attraverso una tabella di collegamento:
-- 1:N – la chiave esterna vive sul lato "molti"
CREATE TABLE books (
id bigint PRIMARY KEY,
title text NOT NULL,
author_id bigint NOT NULL REFERENCES authors(id) ON DELETE CASCADE
);
-- M:N – nessuna FK da sola lo esprime; una tabella di collegamento tiene le due chiavi
CREATE TABLE book_genres (
book_id bigint REFERENCES books(id),
genre_id bigint REFERENCES genres(id),
added_at timestamptz DEFAULT now(), -- le tabelle di collegamento portano attributi
PRIMARY KEY (book_id, genre_id) -- chiave composita: un arco, una volta sola
);
Lato documentale, stesso modello: l’arco uno-a-molti è un Pointer tipizzato, l’arco molti-a-molti è una Relation – nessuna tabella di collegamento da inventare, e l’arco si dichiara nel codice applicativo:
// JavaScript / Node.js — Back4app JS SDK
// One-to-many: a Pointer. Many-to-many: a Relation.
const author = await new Parse.Query('Author').get(authorId);
const book = new Parse.Object('Book');
book.set('title', 'Dune');
book.set('author', author); // Pointer — typed one-to-many edge
await book.save();
const genres = book.relation('genres'); // Relation — many-to-many, unbounded
genres.add([sciFi, classics]);
await book.save(); // Flutter / Dart — Back4app Flutter SDK
// One-to-many: a Pointer. Many-to-many: a Relation.
final book = ParseObject('Book')
..set('title', 'Dune')
..set('author', author.toPointer()); // Pointer — typed one-to-many edge
await book.save();
book.addRelation('genres', [sciFi, classics]); // Relation — many-to-many
await book.save(); // iOS / Swift — Back4app Swift SDK
// One-to-many: a Pointer. Many-to-many: a Relation.
var book = Book()
book.title = "Dune"
book.author = try author.toPointer() // Pointer — typed one-to-many edge
let saved = try await book.save()
let relation = try saved.relation("genres")
try await relation.add([sciFi, classics]).save() // Relation — many-to-many // Android / Kotlin — Back4app Android SDK
// One-to-many: a Pointer. Many-to-many: a Relation.
val book = ParseObject("Book").apply {
put("title", "Dune")
put("author", author) // Pointer — typed one-to-many edge
}
book.save()
val genres = book.getRelation<ParseObject>("genres") // Relation — many-to-many
genres.add(sciFi)
genres.add(classics)
book.save() Concettuale vs. logico vs. fisico: i tre livelli
| Dimensione | Concettuale | Logico | Fisico |
|---|---|---|---|
| Domanda a cui risponde | Che cosa esiste, come si collega | Quali campi, quali chiavi | Come è archiviato, quanto è rapido |
| Pubblico | Tutti | Chi progetta | Chi sviluppa + il motore |
| Contiene | Entità, relazioni | + attributi, tipi, cardinalità | + indici, vincoli, partizioni |
| Cambia quando | Cambia il business | Si affinano i requisiti | Cambiano il motore o la scala |
La disciplina che i livelli impongono: non si discute di indici finché il team non è d’accordo su che cosa sia un “ordine”.
Ogni relazione, ogni implementazione
La tabella che i risultati di ricerca non hanno mai costruito – ogni tipo di arco con la sua implementazione nei due mondi:
| Relazione | Esempio | Implementazione relazionale | Implementazione documentale |
|---|---|---|---|
| Uno-a-uno | Utente ↔ profilo | FK con UNIQUE, o la stessa riga | Incorporarlo – quasi sempre |
| Uno-a-pochi | Persona → indirizzi | Tabella figlia + FK | Array incorporato (limitato) |
| Uno-a-molti | Autore → libri | FK sul lato “molti” | Pointer sul lato “molti” |
| Uno-a-enormità | Dispositivo → eventi | FK + partizionamento | Pointer sul figlio; mai un array |
| Molti-a-molti | Libri ↔ generi | Tabella di collegamento, PK composita | Relation o array di pointer |
| Autoreferenziale | Dipendente → manager | FK verso la propria tabella | Pointer verso la propria classe |
La notazione della cardinalità, per leggere i diagrammi: la zampa di gallina (crow’s foot) disegna una barra per “uno” e una forchetta a tre punte per “molti”; la tradizione entità-relazione scrive 1 e N sulle linee; UML scrive molteplicità come 1 e *. Tre dialetti, una sola grammatica.
Normalizzare, denormalizzare o incorporare?
La modellazione relazionale parte dalla normalizzazione – dividere i dati perché ogni fatto esista una volta sola, il che protegge le scritture dalle anomalie. La modellazione analitica e documentale la piega di proposito: la denormalizzazione duplica i campi più letti per eliminare i join, e l’incorporamento è il cugino documentale della denormalizzazione. La checklist incorporare o referenziare si riduce a tre domande: letti insieme? posseduti da un solo genitore? di dimensione limitata? Tre sì, si incorpora; un solo no, si referenzia. E sopra tutto il resto, la regola moderna che le guide generaliste saltano: elencare prima le query. Un modello è corretto quando i pattern di accesso che deve servire costano poco – le entità da sole non bastano a dirlo.
Casi d’uso tipici
- Progettare un nuovo backend. Il passaggio classico: nominare le entità, tracciare gli archi, scegliere le implementazioni secondo la tabella qui sopra – prima della prima riga di codice.
- La tabella di collegamento con attributi. Iscrizioni, adesioni, righe d’ordine – quando l’arco stesso porta dati (data, quantità, ruolo), la tabella di collegamento (o una classe d’arco con due pointer) diventa un’entità di prima classe.
- Districare uno schema cresciuto nel tempo. I sintomi rimandano agli archi: righe duplicate rivelano un molti-a-molti mal modellato; documenti obesi rivelano un incorporamento senza limiti.
- Migrare da un mondo all’altro. Passare dal relazionale al documentale significa decidere di nuovo, per ogni FK, tra incorporamento e pointer; la tabella qui sopra è il dizionario di traduzione.
- Alimentare AI e analytics. I data warehouse vogliono archi espliciti e stabili – il debito di modellazione riemerge il giorno in cui si tenta un export.
Come modellare ogni arco? Matrice decisionale
| Scegliere… | Quando… | Attenzione a… |
|---|---|---|
| Incorporare | Letti insieme, posseduti, limitati | La crescita: i “pochi” di oggi sono le migliaia di domani |
| Pointer / FK | Ciclo di vita indipendente, cardinalità alta | Indicizzarlo – ogni lookup e ogni include ne paga il prezzo |
| Relation / tabella di collegamento | Molti-a-molti, o l’arco porta dati | La direzione della query: sapere da quale lato si interrogherà |
| Duplicare (denormalizzare) | Un campo molto letto che attraversa un arco | Il fan-out degli aggiornamenti – la duplicazione è un debito con gli interessi |
Limiti e trade-off
- I modelli congelano le ipotesi. Le decisioni di cardinalità (“un utente ha un solo indirizzo”) diventano schema; costano poco sulla carta, molto dopo il lancio – conviene modellare per la cardinalità di domani.
- Entrambi i mondi puniscono l’arco non indicizzato. Colonne FK e campi pointer sono percorsi di join; dimenticarne gli indici è il bug di prestazioni silenzioso più comune.
- L’incorporamento scambia l’integrità con la località. Nessun motore garantisce che una copia incorporata resti coerente con la sua sorgente – da quel momento è compito della logica di aggiornamento.
- Le tabelle di collegamento moltiplicano i join; le Relation li nascondono. Ogni lettura molti-a-molti attraversa l’archiviazione degli archi – conviene mettere a budget il percorso della query, in qualunque dei due mondi.
- Anche modellare per pattern di accesso ha un costo. Ottimizzare per le query di oggi può sposare lo schema al prodotto di oggi; conviene tenere il modello concettuale come riferimento neutro.
La modellazione dei dati su Back4app
Back4app è una piattaforma open-source di Backend as a Service (BaaS) che combina database gestito, API REST e GraphQL generate automaticamente, autenticazione, archiviazione di file e funzioni serverless con Cloud Code. Il suo vocabolario di modellazione è la colonna documentale delle tabelle qui sopra, promossa al primo rango: i Pointer sono archi uno-a-molti tipizzati, le Relation portano il molti-a-molti senza una tabella di collegamento costruita a mano, e gli array coprono i piccoli insiemi limitati. Ogni arco dichiarato è subito percorribile – include() segue i pointer in una sola richiesta, GraphQL annida le Relation in una sola query – e lo schema resta visibile e modificabile nella dashboard, così il modello fisico non esce mai dal campo visivo del team che ha progettato quello concettuale.
Domande frequenti
Che cos'è la modellazione dei dati?
Il processo che traduce l'informazione di un'applicazione – le sue entità, i loro attributi e le relazioni tra esse – in una progettazione che un database può archiviare. Attraversa di norma tre livelli di dettaglio: un modello concettuale che nomina le entità, un modello logico che aggiunge attributi e chiavi, e un modello fisico che si impegna su tabelle, colonne e indici in un motore preciso.
Quali sono i tre tipi di modelli di dati?
Concettuale, logico e fisico – la stessa progettazione a uno zoom crescente. Il concettuale risponde a "che cosa esiste e come si collega" per gli interlocutori di business. Il logico aggiunge attributi, tipi e chiavi restando indipendente dalla tecnologia. Il fisico si impegna su un motore reale: tabelle o collezioni, indici, vincoli. Ogni livello è il precedente più una serie di decisioni.
Che cos'è una relazione uno-a-molti?
Un record genitore collegato a molti figli, dove ogni figlio appartiene a esattamente un genitore – un cliente e i suoi ordini, un autore e i suoi libri. È la relazione più comune in qualunque schema. I database relazionali la implementano con una chiave esterna sul lato "molti"; i database documentali usano un array incorporato per insiemi piccoli e limitati, oppure un pointer verso il genitore per tutto il resto.
Che cos'è una relazione molti-a-molti e perché richiede una tabella di collegamento?
Entrambi i lati si collegano a molti elementi dell'altro – studenti e corsi, libri e generi. Una sola colonna di chiave esterna può puntare a una riga sola, quindi i database relazionali scompongono il molti-a-molti in due relazioni uno-a-molti attraverso una tabella di collegamento che contiene le due chiavi (e spesso attributi della relazione, come la data di iscrizione). I database documentali fanno a meno della tabella di collegamento: array di riferimenti, o un tipo relation dedicato, portano l'arco direttamente.
Come si distingue uno-a-molti da molti-a-molti?
Conviene porre la domanda di appartenenza nei due sensi. "Un autore può avere molti libri?" Sì. "Un libro può avere molti autori?" Se no – uno-a-molti, chiave esterna sul libro. Se sì – molti-a-molti, tabella di collegamento o relation. Sbagliare qui è l'errore di modellazione classico: un molti-a-molti modellato come uno-a-molti duplica righe oppure perde archi in silenzio.
Che cos'è la cardinalità in un diagramma entità-relazione?
Il numero di istanze di un'entità che possono collegarsi alle istanze di un'altra – uno-a-uno, uno-a-molti, molti-a-molti. I diagrammi la esprimono in una di tre notazioni: numeri e lettere sulle linee di collegamento, i simboli a zampa di gallina (crow's foot) – una barra per uno, una forchetta a tre punte per molti – oppure intervalli di molteplicità come 1 e asterisco. Stessa semantica, convenzioni di disegno diverse.
Come modellano le relazioni i database documentali, senza chiavi esterne?
Con tre strumenti: l'incorporamento (annidare i dati collegati nel documento genitore – adatto quando si leggono insieme, appartengono a un solo genitore e sono limitati), i pointer o riferimenti (archiviare l'ID del documento collegato in un campo tipizzato – adatto a cicli di vita indipendenti e a cardinalità alta) e gli oggetti relation o array di ID per il molti-a-molti. La regola guida passa dalla normalizzazione ai pattern di accesso: ciò che si legge insieme deve vivere insieme.
Quali sono gli errori di modellazione dei dati più comuni?
Una top five stabile: modellare un molti-a-molti come uno-a-molti; dimenticare gli indici sui campi di chiave esterna e di pointer (ogni join e ogni lookup ne paga il prezzo); array incorporati senza limiti negli schemi documentali; la denormalizzazione prematura, prima che una query si sia rivelata lenta; e modellare dalle sole entità senza elencare le query – i pattern di accesso – che il modello deve servire.